背景介绍
在做数据大屏时,每一个数据区域都需要写很多相同的逻辑,例如监听用户选择的时间范围、监听页面大小resize等,所以我们需要提取这些公共的部分作为公共组件,即对Echarts进行二次封装。在前期初步形成了三个方案如下:
- 封装一个公共组件,使用slot给父组件提供一个插槽入口,但是使用这种方法时,因为Echarts图表写在插槽里,相关的处理逻辑仍然在父组件代码里,代码复用率低且组件间通信复杂,故不考虑。❌
- 将Echarts进行封装,父组件通过props向子组件传递相关数据,而例如时间范围变化、页面大小改变事件则由子组件监听到后通过emit传递给父组件。
- 使用Vue3的组合式函数,将公共逻辑提取出来进行复用。
封装步骤
经过个人的尝试与研究,二次封装Echarts需要结合使用上面的2和3,以下是具体的封装步骤:
0. 文件目录结构(无关部分已省略)
-src # 根目录
---components
-----MyChart
-------MyChart.vue # 二次封装组件
---composables
-----charts
-------useCharts.ts # 组合式函数声明
---views
-----components
-------UseCase.vue # 数据区域组件
-----index.vue # 主页面文件
-
useEchart.ts
该组合式函数主要功能为创建echarts实例并开启监听屏幕大小变化,触发echarts实例的resize。
import * as Echarts from "echarts";
// 封装Echarts的顶级hooks
export default function (el: HTMLElement) {
// 创建Echarts实例
const echartInstance = Echarts.init(el);
/**
* 创建option
* @param option echarts配置项
*/
const setOption = (option: Echarts.EChartsOption) => {
echartInstance.setOption(option);
};
// 调用Echarts实例来适配echarts大小,这里默认函数为仅调用resize,如果需要其他操作可以使用updataSize
window.addEventListener("resize", () => {
echartInstance.resize();
});
/**
* 导出更新配置项的函数,以供需要更新的场景使用
* @param optione charts配置项
*/
const updataSize = (option: Echarts.EChartsOption) => {
setOption(option);
echartInstance.resize();
};
return { setOption, updataSize };
}
-
MyChart.vue
该组件主要是调用了useEchart进行实例创建,并且监听时间变化后传递给父组件。
另外,该组件通过判断是否传入option来选择是创建echarts实例还是向外提供slot。
<template>
<div class="base-echart" v-if="option">
<div ref="echartDivRef" :style="{ width: width, height: height }"></div>
</div>
<div class="base-echart" v-else>
<slot :style="{ width: width, height: height }"></slot>
</div>
</template>
<script lang="ts" setup>
import { EChartsOption } from "echarts";
import useCharts from "@/hooks/charts/useCharts.ts";
import useBoardStore from "@/store/modules/dataBoard.ts";
const boardStore = useBoardStore();
const timeRange = computed(() => boardStore.timeRange);
const emits = defineEmits(["timeChange"]);
// 监听时间变化
watch(timeRange, () => {
emits("timeChange", timeRange);
});
// 定义prpos
const props = withDefaults(
defineProps<{
option?: EChartsOption;
width?: string;
height?: string;
}>(),
{
width: "100%",
height: "100%",
}
);
const echartDivRef = ref<HTMLElement>();
onMounted(() => {
if (props.option) {
const { setOption } = useCharts(echartDivRef.value!);
watchEffect(() => {
props.option && setOption(props.option);
});
}
});
</script>
<style lang="scss" scoped>
.base-echart {
display: flex;
justify-content: center;
width: 100%;
height: 100%;
}
</style>
-
UseCase.vue
使用MyChart.vue组件,传递option,并在子组件触发timeChange时更新option。
以下代码option中的内容只展示部分,根据实际需要进行配置。
<template>
<my-chart :option="option" @time-change="getData"></my-chart>
</template>
<script lang="ts" setup>
import { getUseCase } from "@/api/dataBoard";
import MyChart from "@/components/MyChart/MyChart.vue";
// 获取根字体大小
let fontsize = ref(parseInt(document.documentElement.style.fontSize.replace("px", "")));
const option = ref({});
const initEcharts = (data: { data: [] }) => {
option.value = {
grid: {
// 设置边距
x: fontsize.value * 4,
y: fontsize.value * 3.5,
x2: 20,
y2: 50,
},
xAxis: {
type: "time", // x轴为时间轴
},
yAxis: {
min: 0, //取0为最小刻度
max: 100, //取100为最大刻度
type: "value", // y轴为数值轴
},
series: [
{
name: "平台使用率",
type: "line",
data: data.data,
color: "#F8B62B",
}
],
};
};
/**
* 获取列表数据
*/
const getData = async () => {
try {
let res = await getUseCase();
initEcharts(res);
} catch (err: any) {
ElMessage.error(err.toString().replace("Error: ", ""));
}
};
onMounted(() => {
getData();
// 监听屏幕尺寸变化
window.addEventListener("resize", function () {
fontsize.value = parseInt(document.documentElement.style.fontSize.replace("px", ""));
Object.assign(option.value, {
grid: {
x: fontsize.value * 4,
y: fontsize.value * 3.5,
},
});
});
});
</script>
-
index.vue
页面使用数据区域组件
<div class="container-item">
<use-case></use-case>
</div>