这个文件提供了一个方便的 Vue 钩子 useEcharts
,使得在 Vue 组件中集成和管理 ECharts 图表变得更加简单和灵活。它支持动态更新图表配置、响应尺寸变化、切换主题等功能。
返回值
domRef
:图表容器的引用。updateOptions
:更新图表配置项的方法。setOptions
:设置图表配置项的方法。
依赖说明:
- @vueuse/core:导入了
useElementSize
,用于监听元素尺寸变化。 -
import { effectScope, nextTick, onScopeDispose, ref, watch } from 'vue' import * as echarts from 'echarts/core' import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart, TreemapChart } from 'echarts/charts' import type { BarSeriesOption, GaugeSeriesOption, LineSeriesOption, PictorialBarSeriesOption, PieSeriesOption, RadarSeriesOption, ScatterSeriesOption, TreemapSeriesOption } from 'echarts/charts' import { DatasetComponent, GridComponent, LegendComponent, TitleComponent, ToolboxComponent, TooltipComponent, TransformComponent } from 'echarts/components' import type { DatasetComponentOption, GridComponentOption, LegendComponentOption, TitleComponentOption, ToolboxComponentOption, TooltipComponentOption } from 'echarts/components' import { LabelLayout, UniversalTransition } from 'echarts/features' import { CanvasRenderer } from 'echarts/renderers' import { useElementSize } from '@vueuse/core' export type ECOption = echarts.ComposeOption< | BarSeriesOption | LineSeriesOption | PieSeriesOption | ScatterSeriesOption | TreemapSeriesOption | PictorialBarSeriesOption | RadarSeriesOption | GaugeSeriesOption | TitleComponentOption | LegendComponentOption | TooltipComponentOption | GridComponentOption | ToolboxComponentOption | DatasetComponentOption > echarts.use([ TitleComponent, LegendComponent, TooltipComponent, GridComponent, DatasetComponent, TransformComponent, ToolboxComponent, BarChart, LineChart, PieChart, ScatterChart, PictorialBarChart, RadarChart, GaugeChart, TreemapChart, LabelLayout, UniversalTransition, CanvasRenderer ]) interface ChartHooks { onRender?: (chart: echarts.ECharts) => void | Promise<void> onUpdated?: (chart: echarts.ECharts) => void | Promise<void> onDestroy?: (chart: echarts.ECharts) => void | Promise<void> } /** * use echarts * * @param optionsFactory echarts options factory function * @param darkMode dark mode */ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: ChartHooks = {}) { const scope = effectScope() const domRef = ref<HTMLElement | null>(null) const initialSize = { width: 0, height: 0 } const { width, height } = useElementSize(domRef, initialSize) const darkMode = ref('light') let chart: echarts.ECharts | null = null const chartOptions: T = optionsFactory() const { onRender = (instance) => { const textColor = darkMode.value ? 'rgb(224, 224, 224)' : 'rgb(31, 31, 31)' const maskColor = darkMode.value ? 'rgba(0, 0, 0, 0.4)' : 'rgba(255, 255, 255, 0.8)' instance.showLoading({ color: 'rgb(var(--primary-6))', textColor, fontSize: 14, maskColor }) }, onUpdated = (instance) => { instance.hideLoading() }, onDestroy } = hooks /** * whether can render chart * * when domRef is ready and initialSize is valid */ function canRender() { return domRef.value && initialSize.width > 0 && initialSize.height > 0 } /** is chart rendered */ function isRendered() { return Boolean(domRef.value && chart) } /** * update chart options * * @param callback callback function */ async function updateOptions( callback: (opts: T, optsFactory: () => T) => ECOption = () => chartOptions ) { if (!isRendered()) return const updatedOpts = callback(chartOptions, optionsFactory) Object.assign(chartOptions, updatedOpts) if (isRendered()) { chart?.clear() } chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' }) await onUpdated?.(chart!) } function setOptions(options: T) { chart?.setOption(options) } /** render chart */ async function render() { if (!isRendered()) { const chartTheme = darkMode.value ? 'dark' : 'light' await nextTick() chart = echarts.init(domRef.value, chartTheme) chart.setOption({ ...chartOptions, backgroundColor: 'transparent' }) await onRender?.(chart) } } /** resize chart */ function resize() { chart?.resize() } /** destroy chart */ async function destroy() { if (!chart) return await onDestroy?.(chart) chart?.dispose() chart = null } /** change chart theme */ async function changeTheme() { await destroy() await render() await onUpdated?.(chart!) } /** * render chart by size * * @param w width * @param h height */ async function renderChartBySize(w: number, h: number) { initialSize.width = w initialSize.height = h // size is abnormal, destroy chart if (!canRender()) { await destroy() return } // resize chart if (isRendered()) { resize() } // render chart await render() } scope.run(() => { watch([width, height], ([newWidth, newHeight]) => { renderChartBySize(newWidth, newHeight) }) watch(darkMode, () => { changeTheme() }) }) onScopeDispose(() => { destroy() scope.stop() }) return { domRef, updateOptions, setOptions } }
使用示例:
-
<template> <div ref="lineRef" class="flex-1"></div> </template> <script setup lang="ts"> import { useEcharts } from '@/hooks/echarts' import { baseChartColor } from '../config'; import type { ECOption } from '@/hooks/echarts' const option: ECOption = { tooltip: { trigger: 'axis' }, legend: { data: ['单一资管计划', '委外投资组合', '公募基金'], icon: 'rich', show: true, itemWidth: 18, itemHeight: 8, textStyle: { color: '#666', fontSize: '12px', }, top: 8, itemGap: 34 }, grid: { show: false }, xAxis: { data: [2016, 2018, 2020, 2022, 2024], type: 'category', boundaryGap: false, axisTick: { show: false }, axisLabel: { interval: 0, color: '#999', fontSize: 12, padding: [10, 0, 0, 0] } }, yAxis: { type: 'value', axisLabel: { color: '#999', fontSize: 12, padding: [0, 10, 0, 0] }, splitLine: { lineStyle: { color: '#ccc', type: 'dashed' } } }, color: baseChartColor, series: [ { name: '单一资管计划', type: 'line', smooth: true, symbol: 'none', data: [7, 7, 8, 9, 2], }, { name: '委外投资组合', data: [1, 2, 3, 4, 20], type: 'line', smooth: true, symbol: 'none', }, { name: '公募基金', data: [2, 7, 8, 4, 10], type: 'line', smooth: true, symbol: 'none' } ] } const { domRef: lineRef } = useEcharts(() => option, { onRender() { } }) </script> <style lang="less" scoped></style>