Vue.js 3 - ECharts组件封装教程(封装BaseEchart通用图表组件)
作者:hangge | 2026-10-30 08:54
在 Vue3 项目中使用 ECharts 进行数据可视化时,我们经常需要在多个页面或组件中展示各种图表,如柱状图、饼图、折线图等。每次使用都需要重复编写初始化、设置配置、监听窗口变化等逻辑,这不仅繁琐而且难以维护。本文我将详细介绍如何封装一个通用的 BaseEchart 组件,将 ECharts 的初始化、配置设置、响应式更新等公共逻辑抽取出来,实现代码的高效复用。

一、基本介绍
1,ECharts 简介
(1)ECharts 是一个基于 JavaScript 的开源可视化图表库,由百度团队开发并贡献给 Apache 基金会。它具有丰富的图表类型和强大的交互能力,凭借其优秀的性能和灵活的配置,成为前端可视化的主流方案。
(2)ECharts 支持的图表类型非常丰富:
- 基础图表:柱状图、折线图、饼图、散点图等
- 高级图表:雷达图、热力图、关系图、地图等
- 特色功能:数据区域缩放、动态切换、多图表联动等
2,为什么要封装组件
(1)在使用 ECharts 绘制图表时,往往会有大量重复的逻辑:
- ECharts 实例的初始化
- 设置图表配置项(setOption)
- 监听窗口变化重新计算尺寸(resize)
- 组件销毁时释放资源(dispose)
(2)既然有这么多可以复用的逻辑,封装一个公共的 BaseEchart 组件,可以带来以下好处:
- 提高复用性:一次封装,多处使用
- 降低维护成本:修改一处,全局生效
- 统一风格:保证项目中图表的一致性
二、组件封装实现
1,安装 ECharts
(1)首先在项目中安装 ECharts 依赖:
npm install echarts --save # 或使用 pnpm pnpm add echarts --save
2,创建 BaseEchart 组件
(1)在项目的 base-ui 文件夹下新建组件目录结构:
src/
└── base-ui/
└── echart/
├── index.ts # 统一导出
├── src/
│ └── base-echart.vue # 组件文件
└── hooks/
└── useEchart.ts # 组合式函数
(2)其中 useEchart.ts 文件代码如下,将 ECharts 的一些初始化逻辑抽取到组合式函数(Composable)中,从而优化代码结构。
提示:使用组合式函数的好处是将图表逻辑与组件解耦,可以在多个组件中复用,也便于单元测试。如果项目需要展示地图,还可以在此文件中注册地图数据。
import * as echarts from 'echarts'
import type { EChartsOption } from 'echarts'
export default function useEchart(el: HTMLElement) {
// 初始化 ECharts 实例
const echartInstance = echarts.init(el)
// 设置图表配置
const setOptions = (options: EChartsOption) => {
echartInstance.setOption(options)
}
// 手动更新图表尺寸
const updateSize = () => {
echartInstance.resize()
}
// 监听窗口变化
window.addEventListener('resize', () => {
echartInstance.resize()
})
// 返回实例和方法
return {
echartInstance,
setOptions,
updateSize
}
}
(3)base-echart.vue 组件是封装的核心,其代码如下:
提示:这里使用了 Vue3 的 <script setup> 语法糖,代码更加简洁。通过 watchEffect 函数收集依赖,当 props 中的 options 发生变化时,会自动触发回调函数更新图表。
<template>
<div class="base-echart">
<div ref="echartDivRef" :style="{ width: width, height: height }"></div>
</div>
</template>
<script lang="ts" setup>
// 1. 导入依赖
import { ref, onMounted, watchEffect, onBeforeUnmount } from 'vue'
import type { EChartsOption } from 'echarts'
import useEchart from '../hooks/useEchart'
// 2. 定义 Props 类型
interface BaseEChartsProps {
options: EChartsOption
width?: string
height?: string
}
// 3. 定义 props,设置默认值
const props = withDefaults(defineProps<BaseEChartsProps>(), {
width: '100%',
height: '360px'
})
// 4. 定义响应式变量
const echartDivRef = ref<HTMLElement>()
// 5. 设置图表配置
let setOptions: (options: EChartsOption) => void
// 6. 组件挂载时初始化
onMounted(() => {
const { setOptions: setOpts } = useEchart(echartDivRef.value!)
setOptions = setOpts
// 设置初始配置
setOptions(props.options)
})
// 7. 监听 props 变化,响应式更新图表
watchEffect(() => {
setOptions?.(props.options)
})
</script>
<style scoped>
.base-echart {
width: 100%;
height: 100%;
}
</style>
(4)最后在 echart/index.ts 中统一导出组件,这是开发中常见的编码规范:
import BaseEchart from './src/base-echart.vue' export default BaseEchart
三、使用示例
1,绘制柱状图
(1)柱状图是最常用的图表类型之一,适合展示分类数据的数值对比。下面是具体的使用样例:
提示:柱状图的 series.type 设置为 'bar',xAxis.data 是 X 轴的分类数据,series.data 是对应的数值。
<template>
<div class="app-container">
<!-- 柱状图 -->
<base-echart :options="barOptions"></base-echart>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { EChartsOption } from 'echarts'
import BaseEchart from './base-ui/echart'
// 柱状图配置
const barOptions = computed<EChartsOption>(() => {
return {
title: {
text: '柱状图示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ['衬衫', '羊毛衫', '雪纺衫', '裤子', '高跟鞋', '袜子']
},
yAxis: {},
series: [
{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}
]
}
})
</script>
<style scoped>
.app-container {
height: 100%;
}
</style>
<style>
/* 全局样式:让容器撑满整个浏览器高度 */
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
</style>
(2)运行效果如下:

2,绘制饼图
(1)饼图是一种常用的数据展示方式,适合展示各部分占比情况。下面是具体的的样例代码:
提示:饼图的 tooltip.trigger 应设置为 'item',表示鼠标悬停到数据项图形上时触发提示框。
<template>
<div class="app-container">
<!-- 饼图 -->
<base-echart :options="pieOptions"></base-echart>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { EChartsOption } from 'echarts'
import BaseEchart from './base-ui/echart'
// 饼图配置
const pieOptions = computed<EChartsOption>(() => {
return {
// 提示框组件
tooltip: {
trigger: 'item' // 数据项图形触发
},
// 图例组件
legend: {
top: '5%',
left: 'center'
},
// 图表数据
series: [
{
name: 'Access From',
type: 'pie',
radius: ['40%', '70%'], // 内半径,外半径(环形图)
avoidLabelOverlap: false,
label: {
show: false,
position: 'center'
},
emphasis: {
label: {
show: true,
fontSize: '40',
fontWeight: 'bold'
}
},
labelLine: {
show: false
},
data: [
{ value: 1048, name: 'Search Engine' },
{ value: 735, name: 'Direct' },
{ value: 580, name: 'Email' },
{ value: 484, name: 'Union Ads' },
{ value: 300, name: 'Video Ads' }
]
}
]
}
})
</script>
<style scoped>
.app-container {
height: 100%;
}
</style>
<style>
/* 全局样式:让容器撑满整个浏览器高度 */
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
</style>
(2)饼图效果如下所示:

3,绘制折线图
(1)折线图适合展示数据的变化趋势。下面是具体的样例代码:
提示:折线图的 tooltip.trigger 应设置为 'axis',表示鼠标悬停到坐标轴上时触发提示框。同时,legend.data 需要与 series 中每项的 name 对应。
<template>
<div class="app-container">
<!-- 折线图 -->
<base-echart :options="lineOptions"></base-echart>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { EChartsOption } from 'echarts'
import BaseEchart from './base-ui/echart'
// 折线图配置
const lineOptions = computed<EChartsOption>(() => {
return {
// 标题
title: {
text: '折线图案例'
},
// 提示框组件
tooltip: {
trigger: 'axis' // 坐标轴触发
},
// 图例组件(需与 series 每项的 name 对应)
legend: {
data: ['邮件', '广告']
},
// 直角坐标系内绘图网格的位置
grid: {
left: '3%',
right: '4%',
bottom: '12%',
containLabel: true
},
// X 轴配置
xAxis: {
type: 'category',
boundaryGap: false,
data: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']
},
// Y 轴配置
yAxis: {
type: 'value'
},
// 图表数据
series: [
{
name: '邮件',
type: 'line',
data: [120, 132, 101, 134, 90, 230, 210]
},
{
name: '广告',
type: 'line',
data: [220, 182, 191, 234, 290, 330, 310]
}
]
}
})
</script>
<style scoped>
.app-container {
height: 100%;
}
</style>
<style>
/* 全局样式:让容器撑满整个浏览器高度 */
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
</style>
(2)折线图效果如下所示:

4,面积图
(1)面积图是折线图的变体,通过填充区域强调数量变化。下面是具体的样例代码:
提示:面积图本质上是折线图,只需要添加 areaStyle 属性即可实现填充效果。可以使用渐变色让图表更加美观。
<template>
<div class="app-container">
<!-- 面积图 -->
<base-echart :options="areaOptions"></base-echart>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { EChartsOption } from 'echarts'
import BaseEchart from './base-ui/echart'
// 面积图配置
const areaOptions = computed<EChartsOption>(() => {
return {
title: {
text: '月度销售额趋势'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
boundaryGap: false,
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '销售额',
type: 'line',
smooth: true, // 平滑曲线
areaStyle: { // 面积填充样式
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{ offset: 0, color: 'rgba(58, 77, 233, 0.8)' },
{ offset: 1, color: 'rgba(58, 77, 233, 0.1)' }
]
}
},
lineStyle: {
color: '#3A4DE9'
},
data: [820, 932, 901, 934, 1290, 1330, 1320, 801, 102, 230, 432, 503]
}
]
}
})
</script>
<style scoped>
.app-container {
height: 100%;
}
</style>
<style>
/* 全局样式:让容器撑满整个浏览器高度 */
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
</style>
(2)面积图效果如下所示:

5,雷达图
(1)雷达图适合展示多维度数据的对比分析,常用于能力评估、产品对比等场景。下面是具体的使用样例:
提示:雷达图需要配置 radar.indicator 数组,每个元素包含 name(维度名称)和 max(最大值)。series.data 中的 value 数组需要与 indicator 的顺序一一对应。
<template>
<div class="app-container">
<!-- 雷达图 -->
<base-echart :options="radarOptions"></base-echart>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { EChartsOption } from 'echarts'
import BaseEchart from './base-ui/echart'
// 雷达图配置
const radarOptions = computed<EChartsOption>(() => {
return {
title: {
text: '员工能力雷达图'
},
legend: {
data: ['张三', '李四']
},
radar: {
indicator: [
{ name: '编程能力', max: 100 },
{ name: '沟通能力', max: 100 },
{ name: '学习能力', max: 100 },
{ name: '团队协作', max: 100 },
{ name: '解决问题', max: 100 },
{ name: '创新思维', max: 100 }
]
},
series: [
{
type: 'radar',
data: [
{
value: [85, 70, 90, 80, 75, 88],
name: '张三',
areaStyle: {
color: 'rgba(84, 112, 198, 0.3)'
}
},
{
value: [90, 85, 75, 95, 82, 70],
name: '李四',
areaStyle: {
color: 'rgba(145, 204, 117, 0.3)'
}
}
]
}
]
}
})
</script>
<style scoped>
.app-container {
height: 100%;
}
</style>
<style>
/* 全局样式:让容器撑满整个浏览器高度 */
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
</style>
(2)雷达图效果如下所示:

6,仪表盘
(1)仪表盘适合展示单个指标的完成进度或百分比,常用于数据监控大屏。下面是具体的样例代码:
提示:仪表盘的 axisLine.lineStyle.color 可以设置分段颜色,用于区分不同等级(如绿色表示正常、黄色表示警告、红色表示危险)。
<template>
<div class="app-container">
<!-- 仪表盘 -->
<base-echart :options="gaugeOptions"></base-echart>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { EChartsOption } from 'echarts'
import BaseEchart from './base-ui/echart'
// 仪表盘配置
const gaugeOptions = computed<EChartsOption>(() => {
return {
series: [
{
type: 'gauge',
center: ['50%', '60%'],
startAngle: 200,
endAngle: -20,
min: 0,
max: 100,
splitNumber: 10,
itemStyle: {
color: '#5470C6'
},
progress: {
show: true,
width: 30
},
pointer: {
show: false
},
// axisLine: {
// lineStyle: {
// color: [
// [0.3, 'green'],
// [0.7, 'yellow'],
// [1, 'orange']
// ]
// }
// },
axisTick: {
distance: -30,
splitNumber: 5,
lineStyle: {
width: 2,
color: '#999'
}
},
splitLine: {
distance: -30,
length: 14,
lineStyle: {
width: 3,
color: '#999'
}
},
axisLabel: {
distance: -20,
fontSize: 12,
color: '#999'
},
title: {
offsetCenter: [0, '80%'],
fontSize: 14
},
detail: {
valueAnimation: true,
formatter: '{value}%',
fontSize: 30,
offsetCenter: [0, '40%']
},
data: [
{
value: 68,
name: '任务完成率'
}
]
}
]
}
})
</script>
<style scoped>
.app-container {
height: 100%;
}
</style>
<style>
/* 全局样式:让容器撑满整个浏览器高度 */
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
</style>
(2)仪表盘效果如下所示:

7,散点图
(1)散点图适合展示两个变量之间的关系,常用于数据分析、回归分析等场景。下面是具体的使用样例:
提示:散点图的数据格式为 [x, y] 二维数组,x 对应 xAxis,y 对应 yAxis。可以通过 symbolSize 根据数据值动态设置点的大小。
<template>
<div class="app-container">
<!-- 散点图 -->
<base-echart :options="scatterOptions"></base-echart>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import type { EChartsOption } from 'echarts'
import BaseEchart from './base-ui/echart'
// 散点图配置
const scatterOptions = computed<EChartsOption>(() => {
// 生成随机数据
const data1 = []
const data2 = []
for (let i = 0; i < 50; i++) {
data1.push([
(Math.random() * 100).toFixed(2),
(Math.random() * 100).toFixed(2),
(Math.random() * 20 + 10).toFixed(2) // 点的大小
])
data2.push([
(Math.random() * 100).toFixed(2),
(Math.random() * 100).toFixed(2),
(Math.random() * 20 + 10).toFixed(2)
])
}
return {
title: {
text: '身高体重分布图'
},
tooltip: {
trigger: 'item',
formatter: (params: any) => {
return `身高: ${params.data[0]}cm<br/>体重: ${params.data[1]}kg`
}
},
legend: {
data: ['男性', '女性']
},
xAxis: {
name: '身高(cm)',
nameLocation: 'middle',
nameGap: 30
},
yAxis: {
name: '体重(kg)',
nameLocation: 'middle',
nameGap: 40
},
series: [
{
name: '男性',
type: 'scatter',
symbolSize: (val: number[]) => val[2],
data: data1,
itemStyle: {
color: '#5470C6'
}
},
{
name: '女性',
type: 'scatter',
symbolSize: (val: number[]) => val[2],
data: data2,
itemStyle: {
color: '#EE6666'
}
}
]
}
})
</script>
<style scoped>
.app-container {
height: 100%;
}
</style>
<style>
/* 全局样式:让容器撑满整个浏览器高度 */
html,
body,
#app {
margin: 0;
padding: 0;
height: 100%;
}
</style>
(2)散点图效果如下所示:

全部评论(0)