您好,欢迎访问一九零五行业门户网

聊聊vue3中echarts用什么形式封装最好?(代码详解)

项目中经常用到echarts,不做封装直接拿来使用也行,但不可避免要写很多重复的配置代码,封装稍不注意又会过度封装,丢失了扩展性和可读性。始终没有找到一个好的实践,偶然看到一篇文章,给了灵感。找到了一个目前认为用起来很舒服的封装。
思路结合项目需求,针对不同类型的图表,配置基础的默认通用配置,例如x/y,label,图例等的样式创建图表组件实例(不要使用id,容易重复,还需要操作dom,直接用ref获取当前组件的el来创建图表),提供type(图表类型),和options(图表配置)两个必要属性根据传入type,加载默认的图表配置深度监听传入的options,变化时更新覆盖默认配置,更新图表提供事件支持,支持echart事件按需绑定交互注意要确保所有传入图表组件的options数组都是shallowreactive类型,避免数组量过大,深度响应式导致性能问题
目录结构├─v-charts│  │  index.ts     // 导出类型定义以及图表组件方便使用│  │  type.d.ts    // 各种图表的类型定义│  │  usecharts.ts // 图表hooks│  │  v-charts.vue // echarts图表组件│  ││  └─options // 图表配置文件│          bar.ts│          gauge.ts│          pie.ts
组件代码v-charts.vue<template>  <div ref="chartref" /></template><script setup>import { proptype } from vue;import * as echarts from echarts/core;import { usecharts, charttype, chartsevents } from ./usecharts;/** * echarts事件类型 * 截至目前,vue3类型声明参数必须是以下内容之一,暂不支持外部引入类型参数 * 1. 类型字面量 * 2. 在同一文件中的接口或类型字面量的引用 * // 文档中有说明:https://cn.vuejs.org/api/sfc-script-setup.html#typescript-only-features */interface eventemitstype {  <t extends chartsevents.eventtype>(e: `${t}`, event: chartsevents.events[uncapitalize<t>]): void;}defineoptions({  name: vcharts});const props = defineprops({  type: {    type: string as proptype<charttype>,    default: bar  },  options: {    type: object as proptype<echarts.echartscoreoption>,    default: () => ({})  }});// 定义事件,提供ts支持,在组件使用时可获得友好提示defineemits<eventemitstype>();const { type, options } = torefs(props);const chartref = shallowref();const { charts, setoptions, initchart } = usecharts({ type, el: chartref });onmounted(async () => {  await initchart();  setoptions(options.value);});watch(  options,  () => {    setoptions(options.value);  },  {    deep: true  });defineexpose({  $charts: charts});</script><style scoped>.v-charts {  width: 100%;  height: 100%;  min-height: 200px;}</style>
usecharts.tsimport { charttype } from ./type;import * as echarts from echarts/core;import { shallowref, ref } from vue;import {  titlecomponent,  legendcomponent,  tooltipcomponent,  gridcomponent,  datasetcomponent,  transformcomponent} from echarts/components;import { barchart, linechart, piechart, gaugechart } from echarts/charts;import { labellayout, universaltransition } from echarts/features;import { canvasrenderer } from echarts/renderers;const optionsmodules = import.meta.glob<{ default: echarts.echartscoreoption }>(./options/**.ts);interface charthookoption {  type?: ref<charttype>;  el: shallowref<htmlelement>;}/** *  视口变化时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();      });    }, 500);  }  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 = ({ type, el }: charthookoption) => {  echarts.use([    barchart,    linechart,    barchart,    piechart,    gaugechart,    titlecomponent,    legendcomponent,    tooltipcomponent,    gridcomponent,    datasetcomponent,    transformcomponent,    labellayout,    universaltransition,    canvasrenderer  ]);  const charts = shallowref<echarts.echarts>();  let options!: echarts.echartscoreoption;  const getoptions = async () => {    const modulekey = `./options/${type.value}.ts`;    const { default: defaultoption } = await optionsmodules[modulekey]();    return defaultoption;  };  const setoptions = (opt: echarts.echartscoreoption) => {    charts.value.setoption(opt);  };  const initchart = async () => {    charts.value = echarts.init(el.value);    options = await getoptions();    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);export * from ./type.d;
type.d.ts/* * @description: * @version: 2.0 * @autor: gc * @date: 2022-03-02 10:21:33 * @lasteditors: gc * @lastedittime: 2022-06-02 17:45:48 */// import * as echarts from 'echarts/core';import * as echarts from 'echarts'import { xaxiscomponentoption, yaxiscomponentoption } from 'echarts';import { ecelementevent, selectchangedpayload, highlightpayload,  } from 'echarts/types/src/util/types'import {  titlecomponentoption,  tooltipcomponentoption,  gridcomponentoption,  datasetcomponentoption,  ariacomponentoption,  axispointercomponentoption,  legendcomponentoption,} from 'echarts/components';// 组件import {  // 系列类型的定义后缀都为 seriesoption  barseriesoption,  lineseriesoption,  pieseriesoption,  funnelseriesoption,  gaugeseriesoption} from 'echarts/charts';type options = lineecoption | barecoption | pieecoption | funneloptiontype baseoptiontype = xaxiscomponentoption | yaxiscomponentoption | titlecomponentoption | tooltipcomponentoption | legendcomponentoption | gridcomponentoptiontype baseoption = echarts.composeoption<baseoptiontype>type lineecoption = echarts.composeoption<lineseriesoption | baseoptiontype>type barecoption = echarts.composeoption<barseriesoption | baseoptiontype>type pieecoption = echarts.composeoption<pieseriesoption | baseoptiontype>type funneloption = echarts.composeoption<funnelseriesoption | baseoptiontype>type gaugeecoption = echarts.composeoption<gaugeseriesoption | gridcomponentoption>type echartsoption = echarts.echartsoption;type charttype = 'bar' | 'line' | 'pie' | 'gauge'// echarts事件namespace chartsevents {  // 鼠标事件类型  type mouseeventtype = 'click' | 'dblclick' | 'mousedown' | 'mousemove' | 'mouseup' | 'mouseover' | 'mouseout' | 'globalout' | 'contextmenu' // 鼠标事件类型  type mouseevents = {    [key in exclude<mouseeventtype,'globalout'|'contextmenu'> as `chart${capitalize<key>}`] :ecelementevent  }  // 其他的事件类型极参数  interface events extends mouseevents {    globalout:ecelementevent,    contextmenu:ecelementevent,    selectchanged: selectchangedpayload;    highlight: highlightpayload;    legendselected: { // 图例选中后的事件      type: 'legendselected',      // 选中的图例名称      name: string      // 所有图例的选中状态表      selected: {        [name: string]: boolean      }    };    // ... 其他类型的事件在这里定义  }  // echarts所有的事件类型  type eventtype = keyof events}export {  baseoption,  charttype,  lineecoption,  barecoption,  options,  pieecoption,  funneloption,  gaugeecoption,  echartsoption,  chartsevents}
options/bar.tsimport { barecoption } from ../type;const options: barecoption = {  legend: {},  tooltip: {},  xaxis: {    type: category,    axisline: {      linestyle: {        // type: dashed,        color: #c8d0d7      }    },    axistick: {      show: false    },    axislabel: {      color: #7d8292    }  },  yaxis: {    type: value,    alignticks: true,    splitline: {      show: true,      linestyle: {        color: #c8d0d7,        type: dashed      }    },    axisline: {      linestyle: {        color: #7d8292      }    }  },  grid: {    left: 60,    bottom: 8%,    top: 20%  },  series: [    {      type: bar,      barwidth: 20,      itemstyle: {        color: {          type: linear,          x: 0,          x2: 0,          y: 0,          y2: 1,          colorstops: [            {              offset: 0,              color: #62a5ff // 0% 处的颜色            },            {              offset: 1,              color: #3365ff // 100% 处的颜色            }          ]        }      }      // label: {      //   show: true,      //   position: top      // }    }  ]};export default options;
项目中使用index.vue<template>  <div>    <section>      <div class="device-statistics chart-box">        <div>累计设备接入统计</div>        <v-charts type="bar" :options="statisdevicebyuserobjectopts" @selectchanged="selectchanged" @chart-click="handlechartclick" />      </div>      <div class="coordinate-statistics chart-box">        <div>坐标数据接入统计</div>        <v-charts type="bar" :options="statiscoordaccess" />      </div>    </section>  </div></template><script setup>import {  usestatisdevicebyuserobject,} from ./hooks;// 设备分类统计const { options: statisdevicebyuserobjectopts,selectchanged,handlechartclick } = usestatisdevicebyuserobject();</script>
/hooks/usestatisdevicebyuserobject.tsexport const usestatisdevicebyuserobject = () => {  // 使用chartsoptions确保所有传入v-charts组件的options数据都是## shallowreactive浅层作用形式,避免大量数据导致性能问题  const options = chartsoptions<barecoption>({    yaxis: {},    xaxis: {},    series: []  });  const init = async () => {    const xdata = [];    const sdata = [];    const dicts = usehashmapdics<["dev_user_object"]>([dev_user_object]);    const data = await statisdevicebyuserobject();    dicts.dictionarymap.dev_user_object.foreach(({ label, value }) => {      if (value === 6) return; // 排除其他      xdata.push(label);      const temp = data.find(({ name }) => name === value);      sdata.push(temp?.qty || 0);            // 给options赋值时要注意options是浅层响应式      options.xaxis = { data: xdata };       options.series = [{ ...options.series[0], data: sdata }];    });  };    // 事件  const selectchanged = (params: chartsevents.events[selectchanged]) => {    console.log(params, 选中图例了);  };  const handlechartclick = (params: chartsevents.events[chartclick]) => {    console.log(params, 点击了图表);  };    onmounted(() => {    init();  });  return {    options,    selectchanged,    handlechartclick  };};
使用时输入@可以看到组件支持的所有事件:
推荐学习:《vue.js视频教程》
以上就是聊聊vue3中echarts用什么形式封装最好?(代码详解)的详细内容。
其它类似信息

推荐信息