基于Vue+WebSocket實現(xiàn)實時數(shù)據(jù)可視化的實戰(zhàn)指南
一、項目背景與核心需求
- 實時接收多個 WebSocket 數(shù)據(jù)源(不同服務(wù)器或端口)
- 設(shè)計模擬數(shù)據(jù)接口,方便本地開發(fā)調(diào)試
- 支持數(shù)據(jù)的自動合并(如車流總量、車輛類型分布)
- 使用 ECharts 動態(tài)展示統(tǒng)計數(shù)據(jù)
- 保證 WebSocket 斷線自動重連,提高穩(wěn)定性
二、項目架構(gòu)與核心狀態(tài)管理
定義一個全局響應(yīng)式對象 sources,分別存儲四個數(shù)據(jù)源的數(shù)據(jù),支持真實數(shù)據(jù)和模擬數(shù)據(jù)統(tǒng)一寫入。
const USE_MOCK = true; // 是否啟用模擬數(shù)據(jù)
const sources = reactive({ su1: {}, su2: {}, su3: {}, su4: {}, });三、WebSocket 連接與模擬數(shù)據(jù)設(shè)計
1. 真實 WebSocket 連接實現(xiàn)
使用原生 WebSocket 連接服務(wù)器,設(shè)置事件監(jiān)聽,支持斷線自動重連。
function createRealWS(url, setTarget) {
const ws = new WebSocket(url);
ws.onopen = () => console.log(`?? WebSocket ${url} 已連接`);
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
setTarget(JSON.parse(data.data));
};
ws.onerror = () => console.error(`WebSocket ${url} 出錯`);
ws.onclose = () => {
console.warn(`WebSocket ${url} 關(guān)閉,3秒后重連...`);
setTimeout(() => createRealWS(url, setTarget), 3000);
};
}2. 模擬數(shù)據(jù)接口
為了方便本地開發(fā),使用定時器生成結(jié)構(gòu)一致的模擬數(shù)據(jù),模擬數(shù)據(jù)每3秒刷新一次。
function createMockSource(setTarget) {
setInterval(() => {
const mock = {
timestamp: Date.now(),
globalTime: new Date().toLocaleString(),
totalVehiCount: Math.floor(Math.random() * 1000),
aveSpeed: +(30 + Math.random() * 10).toFixed(2),
numVehiByType: Object.fromEntries(
[1, 2, 3, 7, 8, 10, 11, 15, 100].map(k => [k, Math.floor(Math.random() * 100)])
),
};
setTarget(mock);
}, 3000);
}3. 初始化所有數(shù)據(jù)源
根據(jù) WebSocket URL 端口號映射到對應(yīng)數(shù)據(jù)源,啟用模擬或真實數(shù)據(jù)。
function initAllSources() {
const urls = [
"ws://xx/wsStatisJd",
"ws://xx/wsStatisJd",
"ws://xx/wsStatisJd",
"ws://xx/wsStatisJd",
];
urls.forEach((url) => {
const key = getSourceKeyByPort(url); // su1 su2 su3 su4
const setFn = (data) => (sources[key] = data);
if (USE_MOCK) {
createMockSource(setFn);
} else {
createRealWS(url, setFn);
}
});
}四、數(shù)據(jù)合并與格式化
合并車流總量
將四個數(shù)據(jù)源的車輛總數(shù)相加,確保數(shù)值準確。
function mergeTotal(...totals) {
return totals.reduce((sum, val) => sum + Number(val || 0), 0);
}合并車輛類型分布
對每種車輛類型進行累加。
格式化合并后的車輛類型數(shù)據(jù),固定順序輸出并計算百分比
function formatVehicleTypeData(numVehiByType, total = 0) {
const fixedOrder = [8, 3, 2, 1, 15, 7, 10, 11, 100];
return fixedOrder.map(key => {
const value = numVehiByType[key] || 0;
const name = vehicleTypeMap[key] || `類型${key}`;
const percent = total > 0 ? ((value / total) * 100).toFixed(1) : "0.0";
return {
name,
value,
percent: Number(percent),
color: vehicleColorMap[name] || "#999999",
};
});
}五、響應(yīng)式數(shù)據(jù)更新與圖表刷新
通過 watchEffect 監(jiān)聽數(shù)據(jù)變化,自動計算合并數(shù)據(jù)并刷新 ECharts 餅圖。
watchEffect(() => {
const { su1, su2, su3, su4 } = sources;
const mergedTotal = mergeTotal(
su1.totalVehiCount,
su2.totalVehiCount,
su3.totalVehiCount,
su4.totalVehiCount
);
const mergedType = mergeVehicleType(
su1.numVehiByType || {},
su2.numVehiByType || {},
su3.numVehiByType || {},
su4.numVehiByType || {}
);
chartData.value = formatVehicleTypeData(mergedType, mergedTotal);
realtimeTime.value = new Date().toLocaleString();
totalVehiCount.value = mergedTotal;
aveSpeed.value = {
su1: su1.aveSpeed || 0,
su2: su2.aveSpeed || 0,
su3: su3.aveSpeed || 0,
su4: su4.aveSpeed || 0,
};
InitEchart2(chartData.value);
});六、ECharts 餅圖動態(tài)渲染
初始化并動態(tài)更新餅圖,顏色對應(yīng)車輛類型,關(guān)閉標簽和提示框保證大屏美觀。
const InitEchart2 = (data) => {
const chartDom = document.getElementById("map-left-4-1-echarts");
if (!chartDom) return;
if (!myChart) {
myChart = echarts.init(chartDom);
}
const colorList = data.map(item => item.color || "#ccc");
myChart.setOption({
color: colorList,
tooltip: { show: false },
series: [{
name: "車輛類型占比",
type: "pie",
radius: ["55%", "80%"],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 1,
borderColor: "#2c3950",
borderWidth: 2,
},
label: { show: false },
emphasis: { scale: false, label: { show: false } },
labelLine: { show: false },
data: data.map(({ name, value }) => ({ name, value })),
}],
});
};七、啟動與定時刷新邏輯
在組件掛載時,初始化數(shù)據(jù)接口和數(shù)據(jù)源,并設(shè)置定時器周期刷新相關(guān)統(tǒng)計數(shù)據(jù)。
onMounted(() => {
curDayCountData(); // 獲取今日車流初始數(shù)據(jù)
initAllSources(); // 啟動 WebSocket / 模擬數(shù)據(jù)
getDeviceOnlineData(); // 設(shè)備在線率數(shù)據(jù)
curDayEventCountData(); // 今日事件統(tǒng)計
eventHistoryCountData(); // 事件歷史統(tǒng)計
dataRefreshTimer = setInterval(() => {
curDayCountData();
getDeviceOnlineData();
curDayEventCountData();
eventHistoryCountData();
}, 30000);
});
onUnmounted(() => {
if (dataRefreshTimer) clearInterval(dataRefreshTimer);
});八、總結(jié)
- 多數(shù)據(jù)源實時管理,靈活切換模擬/真實數(shù)據(jù),提升開發(fā)效率
- 自動重連機制,保證 WebSocket 長連接穩(wěn)定可靠
- 響應(yīng)式合并處理,統(tǒng)一計算統(tǒng)計數(shù)據(jù),確保展示準確
- ECharts 動態(tài)刷新,實現(xiàn)流暢視覺效果,符合大屏需求
如果你正在做交通、工業(yè)或監(jiān)控領(lǐng)域的實時可視化,這個方案值得借鑒。
以上就是基于Vue+WebSocket實現(xiàn)實時數(shù)據(jù)可視化的實戰(zhàn)指南的詳細內(nèi)容,更多關(guān)于Vue WebSocket實時數(shù)據(jù)可視化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue3中關(guān)于路由hash與History的設(shè)置
這篇文章主要介紹了vue3中關(guān)于路由hash與History的設(shè)置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-04-04
vue中g(shù)et請求如何傳遞數(shù)組參數(shù)的方法示例
這篇文章主要介紹了vue中g(shù)et請求如何傳遞數(shù)組參數(shù)的方法示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2019-11-11
vue3使用threejs實現(xiàn)3D卡片水平旋轉(zhuǎn)效果的示例代碼
這篇文章主要介紹了在vue3中使用threejs實現(xiàn)3D卡片水平旋轉(zhuǎn)效果,文中通過代碼示例講解的非常詳細,對大家的學習或工作有一定的幫助,需要的朋友可以參考下2024-04-04
詳解為element-ui的Select和Cascader添加彈層底部操作按鈕
這篇文章主要介紹了詳解為element-ui的Select和Cascader添加彈層底部操作按鈕,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2020-02-02
實例詳解Vue項目使用eslint + prettier規(guī)范代碼風格
這篇文章主要介紹了Vue項目使用eslint + prettier規(guī)范代碼風格,本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2018-08-08

