可视化大屏是 Vue 在中后台和物联网场景中的典型应用,通常需要展示多个图表、数字指标、地图等。本节聚焦三个最核心的问题:页面怎么在不同比例的屏幕上自适配、ECharts 图表如何封装成好用的 Vue 组件、数据如何实时刷新且不乱序。
自适应布局:一次开发,多屏适配
大屏的显示设备五花八门,可能是 16:9 的电视、21:9 的长屏,甚至竖屏。直接用 vw/vh 或媒体查询很难保证设计稿 100% 还原。目前最主流的方案是 比例缩放(Scale),核心思路是设定一个基准分辨率(通常按设计稿,如 1920×1080),然后根据实际窗口大小计算缩放比例,对整个容器做 CSS transform: scale。
实用实现:
- 在
App.vue或页面根组件中,监听窗口resize和页面load事件。 - 定义一个工具方法,根据设计稿宽高计算出合适的缩放比例(通常取
Math.min(窗口宽/设计宽, 窗口高/设计高),这样能保持比例并完整显示,左右或上下可能会留黑边,用背景色填补)。 - 将缩放比例设置给包装元素
div#app的transform,并调整transform-origin为left top,防止偏移。
<template>
<div class="scale-wrapper" :style="scaleStyle">
<router-view /> <!-- 大屏的实际内容 -->
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const designWidth = 1920
const designHeight = 1080
const scale = ref(1)
function updateScale() {
const w = window.innerWidth
const h = window.innerHeight
scale.value = Math.min(w / designWidth, h / designHeight)
}
const scaleStyle = computed(() => ({
width: `${designWidth}px`,
height: `${designHeight}px`,
transform: `scale(${scale.value})`,
transformOrigin: 'left top',
position: 'absolute',
left: '50%',
marginLeft: `-${designWidth / 2}px`, // 水平居中(可选)
}))
onMounted(() => {
updateScale()
window.addEventListener('resize', updateScale)
})
onUnmounted(() => window.removeEventListener('resize', updateScale))
</script>
<style>
body { margin: 0; overflow: hidden; background: #000; }
</style>
如果希望铺满全屏不留黑边,可以使用更激进的方案:scaleX = 窗口宽/设计宽、scaleY = 窗口高/设计高 分别缩放,但这会导致图表变形。一般大屏更倾向于保持比例,牺牲边缘区域。
另一种方案是使用 CSS 媒体查询配合 rem/vw,但面对非标准分辨率时工作量巨大,不如 scale 方案简洁可靠。
ECharts 组件封装:一次配置,处处复用
在可视化大屏中,ECharts 是事实上的标准。手动在每个组件里写 echarts.init、setOption 不仅重复,还容易因为重复初始化、销毁、数据更新不同步引发问题。封装一个通用的 <BaseChart> 组件能极大提高开发效率。
设计要点:
- 接收
options(ECharts 配置项)和theme(主题)prop。 - 内部持有
chart实例,在onMounted初始化,onUnmounted销毁(调用chart.dispose())。 - 监听
options变化(watch),用chart.setOption(newOptions)更新图表。注意使用notMerge: false(默认合并)或手动合并。 - 监听窗口尺寸变化或外部传入的
height,调用chart.resize()。 - 支持事件透传(如
click、hover),通过emits抛出给父组件。
代码示例:
<template>
<div ref="chartRef" :style="{ width: '100%', height: props.height || '400px' }" />
</template>
<script setup>
import * as echarts from 'echarts'
import { ref, onMounted, onUnmounted, watch } from 'vue'
const props = defineProps({
options: Object,
theme: { type: String, default: 'dark' }
})
const emit = defineEmits(['chartClick', 'chartReady'])
const chartRef = ref(null)
let chartInstance = null
function initChart() {
if (!chartRef.value) return
// 如果已有实例,先销毁再重建(当 theme 变化时)
if (chartInstance) {
chartInstance.dispose()
}
chartInstance = echarts.init(chartRef.value, props.theme)
chartInstance.setOption(props.options || {})
// 常用事件透传
chartInstance.on('click', (params) => emit('chartClick', params))
emit('chartReady', chartInstance)
}
// 监听 options 变化更新图表
watch(
() => props.options,
(newOpts) => {
if (!chartInstance || !newOpts) return
chartInstance.setOption(newOpts, { notMerge: true }) // 全量替换,避免旧配置残留
},
{ deep: true } // 深度监听,但大数据量时可能影响性能,可考虑 lodash isEqual
)
// 监听 theme 变化重建
watch(() => props.theme, () => {
initChart()
})
// 响应窗口大小变化
function onResize() {
chartInstance?.resize()
}
onMounted(() => {
initChart()
window.addEventListener('resize', onResize)
})
onUnmounted(() => {
window.removeEventListener('resize', onResize)
chartInstance?.dispose()
})
</script>
使用方式:
<BaseChart :options="lineChartOptions" @chartClick="handleChartClick" />
这样,大屏页面中的各个图表组件只要关注数据计算,生成不同的 options 对象即可。所有图表的主题、交互、自适应都由这一个组件负责,避免了大量重复代码。
数据刷新:实时、有序、不抖动
大屏通常需要实时展示后端推送的数据(WebSocket 或轮询),数据源源不断到来,图表需要平滑更新而不是整页闪烁。核心要处理好三个问题:
1. 更新频率控制
避免每个数据点都触发一次 setOption,可以设置一个更新间隔(如 1 秒),将这段时间内到达的所有数据合并后一次性更新。
import { useWebSocket } from '@/hooks/useWebSocket'
const { data: wsData } = useWebSocket('ws://...')
const mergedData = ref([])
// 使用 computed 或 watch + debounce 控制更新
watchEffect(() => {
if (wsData.value.length) {
// 假设 wsData 是一个缓冲数组,每隔 1 秒整批更新
mergedData.value = [...mergedData.value, ...wsData.value]
updateChartOptions(mergedData.value)
}
})
2. 图表数据更新而非重新创建实例
已经封装的 BaseChart 内部通过 watch 自动更新,传入新的 options 即可。对于时间序列折线图,要注意 setOption 的 notMerge 参数:如果只想追加新点,应该用 notMerge: false(默认)并仅传入增量数据;但为了避免配置污染,更稳妥的做法是每次传入完整的 options,setOption 会默认合并。
若使用 notMerge: true(全量替换),可以避免旧 series 配置残留,但全量设置大数据量时性能稍高。对于大屏可酌情使用。
3. 防止内存泄漏与竞态
WebSocket连接要在组件卸载时关闭。- 如果同时有多个请求(轮询),需用
AbortController或请求标识防止旧请求返回覆盖新数据。 - 图表实例的
dispose必须执行,否则组件卸载后仍然挂在 ECharts 的实例池中。
真实建议:不要追求“每个数据点实时绘制”,人眼分辨能力有限,1-2 秒更新一次完全够用。将数据在一个缓存队列中攒一攒再更新,既能保证流畅,也减轻后端的推送压力。
总结:可视化大屏的技术重点在于“看起来一致、改起来方便、跑起来稳定”。Scale 缩放让界面在各种屏幕上完美复现设计稿;通用图表组件让 90% 的图表开发变成填 JSON;合理地控制数据刷新节奏,让整个大屏流畅而不卡顿。这三点搞定,剩下的就是让 UI 设计师尽情发挥。