Vue3 项目中 echarts 封装

5,178 阅读2分钟

整理下项目中 echarts 的封装方式,利用 Vue3 的组合 API 对 echarts 的通用逻辑进行封装。在使用 echarts 组件的时候,往往数据不是静态不变的,而是需要我们从后端异步查询,然后再赋值给图表组件。项目中使用 antdv 动态加载组件 Spin,表示加载数据中。如果没有符合条件的数据,则需要显示数据为空的样式。如果大家有更好的方案,欢迎留言评论,我们一起讨论。

useECharts 逻辑封装

在 useECharts 中,按需引入 ECharts 图表和组件;并对图表初始化、监听图表容器变化进行封装。将 ECharts 实例使用 shallowRef 定义,而不是 ref 类型,这样 Proxy 不会应用到 ECharts 实例底下的各个属性上。否则浏览器大小 resize 变化的时候会报错。详见:github.com/apache/echa…

// useECharts.ts
import * as echarts from 'echarts/core'
import {
  BarChart,
  LineChart,
  LineSeriesOption,
  PieChart,
  PieSeriesOption,
} from 'echarts/charts'
import {
  LegendComponent,
  LegendComponentOption,
  TitleComponent,
  // 组件类型的定义后缀都为 ComponentOption
  TitleComponentOption,
  TooltipComponent,
  TooltipComponentOption,
  GridComponent,
  GridComponentOption,
  // 数据集组件
  DatasetComponent,
  DatasetComponentOption,
  // 内置数据转换器组件 (filter, sort)
  TransformComponent,
} from 'echarts/components'

import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'

import { shallowRef, onMounted, onBeforeUnmount } from 'vue'

// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = echarts.ComposeOption<
LineSeriesOption
| PieSeriesOption
| LegendComponentOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
>

// 注册必须的组件
echarts.use([
  LegendComponent,
  TitleComponent,
  TooltipComponent,
  GridComponent,
  DatasetComponent,
  TransformComponent,
  BarChart,
  LineChart,
  PieChart,
  LabelLayout,
  UniversalTransition,
  CanvasRenderer,
])

export default function useChart() {
  // https://github.com/apache/echarts/issues/13943
  const canvasEl = shallowRef()
  const myChart = shallowRef()

  const resizeFn = () => {
    myChart.value?.resize()
  }

  onMounted(() => {
    myChart.value = echarts.init(canvasEl.value)
    window.addEventListener('resize', resizeFn)
  })

  onBeforeUnmount(() => {
    window.removeEventListener('resize', resizeFn)
    myChart.value?.dispose()
    myChart.value = null
  })

  return {
    myChart,
    canvasEl,
  }
}

MyECharts 组件封装

定制了图表加载态 loading; 图表数据为空时的属性 dataEmptyFlag。通过监听 options 的变化,更新绑定 myChart 的 option。

注意: 组件 propsoptionsref 类型,也就是整体赋值操作 options 才会触发组件 watch 的响应。这样设计可以避免深层监听 options 的变化,影响性能。

// MyECharts.vue
<template>
  <div>
    <div class="chart-container" :style="containerStyle">
      <div v-show="dataEmptyFlag" class="chart-empty">
        <span class="empty-title">没有符合条件的内容</span>
        <span>请修改查询条件后重试</span>
      </div>
      <div
        ref="canvasEl"
        :style="containerStyle"
      />
      <div
        v-show="loading"
        class="chart-loading"
      ><Spin /></div>
    </div>
  </div>
</template>
<script setup lang="ts">
  import { Spin } from 'ant-design-vue'
  import {
    ref, watch,
  } from 'vue'
  import useChart from './useECharts'
  import type { ECOption } from './useECharts'


  interface ChartProps{
    containerStyle?: any
    loading: boolean
    dataEmptyFlag: boolean
    options: ECOption
  }

  const props = withDefaults(defineProps<ChartProps>(), {
    containerStyle: {
      height: '350px',
      width: '600px'
    },
    loading: false,
    dataEmptyFlag: false,
  })

  const { myChart, canvasEl } = useChart()

  watch(() => props?.options,(cur)=>{
    if(myChart){
      myChart.value.setOption(cur)
    }
  })

</script>
<style scoped>
.gov-line-chart-name {
	color: #000000;
	font-size: 14px;
	font-weight: 500;
	height: 22px;
	line-height: 22px;
}
.chart-container {
	position: relative;
	width: 100%;
}
.chart-loading {
	align-items: center;
	background-color: #ffffff;
	bottom: 0;
	display: flex;
	justify-content: center;
	left: 0;
	position: absolute;
	right: 0;
	top: 0;
	z-index: 1999;
}
.chart-empty{
  position: absolute;
  z-index: 1;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  color: #888;
  font-size: 14px;
  width: 100%;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}
.empty-title{
  font-size: 16px;
  font-weight: 500;
  color: #000;
}
</style>

使用

在 App.vue 中使用如下:

<template>
    <MyECharts :loading="loading" :options="options"></MyECharts>
</template>
<script lang="ts" setup>
  import { ref, onMounted, } from 'vue'
  import MyECharts from '../components/MyECharts.vue'
  const loading = ref(false)
  const dataEmptyFlag = ref(false)
  const options = ref()
  const fetchData = () => new Promise((resolve)=>{
    setTimeout(()=>{
      resolve({
        xData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
        yData: [150, 230, 224, 218, 135, 147, 260],
      })
    }, 1000)
  })
  // 基本不变的 echarts 属性设置
  const baseOption = {
      xAxis: {
        type: 'category',
        data: []
      },
      yAxis: {
        type: 'value'
      },
      series: []
    }

  const loadECharts = () => {
    loading.value = true
    fetchData().then(res => {
      if(res){
        baseOption.xAxis.data = res.xData
        baseOption.series = []
        baseOption.series.push({
          data: res.yData,
          type: 'line'
        })
      }else{
       //没有数据显示空态
        dataEmptyFlag.value = true
      }
      // 赋值操作
      options.value = {...baseOption}
    }).finally(()=>{
      loading.value = false
    })
  }

  onMounted(loadECharts)
</script>

推荐

echarts图表案例资源:
Made A Pie
ECharts官方案例
MCChart
PPChart

本文正在参加「金石计划」