在大屏可视化开发过程中,ECharts 几乎是前端标配。但实际使用中,往往会遇到以下几个常见问题:
全局复用困难:如果每个页面都单独引入并初始化 ECharts,代码会很冗余。
容器自适应问题:窗口或容器尺寸变化时,图表需要随之自适应,但 window.resize 并不总是好用。
字体无法随缩放变化:图表元素缩放了,但文字字号往往固定不变,导致比例失衡。
地图注册、轮播高亮等业务扩展:大屏项目中常常需要动态地图、自动高亮系列数据等效果。
为了解决这些问题,我封装了一个 全局 EChart 组件,不仅能实现自动适配,还能统一处理字体缩放、地图注册和轮播展示等逻辑。
功能亮点
全局封装:通过 标签即可使用,不再关心初始化逻辑。
容器级 resize:基于 ResizeObserver 实现,比 window.onresize 更精准。
字体自适应:结合 $h() 方法,保证不同屏幕下字体也能等比缩放。
地图注册:支持 mapJson + mapName,动态注册地图。
暴露实例:通过 getChart() 获取 ECharts 实例,方便父组件调用。
使用方式
- 引入组件
<EChart
id="one-two"
height="18vh"
width="22vw"
:options="generateOption()"
:regOption="generateOption"
/>
- 在父组件中生成配置项
methods: {
generateOption (type) {
const fz = this.$h(14) // 自适应字号方法
return {
title: {
text: '示例图表',
textStyle: {
fontSize: fz
}
},
tooltip: {},
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ type: 'bar', data: [5, 20, 36] }]
}
}
}
这样,图表中的文字就能随着容器缩放而缩放。
核心实现代码
<template>
<div
class="charts"
ref="charts"
:id="id"
:class="className"
:style="{ height: height, width: width }"
/>
</template>
js部分
import { shallowRef } from 'vue'
import ResizeObserver from 'resize-observer-polyfill'
export default {
name: 'EChart',
props: {
className: { type: String, default: 'chart' },
id: { type: String, default: 'chart' },
width: { type: String, default: '100%' },
height: { type: String, default: '2.5rem' },
options: { type: Object, default: () => ({}) },
regOption: { type: Function, default: null },
mapJson: { type: Object, default: () => ({}) },
mapName: { type: String, default: '' },
loopSeries: { type: Boolean, default: false },
loopInterval: { type: Number, default: 3000 }
},
data () {
return {
chart: null,
charts: null,
resizeObserver: null,
}
},
watch: {
options: {
handler (options) {
this.chart?.showLoading()
this.chart?.setOption(options, true) // 覆盖更新
this.chart?.hideLoading()
},
deep: true
},
mapJson: {
handler (options) {
if (options && this.mapName !== '') {
this.$echarts.registerMap(this.mapName, options)
}
this.chart?.setOption(this.options, true)
},
deep: true,
}
},
mounted () {
this.charts = this.$refs.charts
this.initChart()
// 监听容器尺寸变化
const debouncedResize = this.debounce(() => {
this.resizeChartMethod()
}, 200)
this.resizeObserver = new ResizeObserver(debouncedResize)
this.resizeObserver.observe(this.charts)
},
activated () {
this.chart?.resize()
},
beforeDestroy () {
if (this.resizeObserver) {
this.resizeObserver.unobserve(this.charts)
this.resizeObserver.disconnect()
}
if (this.chart) {
this.chart.dispose()
this.chart = null
}
},
methods: {
getChart () {
return this.chart
},
async resizeChartMethod () {
const newOpt = this.regOption()
await this.chart?.setOption(newOpt, true)
await this.chart?.resize()
},
async initChart () {
this.chart = shallowRef(this.$echarts.init(this.charts, 'tdTheme'))
if (this.mapName !== '') {
this.$echarts.registerMap(this.mapName, this.mapJson)
}
this.chart.showLoading()
await this.chart.setOption(this.options, true)
await this.chart.hideLoading()
},
debounce (fn, delay = 300) {
let timer = null
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => fn.apply(this, args), delay)
}
}
}
}