vue3 echart组件封装

  • vue3 echart组件封装已关闭评论
  • 100 次浏览
  • A+
所属分类:Web前端
摘要

项目中用到了很多echart图表,进行了简单的组件封装,主要包含以下功能:

项目中用到了很多echart图表,进行了简单的组件封装,主要包含以下功能:

  • 创建图表实例,渲染图表
  • 支持传入自定义函数,可拿到图表实例,实现个性化功能
  • 支持配置更新后图表自动刷新,可配置是清空后再刷新
  • loading状态控制
  • resize时图表更新
  • 支持饼图默认高亮功能

实现

资源引入

  • echart资源按需引入
  • 第三方组件引入(echarts-liquidfill,水波纹图表)
/* 即下文中的 @/modules/echartPlugin */  // https://echarts.apache.org/handbook/zh/basics/import#%E6%8C%89%E9%9C%80%E5%BC%95%E5%85%A5-echarts-%E5%9B%BE%E8%A1%A8%E5%92%8C%E7%BB%84%E4%BB%B6 import * as echarts from "echarts/core"; import {   BarChart,   // 系列类型的定义后缀都为 SeriesOption   BarSeriesOption,   PieChart,   PieSeriesOption,   LineChart,   LineSeriesOption,   LinesChart,   LinesSeriesOption,   EffectScatterChart,   EffectScatterSeriesOption, } from "echarts/charts"; import {   TitleComponent,   // 组件类型的定义后缀都为 ComponentOption   TitleComponentOption,   TooltipComponent,   TooltipComponentOption,   DatasetComponent,   DatasetComponentOption,   GridComponent,   GridComponentOption,   DataZoomComponent,   DataZoomComponentOption,   LegendComponent,   LegendComponentOption,   GeoComponent,   GeoComponentOption, } from "echarts/components"; import { CanvasRenderer } from "echarts/renderers"; import "echarts-liquidfill";  // 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型 export type ECOption = echarts.ComposeOption<   | BarSeriesOption   | TitleComponentOption   | TooltipComponentOption   | GridComponentOption   | DatasetComponentOption   | DataZoomComponentOption   | PieSeriesOption   | LegendComponentOption   | GeoComponentOption   | LinesSeriesOption   | LineSeriesOption   | EffectScatterSeriesOption >;  // https://www.npmjs.com/package/echarts-liquidfill export interface LiquidFillOption {   series: {     type: "liquidFill";     data: number[];     color?: string[];     radius?: string;     center?: [string, string];     label?: {       color?: string;       insideColor?: string;       fontSize?: number;       formatter?: (param: {         borderColor: string;         color: string;         data: number;         dataIndex: number;         dataType: undefined;         name: string;         value: number;       }) => string | number;     };     shape?:       | "circle"       | "rect"       | "roundRect"       | "triangle"       | "diamond"       | "pin"       | "arrow";     [name: string]: unknown;   }[];   [name: string]: unknown; }  // 注册必须的组件 echarts.use([   TitleComponent,   TooltipComponent,   GridComponent,   BarChart,   LinesChart,   CanvasRenderer,   DatasetComponent,   DataZoomComponent,   PieChart,   LegendComponent,   GeoComponent,   LineChart,   EffectScatterChart, ]);  export default echarts; 

组件封装

<template>   <div class="h-echart-wrapper" ref="chartWrapperDom">     <div class="h-echart" ref="chartDom">loading</div>   </div> </template> <script lang="ts" src="./index.ts"></script> <style lang="less" scoped> .h-echart-wrapper {   height: 100%; } .h-echart {   height: 100%;   width: 100%;   text-align: center; } </style> 
import {   defineComponent,   onMounted,   onUnmounted,   PropType,   ref,   watch,   toRaw, } from "vue"; import echarts, { ECOption, LiquidFillOption } from "@/modules/echartPlugin"; import ResizeObserver from "resize-observer-polyfill";  export default defineComponent({   name: "h-echart",   props: {     // echart配置     options: {       type: Object as PropType<ECOption | LiquidFillOption>,       required: true,     },     // 饼图是否需要默认高亮     needDefaultHighLight: {       type: Boolean,       default: false,     },     loading: Boolean,     // 自定义函数,会暴露echart实例出去,可以实现个性化操作     customFn: Function as PropType<       (echartInstance: null | echarts.ECharts) => void     >,     // 更新图表之前是否先清空     clearBeforeUpdate: Boolean,   },   setup(props) {     const chartWrapperDom = ref<null | HTMLElement>(null);     const chartDom = ref<null | HTMLElement>(null);     // WARN: echarts5 实例用响应式对象存放时会导致功能tooltip功能异常     let echartInstance: null | echarts.ECharts = null;     let chartWrapperResize: null | ResizeObserver = null;     let highlightName: string | null = null;     let firstRender = true;      const setOptions = (options?: ECOption | LiquidFillOption) => {       echartInstance &&         options &&         echartInstance.setOption(toRaw(options), {           notMerge: true,         });        if (props.needDefaultHighLight && firstRender) {         firstRender = false;         const _options = props.options as ECOption;          // eslint-disable-next-line @typescript-eslint/ban-ts-comment         // @ts-ignore         if (_options.series && _options.series[0] && _options.series[0].data) {           // eslint-disable-next-line @typescript-eslint/ban-ts-comment           // @ts-ignore           const name = _options.series[0].data[0].name as string;            setTimeout(() => {             // 默认高亮             echartInstance &&               echartInstance.dispatchAction({                 type: "highlight",                 seriesIndex: 0,                 name,               });              highlightName = name;           }, 600);         }       }     };      watch(       () => props.loading,       (newLoading) => {         if (newLoading !== undefined && echartInstance) {           newLoading             ? echartInstance.showLoading({                 textColor: "rgb(255 255 255 / 0%)",                 showSpinner: false,                 zlevel: 0,               })             : echartInstance.hideLoading();         }       }     );      const init = () => {       chartDom.value && (echartInstance = echarts.init(chartDom.value));        props.customFn && props.customFn(echartInstance);        if (props.needDefaultHighLight && echartInstance) {         echartInstance.on("mouseover", function (e) {           if (e.name !== highlightName) {             echartInstance!.dispatchAction({               type: "downplay",               seriesIndex: 0,               name: highlightName,             });           }         });          echartInstance.on("mouseout", function (e) {           highlightName = e.name;           echartInstance!.dispatchAction({             type: "highlight",             seriesIndex: 0,             name: e.name,           });         });       }       setOptions(props.options);     };      onMounted(() => {       // 初始化图表实例       setTimeout(init, 300);        // 观察包裹层变化,进行图表resize       if (chartWrapperDom.value) {         chartWrapperResize = new ResizeObserver(() => {           echartInstance && echartInstance.resize();         });          chartWrapperResize.observe(chartWrapperDom.value);       }     });      // 观察者清理     onUnmounted(() => {       chartWrapperResize?.disconnect();     });     watch(       () => props,       // 配置变化,重新设置       (newVal) => {         if (newVal.clearBeforeUpdate) {           echartInstance && echartInstance.clear();         }         setOptions(toRaw(newVal.options));       },       { immediate: true, deep: true }     );      return {       chartDom,       chartWrapperDom,     };   }, }); 

组件注册及全局类型声明

/* ./components/index.ts */ import { App } from "vue"; import HEchart from "./h-echart"; import HIframeKeepAlive from "./h-iframe-keep-alive/index.vue";  export default function useCompoments(app: App<Element>) {   app &&     app.component &&     [       HEchart,       HIframeKeepAlive,     ].forEach((_component) => {       app.component(_component.name, _component);     }); }  // 声明全局组件类型 // https://github.com/johnsoncodehk/volar/blob/master/extensions/vscode-vue-language-features/README.md declare module "@vue/runtime-core" {   export interface GlobalComponents {     HEchart: typeof HEchart;     HIframeKeepAlive: typeof HIframeKeepAlive;   } } 
import useCompoments from "./components";  const app = createApp(App).use(router); tempApp = app;  // 注册所自定义组件 useCompoments(app); 

使用

    <div class="chart-wrapper">       <h-echart :options="boardPieOptions" needDefaultHighLight />     </div> 
  const boardPieOptions = computed(() => {     return getBoardPieOptions(props.arrivalNodeStats.types);   });