不用手动刷新,echarts文字也可自动自适应,vue全局ECharts组件封装与自适应实践,

108 阅读2分钟

效果图演示

在大屏可视化开发过程中,ECharts 几乎是前端标配。但实际使用中,往往会遇到以下几个常见问题:

全局复用困难:如果每个页面都单独引入并初始化 ECharts,代码会很冗余。

容器自适应问题:窗口或容器尺寸变化时,图表需要随之自适应,但 window.resize 并不总是好用。

字体无法随缩放变化:图表元素缩放了,但文字字号往往固定不变,导致比例失衡。

地图注册、轮播高亮等业务扩展:大屏项目中常常需要动态地图、自动高亮系列数据等效果。

为了解决这些问题,我封装了一个 全局 EChart 组件,不仅能实现自动适配,还能统一处理字体缩放、地图注册和轮播展示等逻辑。

功能亮点

全局封装:通过 标签即可使用,不再关心初始化逻辑。

容器级 resize:基于 ResizeObserver 实现,比 window.onresize 更精准。

字体自适应:结合 $h() 方法,保证不同屏幕下字体也能等比缩放。

地图注册:支持 mapJson + mapName,动态注册地图。

暴露实例:通过 getChart() 获取 ECharts 实例,方便父组件调用。

使用方式

  1. 引入组件
<EChart 
  id="one-two" 
  height="18vh" 
  width="22vw"
  :options="generateOption()" 
  :regOption="generateOption"
/>

  1. 在父组件中生成配置项
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)
      }
    }
  }
}