大家好!今天我將分享一個簡單卻強大的實時庫存儀表盤項目,基于Vue.js和WebSocket技術。這個項目適合初學者學習前端實時數據處理,也能為你的技術博客或作品集增添亮點!通過這個教程,你將學會如何使用WebSocket實現實時數據更新,創建用戶友好的界面,并處理斷線重連等實際場景。快來跟我一起動手打造吧!
項目簡介
這個實時庫存儀表盤展示了庫存預警和總庫存量,通過WebSocket從服務器獲取實時數據。界面簡潔直觀,包含動態時間顯示、庫存數據列表和錯誤提示彈窗。整個項目使用Vue.js 2.6.14和純HTML/CSS,無需復雜依賴,打開瀏覽器即可運行,適合快速上手和分享。
功能亮點
實時數據更新:通過WebSocket接收服務器推送的庫存數據,動態更新界面。
心跳機制:每10秒發送心跳消息,確保連接穩定。
斷線重連:連接斷開后自動每3秒嘗試重連,并顯示友好提示。
簡潔界面:包含實時時間、庫存預警列表和總庫存展示,易于擴展。
零依賴部署:通過CDN加載Vue.js,無需本地開發環境。
項目代碼解析
以下是項目的核心代碼(完整代碼見文末)。我將重點講解WebSocket的關鍵邏輯和實現細節。
1. HTML結構
儀表盤的HTML結構分為三部分:
頭部:顯示標題和實時時間。
內容區:展示庫存預警列表和總庫存量。
彈窗:在WebSocket連接失敗時顯示提示。
<div id="app" class="container"><div class="header"><h1>實時庫存儀表盤</h1><span class="time">{{ currentTime }}</span></div><div class="content"><div class="data-box"><h2>庫存預警</h2><div class="data-item" v-for="product in products" :key="product.id">{{ product.name }}: {{ product.quantity }} (萬)</div></div><div class="data-box"><h2>庫存總量</h2><div class="data-item">總庫存: {{ totalStock }} 萬</div></div></div><div class="popup" :class="{ show: isPopupVisible }"><p>數據連接失敗,正在嘗試重新連接...<br>請確保網絡正常或聯系技術人員。</p></div>
</div>
2. WebSocket核心邏輯
WebSocket是實現實時數據更新的關鍵。以下是Vue.js中與WebSocket相關的核心方法:
連接WebSocket:初始化WebSocket連接,發送初始消息,并啟動心跳機制。
connectWebSocket() {this.ws = new WebSocket('ws://1.94.0.197:6061/websocket');this.ws.onopen = () => {console.log('WebSocket已連接');if (this.isReconnecting) {this.isPopupVisible = false;this.isReconnecting = false;}this.ws.send(JSON.stringify({ type: 'init' }));this.startHeartbeat();};// ... 其他事件處理
}
心跳機制:每10秒發送心跳消息,保持連接活躍。
startHeartbeat() {this.heartbeatInterval = setInterval(() => {if (this.ws && this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify({ type: 'heartbeat' }));}}, 10000);
}
斷線重連:連接斷開后,每3秒嘗試重連,并顯示彈窗提示。
reconnect() {this.isReconnecting = true;setTimeout(() => {console.log('嘗試重連WebSocket...');this.connectWebSocket();}, 3000);
}
數據更新:接收服務器數據并更新界面。
updateData(data) {this.products = data.getLowStock || [];this.totalStock = data.getCount?.totalStock || 0;
}
3. CSS樣式
樣式設計簡潔現代,使用了淺色背景和卡片式布局,確保界面清晰且美觀:
容器:居中顯示,帶陰影和圓角。
彈窗:居中彈出,紅色文字提示錯誤。
響應式:適配不同屏幕尺寸。
.container {max-width: 800px;margin: 0 auto;background: white;padding: 20px;border-radius: 8px;box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.popup {position: fixed;top: 50%;left: 50%;transform: translate(-50%, -50%);background: white;padding: 20px;border-radius: 8px;box-shadow: 0 4px 16px rgba(0,0,0,0.2);display: none;
}
.popup.show { display: block; }
4. Vue.js邏輯
數據綁定:使用Vue的響應式數據(如products和totalStock)動態更新界面。
生命周期管理:
mounted:啟動時間更新和WebSocket連接。
beforeDestroy:清理定時器和WebSocket連接,避免內存泄漏。
時間顯示:每秒更新當前時間,格式為“YYYY-MM-DD HH:mm:ss”。
updateTime() {const now = new Date();this.currentTime = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit',hour: '2-digit', minute: '2-digit', second: '2-digit'});
}
完整代碼
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>WebSocket 實時庫存儀表盤</title><script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.min.js"></script><style>body { font-family: Arial, sans-serif; background-color: #f0f2f5; margin: 0; padding: 20px; }.container { max-width: 800px; margin: 0 auto; background: white; padding: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }.header { display: flex; justify-content: space-between; align-items: center; background: #2c3e50; color: white; padding: 10px 20px; border-radius: 8px 8px 0 0; }.header h1 { margin: 0; font-size: 24px; }.time { font-size: 16px; }.content { padding: 20px; }.data-box { margin-bottom: 20px; }.data-box h2 { margin: 0 0 10px; font-size: 18px; color: #34495e; }.data-item { padding: 10px; background: #ecf0f1; border-radius: 4px; margin: 5px 0; }.popup { position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 20px; border-radius: 8px; box-shadow: 0 4px 16px rgba(0,0,0,0.2); display: none; }.popup.show { display: block; }.popup p { margin: 0; font-size: 16px; color: #e74c3c; text-align: center; }</style>
</head>
<body><div id="app" class="container"><div class="header"><h1>實時庫存儀表盤</h1><span class="time">{{ currentTime }}</span></div><div class="content"><div class="data-box"><h2>庫存預警</h2><div class="data-item" v-for="product in products" :key="product.id">{{ product.name }}: {{ product.quantity }} (萬)</div></div><div class="data-box"><h2>庫存總量</h2><div class="data-item">總庫存: {{ totalStock }} 萬</div></div></div><div class="popup" :class="{ show: isPopupVisible }"><p>數據連接失敗,正在嘗試重新連接...<br>請確保網絡正常或聯系技術人員。</p></div></div><script>new Vue({el: '#app',data() {return {products: [],totalStock: 0,currentTime: '',isPopupVisible: false,ws: null,isReconnecting: false,heartbeatInterval: null,timer: null};},mounted() {this.updateTime();this.timer = setInterval(this.updateTime, 1000);this.connectWebSocket();},beforeDestroy() {clearInterval(this.timer);clearInterval(this.heartbeatInterval);if (this.ws) this.ws.close();},methods: {updateTime() {const now = new Date();this.currentTime = now.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit',hour: '2-digit', minute: '2-digit', second: '2-digit'});},connectWebSocket() {this.ws = new WebSocket('ws://1.94.0.197:6061/websocket');this.ws.onopen = () => {console.log('WebSocket已連接');if (this.isReconnecting) {this.isPopupVisible = false;this.isReconnecting = false;}this.ws.send(JSON.stringify({ type: 'init' }));this.startHeartbeat();};this.ws.onmessage = (event) => {const data = JSON.parse(event.data);this.updateData(data);};this.ws.onerror = (error) => {console.error('WebSocket錯誤:', error);};this.ws.onclose = () => {console.log('WebSocket連接關閉');this.isPopupVisible = true;this.stopHeartbeat();if (!this.isReconnecting) {this.reconnect();}};},reconnect() {this.isReconnecting = true;setTimeout(() => {console.log('嘗試重連WebSocket...');this.connectWebSocket();}, 3000);},startHeartbeat() {this.heartbeatInterval = setInterval(() => {if (this.ws && this.ws.readyState === WebSocket.OPEN) {this.ws.send(JSON.stringify({ type: 'heartbeat' }));}}, 10000);},stopHeartbeat() {clearInterval(this.heartbeatInterval);},updateData(data) {this.products = data.getLowStock || [];this.totalStock = data.getCount?.totalStock || 0;}}});</script>
</body>
</html>