返回 导航

Vue.js

hangge.com

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)

回到顶部