第 10 章:大屏看板自动轮播高亮(ECharts 大屏经典效果)

第 10 章:大屏看板自动轮播高亮(ECharts 大屏经典效果)

本章目标:实现图表自动轮播高亮,鼠标悬浮暂停,离开继续,页面销毁自动清理定时器。

这是数据大屏、运营看板最常用的动效,复用我们之前的组合图代码。

完整页面代码

复制代码
<template>
  <div class="chart-container">
    <div style="margin-bottom:15px;display:flex;align-items:center;gap:10px;">
      <span>自动轮播:</span>
      <el-switch v-model="autoPlay" @change="toggleAutoPlay" />
      <el-button type="primary" @click="loadComboData">重置数据</el-button>
    </div>

    <el-button type="warning" @click="loadComboEmptyData" style="margin-left:10px;">加载空数据(测试空态)</el-button>
    <el-button type="success" @click="exportChartImage" style="margin-left:10px;">导出图片</el-button>
    <el-button type="info" @click="exportDataCsv" style="margin-left:10px;">导出CSV</el-button>

    <div
      ref="comboChartRef"
      v-loading="loading"
      @mouseenter="stopAutoPlay"
      @mouseleave="startAutoPlay"
      style="
        width: 100%;
        height: 420px;
        border: 1px solid #eee;
        margin-top: 15px;
      "
    ></div>
  </div>
</template>

<script setup>
import { onMounted, onUnmounted, ref } from 'vue'
import { useEcharts } from '@/composables/echartHelper'
import { ElMessage } from 'element-plus'

const {
  chartRef: comboChartRef,
  chartInstance,
  setChartOption: setComboOption
} = useEcharts()
const loading = ref(false)
const autoPlay = ref(true) // 开关控制
let playTimer = null // 定时器
let currentIndex = 0 // 当前高亮的索引
let seriesLength = 0 // 数据总长度

let cacheData = {
  xData: [],
  saleData: [],
  rateData: []
}

const getEmptyOption = () => ({
  title: { text: '' },
  tooltip: {},
  legend: { data: [] },
  series: [],
  graphic: [
    {
      type: 'text',
      left: 'center',
      top: 'middle',
      style: {
        text: '暂无数据',
        fill: '#999',
        fontSize: 14
      }
    }
  ]
})

const buildComboOption = (xData, saleData, rateData) => {
  cacheData.xData = xData
  cacheData.saleData = saleData
  cacheData.rateData = rateData
  seriesLength = xData.length // 记录数据长度
  currentIndex = 0 // 重置轮播索引
  return {
    title: {
      text: '月度销售额 + 同比增长率 组合统计',
      left: 'center'
    },
    tooltip: {
      trigger: 'axis',
      axisPointer: { type: 'cross' }
    },
    legend: {
      data: ['销售额', '同比增长率'],
      bottom: 10
    },
    xAxis: [
      {
        type: 'category',
        data: xData,
        axisPointer: { type: 'shadow' }
      }
    ],
    yAxis: [
      {
        type: 'value',
        name: '销售额(万元)',
        min: 0,
        position: 'left'
      },
      {
        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 fetchChartData = async () => {
  await new Promise(resolve => setTimeout(resolve, 500))
  return {
    xData: ['1月','2月','3月','4月','5月','6月'],
    saleData: [32, 45, 38, 52, 48, 60],
    rateData: [12, 18, 15, 22, 19, 28]
  }
}

const loadComboData = async () => {
  loading.value = true
  try {
    const res = await fetchChartData()
    setComboOption(buildComboOption(res.xData, res.saleData, res.rateData), true)
    // 数据渲染完成后,启动自动轮播
    if (autoPlay.value) startAutoPlay()
  } finally {
    loading.value = false
  }
}

const loadComboEmptyData = () => {
  stopAutoPlay() // 空数据停止轮播
  cacheData = { xData: [], saleData: [], rateData: [] }
  seriesLength = 0
  setComboOption(getEmptyOption(), true)
  ElMessage.warning('暂无组合图数据')
}

// ✅ 核心:自动轮播高亮方法
const startAutoPlay = () => {
  // 空数据 / 开关关闭 / 定时器已存在 都不启动
  if (!autoPlay.value || !seriesLength || playTimer) return
  playTimer = setInterval(() => {
    if (!chartInstance.value) return
    // 先清除之前的高亮
    chartInstance.value.dispatchAction({
      type: 'downplay',
      seriesIndex: 0
    })
    // 高亮当前索引
    chartInstance.value.dispatchAction({
      type: 'showTip',
      seriesIndex: 0,
      dataIndex: currentIndex
    })
    // 索引自增,循环
    currentIndex++
    if (currentIndex >= seriesLength) {
      currentIndex = 0
    }
  }, 2000) // 2秒轮播一个点
}

// 停止轮播
const stopAutoPlay = () => {
  if (playTimer) {
    clearInterval(playTimer)
    playTimer = null
  }
}

// 开关切换
const toggleAutoPlay = (val) => {
  if (val) {
    startAutoPlay()
  } else {
    stopAutoPlay()
  }
}

const exportChartImage = () => {
  if (!chartInstance.value) {
    ElMessage.warning('图表实例不存在!')
    return
  }
  const imgUrl = chartInstance.value.getDataURL({
    type: 'png',
    pixelRatio: 2
  })
  const a = document.createElement('a')
  a.href = imgUrl
  a.download = '月度销售统计图表.png'
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  ElMessage.success('图片导出成功')
}

const exportDataCsv = () => {
  const { xData, saleData, rateData } = cacheData
  if (!xData.length) {
    ElMessage.warning('没有数据可导出!')
    return
  }
  let csvStr = '月份,销售额(万元),同比增长率(%)\n'
  xData.forEach((item, index) => {
    csvStr += `${item},${saleData[index]},${rateData[index]}\n`
  })
  const blob = new Blob([csvStr], { type: 'text/csv;charset=utf-8;' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = '月度销售数据.csv'
  document.body.appendChild(a)
  a.click()
  document.body.removeChild(a)
  URL.revokeObjectURL(url)
  ElMessage.success('数据CSV导出成功')
}

onMounted(() => {
  loadComboData()
})

// 页面销毁清理定时器,防止内存泄漏(若依多标签页切换必须加)
onUnmounted(() => {
  stopAutoPlay()
})
</script>

第 10 章测试清单

  1. 替换代码,保存刷新
  2. 逐项测试:
    ✅ 页面加载后,图表每 2 秒自动切换高亮 tooltip,循环轮播
    ✅ 鼠标悬浮到图表上,轮播暂停;鼠标移开,继续轮播
    ✅ 关闭【自动轮播】开关,轮播停止;打开恢复
    ✅ 点击【加载空数据】,轮播停止,显示空态
    ✅ 切换若依菜单离开页面,控制台无定时器泄漏报错
    ✅ 导出图片、CSV 功能正常

核心知识点

  1. dispatchAction :ECharts 原生指令 API,showTip 显示高亮 tooltip,downplay 取消高亮,实现自动轮播
  2. 定时器管理playTimer 变量保存定时器句柄,启动前判断是否已存在,防止重复启动多个定时器
  3. 生命周期清理onUnmounted 里必须 clearInterval,否则若依多标签页反复进出,定时器会越堆越多,内存泄漏
  4. 交互联动@mouseenter 暂停,@mouseleave 继续,是大屏轮播标配交互
相关推荐
志尊宝1 小时前
Vue3 零基础每日笔记(052):路由常见坑一次排雷——404、刷新丢参数、部署白屏
前端·javascript·vue.js·笔记·html5
雪芽蓝域zzs2 小时前
第 11 章:封装通用 BaseEchart 基础图表组件(Vue3 + 若依,全局复用)
vue.js·echars
Liu.7742 小时前
vue-virtual-scroller 插件实战用法
vue.js
吠品3 小时前
STM32最小系统板引脚梳理与配置实操
前端·javascript·vue.js
Z小明3 小时前
第 9 章 Pinia 状态管理
vue.js
雪芽蓝域zzs3 小时前
第 13 章:对接后端真实接口(Axios 请求,替换 Mock 数据,完整业务闭环)
vue.js·echars
Aaswk4 小时前
从 HTML/CSS/JS 到 Vue + Axios 的一篇实战笔记
前端·css·vue.js·html
+VX:Fegn08954 小时前
计算机毕业设计|基于springboot + vue图书借阅管理系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
星光开发者5 小时前
基于Spring Boot的充电桩管理系统的设计与实现-计算机毕设【课程设计】57105
vue.js·spring boot·vscode·mysql·django·php·express