业务项目中Echarts图表组件的封装实践方案

背景:如果我们的项目是一个可视化类/营销看板类/大屏展示类业务项目,不可避免的会使用到各种图表展示。那在一个项目中如何封装一个图表组件既能够快速复用、UI统一,又可以灵活扩充Echarts的各种复杂配置项配置就变得极为重要。

封装目标

  • 符合当前系统的业务UI(轴线、分隔线、配色、面积色、legend 等等)及场景
  • 可基于基础配置项便捷扩充其他特殊配置项
  • 可支持Echarts原生配置项,不引入过多额外的配置项!!!

封装误区

  • 基于Echarts配置项封装了基础配置项组件,但是组件无法扩充额外的配置项,使用不灵活(即总是需要频繁修改封装组件)
  • 在Echarts的的配置项上又封装了一层,改变了很多的配置项内容,使用成本较高(往往需要查看组件源码才知道要传入什么配置项属性)

封装思路

业务项目中Echarts图表组件的封装实践方案_第1张图片

Vue项目实践

线图封装

<template>
  <div v-if="notEmpty" :id="id" class="echarts-line"></div>
  <div v-else class="echarts-empty">暂无数据</div>
</template>

<script>
import echarts from 'echarts';
import deepmerge from 'deepmerge';

// 系统自定义区域
const colors = []; // 系统自定义的主题配色

export default {
  name: 'EchartsLine',
  props: {
    echartsData: {
      type: Object,
      required: true,
    },
  },
  data() {
    return {
      lineChart: null,
    };
  },
  computed: {
    id() {
      return `echarts_line_${this.echartsData.id}`;
    },
    notEmpty() {
      return this.echartsData && this.echartsData.category.length > 0 && this.echartsData.series.length > 0;
    },
  },
  watch: {
    echartsData(value) {
      if (this.lineChart) {
        this.lineChart.setOption(this.getMergeOptions(value));
        this.lineChart.resize();
      }
    },
  },
  mounted() {
    this.init();
  },
  beforeDestroy() {
    window.removeEventListener('resize', this._listenerResize);
  },
  methods: {
    // [private] 处理轴的类型配置项,支持x轴为类目轴 | y轴为类目轴 | 双数据轴
    _dealAxisType(type, category) {
      const categoryAxis = {
        type: 'category',
        boundaryGap: true,
        data: category,
      };
      const valueAxis = {
        type: 'value',
      };

      switch (type) {
        case 'xCategory':
          return {
            xAxis: categoryAxis,
            yAxis: valueAxis,
          };
        case 'yCategory':
          return {
            yAxis: categoryAxis,
            xAxis: valueAxis,
          };
        case 'doubleValue':
          return {
            xAxis: {
              max: 'dataMax',
              boundaryGap: true,
              splitLine: {
                show: false,
              },
            },
            yAxis: {},
          };
        default:
          return {
            xAxis: categoryAxis,
            yAxis: valueAxis,
          };
      }
    },

    // [private] 获取线图默认配置项,系统整体统一UI
    _getDefaultOptions(type, category) {
      return {
        title: {
          subtext: '',
          left: 'center',
          textStyle: {
            color: '#98a6ad',
            fontSize: 16,
            fontWeight: 'normal',
          },
        },
        legend: {
          type: 'scroll',
          bottom: '0',
        },
        grid: {
          top: '30px',
          bottom: '50px',
        },
        color: colors,
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'cross',
          },
        },
        ...this._dealAxisType(type, category),
      };
    },

    // [private] 监听resize时间
    _listenerResize() {
      if (this.lineChart) {
        this.lineChart.resize();
      }
    },

    /**
     * [public] getMergeOptions 获取合并后的图表配置项,自定义配置项与默认配置项融合,若自定义配置项与默认配置项冲突则自定义配置项生效
     * 配置项合并规则:https://www.npmjs.com/package/deepmerge
     * @param type {String} 
     * @param category {Array}
     * @param series {Array}
     * @param echartsConfig {Object}
     */
    getMergeOptions({
      type = 'xCategory',
      category,
      series,
      echartsConfig = {},
    }) {

      // 01. 用户传进来的配置项和默认配置项进行合并
      const mergeOptions = deepmerge(
        this._getDefaultOptions(type, category),
        echartsConfig,
      );

      // 02. Series配置项合并【此处为示例】
      const mergeSeries = [];
      if (series && series.length > 0) {
        series.forEach((item) => {
          mergeSeries.push(
            deepmerge(item, {
              type: 'line',
            }),
          );
        });
      }

      // 03. 返回合并后的整体配置项 
      return {
        ...mergeOptions,
        series: mergeSeries,
      };
    },

    // [public] 交互点,获取图表实例,用于触发图表API
    getEchartsInstance() {
      if (this.lineChart) {
        return this.lineChart;
      }
      return null;
    },

    // [public] 初始化图表
    init() {
      this.$nextTick(() => {
        this.lineChart = echarts.init(document.getElementById(this.id));
        this.lineChart.setOption(
          this.getMergeOptions(this.echartsData),
        );
        window.addEventListener('resize', this._listenerResize);
      });
    },
  },

};
</script>

<style lang="less" rel="stylesheet/less" scoped>
.echarts-line{
  height: 350px;
}

.echarts-empty{
  height: 200px;
  line-height: 200px;
  text-align: center;
}
</style>

业务调用

<v-line-chart class="echarts-item" :echarts-data="chartData"></v-line-chart>
 this.chartData = {
   id: 'lineChart',
   category:['test1','test2','test3','test4'],
   series: [{
     name: '测试图表',
     data:[10,20,30,40],
   }],
   echartsConfig: { // 所有Echarts原生配置项放在该属性下
     legend: {
       show: false,
     },
   },
};

参考

  • Echarts官网:https://echarts.apache.org/zh/index.html
  • deepMerge gitHub:https://github.com/TehShrike/deepmerge

业务方案简单封装,不作为公共UI库使用,欢迎讨论其他实现方案

你可能感兴趣的:(经典案例,echarts,vue.js,前端,实践方案)