第 11 章:封装通用 BaseEchart 基础图表组件(Vue3 + 若依,全局复用)

第 11 章:封装通用 BaseEchart 基础图表组件(Vue3 + 若依,全局复用)

本章目标:

将前面所有通用逻辑(实例初始化、resize、销毁、导出图片、自动轮播)抽离成独立可复用组件 BaseEchart.vue

之后任何页面,只需要写一行<BaseEchart />,传入option即可渲染图表,不用重复写 ref、onMounted、定时器清理等重复代码。

1、新建组件 src/components/BaseEchart/index.vue

复制代码
<template>
  <div
    ref="chartRef"
    class="base-echart"
    :style="{ width: width, height: height }"
    @mouseenter="handleMouseEnter"
    @mouseleave="handleMouseLeave"
  ></div>
</template>

<script setup>
import { ref, watch, onMounted, onUnmounted } from 'vue'
import * as echarts from 'echarts'

const props = defineProps({
  // 图表配置项
  option: {
    type: Object,
    default: () => ({})
  },
  width: {
    type: String,
    default: '100%'
  },
  height: {
    type: String,
    default: '400px'
  },
  // 是否开启自动轮播高亮
  autoPlay: {
    type: Boolean,
    default: false
  },
  // 轮播间隔毫秒
  playInterval: {
    type: Number,
    default: 2000
  }
})

const emit = defineEmits(['chartReady'])

const chartRef = ref(null)
let chartInstance = null
let timer = null
let currentIndex = 0
let dataLen = 0

// 初始化图表
const initChart = () => {
  chartInstance = echarts.init(chartRef.value)
  chartInstance.setOption(props.option, true)
  emit('chartReady', chartInstance)
  // 开启轮播
  if (props.autoPlay) {
    startAutoPlay()
  }
}

// 监听option变化,更新图表
watch(
  () => props.option,
  (newOpt) => {
    if (!chartInstance) return
    chartInstance.setOption(newOpt, true)
    // 重置轮播索引
    if(props.autoPlay){
      clearTimer()
      dataLen = newOpt.series?.[0]?.data?.length || 0
      currentIndex = 0
      startAutoPlay()
    }
  },
  { deep: true }
)

// 窗口自适应
const resizeHandler = () => {
  chartInstance?.resize()
}

// 轮播相关
function startAutoPlay() {
  if(timer) return
  dataLen = props.option.series?.[0]?.data?.length || 0
  if(dataLen ===0) return
  timer = setInterval(()=>{
    chartInstance.dispatchAction({type:'downplay', seriesIndex:0})
    chartInstance.dispatchAction({
      type: 'showTip',
      seriesIndex:0,
      dataIndex: currentIndex
    })
    currentIndex ++
    if(currentIndex >= dataLen) currentIndex = 0
  }, props.playInterval)
}
function clearTimer(){
  if(timer){
    clearInterval(timer)
    timer = null
  }
}
const handleMouseEnter = ()=> clearTimer()
const handleMouseLeave = ()=>{
  if(props.autoPlay) startAutoPlay()
}

// 导出图片方法,暴露给父组件
const getChartImageUrl = () => {
  if(!chartInstance) return null
  return chartInstance.getDataURL({type:'png', pixelRatio:2})
}

onMounted(() => {
  initChart()
  window.addEventListener('resize', resizeHandler)
})

onUnmounted(() => {
  clearTimer()
  chartInstance?.dispose()
  window.removeEventListener('resize', resizeHandler)
})

// 对外暴露方法
defineExpose({
  getChartImageUrl,
  chartInstance
})
</script>

<style scoped>
.base-echart{
  box-sizing: border-box;
}
</style>

2、页面使用示例(父页面,直接调用 BaseEchart 组件)

复制代码
<template>
  <div class="page-container">
    <div style="margin-bottom:15px;display:flex;gap:10px;align-items:center;">
      <el-switch v-model="autoPlay" @change="onPlayChange" />
      <span>开启自动轮播</span>
      <el-button type="primary" @click="loadData">刷新图表</el-button>
      <el-button type="success" @click="exportImg">导出图片</el-button>
    </div>
    <!-- 一行代码引入图表! -->
    <BaseEchart
      ref="baseChartRef"
      :option="chartOption"
      :auto-play="autoPlay"
      height="420px"
    />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import BaseEchart from '@/components/BaseEchart/index.vue'
import { ElMessage } from 'element-plus'

const baseChartRef = ref(null)
const autoPlay = ref(true)
const chartOption = ref({})

// 构建组合图配置
function buildOption(xData, saleData, rateData) {
  return {
    title: { text: '月度销售额 + 同比增长率 组合统计', left: 'center' },
    tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } },
    legend: { data: ['销售额', '同比增长率'], bottom: 10 },
    xAxis: [{ type: 'category', data: xData }],
    yAxis: [
      { type: 'value', name: '销售额(万元)', min:0 },
      { type: 'value', name: '增长率(%)', position:'right', axisLabel:{formatter:'{value} %'} }
    ],
    series: [
      { name:'销售额', type:'bar', data: saleData, itemStyle:{color:'#409EFF'} },
      { name:'同比增长率', type:'line', yAxisIndex:1, smooth:true, data:rateData, itemStyle:{color:'#67C23A'} }
    ]
  }
}

// 加载数据
const loadData = () => {
  const xData = ['1月','2月','3月','4月','5月','6月']
  const saleData = [32, 45, 38, 52, 48, 60]
  const rateData = [12, 18, 15, 22, 19, 28]
  chartOption.value = buildOption(xData, saleData, rateData)
  ElMessage.success('图表刷新完成')
}

// 轮播开关切换
const onPlayChange = ()=>{
  // 组件内部监听autoPlay自动处理
}

// 导出图片,调用子组件暴露的方法
const exportImg = ()=>{
  const imgUrl = baseChartRef.value.getChartImageUrl()
  if(!imgUrl){
    ElMessage.warning('图表实例不存在')
    return
  }
  const a = document.createElement('a')
  a.href = imgUrl
  a.download = '销售统计图表.png'
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('图片导出成功')
}

loadData()
</script>

第 11 章测试清单

  1. 在若依项目新建src/components/BaseEchart/index.vue粘贴组件代码
  2. 新建页面,粘贴父页面代码
  3. 逐项测试
    ✅ 页面渲染组合图表,自动轮播高亮,鼠标悬浮暂停,移开继续
    ✅ 切换autoPlay开关,轮播启停
    ✅ 点击导出图片,正常下载 PNG
    ✅ 修改chartOption,图表自动更新(watch 监听 option)
    ✅ 路由切换离开页面,自动销毁 echart 实例 + 清除定时器,无内存泄漏
    ✅ 窗口缩放自适应

核心知识点

  1. defineExpose:子组件向外暴露实例和方法,父组件通过 ref 调用子组件内部函数
  2. watch(option,{deep:true}):深度监听配置,数据变更自动刷新图表
  3. 封装后优势:以后做折线、饼图、柱状图,只需要传 option,不用重复写初始化、销毁、resize、定时器逻辑
  4. 组件解耦:基础渲染逻辑全部封装在 BaseEchart,业务页面只关心数据和 option 配置

后续扩展方向:可以在 BaseEchart 内部内置空态文字、loading、统一样式,所有图表共用一套空态。

相关推荐
咖啡无伴侣13 分钟前
1. 别再手动改代码格式了!Vue3 项目工程化规范搭建实战
vue.js·架构
知野小兔3 小时前
Vuex和Pinia状态管理
vue.js
VX_bysjlw9854 小时前
数码设备销售网站设计与实现39138-计算机毕设原创(免费领源码+带部署教程)
java·vue.js·spring boot·mysql·tomcat·mybatis·idea
whi8 小时前
一个小工具,解决了一个困扰 Vue 开发者多年的类型检查难题
vue.js·typescript
xcs1940520 小时前
前端 vue 的前端页面debugger 进不去
前端·javascript·vue.js
雪芽蓝域zzs1 天前
第 5 节:Marker 点击弹出 InfoWindow 信息窗口
vue.js
大包子1 天前
structuredClone vs lodash.cloneDeep:核心差异与选型指南
javascript·vue.js
雪芽蓝域zzs1 天前
第 3 节:地图点击事件,点击获取经纬度
vue.js
WayneX1 天前
开源 Vue 3 组件库 Morya UI:把组件、文档、AI 工具链一起做进一个包
前端·vue.js·前端框架
hai_android1 天前
Chat 聊天模块功能总结
前端·javascript·vue.js