背景:如果我们的项目是一个可视化类/营销看板类/大屏展示类业务项目,不可避免的会使用到各种图表展示。那在一个项目中如何封装一个图表组件既能够快速复用、UI统一,又可以灵活扩充Echarts的各种复杂配置项配置就变得极为重要。
封装目标
- 符合当前系统的业务UI(轴线、分隔线、配色、面积色、legend 等等)及场景
- 可基于基础配置项便捷扩充其他特殊配置项
- 可支持Echarts原生配置项,不引入过多额外的配置项!!!
封装误区
- 基于Echarts配置项封装了基础配置项组件,但是组件无法扩充额外的配置项,使用不灵活(即总是需要频繁修改封装组件)
- 在Echarts的的配置项上又封装了一层,改变了很多的配置项内容,使用成本较高(往往需要查看组件源码才知道要传入什么配置项属性)
封装思路
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库使用,欢迎讨论其他实现方案