Vue + WebSocket 实时数据可视化实战:多源融合与模拟数据双模式设计

在现代交通大屏项目中,实时数据的采集和可视化尤为重要。本文结合 Vue3 和 ECharts,分享一个支持多 WebSocket 数据源实时合并、模拟数据调试、自动重连的完整设计方案,帮助你快速搭建健壮的数据可视化组件。


一、项目背景与核心需求

  • 实时接收多个 WebSocket 数据源(不同服务器或端口)

  • 设计模拟数据接口,方便本地开发调试

  • 支持数据的自动合并(如车流总量、车辆类型分布)

  • 使用 ECharts 动态展示统计数据

  • 保证 WebSocket 断线自动重连,提高稳定性


二、项目架构与核心状态管理

定义一个全局响应式对象 sources,分别存储四个数据源的数据,支持真实数据和模拟数据统一写入。

复制代码
复制代码
const USE_MOCK = true; // 是否启用模拟数据 
const sources = reactive({ su1: {}, su2: {}, su3: {}, su4: {}, });

三、WebSocket 连接与模拟数据设计

1. 真实 WebSocket 连接实现

使用原生 WebSocket 连接服务器,设置事件监听,支持断线自动重连。

复制代码
复制代码
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} 关闭,3秒后重连...`);
    setTimeout(() => createRealWS(url, setTarget), 3000);
  };
}

2. 模拟数据接口

为了方便本地开发,使用定时器生成结构一致的模拟数据,模拟数据每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. 初始化所有数据源

根据 WebSocket URL 端口号映射到对应数据源,启用模拟或真实数据。

复制代码
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);
    }
  });
}

四、数据合并与格式化

合并车流总量

将四个数据源的车辆总数相加,确保数值准确。

复制代码
复制代码
function mergeTotal(...totals) {
  return totals.reduce((sum, val) => sum + Number(val || 0), 0);
}

合并车辆类型分布

对每种车辆类型进行累加。

复制代码

格式化合并后的车辆类型数据,固定顺序输出并计算百分比

复制代码
复制代码
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",
    };
  });
}

五、响应式数据更新与图表刷新

通过 watchEffect 监听数据变化,自动计算合并数据并刷新 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 饼图动态渲染

初始化并动态更新饼图,颜色对应车辆类型,关闭标签和提示框保证大屏美观。

复制代码
复制代码
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 })),
    }],
  });
};

七、启动与定时刷新逻辑

在组件挂载时,初始化数据接口和数据源,并设置定时器周期刷新相关统计数据。

复制代码
复制代码
onMounted(() => {
  curDayCountData();       // 获取今日车流初始数据
  initAllSources();        // 启动 WebSocket / 模拟数据
  getDeviceOnlineData();   // 设备在线率数据
  curDayEventCountData();  // 今日事件统计
  eventHistoryCountData(); // 事件历史统计

  dataRefreshTimer = setInterval(() => {
    curDayCountData();
    getDeviceOnlineData();
    curDayEventCountData();
    eventHistoryCountData();
  }, 30000);
});

onUnmounted(() => {
  if (dataRefreshTimer) clearInterval(dataRefreshTimer);
});

八、总结

  • 多数据源实时管理,灵活切换模拟/真实数据,提升开发效率

  • 自动重连机制,保证 WebSocket 长连接稳定可靠

  • 响应式合并处理,统一计算统计数据,确保展示准确

  • ECharts 动态刷新,实现流畅视觉效果,符合大屏需求

如果你正在做交通、工业或监控领域的实时可视化,这个方案值得借鉴。

相关推荐
sdghterhd10 小时前
WebSocket 快速入门教程(附示例源码)
网络·websocket·网络协议
leoZ23113 小时前
Claude 驱动的全栈开发:Spring Boot + Vue 的 BS 架构实践
vue.js·spring boot·架构
最爱老式锅包肉15 小时前
《HarmonyOS技术精讲-ArkGraphics 2D(方舟2D图形服务)》第九篇:数据可视化——柱状图与饼图的绘制实战
华为·信息可视化·harmonyos
荒诞英雄17 小时前
从 17MB Vendor 大包到按需加载:Vite 多入口 Vue 组件库首屏优化实践
vue.js·vite
jsonbro17 小时前
手把手带你实现发布订阅模式
前端·vue.js·设计模式
宠友信息18 小时前
MySQL 租户数据隔离在即时通讯源码后端中的落地方式
java·数据库·spring boot·redis·websocket·mysql
Cobyte19 小时前
23.Vue Vapor 组件 attrs 的实现
前端·javascript·vue.js
一键生成网站19 小时前
政务国企数据可视化大屏AI工具对比与信创安全选型指南
人工智能·信息可视化·政务·
斯特凡今天也很帅20 小时前
xml页面可以打开,不报错,但是显示数据的地方也不显示,我是怎么解决的
xml·vue.js
柯克七七21 小时前
给老项目加了 TypeScript,本来只想自己爽,结果全公司代码审查标准被我抬高了
前端·vue.js·typescript