整理下项目中 echarts 的封装方式,利用 Vue3 的组合 API 对 echarts 的通用逻辑进行封装。在使用 echarts 组件的时候,往往数据不是静态不变的,而是需要我们从后端异步查询,然后再赋值给图表组件。项目中使用 antdv 动态加载组件 Spin,表示加载数据中。如果没有符合条件的数据,则需要显示数据为空的样式。如果大家有更好的方案,欢迎留言评论,我们一起讨论。
useECharts 逻辑封装
在 useECharts 中,按需引入 ECharts 图表和组件;并对图表初始化、监听图表容器变化进行封装。将 ECharts 实例使用 shallowRef 定义,而不是 ref 类型,这样 Proxy 不会应用到 ECharts 实例底下的各个属性上。否则浏览器大小 resize 变化的时候会报错。详见:github.com/apache/echa…
// useECharts.ts
import * as echarts from 'echarts/core'
import {
BarChart,
LineChart,
LineSeriesOption,
PieChart,
PieSeriesOption,
} from 'echarts/charts'
import {
LegendComponent,
LegendComponentOption,
TitleComponent,
// 组件类型的定义后缀都为 ComponentOption
TitleComponentOption,
TooltipComponent,
TooltipComponentOption,
GridComponent,
GridComponentOption,
// 数据集组件
DatasetComponent,
DatasetComponentOption,
// 内置数据转换器组件 (filter, sort)
TransformComponent,
} from 'echarts/components'
import { LabelLayout, UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
import { shallowRef, onMounted, onBeforeUnmount } from 'vue'
// 通过 ComposeOption 来组合出一个只有必须组件和图表的 Option 类型
export type ECOption = echarts.ComposeOption<
LineSeriesOption
| PieSeriesOption
| LegendComponentOption
| TitleComponentOption
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
>
// 注册必须的组件
echarts.use([
LegendComponent,
TitleComponent,
TooltipComponent,
GridComponent,
DatasetComponent,
TransformComponent,
BarChart,
LineChart,
PieChart,
LabelLayout,
UniversalTransition,
CanvasRenderer,
])
export default function useChart() {
// https://github.com/apache/echarts/issues/13943
const canvasEl = shallowRef()
const myChart = shallowRef()
const resizeFn = () => {
myChart.value?.resize()
}
onMounted(() => {
myChart.value = echarts.init(canvasEl.value)
window.addEventListener('resize', resizeFn)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', resizeFn)
myChart.value?.dispose()
myChart.value = null
})
return {
myChart,
canvasEl,
}
}
MyECharts 组件封装
定制了图表加载态 loading
; 图表数据为空时的属性 dataEmptyFlag
。通过监听 options
的变化,更新绑定 myChart
的 option。
注意: 组件 props
中 options
是 ref
类型,也就是整体赋值操作 options
才会触发组件 watch
的响应。这样设计可以避免深层监听 options
的变化,影响性能。
// MyECharts.vue
<template>
<div>
<div class="chart-container" :style="containerStyle">
<div v-show="dataEmptyFlag" class="chart-empty">
<span class="empty-title">没有符合条件的内容</span>
<span>请修改查询条件后重试</span>
</div>
<div
ref="canvasEl"
:style="containerStyle"
/>
<div
v-show="loading"
class="chart-loading"
><Spin /></div>
</div>
</div>
</template>
<script setup lang="ts">
import { Spin } from 'ant-design-vue'
import {
ref, watch,
} from 'vue'
import useChart from './useECharts'
import type { ECOption } from './useECharts'
interface ChartProps{
containerStyle?: any
loading: boolean
dataEmptyFlag: boolean
options: ECOption
}
const props = withDefaults(defineProps<ChartProps>(), {
containerStyle: {
height: '350px',
width: '600px'
},
loading: false,
dataEmptyFlag: false,
})
const { myChart, canvasEl } = useChart()
watch(() => props?.options,(cur)=>{
if(myChart){
myChart.value.setOption(cur)
}
})
</script>
<style scoped>
.gov-line-chart-name {
color: #000000;
font-size: 14px;
font-weight: 500;
height: 22px;
line-height: 22px;
}
.chart-container {
position: relative;
width: 100%;
}
.chart-loading {
align-items: center;
background-color: #ffffff;
bottom: 0;
display: flex;
justify-content: center;
left: 0;
position: absolute;
right: 0;
top: 0;
z-index: 1999;
}
.chart-empty{
position: absolute;
z-index: 1;
left: 0;
right: 0;
top: 0;
bottom: 0;
color: #888;
font-size: 14px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
}
.empty-title{
font-size: 16px;
font-weight: 500;
color: #000;
}
</style>
使用
在 App.vue 中使用如下:
<template>
<MyECharts :loading="loading" :options="options"></MyECharts>
</template>
<script lang="ts" setup>
import { ref, onMounted, } from 'vue'
import MyECharts from '../components/MyECharts.vue'
const loading = ref(false)
const dataEmptyFlag = ref(false)
const options = ref()
const fetchData = () => new Promise((resolve)=>{
setTimeout(()=>{
resolve({
xData: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
yData: [150, 230, 224, 218, 135, 147, 260],
})
}, 1000)
})
// 基本不变的 echarts 属性设置
const baseOption = {
xAxis: {
type: 'category',
data: []
},
yAxis: {
type: 'value'
},
series: []
}
const loadECharts = () => {
loading.value = true
fetchData().then(res => {
if(res){
baseOption.xAxis.data = res.xData
baseOption.series = []
baseOption.series.push({
data: res.yData,
type: 'line'
})
}else{
//没有数据显示空态
dataEmptyFlag.value = true
}
// 赋值操作
options.value = {...baseOption}
}).finally(()=>{
loading.value = false
})
}
onMounted(loadECharts)
</script>
推荐
echarts图表案例资源:
Made A Pie
ECharts官方案例
MCChart
PPChart
本文正在参加「金石计划」