ECharts 图表封装
相关方法
说明
ECharts 图表封装提供了一套完整的 Vue3 Hooks,用于简化 ECharts 图表的使用。
支持按需引入组件、事件监听、智能 resize、数据获取等功能,提供更灵活的使用方式。
概述
提供了四个主要函数:
useEcharts- 基础图表 Hook(增强版,支持按需引入)useChartWithData- 带数据获取的高级 HookuseSimpleChart- 简化版 Hook,适用于静态配置registerEchartsComponents- 全局组件注册辅助函数
功能特性
✅ 按需引入组件 - 避免打包未使用的图表类型,大幅减少体积
✅ 完整的事件系统 - 支持 on/off/once 事件监听
✅ 智能 Resize - 支持 ResizeObserver 监听容器大小变化
✅ 丰富的 API - 提供 clear、dispatchAction、坐标转换等方法
✅ TypeScript 类型支持 - 完整的类型定义和提示
✅ 自动加载状态管理 - 内置 loading 状态
✅ 错误处理机制 - 统一的错误处理
✅ 更好的内存管理 - 使用 shallowRef,自动清理资源
基础用法
useEcharts - 基础 Hook
适用于完全自定义控制的场景,支持按需引入组件。
按需引入(推荐)
import { useEcharts } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { onMounted } from 'vue';
const chart = useEcharts({
components: [LineChart, GridComponent, TooltipComponent, CanvasRenderer],
autoResize: true, // 自动响应大小变化
useResizeObserver: true, // 使用 ResizeObserver 监听容器(优先)
resizeDelay: 300, // 防抖延迟
theme: 'dark', // 主题
});
onMounted(() => {
chart.setOption({
title: { text: '折线图示例' },
xAxis: { type: 'category', data: ['Mon', 'Tue', 'Wed'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150], type: 'line' }],
});
// 监听点击事件
chart.on('click', (params) => {
console.log('点击了:', params.name);
});
});
// 在模板中
return { chartRef: chart.chartRef };配置选项:
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
components | Array | [] | 需要注册的 ECharts 组件(图表、组件、渲染器) |
autoResize | boolean | true | 是否自动响应大小变化 |
useResizeObserver | boolean | true | 使用 ResizeObserver 监听容器(优先于 window) |
resizeDelay | number | 300 | resize 防抖延迟(毫秒) |
theme | string | object | - | ECharts 主题 |
基础方法:
| 方法 | 说明 |
|---|---|
initChart() | 初始化图表实例 |
setOption(option, opts) | 设置图表配置(完全替换) |
updateOption(option) | 更新图表配置(合并模式) |
resize(opts) | 手动调整图表大小 |
clear() | 清空图表 |
showLoading(config) | 显示加载动画 |
hideLoading() | 隐藏加载动画 |
dispose() | 销毁图表实例 |
getInstance() | 获取 ECharts 实例 |
事件方法:
| 方法 | 说明 |
|---|---|
on(eventName, handler, context) | 监听事件 |
off(eventName, handler) | 移除事件监听 |
once(eventName, handler, context) | 监听事件(仅一次) |
扩展方法:
| 方法 | 说明 |
|---|---|
dispatchAction(payload) | 触发图表行为 |
getDom() | 获取图表 DOM 容器 |
getWidth() | 获取图表宽度 |
getHeight() | 获取图表高度 |
convertToPixel(finder, value) | 逻辑坐标转换为像素坐标 |
convertFromPixel(finder, value) | 像素坐标转换为逻辑坐标 |
useSimpleChart - 简化版 Hook
适用于静态配置或简单场景,无需数据获取。
import { useSimpleChart } from '@vue3-simple-bui/platform';
import { PieChart } from 'echarts/charts';
import { LegendComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
// 静态配置
const chart = useSimpleChart(
{
title: { text: '饼图示例' },
series: [
{
type: 'pie',
data: [
{ value: 335, name: '直接访问' },
{ value: 234, name: '邮件营销' },
{ value: 1548, name: '搜索引擎' },
],
},
],
},
{
components: [PieChart, LegendComponent, TooltipComponent, CanvasRenderer],
},
);
// 动态配置(函数形式)
const dynamicChart = useSimpleChart(
() => ({
series: [{ data: props.chartData, type: 'bar' }],
}),
{
components: [BarChart, GridComponent, CanvasRenderer],
},
);
return { chartRef: chart.chartRef };useChartWithData - 高级 Hook
适用于需要异步获取数据的场景,自动处理加载状态和错误。
场景 A:静态数据图表
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { pieChartOptionFunc } from './config/cloud-chart-options';
const pieChart = useChartWithData({
components: [LineChart, GridComponent, CanvasRenderer],
optionGenerator: pieChartOptionFunc,
immediate: true, // 默认 true,自动初始化
});
// 在模板中
return {
pieChartRef: pieChart.chartRef,
};场景 B:从 API 获取数据
import { useChartWithData } from '@vue3-simple-bui/platform';
import { BarChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { getTaskStatistics } from '@/api/dashboard';
const taskChart = useChartWithData({
components: [BarChart, GridComponent, TooltipComponent, CanvasRenderer],
// 数据获取函数
fetchData: async () => {
const res = await getTaskStatistics();
if (res.code === 200) {
return res.data;
}
throw new Error(res.message || '数据加载失败');
},
if (res.code === 200) {
return res.data;
}
throw new Error(res.message || '数据加载失败');
},
// 配置生成函数
optionGenerator: (data) => ({
title: { text: '任务统计' },
series: [
{
type: 'pie',
data: data.taskList,
},
],
}),
// 错误处理(可选)
onError: (error) => {
console.error('图表加载失败:', error);
message.error(`加载失败: ${error.message}`);
},
// 其他选项
autoResize: true,
resizeDelay: 300,
});
// 手动刷新
const handleRefresh = () => {
taskChart.refresh();
};场景 C:延迟初始化
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
const chart = useChartWithData({
components: [LineChart, GridComponent, CanvasRenderer],
immediate: false, // 不自动初始化
optionGenerator: myOptionFunc,
});
onMounted(() => {
// 在某个时机手动加载
setTimeout(() => {
chart.loadChart();
}, 1000);
});场景 D:使用现有的配置函数
import { useChartWithData } from '@vue3-simple-bui/platform';
import { GaugeChart, PieChart } from 'echarts/charts';
import { TitleComponent, LegendComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import * as chartOptions from './config/cloud-chart-options';
// 内存使用率仪表盘
const memoryGauge = useChartWithData({
components: [GaugeChart, TitleComponent, CanvasRenderer],
fetchData: async () => {
const res = await getMemoryUsage();
if (res.code === 200) return res.data;
throw new Error(res.message);
},
optionGenerator: chartOptions.memoryGaugeOptionFunc,
});
// CPU 使用率仪表盘
const cpuGauge = useChartWithData({
components: [GaugeChart, TitleComponent, CanvasRenderer],
fetchData: async () => {
const res = await getCpuUsage();
if (res.code === 200) return res.data;
throw new Error(res.message);
},
optionGenerator: chartOptions.cpuGaugeOptionFunc,
});
// 磁盘使用饼图
const diskPie = useChartWithData({
components: [PieChart, LegendComponent, CanvasRenderer],
fetchData: async () => {
const res = await getDiskUsage();
if (res.code === 200) return res.data;
throw new Error(res.message);
},
optionGenerator: chartOptions.diskPieOptionFunc,
});进阶用法
事件监听
使用 on、off、once 方法监听图表事件。
import { useEcharts } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
const chart = useEcharts({
components: [LineChart, GridComponent, CanvasRenderer],
});
onMounted(() => {
chart.setOption({
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ data: [120, 200, 150], type: 'line' }],
});
// 监听点击事件
chart.on('click', (params) => {
console.log('点击了:', params.name, params.value);
// 触发高亮
chart.dispatchAction({
type: 'highlight',
seriesIndex: 0,
dataIndex: params.dataIndex,
});
});
// 监听鼠标移出
chart.on('mouseout', () => {
chart.dispatchAction({
type: 'downplay',
seriesIndex: 0,
});
});
// 单次监听(渲染完成)
chart.once('finished', () => {
console.log('图表渲染完成');
});
});
// 移除事件监听
const removeListeners = () => {
chart.off('click');
chart.off('mouseout');
};全局注册组件
适用于多处使用相同组件的场景,可在应用入口一次性注册。
// main.ts 或 config/echarts.ts
import { registerEchartsComponents } from '@vue3-simple-bui/platform';
import { LineChart, BarChart, PieChart } from 'echarts/charts';
import {
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
} from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
// 全局注册常用组件
registerEchartsComponents([
LineChart,
BarChart,
PieChart,
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent,
CanvasRenderer,
]);
// 之后使用时不需要传 components
const chart = useEcharts(); 响应式配置更新
监听数据变化,自动更新图表。
import { ref, watch } from 'vue';
import { useEcharts } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
const chartData = ref([120, 200, 150]);
const chart = useEcharts({
components: [LineChart, GridComponent, CanvasRenderer],
});
// 监听数据变化
watch(chartData, (newData) => {
chart.updateOption({
// 使用 updateOption 合并更新
series: [{ data: newData }],
});
});
// 修改数据,图表自动更新
const updateData = () => {
chartData.value = [150, 230, 180];
};图表联动
多个图表之间的交互联动。
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent, DataZoomComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
const chart1 = useChartWithData({
components: [LineChart, GridComponent, DataZoomComponent, CanvasRenderer],
optionGenerator: option1Func,
});
const chart2 = useChartWithData({
components: [LineChart, GridComponent, DataZoomComponent, CanvasRenderer],
optionGenerator: option2Func,
});
onMounted(() => {
const instance1 = chart1.getInstance();
const instance2 = chart2.getInstance();
// 图表1 的 dataZoom 联动到图表2
instance1?.on('dataZoom', (params) => {
instance2?.dispatchAction({
type: 'dataZoom',
...params,
});
});
});完整示例
<template>
<div class="dashboard">
<div ref="chart1Ref" class="chart"></div>
<div ref="chart2Ref" class="chart"></div>
<a-button @click="refreshCharts">刷新</a-button>
</div>
</template>
<script setup lang="ts">
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart, BarChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { getChartData } from '@/api/dashboard';
import * as chartOptions from './config/cloud-chart-options';
import { message } from 'ant-design-vue';
// 图表1:静态配置
const chart1 = useChartWithData({
components: [LineChart, GridComponent, TooltipComponent, CanvasRenderer],
optionGenerator: chartOptions.pieChartOptionFunc,
});
// 图表2:动态数据
const chart2 = useChartWithData({
components: [BarChart, GridComponent, TooltipComponent, CanvasRenderer],
fetchData: async () => {
const res = await getChartData();
if (res.code === 200) return res.data;
throw new Error(res.message);
},
optionGenerator: (data) => ({
xAxis: { data: data.categories },
series: [{ data: data.values, type: 'bar' }],
}),
onError: (err) => {
message.error(`数据加载失败: ${err.message}`);
},
});
// 刷新所有图表
const refreshCharts = () => {
chart1.refresh();
chart2.refresh();
};
// 暴露给模板
defineExpose({
chart1Ref: chart1.chartRef,
chart2Ref: chart2.chartRef,
refreshCharts,
});
</script>
<style scoped>
.dashboard {
display: flex;
gap: 20px;
}
.chart {
width: 100%;
height: 400px;
}
</style>TypeScript 类型定义
interface UseEchartsOptions {
autoResize?: boolean; // 是否自动响应大小变化
useResizeObserver?: boolean; // 使用 ResizeObserver(优先)
resizeDelay?: number; // resize 防抖延迟(毫秒)
theme?: string | object; // ECharts 主题
components?: any[]; // 需要注册的组件
}
interface UseChartWithDataOptions<T = any> extends UseEchartsOptions {
fetchData?: () => Promise<T>; // 数据获取函数
optionGenerator: (data: T) => EChartsOption; // 配置生成函数
onError?: (error: Error) => void; // 错误处理
immediate?: boolean; // 是否立即加载(默认 true)
}
type EventCallback = (params: any) => void;常见问题
Q1: 图表不显示?
确保:
- 容器元素有明确的宽高
- chartRef 正确绑定到 DOM 元素
- 已注册必需的组件(图表类型、组件、渲染器)
- 检查控制台是否有错误
<template>
<!-- ✅ 正确:明确宽高 -->
<div ref="chartRef" style="width: 100%; height: 400px;"></div>
</template>Q2: 报错 "Component xxx not exists"
需要注册对应的组件。ECharts 5.x 需要按需引入。
// ❌ 错误:缺少组件
const chart = useEcharts();
// ✅ 正确:注册所需组件
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
const chart = useEcharts({
components: [LineChart, GridComponent, CanvasRenderer],
});常见组件对照表:
| 图表类型 | 需要的组件 |
|---|---|
| 折线图 | LineChart + GridComponent + CanvasRenderer |
| 柱状图 | BarChart + GridComponent + CanvasRenderer |
| 饼图 | PieChart + LegendComponent + CanvasRenderer |
| 仪表盘 | GaugeChart + CanvasRenderer |
Q3: 如何手动刷新图表?
// useChartWithData 提供 refresh 方法
const chart = useChartWithData({ ... });
chart.refresh(); // 重新获取数据并渲染
// useEcharts 可以重新设置配置
const chart = useEcharts({ ... });
chart.setOption(newOption); // 重新设置配置Q4: 如何访问原生 ECharts 实例?
const chart = useEcharts({ ... });
const instance = chart.getInstance();
instance?.on('click', (params) => {
console.log(params);
});Q5: 图表容器大小变化时不响应?
确保 autoResize: true 和 useResizeObserver: true(默认都开启)。
const chart = useEcharts({
autoResize: true, // 默认 true
useResizeObserver: true, // 优先使用 ResizeObserver
resizeDelay: 300, // 防抖延迟
});ResizeObserver vs Window Resize:
useResizeObserver: true- 监听容器大小变化(推荐)useResizeObserver: false- 降级到监听 window.resize
Q6: 如何优化打包体积?
使用按需引入,只注册需要的组件。
// ❌ 不推荐:全局注册所有组件(~200KB)
registerEchartsComponents([
LineChart,
BarChart,
PieChart,
GaugeChart, // 所有图表类型
GridComponent,
TooltipComponent,
LegendComponent,
TitleComponent, // 所有组件
CanvasRenderer,
]);
// ✅ 推荐:按需注册(~50KB)
const chart = useEcharts({
components: [LineChart, GridComponent, CanvasRenderer], // 仅注册需要的
});体积对比:
| 方式 | 大小 | 节省 |
|---|---|---|
| 全局注册所有组件 | ~200KB | - |
| 按需注册(折线图) | ~50KB | 75% |
| 按需注册(折线图+柱状图) | ~65KB | 67% |
Q7: 如何实现图表的响应式更新?
import { ref, watch } from 'vue';
const chartData = ref([120, 200, 150]);
const chart = useEcharts({
components: [LineChart, GridComponent, CanvasRenderer],
});
// 方式1:使用 watch 监听
watch(chartData, (newData) => {
chart.updateOption({ series: [{ data: newData }] }); // 合并更新
});
// 方式2:使用 computed
const chartOption = computed(() => ({
series: [{ data: chartData.value, type: 'line' }],
}));
watch(chartOption, (newOption) => {
chart.setOption(newOption); // 完全替换
});最佳实践
1. 优先使用按需引入
// ✅ 推荐:减少打包体积
import { LineChart } from 'echarts/charts';
import { GridComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
const chart = useEcharts({
components: [LineChart, GridComponent, CanvasRenderer],
});
// ❌ 不推荐:引入全部
import * as echarts from 'echarts';2. 封装业务图表 Hook
将常用的图表封装成独立的 Hook,提高复用性。
// hooks/useBusinessChart.ts
import { useChartWithData } from '@vue3-simple-bui/platform';
import { LineChart } from 'echarts/charts';
import { GridComponent, TooltipComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
import { fetchSalesData } from '@/api/sales';
export const useSalesChart = () => {
return useChartWithData({
components: [LineChart, GridComponent, TooltipComponent, CanvasRenderer],
fetchData: fetchSalesData,
optionGenerator: (data) => ({
title: { text: '销售数据' },
xAxis: { type: 'category', data: data.dates },
yAxis: { type: 'value' },
series: [{ data: data.values, type: 'line' }],
}),
});
};3. 全局注册常用组件
对于多处使用的组件,可在应用入口统一注册。
// config/echarts.ts
import { registerEchartsComponents } from '@vue3-simple-bui/platform';
import { LineChart, BarChart, PieChart } from 'echarts/charts';
import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components';
import { CanvasRenderer } from 'echarts/renderers';
export const setupEcharts = () => {
registerEchartsComponents([
LineChart,
BarChart,
PieChart,
GridComponent,
TooltipComponent,
LegendComponent,
CanvasRenderer,
]);
};
// main.ts
import { setupEcharts } from './config/echarts';
setupEcharts();4. 使用 TypeScript 类型
充分利用 TypeScript 的类型提示。
import type { EChartsOption } from 'echarts/types/dist/shared';
const option: EChartsOption = {
// 完整的类型提示
title: { text: '标题' },
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
yAxis: { type: 'value' },
series: [{ data: [1, 2, 3], type: 'line' }],
};5. 统一错误处理
const chart = useChartWithData({
fetchData: async () => {
const res = await fetchData();
if (res.code !== 200) {
throw new Error(res.message);
}
return res.data;
},
optionGenerator: generateOption,
onError: (error) => {
message.error(`加载失败: ${error.message}`);
console.error('图表错误:', error);
},
});6. 性能优化
const chart = useEcharts({
components: [LineChart, GridComponent, CanvasRenderer],
resizeDelay: 300, // 调整防抖延迟
useResizeObserver: true, // 使用 ResizeObserver
});
// 使用 shallowRef 存储大量数据
import { shallowRef } from 'vue';
const bigData = shallowRef(largeArray);7. 组件清理
虽然 Hook 会自动清理,但手动清理事件监听是好习惯。
import { onBeforeUnmount } from 'vue';
const chart = useEcharts({
components: [LineChart, GridComponent, CanvasRenderer],
});
const handleClick = (params: any) => {
console.log(params);
};
onMounted(() => {
chart.on('click', handleClick);
});
onBeforeUnmount(() => {
chart.off('click', handleClick); // 手动清理
});