Vue3 EMS 项目实战:ECharts 按需封装与 ZoomChart 图表放大

系列:组件与工具专题
本篇主题echarts.ts 按需引入 + Echart 组件 + ZoomChart + 大屏适配
源码src/utils/echarts.tssrc/components/Echart/src/components/ZoomChart/index.vue


一、EMS 项目的图表需求

储能 EMS 页面图表密度高:

  • 电量曲线、SOC 曲线、收益柱状图
  • 液位图(liquidfill)、3D 关系图(echarts-gl)
  • 侧边栏折叠/展开时图表需自动 resize
  • 小图点击「放大」查看细节

本项目封装了 Echart 基础组件和 ZoomChart 放大组件,并统一主题与尺寸缩放。


二、ECharts 按需引入,控制包体积

// utils/echarts.ts
import * as echarts from 'echarts/core'
import 'echarts-liquidfill'
import { LineChart, BarChart, PieChart, GraphChart, ScatterChart, LinesChart, TreeChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, GridComponent, LegendComponent, DataZoomComponent, ... } from 'echarts/components'
import { CanvasRenderer } from 'echarts/renderers'

echarts.use([
  LineChart, BarChart, PieChart, ScatterChart, TreeChart, GraphChart, LinesChart,
  TitleComponent, TooltipComponent, GridComponent, LegendComponent, DataZoomComponent,
  CanvasRenderer,
])
export default echarts

好处:只打包用到的 chart / component,避免 import echarts from 'echarts' 全量引入。

新增图表类型时,在 echarts.use([...]) 中注册即可。


三、Echart 组件:生命周期与 resize

<template>
  <div :id="id" ref="chartDom" class="__echart__hnt" :style="{ height, width }"></div>
</template>

3.1 初始化与 markRaw

onMounted(async () => {
  await nextTick()
  myChart.value = markRaw(echarts.init(chartDom.value, 'normal'))
  myChart.value.setOption(props.options, true)
  emit('mountEchat', myChart.value)
})

markRaw 避免 ECharts 实例被 Vue 代理,防止性能问题和奇怪 bug。

3.2 三重 resize 保障

触发源 处理方式
窗口 resize window.addEventListener('resize', initChart)
全屏切换 document.addEventListener('fullscreenchange', resizeHandler)
容器尺寸变化 ResizeObserver 监听 chartDom(侧边栏折叠场景)
resizeObserver = new ResizeObserver(() => {
  resizeHandler()
})
resizeObserver.observe(chartDom.value)

3.3 统一主题 normal

./normal.ts 定义 EMS 品牌色、坐标轴、tooltip 等默认样式,refreshNormal() 在 init 前刷新主题变量,保证多图表视觉一致。

3.4 数据点 clickFn

option 的 data 项可挂 clickFn,组件内统一监听:

myChart.value.on('click', (params) => {
  params.data?.clickFn?.(params.data, params)
})

鼠标 hover 时自动切换 cursor 为 pointer。


四、sizeFn:1920 基准的大屏缩放

EMS 大屏按 1920 设计,小屏需等比缩放字号和间距:

export const sizeFn = (val?: number) => {
  let clientWidth = window.innerWidth || document.documentElement.clientWidth
  let scale = clientWidth / 1920
  if (val === undefined) return scale
  return val * scale
}

Echart 组件配合 useSizeStore,在 resize 时重新计算 option 中的 fontSize、grid 等。


五、ZoomChart:一键放大图表

小模块嵌入的图表空间有限,用户需要放大查看。ZoomChart 封装「放大镜按钮 + 弹窗大图」:

<ZoomChart
  :title="$t('100xxxx')"
  :options="chartOptions"
  width="70%"
  chart-height="550px"
/>

5.1 Teleport 浮动按钮

按钮通过 Teleport 挂到图表容器右下角,不破坏原有布局:

const findAnchorContainer = () => {
  // 向上查找 .chart / .chartBox 容器
  for (const selector of anchorSelectorList.value) {
    const found = triggerMount.value?.closest(selector)
    if (found) return found
  }
  return triggerMount.value?.parentElement
}

5.2 弹窗内复用 Echart

点击放大后打开 h-dialog,内部渲染同一份 cloneDeep(options) 的 Echart,宽高可配置。

const showFn = () => {
  chartOptions.value = cloneDeep(props.options)
  showModal.value = true
}

Props 亮点

Prop 作用
noButton 隐藏按钮,外部控制打开
autoAnchor 自动定位到父 chart 容器
fullscreen 全屏弹窗模式
anchorSelectors 自定义锚点选择器

六、compareFun:实时数据涨跌标记

BMS 实时监控需对比新旧值,ECharts rich text 中展示涨跌箭头:

export function compareFun(oldVal: string | number, newVal: string | number) {
  oldVal = Number(oldVal)
  newVal = Number(newVal)
  if (isNaN(oldVal) || isNaN(newVal)) return ''
  if (oldVal > newVal) return '{down|}'
  if (oldVal < newVal) return '{rise|}'
  return ''
}

配合 HDataItem 组件在数值旁展示趋势(见专题第 8 篇)。


七、典型页面用法

<template>
  <div class="chartBox">
    <Echart :options="lineOptions" height="280px" @mountEchat="onChartReady" />
    <ZoomChart title="电量曲线" :options="lineOptions" />
  </div>
</template>

<script setup>
const lineOptions = computed(() => ({
  xAxis: { type: 'category', data: times.value },
  yAxis: { type: 'value' },
  series: [{ type: 'line', data: values.value }],
}))
</script>

八、本篇小结

模块 职责
echarts.ts 按需注册 chart/component
Echart init、resize、主题、事件
ZoomChart 放大镜 + Dialog 大图
sizeFn 1920 基准缩放
compareFun 涨跌 rich text

踩坑

  1. ECharts 实例必须 markRaw,否则 tooltip 异常
  2. 侧边栏动画结束后图表可能尺寸不对,靠 ResizeObserver 而非仅 window resize
  3. ZoomChart 打开时用 cloneDeep,避免弹窗内外 option 互相污染

源码索引src/utils/echarts.tssrc/components/Echart/index.vuesrc/components/ZoomChart/index.vuesrc/utils/common.ts

更多推荐