ECharts实战:滑动缩放+选中背景高亮,打造高颜值统计图表
在后台管理系统开发中,统计图表是高频刚需组件,但面对多单位数据展示、选中项需突出的场景,普通柱状图往往难以满足体验要求。本文基于Vue3+TSX+ECharts,手把手拆解「x轴滑动缩放」+「选中项背景高亮」两大核心功能实现,精准解决多单位数据拥挤、选中状态不直观的痛点,附完整可复用代码,复制即可集成到项目中,大幅提升开发效率!
先上最终效果预览(核心亮点):

- 单位数量>10时,自动触发x轴滑动滑块,支持拖拽缩放,彻底解决标签拥挤重叠问题;
- 选中某一单位后,对应柱子底部自动显示专属淡蓝色渐变背景,视觉上快速聚焦目标数据,提升交互体验;
- 采用堆叠柱状图+垂直渐变配色,兼顾美观度与数据可读性,完美适配后台管理系统UI风格,无需额外调整。
一、核心需求拆解
本次实战聚焦「各单位营业执照统计图表」开发,结合后台系统实际使用场景,明确3个核心需求,避免无效开发:
- 基础展示:清晰呈现多单位的「使用中」「变更中」「待变更」三类营业执照数量,采用堆叠柱状图,直观对比各类数据占比;
- 交互优化:当单位数量超过10个时,x轴支持滑动缩放,保证界面整洁,避免标签重叠遮挡,提升数据浏览体验;
- 视觉聚焦:支持选中某一单位,通过背景高亮突出该单位,解决多数据场景下目标单位难以定位的问题。
技术栈说明:Vue3 + TSX + ECharts,组件化封装设计,支持props灵活传参,可直接复用在各类多单位统计场景(如部门数据统计、设备状态统计等)。
二、核心功能实现(重点讲解滑动+背景)
先贴完整可复用组件代码,关键代码已标注详细注释,后续逐一对「滑动缩放」和「背景高亮」两大核心功能拆解,新手也能轻松看懂、快速复用。
完整组件代码(可直接复制复用)
html
<template>
<div class="bg-#FFFFFF px-24 py-26 br-10">
<TitleLine :font-weight="400" :font-size="16" :title="title" />
<div class="h-300 overflow-x-auto" style="margin-top: -34px">
<ECharts :option="getChartOptions()" style="min-width: 1000px" />
</div>
</div>
</template>
<script setup lang="tsx" name="CenterEcharts">
import ECharts from "@/components/ECharts/index.vue";
import * as echarts from "echarts";
import { computed, ref, watch } from "vue";
// 组件props定义,支持灵活传参,适配不同场景
const props = defineProps({
title: {
type: String,
default: "各单位营业执照统计" // 默认标题,可自定义
},
licenseList: {
type: Array,
default: () => [], // 统计数据数组,必传
required: true
},
selectedData: {
type: Object,
default: () => ({}), // 选中的单位数据,与外部选中逻辑联动
required: true
}
});
// 响应式存储选中数据,监听props变化实时更新
const selectedData = ref<Record<string, any>>(props.selectedData);
// 从props中提取图表所需数据,computed响应式依赖,数据变化自动更新图表
const comName = computed(() => props.licenseList.map((item: any) => item.comName)); // 单位名称
const isUseNum = computed(() => props.licenseList.map((item: any) => item.isUseNum)); // 使用中数量
const waitChangedNum = computed(() => props.licenseList.map((item: any) => item.waitChangedNum)); // 待变更数量
const changesProNum = computed(() => props.licenseList.map((item: any) => item.changesProNum)); // 变更中数量
// 监听选中数据变化,实时更新图表背景高亮状态
watch(
() => props.selectedData,
newVal => {
selectedData.value = newVal;
},
{ deep: true, immediate: true } // deep监听对象变化,immediate初始渲染同步状态
);
// 核心:图表配置项生成函数,封装所有图表逻辑
const getChartOptions = () => {
const option: any = {
// 提示框配置,hover时显示详细数据,提升可读性
tooltip: {
trigger: "axis",
axisPointer: { type: "shadow" } // 阴影指示器,贴合柱状图场景
},
// 图例配置,区分三类数据,样式贴合后台UI
legend: {
data: ["待变更", "变更中", "使用中"],
top: "-4",
right: "0%",
textStyle: { fontSize: 14, color: "#1D2129" },
itemWidth: 10,
itemHeight: 10,
icon: "circle" // 图例图标改为圆形,更简洁
},
// 网格配置,调整内边距,避免标签被遮挡
grid: {
left: "0%",
right: "0%",
bottom: "5",
top: 44,
containLabel: true // 关键:确保标签不被网格裁剪
},
// x轴配置,核心解决标签拥挤问题
xAxis: [
{
type: "category",
data: comName.value,
axisLabel: {
fontSize: 12,
color: "#375881",
padding: [0, 0, 0, 30],
interval: 0, // 强制显示所有标签,不自动隐藏
hideOverlap: false, // 禁止标签重叠隐藏
inside: false, // 标签朝外,不遮挡柱子
lineHeight: 16,
// 标签换行处理,避免过长遮挡,每4个字换行,超过8个字省略
formatter: function (value: string) {
const maxLength = 4;
let result = "";
for (let i = 0; i < value?.length || 0; i += maxLength) {
result += value.slice(i, i + maxLength) + "\n";
}
return result.length > 8 ? result.substring(0, 8) + "..." : result;
}
},
axisLine: { lineStyle: { color: "#eeeff2" } }, // x轴线样式,浅灰色更柔和
axisTick: { show: false }, // 隐藏x轴刻度线,提升整洁度
splitArea: { show: false }, // 不显示分割区域
splitLine: { show: false }, // 不显示分割线
boundaryGap: true, // 两边留白,保证柱子居中显示
axisWidth: 10 // 调整分类宽度,避免过宽
}
],
// y轴配置,简洁清晰,突出数据
yAxis: {
type: "value",
axisLine: { show: false }, // 隐藏y轴线
splitArea: { show: false },
// 辅助线配置,浅灰色虚线,提升数据对比性
splitLine: {
show: true,
lineStyle: { color: "#E5E6EB", width: 1, type: "dashed", opacity: 0.7 }
},
axisLabel: { fontSize: 12, color: "#375881" }, // y轴标签样式
axisTick: {
show: true,
inside: false,
length: 4,
lineStyle: { color: "#375881", width: 2 }
}
},
// 系列数据配置,三类数据堆叠显示
series: [
// 使用中柱子
{
name: "使用中",
type: "bar",
barMaxWidth: 17, // 柱子最大宽度,避免过宽拥挤
stack: "数据", // 堆叠配置,三类数据叠加显示
barCategoryGap: "20%", // 柱子间距,保证居中
barGap: "-60%",
emphasis: { focus: "series" }, // hover时高亮整个系列
// 垂直渐变配色,提升美观度
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(135, 255, 241, 1)" },
{ offset: 1, color: "rgba(115, 232, 255, 1)" }
])
},
data: isUseNum.value,
z: 2 // 层级高于背景柱,避免被遮挡
},
// 变更中柱子
{
name: "变更中",
type: "bar",
stack: "数据",
barMaxWidth: 17,
barCategoryGap: "20%",
barGap: "-60%",
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(110, 180, 255, 1)" },
{ offset: 1, color: "rgba(51, 150, 255, 1)" }
])
},
emphasis: { focus: "series" },
data: changesProNum.value,
z: 2
},
// 待变更柱子
{
name: "待变更",
type: "bar",
stack: "数据",
barMaxWidth: 17,
barGap: "-60%",
barCategoryGap: "20%",
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(255, 224, 130, 1)" },
{ offset: 1, color: "rgba(255, 182, 117, 1)" }
])
},
emphasis: { focus: "series" },
data: waitChangedNum.value,
z: 2
}
]
};
// 重点1:x轴滑动缩放(dataZoom配置),解决多单位数据拥挤问题
if (comName.value?.length > 10) {
option.dataZoom = [
{
type: "slider", // 滑块式缩放,后台系统最常用、最直观
show: true,
bottom: "0", // 滑块位置在x轴底部
height: 8, // 滑块高度,简洁不突兀
borderColor: "transparent", // 隐藏边框
backgroundColor: "#F5F6F7", // 滑块背景色,贴合后台浅色系
fillerColor: "#e0e0e0", // 滑块填充色
showDetail: false, // 不显示缩放详情,避免冗余
start: 0,
end: 50, // 初始显示前50%数据,可根据需求调整
handleSize: 10,
borderRadius: 4,
handleStyle: {
color: "#1A75FF",
borderColor: "#f2f2f2",
opacity: 0, // 隐藏手柄,仅显示滑块,提升整洁度
shadowBlur: 3,
shadowColor: "rgba(0, 0, 0, 0.3)"
},
showDataShadow: false,
textStyle: { fontSize: 0 }, // 隐藏文字,避免冗余
zoomLock: false,
// 最小缩放范围,保证每次至少显示10个单位,避免缩放过度
minValueSpan: comName.value?.length > 10 ? 10 : comName.value?.length,
brushSelect: false
}
];
}
// 重点2:选中项背景高亮(伪背景柱实现),解决目标单位定位问题
if (comName.value && comName.value.length) {
// 计算背景柱最大高度,取三类数据最大值+1,避免背景柱留白
const max1 = Math.max(...(isUseNum.value || [0]));
const max2 = Math.max(...(changesProNum.value || [0]));
const max3 = Math.max(...(waitChangedNum.value || [0]));
const maxHeight = Math.max(max1, max2, max3) + 1;
// 插入背景柱(unshift插入到series最前面,z值设为1,确保在数据柱下方)
option.series.unshift({
name: "背景",
type: "bar",
barMaxWidth: 80, // 背景柱宽度大于数据柱,突出显示
barCategoryGap: "20%", // 与数据柱间距一致,保证精准对齐
itemStyle: {
// 根据选中数据,动态切换背景颜色
color: (params: any) => {
// 匹配选中单位,显示淡蓝色渐变背景
if (params.name === selectedData.value.label) {
return new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "rgba(189, 223, 255, 0.78)" },
{ offset: 1, color: "rgba(173, 235, 255, 0.47)" }
]);
}
// 未选中项:透明背景,不显示
return "rgba(0,0,0,0)";
}
},
z: 1, // 层级低于数据柱,避免遮挡数据
silent: true, // 禁用交互,防止背景柱影响数据柱的点击、hover
tooltip: { show: false }, // 隐藏背景柱提示框,避免冗余
data: comName.value.map(() => maxHeight) // 每个单位都有背景柱,高度统一
});
}
return option;
};
</script>
重点1:x轴滑动缩放(dataZoom)实现详解
当单位数量超过10个时,x轴标签会出现拥挤、重叠问题,严重影响阅读体验,此时借助ECharts的dataZoom组件实现滑动缩放,是后台系统多数据图表的最优解,这里选择「slider」滑块式缩放,更符合后台用户的操作习惯。
核心配置要点(避坑指南,新手必看):
- 触发条件:通过
comName.value?.length > 10做判断,数据量少(≤10个单位)时不显示滑块,避免多余UI,提升页面整洁度; - 滑块样式优化:设置
backgroundColor: "#F5F6F7"、fillerColor: "#e0e0e0",贴合后台浅色系UI风格;handleStyle.opacity: 0隐藏手柄,仅显示滑块,避免视觉冗余; - 缩放范围控制:
minValueSpan设为10,确保每次缩放后至少显示10个单位,避免缩放过度导致数据显示不全,提升浏览体验; - 初始范围设置:
start: 0, end: 50,初始显示前50%的数据,可根据实际需求调整(如数据量极大,可设为30); - 容器配合要点:外层div必须设置
overflow-x-auto,同时ECharts容器设置min-width: 1000px,确保滑块滑动时,图表可横向滚动,避免出现滚动条冲突、图表卡顿问题。
补充说明:ECharts的dataZoom还有「inside」类型(鼠标滚轮缩放),适合数据量极大的场景,可根据项目需求添加;本文选择slider类型,操作更直观,更适配后台用户的使用习惯。
重点2:选中项背景高亮(伪背景柱)实现详解
在多单位统计场景中,用户往往需要快速定位某一单位的数据,此时通过「伪背景柱」实现选中项高亮,是最优方案------在series最前面插入一个透明背景柱,仅当单位被选中时显示渐变背景,核心优势是不影响原有数据柱的交互和显示,且实现简单、兼容性好。
核心实现步骤(一步步拆解,新手可跟着敲):
- 计算背景柱高度:取「使用中」「变更中」「待变更」三类数据的最大值+1,确保背景柱能完全覆盖数据柱,避免留白,视觉上更协调;
- 插入背景柱:通过
option.series.unshift()将背景柱插入到series最前面,设置z: 1,确保背景柱在数据柱(z: 2)下方,不遮挡数据; - 动态切换背景色:通过
itemStyle.color的回调函数,判断当前柱子对应的单位是否为选中项,选中则显示淡蓝色渐变背景,未选中则设为透明,实现动态高亮; - 禁用背景柱交互:设置
silent: true(禁用点击、hover等交互)和tooltip: { show: false }(隐藏提示框),避免背景柱影响数据柱的正常交互,提升用户体验; - 对齐优化:背景柱的
barCategoryGap与数据柱保持一致(均为20%),确保背景柱与数据柱精准对齐,避免出现错位问题,视觉上更整洁。
避坑提醒:背景柱的实现方式有多种,除了本文的伪背景柱叠加,还可以使用ECharts的showBackground属性,但该属性无法实现"选中项单独高亮",灵活性较差,因此伪背景柱是本次需求的最优选择,也适用于其他需要选中高亮的柱状图场景。
三、关键优化点(提升体验和美观度,落地级细节)
- 标签换行优化:x轴标签通过formatter函数,每4个字换行,超过8个字省略,彻底解决标签过长、重叠的问题,提升可读性;
- 渐变配色优化:数据柱和背景柱均采用垂直渐变配色,不仅提升图表美观度,还能清晰区分不同状态的数据,避免单调;
- 响应式同步:通过watch监听selectedData变化,开启
deep: true和immediate: true,确保选中状态实时同步,背景高亮无延迟; - 层级管理:通过z值严格控制背景柱(z:1)和数据柱(z:2)的层级,避免背景柱遮挡数据,同时确保hover时数据柱正常高亮;
- 样式统一:x轴、y轴的颜色、字体大小、线条样式统一,贴合后台管理系统的UI风格,提升页面整体质感,无需额外调整即可集成。
四、使用方式(组件复用,快速集成)
该组件已完成封装,传入3个必传props即可快速集成到项目中,无需修改内部逻辑,大幅提升开发效率:
html
<CenterEcharts
title="各单位营业执照统计"
:licenseList="licenseList"
:selectedData="selectedData"
/>
props详细说明(清晰易懂,避免传参踩坑):
- title:图表标题,可选,默认值为"各单位营业执照统计",可根据实际场景自定义(如"各部门设备统计");
- licenseList:统计数据数组,必传,格式为
[{ comName: "单位名称", isUseNum: 数量, waitChangedNum: 数量, changesProNum: 数量 }],数组长度无限制,自动适配滑动功能; - selectedData:选中的单位数据,必传,格式为
{ label: "单位名称" },需与外部选中逻辑(如下拉选择、点击列表)联动,实现背景高亮同步。
五、常见问题及解决方案(实战避坑,节省调试时间)
- 问题1:滑动滑块时,图表不跟随滚动,出现卡顿或滚动条异常? 解决方案:外层div必须设置
overflow-x-auto,且ECharts容器必须设置min-width: 1000px,确保有足够的滚动空间,避免滚动条冲突; - 问题2:背景柱与数据柱不对齐,出现错位? 解决方案:背景柱和数据柱的
barCategoryGap必须保持一致,本文均设为20%,不可单独修改某一个的间距; - 问题3:选中项背景不更新,或切换选中项时背景无变化? 解决方案:确保watch监听selectedData时,开启
deep: true(监听对象内部变化)和immediate: true(初始渲染同步状态),避免监听失效; - 问题4:数据量少(≤10个单位)时,滑块仍显示,造成UI冗余? 解决方案:严格判断
comName.value?.length > 10,只有数据量超过10时才渲染dataZoom,避免无效UI展示; - 问题5:背景柱遮挡数据柱,或hover时数据柱不高亮? 解决方案:确保背景柱的z值(1)小于数据柱的z值(2),同时数据柱设置
emphasis: { focus: "series" },保证hover时正常高亮。
六、总结
本文基于Vue3+TSX+ECharts,完整实现了「x轴滑动缩放」和「选中项背景高亮」两大核心功能,精准解决了后台管理系统中多单位统计图表的痛点,组件已封装完成,可直接复制复用,大幅节省开发和调试时间。
核心亮点(开发者关注重点):
- 滑动缩放:基于dataZoom实现,自动适配数据量,交互直观,彻底解决标签拥挤问题;
- 背景高亮:通过伪背景柱叠加实现,不影响原有交互,视觉突出,快速定位目标数据;
- 组件复用:props灵活传参,适配各类多单位统计场景,无需重复开发;
- 细节拉满:包含标签换行、渐变配色、层级管理等落地级优化,同时提供避坑指南,新手也能快速上手。
如果需要调整滑动速度、背景颜色、柱子样式,可直接修改对应配置;若有个性化需求(如添加鼠标滚轮缩放、修改渐变颜色),欢迎留言讨论,一起优化完善!
最后,附上掘金常用标签,方便大家搜索、交流:#ECharts #Vue3 #TSX #前端实战 #图表优化 #后台管理系统 #组件封装