vue3对echarts图表的二次封装之按需加载,折线图、饼图等

欢迎点击领取-《前端开发面试题进阶秘籍》:前端登顶之巅-最全面的前端知识点梳理总结

效果展示

在这里插入图片描述

1、echarts是我们后台系统中最常用的数据统计图形展示,外界对它的二次封装也不计层数;
2、在业务代码内每次的初始dom和绑定setOption导致代码量的堆积不利于维护
3、拓展公共echarts组件,通过不同入参开启对应的功能更利于维护和排查问题
4、echarts的版本5.4.x

  • 当前代码示例只引用了:BarChart, LineChart, PieChart, GaugeChart 这几种图形类型,其余类型需按需引用,在useCharts.ts内进行
1. 创建v-charts文件夹;可自定义文件名
1.1 创建useCharts.ts 文件

该文件用作于处理和初始化echarts的公用逻辑,抽离出来使用vue3的hooks用来注水操作

1.2 创建v-charts.vue 文件

该文件用作于echarts的dom承载和相关api的入参只有少量逻辑;

2. 相关代码的处理方式:

useCharts.ts文件

import * as echarts from 'echarts/core'
import {
  TitleComponent,
  LegendComponent,
  TooltipComponent,
  GridComponent,
  DatasetComponent,
  TransformComponent,
  ToolboxComponent,
  MarkAreaComponent,
  MarkLineComponent,
  MarkPointComponent
} from 'echarts/components'
import { BarChart, LineChart, PieChart, GaugeChart } from 'echarts/charts'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
import { ShallowRef, shallowRef, Ref, onBeforeUnmount, watch, useAttrs, shallowReactive } from 'vue'

interface ChartHookOption {
  theme?: Ref<string>
  el: ShallowRef<HTMLElement>
  options: any
}

/**
 *  视口变化时echart图表自适应调整
 */
class ChartsResize {
  #charts = new Set<echarts.ECharts>() // 缓存已经创建的图表实例
  #timeId = null
  constructor() {
    window.addEventListener('resize', this.handleResize.bind(this)) // 视口变化时调整图表
  }

  getCharts() {
    return [...this.#charts]
  }

  handleResize() {
    clearTimeout(this.#timeId)
    this.#timeId = setTimeout(() => {
      this.#charts.forEach((chart) => {
        chart.resize()
      })
    }, 350)
  }

  add(chart: echarts.ECharts) {
    this.#charts.add(chart)
  }

  remove(chart: echarts.ECharts) {
    this.#charts.delete(chart)
  }

  removeListener() {
    window.removeEventListener('resize', this.handleResize)
  }
}

export const chartsResize = new ChartsResize()

export const useCharts = ({ el, theme, options }: ChartHookOption) => {
  echarts.use([
    BarChart,
    LineChart,
    BarChart,
    PieChart,
    GaugeChart,
    TitleComponent,
    LegendComponent,
    TooltipComponent,
    GridComponent,
    DatasetComponent,
    TransformComponent,
    LabelLayout,
    UniversalTransition,
    CanvasRenderer,
    ToolboxComponent,
    MarkAreaComponent,
    MarkLineComponent,
    MarkPointComponent
  ])

  const charts = shallowRef<echarts.ECharts>()

  const setOptions = (opt: echarts.EChartsCoreOption) => {
    charts.value.setOption(opt)
  }

  const initChart = () => {
    charts.value = echarts.init(el.value, theme)
    charts.value.setOption(options)
    chartsResize.add(charts.value) // 将图表实例添加到缓存中
    initEvent() // 添加事件支持
  }

  // 初始化事件
  const attrs = useAttrs()

  const initEvent = () => {
    Object.keys(attrs).forEach((attrKey) => {
      if (/^on/.test(attrKey)) {
        const cb = attrs[attrKey]
        attrKey = attrKey.replace(/^on(Chart)?/, '')
        attrKey = `${attrKey[0]}${attrKey.substring(1)}`
        typeof cb === 'function' && charts.value?.on(attrKey, cb as () => void)
      }
    })
  }

  onBeforeUnmount(() => {
    chartsResize.remove(charts.value) // 移除缓存
  })

  return {
    charts,
    setOptions,
    initChart,
    initEvent
  }
}

export const chartsOptions = <T extends echarts.EChartsCoreOption>(option: T) => shallowReactive<T>(option)

v-charts.vue代码模块

<template>
  <div class="v-charts" ref="chartRef" />
</template>

<script lang="ts" setup>
import * as echarts from 'echarts/core'
import { useCharts, chartsResize } from './useCharts'
import { PropType, toRefs, shallowRef, onMounted, watch, nextTick } from 'vue'

const props = defineProps({
  theme: String,
  delay: [String, Boolean],
  isWatch: [String, Boolean, Object],
  options: {
    type: Object as PropType<echarts.EChartsCoreOption>,
    default: () => ({})
  },
})

const { theme, options } = toRefs(props)

const chartRef = shallowRef()

const { charts, setOptions, initChart } = useCharts({ theme, el: chartRef, options })

// 开启默认放大缩放功能
const turnOndataZoom = () => {
  charts.value.dispatchAction({
    type: 'takeGlobalCursor',
    key: 'dataZoomSelect',
    dataZoomSelectActive: true
  })
}

onMounted(async () => {
  await initChart()
  setOptions(options.value)
})

watch(
  options,
  () => {
    setOptions(options.value)
    nextTick(() => turnOndataZoom())
  },
  {
    deep: true
  }
)

watch(
  () => props.isWatch, // 是否开启外部左侧菜单引起的布局适配问题
  () => {
    chartsResize.handleResize()
  },
  {
    deep: true,
    immediate: true
  }
)

defineExpose({
  chartRef: chartRef,
  $charts: charts
})
</script>

<script lang="ts">
export default { name: "v-charts" };
</script>

<style lang="scss" scoped>
.v-charts {
  width: 100%;
  height: 100%;
  clear: both;
  min-height: 360px;
}
</style>

3、引用组件或者全局注入

3.1 创建index.js文件

import vCharts from './v-charts/v-charts.vue'
export * from './v-charts/useCharts';
const components = [vCharts]; // 可添加需要全局注入的公共组件
const install = function (Vue: any) {
  components.forEach((app) => {
    Vue.component(app.name, app);
  });
};

export default install;

3.2 在main.ts文件内进行引用

import CustomUi from '@/components/index'
app.use(CustomUi)
4、在业务文件中的使用
<template>
	<v-charts ref="myCharts" :isWatch="isActiveName" :options="setOptions" />
</template>
<script lang="ts" setup>
import { ref, reactive, shallowRef, onBeforeMount } from 'vue'
const option = {
  toolbox: {
    feature: {
      dataZoom: {
        icon: null
      }
    }
  },
  xAxis: {
    data: ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
  },
  yAxis: {},
  series: [
    {
      type: "line",
      data: [23, 24, 18, 25, 27, 28, 25],
    },
  ],
};

const myCharts = ref(null)
const isActiveName = ref<boolean>(false)
const setOptions = shallowRef<Record<string, any>>({})

onBeforeMount(() => {
  setOptions.value = option
})
</script>
5、完结
  1. 第一版的时候setOption这块的内容是在组件内部;因为我们的业务偏复杂,操作setOption内容较多又涉及到轮询处理数据结构;所以将该内容放置外了;这块可以依据自身需求而定
  2. 封装echarts没有过于封装,主要是针对按需情况和初始化绑定,自适应屏幕而定的封装
  3. 不通的需求处理方式不通,有不合理之处还请各位谅解
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SunnyRun!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值