第 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 章测试清单
- 替换代码,保存刷新
- 逐项测试:
✅ 页面加载后,图表每 2 秒自动切换高亮 tooltip,循环轮播
✅ 鼠标悬浮到图表上,轮播暂停;鼠标移开,继续轮播
✅ 关闭【自动轮播】开关,轮播停止;打开恢复
✅ 点击【加载空数据】,轮播停止,显示空态
✅ 切换若依菜单离开页面,控制台无定时器泄漏报错
✅ 导出图片、CSV 功能正常
核心知识点
dispatchAction:ECharts 原生指令 API,showTip显示高亮 tooltip,downplay取消高亮,实现自动轮播- 定时器管理 :
playTimer变量保存定时器句柄,启动前判断是否已存在,防止重复启动多个定时器 - 生命周期清理 :
onUnmounted里必须clearInterval,否则若依多标签页反复进出,定时器会越堆越多,内存泄漏 - 交互联动 :
@mouseenter暂停,@mouseleave继续,是大屏轮播标配交互
