可视化大屏系列(3):ECharts 实现生产状态、环境监测、告警提醒

在这里插入图片描述

目录

一、响应式大屏布局设计
二、图表容器适配与缩放方案
三、环境监测与生产状态图表设计
四、实时数据推送:WebSocket/SSE 实践
五、告警数据的图形高亮与动画提醒
六、主题定制与图表动态切换
七、总结与扩展建议


一、响应式大屏布局设计

在智慧工地场景中,可视化大屏常部署于会议室 LED 大屏或电视墙上,屏幕尺寸差异较大,若不做适配容易出现布局错乱或缩放异常。因此,构建一套适配不同分辨率、兼容主流大屏尺寸的响应式布局方案,是系统建设的关键第一步。

1. 设计思路与目标

目标:

  • 自动缩放:适配常见大屏分辨率(1920×1080、2560×1440 等);
  • 统一缩放比例:以设计稿为基准,保持页面元素比例不变;
  • 灵活布局:兼容纵向、横向滚动区域,自适应组件尺寸;
  • 统一组件容器:通过统一容器封装,复用性更强。

思路:

  • 使用 transform: scale() 按比例缩放整个容器;
  • 以设计稿(如 1920×1080)为基准,计算宽高缩放比;
  • 选取较小缩放比,保持容器完整显示;
  • 配合 Tailwind CSSSCSS 进行自适应样式设计。

2. 封装大屏容器组件

封装一个名为 <ScreenContainer> 的通用容器组件,自动根据窗口尺寸进行缩放。

Vue 3 + <script setup> 实现方式:
<!-- components/ScreenContainer.vue -->
<template>
  <div class="w-screen h-screen bg-[#0f1c2e] overflow-hidden text-white">
    <div ref="scaleWrapper" class="w-full h-full origin-top-left" :style="scaleStyle">
      <slot />
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted, onBeforeUnmount } from 'vue'

const DESIGN_WIDTH = 1920
const DESIGN_HEIGHT = 1080

const scaleWrapper = ref(null)
const scaleStyle = ref({})

const updateScale = () => {
  const w = window.innerWidth
  const h = window.innerHeight
  const scale = Math.min(w / DESIGN_WIDTH, h / DESIGN_HEIGHT)
  scaleStyle.value = {
    width: `${DESIGN_WIDTH}px`,
    height: `${DESIGN_HEIGHT}px`,
    transform: `scale(${scale})`,
  }
}

onMounted(() => {
  updateScale()
  window.addEventListener('resize', updateScale)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', updateScale)
})
</script>

使用方法:

<template>
  <ScreenContainer>
    <!-- 大屏布局区域 -->
    <Header />
    <MainGrid />
    <Footer />
  </ScreenContainer>
</template>

<script setup>
import ScreenContainer from '@/components/ScreenContainer.vue'
</script>

3. 栅格化布局设计(主内容区)

布局采用 Tailwind CSSgrid 布局(或 SCSS + rem/vw),划分左中右三栏或九宫格区域,适合放置多个图表、统计卡片、地图区域等。

<!-- 例:三列九宫格结构 -->
<div class="grid grid-cols-3 grid-rows-3 gap-4 p-4">
  <div class="col-span-1 row-span-1 bg-blue-900 rounded-xl p-2">左上</div>
  <div class="col-span-1 row-span-2 bg-green-900 rounded-xl p-2">中中</div>
  <div class="col-span-1 row-span-1 bg-yellow-900 rounded-xl p-2">右上</div>
  <!-- 其他区域 -->
</div>

常用辅助样式建议:

Tailwind 类名 功能
aspect-video 固定视频/图表宽高比
w-full h-full 占满容器
overflow-hidden 防止内容溢出
rounded-2xl 美观圆角
backdrop-blur 背景模糊层效果

4. 响应式适配策略总结

适配方式 适用场景 优点 缺点
transform scale 大屏固定比例展示 还原设计稿,结构清晰 文字随缩放可能模糊
rem + vw 单位 普通 PC 页面或小屏幕 更自然的文字与组件缩放 样式调整工作量大
grid + flex 自适应布局区域 模块化布局、快速搭建 需配合缩放或动态计算

二、图表容器适配与缩放方案

在智慧工地的大屏应用中,ECharts 图表承担着展示生产状态、环境监测、人员行为等核心数据的重任。为了确保图表在不同分辨率下依然保持清晰、布局合理,图表容器与图表自身的缩放适配能力就显得尤为关键。


1. 图表容器宽高与响应式设计

图表的显示区域需要具备以下能力:

  • 支持 自适应缩放:不同屏幕下等比例缩放;
  • 保持 宽高比不失真:尤其是环状图、雷达图等;
  • 配合 transform: scale()resize 事件重新渲染图表。

推荐做法是:使用百分比宽高 + resizeObserver/监听容器变化重新调整图表尺寸

<!-- 示例:响应式图表容器 -->
<template>
  <div ref="chartRef" class="w-full h-full" />
</template>

<script setup>
import { onMounted, ref, onBeforeUnmount } from 'vue'
import * as echarts from 'echarts'

const chartRef = ref(null)
let chartInstance = null

const initChart = () => {
  if (!chartRef.value) return
  chartInstance = echarts.init(chartRef.value)
  chartInstance.setOption({
    title: { text: '环境温湿度' },
    xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
    yAxis: { type: 'value' },
    series: [{ type: 'line', data: [120, 200, 150] }]
  })
}

const resizeChart = () => {
  if (chartInstance) chartInstance.resize()
}

onMounted(() => {
  initChart()
  window.addEventListener('resize', resizeChart)
})

onBeforeUnmount(() => {
  window.removeEventListener('resize', resizeChart)
})
</script>

2. ECharts 图表组件化封装

为了提升图表复用性,我们建议封装一个通用 <BaseChart /> 组件,通过 props 传入图表配置项、主题、颜色、图表类型等参数,实现图表“即插即用”。

💡 示例:通用图表组件封装
<!-- components/BaseChart.vue -->
<template>
  <div ref="chartDom" class="w-full h-full" />
</template>

<script setup>
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
import * as echarts from 'echarts'

const props = defineProps({
  option: Object,
  theme: String,
  autoResize: { type: Boolean, default: true }
})

const chartDom = ref(null)
let chart = null

const renderChart = () => {
  if (!chartDom.value) return
  chart = echarts.init(chartDom.value, props.theme || 'light')
  chart.setOption(props.option)
}

const resize = () => chart?.resize()

onMounted(() => {
  renderChart()
  if (props.autoResize) window.addEventListener('resize', resize)
})

onBeforeUnmount(() => {
  if (props.autoResize) window.removeEventListener('resize', resize)
  chart?.dispose()
})

watch(() => props.option, (newOpt) => {
  if (chart && newOpt) {
    chart.setOption(newOpt, true)
  }
}, { deep: true })
</script>

3. 图表缩放适配策略汇总

方式 原理说明 场景适用
echarts.resize() 调用 ECharts 提供的 API,重新计算尺寸 容器宽高变化、页面切换
transform: scale() 容器整体缩放,图表随之变化(字体可能模糊) 大屏场景,全页面缩放
vw/rem + media query CSS 媒体查询+相对单位 小屏、PC端响应式布局
ResizeObserver 监听容器尺寸变化,自动触发图表调整 多区域自适应图表容器

4. 常见图表适配示例

饼图适配:
option = {
  tooltip: { trigger: 'item' },
  legend: { top: '5%', left: 'center' },
  series: [
    {
      type: 'pie',
      radius: ['40%', '70%'],
      avoidLabelOverlap: false,
      label: { show: false },
      data: [{ name: '正常', value: 60 }, { name: '告警', value: 40 }]
    }
  ]
}
柱状图自适应字体大小:
xAxis: {
  axisLabel: {
    fontSize: 12 * scale, // 根据大屏缩放比例动态设置
    rotate: 30
  }
}

scale 可以从外部传入当前页面的缩放比例,由父组件或布局容器提供。


5. 图表性能优化建议

  • ⚙️ 避免频繁 setOption 全量渲染,使用 setOption(option, false) 增量更新;
  • ⚙️ 图表大量数据时,开启 samplingdataZoom
  • ⚙️ 可视区域以外图表延迟加载,减少初始渲染压力;
  • ⚙️ 批量图表封装 IntersectionObserver 实现懒加载;
  • ⚙️ 页面缩放时 节流 resize 函数调用

三、环境监测与生产状态图表设计

智慧工地的大屏可视化不仅承担数据汇总展示的作用,更承担着实时环境监控、生产效率监管与异常告警的任务。在实际开发中,围绕“环境监测”和“生产状态”这两个维度,我们需要设计一套清晰、动态、交互友好的图表系统。


1. 业务场景需求分析

✅ 环境监测图表数据示例:
  • PM2.5、PM10、温湿度、噪音、风速等环境数据;
  • 需要实时展示当前数据 + 近12小时趋势;
  • 某些指标如 PM2.5 超标需高亮告警
✅ 生产状态图表数据示例:
  • 各施工区域或设备的当前工作状态(运转/待机/故障);
  • 各班组产量/工序完成情况;
  • 效率对比图、环比柱状图、占比饼图等。

2. 图表类型设计与适配建议

图表类型 适用场景 展示重点
折线图 温湿度/噪音趋势 实时变化趋势
柱状图 工序完成率/设备运行 直观对比效率
饼图/环形图 当前状态占比 正常/故障比重
仪表盘 实时数值展示 当前 PM2.5 等指标
雷达图 多项环境指标对比 安全等级综合评估

3. 通用图表组件封装方案

基于 Vue3 + ECharts + 组合式 API,我们封装统一的 <ChartCard /> 图表展示组件,实现数据和样式传参即可复用:

📦 示例:ChartCard.vue
<template>
  <div class="chart-card w-full h-full bg-white rounded-xl p-4 shadow-md">
    <div class="flex justify-between items-center mb-2">
      <h3 class="text-lg font-bold">{{ title }}</h3>
      <span v-if="unit" class="text-gray-500 text-sm">单位:{{ unit }}</span>
    </div>
    <BaseChart :option="option" :theme="theme" />
  </div>
</template>

<script setup>
import { computed } from 'vue'
import BaseChart from './BaseChart.vue'

const props = defineProps({
  title: String,
  unit: String,
  chartData: Array,
  type: String,
  theme: String
})

// 根据 type 动态生成 ECharts 配置
const option = computed(() => {
  switch (props.type) {
    case 'line':
      return {
        xAxis: { type: 'category', data: props.chartData.map(i => i.time) },
        yAxis: { type: 'value' },
        series: [{ data: props.chartData.map(i => i.value), type: 'line' }]
      }
    case 'gauge':
      return {
        series: [{
          type: 'gauge',
          data: [{ value: props.chartData[0]?.value || 0 }],
          detail: { valueAnimation: true }
        }]
      }
    // 其他类型可扩展
    default:
      return {}
  }
})
</script>

使用方式:

<ChartCard
  title="PM2.5 实时监测"
  unit="μg/m³"
  type="gauge"
  :chartData="[{ value: 55 }]"
/>

4. 数据格式与动态更新设计

数据应统一设计格式,方便传入图表组件,例如:

// 后端推送或接口获取数据格式
{
  env: {
    pm25: 55,
    humidity: 46,
    temperature: 27.3,
    noise: 56,
    windSpeed: 3.5,
    timestamp: "2025-04-21 10:00:00"
  },
  production: [
    { area: '塔吊A区', status: '运行中' },
    { area: '升降梯B区', status: '故障' },
    ...
  ]
}

动态更新通过 props 数据响应 + ECharts 的 setOption({ ... }, true) 实现。

如实时线图追加方式:

const updateLineChart = (newData) => {
  const option = chartInstance.getOption()
  option.series[0].data.push(newData.value)
  option.xAxis[0].data.push(newData.time)
  chartInstance.setOption(option, true)
}

5. 状态告警与高亮处理

可通过图表颜色变化、背景高亮等方式提示异常,如:

color: value > limit ? 'red' : 'green'

仪表盘中:

axisLine: {
  lineStyle: {
    color: [
      [0.6, '#91cc75'], // 安全
      [0.8, '#fac858'], // 预警
      [1, '#ee6666']     // 危险
    ]
  }
}

6. 多图联动布局(组合图表)

通过 grid 布局、Flex 容器将多个图表组合,形成板块式视图:

<div class="grid grid-cols-3 gap-4">
  <ChartCard title="PM2.5" :chartData="pmData" type="gauge" />
  <ChartCard title="温湿度" :chartData="tempData" type="line" />
  <ChartCard title="噪音水平" :chartData="noiseData" type="bar" />
</div>

四、实时数据推送:WebSocket/SSE 实践

智慧工地的大屏展示系统要求数据实时性强,尤其在环境监测、设备状态、告警提醒等模块中,传统轮询方式无法满足“秒级响应”的需求。此时,WebSocketSSE(Server-Sent Events) 成为实现“前后端实时通信”的关键技术。


1. WebSocket / SSE 技术简介

特性 WebSocket SSE(Server-Sent Events)
双向通信 ✅ 支持(客户端与服务端双向) ⛔ 仅服务端推送
通道持久化 ✅ 支持持久连接 ✅ 支持持久连接
支持断线重连 ✅ 需手动实现 ✅ 内置重连
应用场景 聊天、物联网状态、工地设备告警 实时数据推送,如温度、湿度曲线等
浏览器兼容性 ✅ 所有现代浏览器 ✅ 更佳兼容性,但 IE 不支持

本文以 WebSocket 为主,SSE 可用于环境数据轻量推送作为备选方案。


2. 实践场景与数据示例

✅ 场景1:PM2.5 实时监测
  • 服务端每隔5秒推送当前传感器数据;
  • 前端自动追加到折线图末尾,模拟趋势变化。
✅ 场景2:设备状态告警推送
  • 如升降梯故障、施工机械过热;
  • 服务端推送告警状态,前端图表变色高亮。

3. Vue3 中封装 WebSocket 通用模块

我们可以封装一个 WebSocket 管理模块 useWebSocket.js,支持自动重连、消息处理回调、统一状态管理等。

📦 示例代码:/composables/useWebSocket.js
import { ref, onUnmounted } from 'vue'

export function useWebSocket(url, onMessage) {
  const ws = ref(null)
  const isConnected = ref(false)
  let reconnectTimer = null

  const connect = () => {
    if (ws.value) ws.value.close()

    ws.value = new WebSocket(url)

    ws.value.onopen = () => {
      isConnected.value = true
      console.log('[WebSocket] 已连接')
    }

    ws.value.onmessage = (e) => {
      const data = JSON.parse(e.data)
      onMessage && onMessage(data)
    }

    ws.value.onerror = (e) => {
      console.error('[WebSocket] 错误:', e)
    }

    ws.value.onclose = () => {
      isConnected.value = false
      console.warn('[WebSocket] 连接关闭,5秒后尝试重连...')
      reconnectTimer = setTimeout(connect, 5000)
    }
  }

  onUnmounted(() => {
    if (ws.value) ws.value.close()
    clearTimeout(reconnectTimer)
  })

  connect()

  return {
    isConnected,
    send: (msg) => ws.value?.send(JSON.stringify(msg)),
  }
}

4. 图表组件接收 WebSocket 数据并更新

以折线图为例,结合 ECharts setOption() 动态更新图表数据:

// chartData 是响应式数组
const chartData = ref([])

const { isConnected } = useWebSocket('wss://api.example.com/ws/env', (data) => {
  // 假设 data: { type: 'pm25', value: 55, time: '10:00' }
  chartData.value.push({ name: data.time, value: data.value })
  // 限制最多保留30个点
  if (chartData.value.length > 30) chartData.value.shift()
})

<ChartCard /> 中使用 watchEffect 监听 chartData 变化:

watchEffect(() => {
  myChart.setOption({
    xAxis: { data: chartData.value.map(d => d.name) },
    series: [{ data: chartData.value.map(d => d.value) }]
  }, true)
})

5. WebSocket 消息处理通用设计建议

建议服务端推送数据统一格式,如:

{
  "type": "pm25",
  "value": 68,
  "unit": "μg/m³",
  "time": "2025-04-21 10:30:12"
}

前端根据 type 区分展示内容,提高拓展性。


6. SSE 简要使用示例(备选)

如服务端支持 SSE,可通过如下方式连接:

const evtSource = new EventSource('https://api.example.com/sse/env')

evtSource.onmessage = function(e) {
  const data = JSON.parse(e.data)
  console.log('[SSE] 接收到数据:', data)
}

7. 实时推送小结与优化建议

建议 说明
消息格式标准化 保证 type + value + time 结构
多图表场景下使用 type 分发 一套 WebSocket 连接服务多个模块
后端支持心跳机制 保持长连接稳定性
前端封装自动重连逻辑 应对偶发断线问题
图表更新加节流 避免频繁重绘卡顿

五、告警数据的图形高亮与动画提醒

在图表中高亮告警项,可以通过以下方式:

  • ECharts 动态更新数据点样式
  • 添加闪烁动画(如定时更换颜色)
  • 结合动画库实现弹出提示
// 更新告警状态样式
chart.setOption({
  series: [{
    name: '粉尘浓度',
    markPoint: {
      data: [{ type: 'max', name: '最大值' }],
      itemStyle: { color: 'red' }
    }
  }]
})

五、告警数据的图形高亮与动画提醒

1. 告警信息的识别与结构定义

通常,告警数据从后端实时推送,或定期轮询而来。建议统一定义告警消息的结构:

{
  "type": "temperature",
  "level": "warning",
  "value": 88,
  "location": "塔吊#3",
  "time": "2025-04-21 15:12:34",
  "msg": "温度过高,超出阈值"
}

前端根据 type 识别图表模块,根据 level 渲染不同颜色/动画。


2. 图表中的告警高亮处理

✅ 示例场景:柱状图中某设备温度过高,需高亮该柱体

我们可在图表初始化后动态修改指定数据项的样式:

myChart.setOption({
  series: [{
    type: 'bar',
    data: chartData.map(item => ({
      value: item.value,
      itemStyle: item.value > item.warn ? {
        color: '#ff4d4f',
        shadowColor: '#ff7875',
        shadowBlur: 10
      } : {}
    }))
  }]
})

如果你使用的是封装组件,只需传入 warn 字段,组件内部封装处理:

<ChartBar :data="deviceList" :warnKey="'warn'" :valueKey="'value'" />

3. 使用 ECharts 动画实现跳动/闪烁效果

告警时可以通过颜色渐变动画、放大缩小动画增强提醒效果。例如:

✅ 圆点闪烁动画:
symbol: 'circle',
symbolSize: 18,
itemStyle: {
  color: '#ff4d4f',
  opacity: 0.8
},
emphasis: {
  scale: true
}
✅ 使用 timelinesetInterval 控制样式闪烁:
let isRed = true
setInterval(() => {
  myChart.setOption({
    series: [{
      itemStyle: {
        color: isRed ? '#ff4d4f' : '#fa8c16'
      }
    }]
  })
  isRed = !isRed
}, 600)

⚠️ 注意:频繁更新图表可能引发性能问题,建议节流控制或对小型组件单独处理。


4. 整合 WebSocket 告警推送与图表响应

将告警消息绑定图表响应逻辑:

const { isConnected } = useWebSocket('wss://api.example.com/ws/alerts', (alert) => {
  if (alert.type === 'temperature') {
    updateChartHighlight(alert.location, alert.value)
    notifyUser(alert.msg)
  }
})

updateChartHighlight 为封装函数,标记指定设备:

function updateChartHighlight(name, value) {
  const index = chartData.findIndex(item => item.name === name)
  if (index !== -1) {
    chartData[index].value = value
    chartData[index].isWarn = true
  }
}

5. 提示与用户交互联动

除了图表变化,我们还可以通过以下方式进一步提醒用户:

  • 顶部/右侧浮动告警面板
  • 播放提示音(可选)
  • 点击图表高亮项跳转告警详情页

示例代码:

<template>
  <u-notify :show="showAlert" type="error" message="塔吊#3温度过高" />
</template>

<script setup>
const showAlert = ref(false)
watch(alertData, () => {
  showAlert.value = true
  setTimeout(() => showAlert.value = false, 3000)
})
</script>

6. 动态聚焦与视图滚动联动(适用于地图 + 列表)

若页面存在地图或设备列表,可以通过告警触发视图自动滚动或聚焦设备图标(适配地图场景):

map.panTo([alert.lng, alert.lat])
highlightDeviceMarker(alert.id)
scrollToDevice(alert.id)

7. 小结与优化建议

功能要点 建议做法
告警状态样式区分 根据 level 区分颜色:正常、预警、严重等
动画效果 控制频率,适度使用 shadowscale 动效
多图联动 统一状态标识(如设备ID),实现图表+地图联动
用户可交互操作 点击跳转、查看历史数据、确认告警等
封装图表告警能力 提供如 highlightWarn(name) 方法方便外部调用

七、总结与扩展建议

本篇我们实现了基于 Vue3 + ECharts 的多种数据监控图表:

  • 实现响应式大屏适配与组件封装;
  • 支持 WebSocket 推送,实现真正的“实时监测”;
  • 灵活切换图表主题,提升视觉统一性;
  • 模块结构可支持快速扩展:如 AI 预测分析、安全预警等级评估等。

扩展建议:

  • 加入大屏轮播(多个图表定时切换展示);
  • 引入数据缓存与降采样策略,提升大屏性能;
  • 集成图表下载、报表生成等能力,增强业务支持。
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐