echarts在vue或者react中使用存在的问题
- 每个图表需要从头到尾写地一遍完整的option配置,这样一来的话就会显得十分的冗余
- 在同一个项目中,其实不难发现各类图表设计十分相似,甚至是相同,因此我们没必要一直做重复的工作,去写差不多配置的option
- 再一个就是窗口缩放时的适应问题
- 那么在项目中如何按需引入echarts图表 减少打包体积的大小就显得尤为重要了
解决方案
- 统一封装图表组件,统一管理图表配置,做到业务数据和echarts的 option样式配置数据分离
- 统一解决窗口缩放时的图表的适应问题, 解决窗口缩放引起的echarts图形变形的问题
- 按需引入echarts图表 减少打包体积的大小,(全量引入按需引入 打包后的体积减少了三分之一)
插件安装:
- npm/cnpm element-resize-detector -S 动态监听元素尺寸变化,与window的resize事件不同,resize只能监听window的窗口尺寸变化,不能监听某个元素尺寸变化
- echarts 图表组件库
- lodash 主要利用其中的 merge 函数,用于将多个对象合并成一个新的对象。具体用法可参考:lodash.merge | Lodash中文文档 | Lodash中文网
封装 echarts
将 ECharts 的配置封装到插件中,这里我们可以实现:按需引入图表、按需引入图表配置比如提示框,标题等等
然后在 main.js 里进行全局注册,就可以在需要使用的地方很方便快捷的进行使用了
其代码如下:
plugins/echarts.js
// 引入 echarts 核心模块,核心模块提供了 echarts 使用必须要的接口。
import * as echarts from "echarts/core";
// 按需引入折线图、饼状图、柱状图,图表后缀都为 Chart
import { BarChart, PieChart, LineChart } from "echarts/charts";
// 引入提示框,标题,直角坐标系,数据集,内置数据转换器组件,组件后缀都为 Component
import {
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LegendComponent,
} from "echarts/components";
// 标签自动布局、全局过渡动画等特性
import { LabelLayout, UniversalTransition } from "echarts/features";
// 引入 Canvas 渲染器,注意引入 CanvasRenderer 或者 SVGRenderer 是必须的一步
import { CanvasRenderer } from "echarts/renderers";
// 注册必须的组件
echarts.use([
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
LegendComponent,
BarChart,
PieChart,
LineChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
]);
export default echarts;
main.js
import echarts from './plugins/echarts';
const app = createApp(App);
app.provide("$echarts", echarts);
这样做的优点如下:
-
模块化和组织性: 将 ECharts 的配置封装到插件中,使得项目中的 ECharts 相关配置和代码能够按功能模块进行组织和管理。这样做有助于提高代码的可维护性和可读性。
-
复用性: 将 ECharts 相关配置封装到插件中,可以在不同的组件中复用相同的配置。这样做有助于减少重复的代码编写,提高开发效率。
-
全局性: 将 ECharts 的配置通过 Vue 的 provide/provide API 全局挂载,使得在整个 Vue 应用中都能够方便地使用相同的 ECharts 实例。这样做有助于保持应用中 ECharts 实例的一致性,避免了在不同组件中重复创建 ECharts 实例的问题。
-
便捷性: 通过将 ECharts 配置封装到插件中,并通过 provide/provide API 进行全局挂载,可以在 Vue 应用的任意组件中直接使用
$echarts
实例,无需每次都单独引入和配置 ECharts。这样做简化了在 Vue 应用中使用 ECharts 的流程,提高了开发效率。
使用实例
这里以封装一个饼图为例,代码如下
封装饼图组件
在 components 目录下,新建 Echart 文件夹
在 Echart 文件夹下,新建 pie 文件夹,用来封装 pie 组件 和 存放其 option 配置
在 Echart 文件夹下,新建 color.js ,用来放置通用的颜色数组
pie/chartPie.vue
<template>
<div ref="chartRef" class="chart"></div>
</template>
<script setup>
import { defineProps, ref, onMounted, onUnmounted, watch, inject } from "vue";
import { merge } from "lodash";
import ResizeListener from "element-resize-detector";
import { COLORSARRAY } from "../color.js";
import { BASEOPTIONS } from "./defaultOptions.js";
let echartInstance = null;
// 导入全局挂载的echarts
const echarts = inject("$echarts");
// 使用vue的refs声明dom节点,方便后续操作
const chartRef = ref(null);
const props = defineProps({
seriesData: {
type: Array,
required: true,
default: () => [],
},
// 需要特殊定制的配置
extraOption: {
type: Object,
default: () => ({}),
},
});
// 监控seriesData的变化,当变化时更新echart视图
watch(
() => props.seriesData,
(newVal) => {
updateChartView();
},
{
// immediate: true,
deep: true,
}
);
/**
* 对chart元素尺寸进行监听,当发生变化时同步更新echart视图
*/
const addChartResizeListener = () => {
const instance = ResizeListener({
strategy: "scroll",
callOnAdd: true,
});
instance.listenTo(chartRef.value, () => {
if (chartRef.value) {
// chartRef.value.resize();
echartInstance.resize();
}
});
};
/**
* 当窗口缩放时,echart动态调整自身大小
*/
const handleWindowResize = () => {
if (chartRef.value) {
echartInstance.resize();
// chartRef.value?.resize();
}
};
const assembleDataToOption = (seriesData) => {
// 这部分的图例formatter取决于UI要求,如果你的项目中不需要,就可以不写formatter
// 由于echarts版本的迭代,这里的写法也有稍许改变
const formatter = (name) => {
const total = props.seriesData.reduce((acc, cur) => acc + cur.value, 0);
const data = props.seriesData.find((item) => item.name === name) || {};
const percent = data.value
? `${Math.round((data.value / total) * 100)}%`
: "0%";
return `${name} ${percent}`;
};
return merge(
{},
BASEOPTIONS,
{ color: COLORSARRAY },
{
legend: { formatter },
series: [{ data: props.seriesData }],
},
props.extraOption
);
};
const updateChartView = () => {
if (!chartRef.value) return;
const fullOption = assembleDataToOption();
echartInstance = echarts.init(chartRef.value);
echartInstance.setOption(fullOption);
};
onMounted(() => {
// echarts.init(chartRef.value)
updateChartView();
window.addEventListener("resize", handleWindowResize);
addChartResizeListener();
});
onUnmounted(() => {
window.removeEventListener("resize", handleWindowResize);
});
</script>
<style lang="scss">
.chart {
width: 100%;
height: 450px;
}
</style>
pie/defaultOptions.js
// 这里的数据会被深度合并
const BASEOPTIONS = {
title: {
text: "产品成功率",
left: "center",
padding: 70
},
tooltip: {
trigger: "item",
},
legend: {
orient: "vertical",
left: "left",
},
series: [
{
name: "占比",
type: "pie",
radius: "50%",
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: "rgba(0, 0, 0, 0.5)",
},
},
data: [], // 这里在使用的时候会被业务数据替换
},
],
};
export { BASEOPTIONS };
pie/index.vue
<template>
<chart-pie v-bind="$props" />
</template>
<script setup>
import ChartPie from "./chartPie.vue";
</script>
Echart/color.js
// 通用的颜色数组
const COLORSARRAY = ["#f75981", "#90e2a9", "#fe883a", "#2d90d1"];
export {COLORSARRAY};
使用方式
<chartPie :seriesData="dataList" :extraOption="extraOption"></chartPie>
import chartPie from "@/components/Echart/pie/chartPie.vue";
import { ref } from "vue";
const dataList = ref([
{ name: "对接成功", value: 0 },
{ name: "未成功", value: 0 },
]);
const extraOption = {
color: ["#fe883a", "#2d90d1", "#f75981", "#90e2a9"],
};
// 请求,具体代码省略
if (res.data[0] && res.data[0].data) {
dataList.value[0].value = res.data[0].data.chenggong_count || 0;
dataList.value[1].value = res.data[0].data.shenqing_count || 0;
}
最终效果如下: