第 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、统一样式,所有图表共用一套空态。

相关推荐
Liu.7741 小时前
vue-virtual-scroller 插件实战用法
vue.js
吠品2 小时前
STM32最小系统板引脚梳理与配置实操
前端·javascript·vue.js
Z小明2 小时前
第 9 章 Pinia 状态管理
vue.js
雪芽蓝域zzs2 小时前
第 13 章:对接后端真实接口(Axios 请求,替换 Mock 数据,完整业务闭环)
vue.js·echars
Aaswk3 小时前
从 HTML/CSS/JS 到 Vue + Axios 的一篇实战笔记
前端·css·vue.js·html
+VX:Fegn08953 小时前
计算机毕业设计|基于springboot + vue图书借阅管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
星光开发者4 小时前
基于Spring Boot的充电桩管理系统的设计与实现-计算机毕设【课程设计】57105
vue.js·spring boot·vscode·mysql·django·php·express
ℋᙚᵐⁱᒻᵉ鲸落4 小时前
ETypeError: nameRef.value?.focus is not a functio. 设置el-input自动聚焦时报错
前端·javascript·vue.js·elementui
+VX:Fegn089513 小时前
计算机毕业设计|基于springboot + vue蛋糕店管理系统(源码+数据库+文档)
前端·数据库·vue.js·spring boot·课程设计