主题
黑马就业数据平台(后端接口)(三)
本页对应 黑马就业数据平台(后端接口),共 7 段视频,约 52 分钟。
学习目标
- 能为 ECharts 折线图添加并配置提示框组件,设置
show: true与trigger: 'axis',实现鼠标悬停时按坐标轴触发提示。 - 能独立封装薪资分布饼图渲染函数,完成数据接收、ECharts 实例初始化、配置项调用与
setOption整合,并在页面中正确渲染图表。 - 能修改薪资分布饼图的关键配置,包括标题、图例位置、
series.name、内外半径radius、itemStyle、label、labelLine、高亮状态及扇区颜色。 - 能对薪资分布原始数据进行转换处理,通过
map将标签映射为name、将男女人数求和映射为value,并验证提示框数据与图表展示一致。 - 能为每组薪资柱状图整合多系列示例,配置
grid、tooltip、X 轴轴线样式、Y 轴分割线样式及虚线效果。 - 能替换每组薪资 X 轴数据并设置坐标轴文字颜色,通过中括号语法读取分组数据,使用
map提取每组名称并验证图表显示效果。
知识点
需求与实现思路

本节要做的是数据看板中的“班级薪资分布”圆环图:标题在左上,图例在底部居中,四段薪资数据用不同颜色表示,扇区之间有白色间隔和圆角。整体步骤仍是:先找 ECharts 示例,再整合到项目,最后逐项调整配置。
为什么需要:直接手写配置容易漏项,先找官方示例能快速得到可运行的骨架。怎么想:示例只负责“能显示”,项目里还要封装函数、接真实数据、按设计稿改样式。怎么做:按 title → series → color 的顺序调整。易错点:不要一上来就改所有属性,先让图出来,再逐项对照需求调。
找示例并整合到项目
为什么需要:薪资分布是饼图/圆环图,不是折线图,示例类型要选对。怎么想:在 ECharts 示例页进入“饼图”,找带内外半径的圆环图示例,转写稿中选的是第二个圆环图。怎么做:在 index.js 中封装 renderSalary(salaryData),在 getData() 里解构出 salaryData 并调用;先 console.log(salaryData) 验证数据,再删掉打印。给图表容器加上 id="salary"。易错点:函数名、容器 id、调用位置要对应;不要把所有配置直接塞进 getData()。
先初始化实例,再用 setOption 出图
为什么需要:ECharts 绘图分“初始化实例”和“设置配置项”两步。怎么想:先拿到 DOM,再 echarts.init(dom),最后 myChart.setOption(option)。怎么做:
javascript
const dom = document.querySelector('#salary')
const myChart = echarts.init(dom)
// 准备 option
myChart.setOption(option)易错点:querySelector 的 id 必须和页面一致;没有初始化实例就调用 setOption 会报错;示例配置先复制过来,确认图能出现再改。
调整 title 与 legend
为什么需要:示例的标题和图例位置通常不符合设计稿。怎么想:标题放左上,图例从默认顶部改到底部居中。怎么做:title 设置 text: '班级薪资分布'、left: 10、top: 15;legend 设置 bottom: '5%'、left: 'center'。转写稿中还通过文档查过 textStyle.fontSize 来试标题字号,但关键代码最终未保留该设置。易错点:图例默认在上方,容易和标题挤在一起;bottom 和 top 不要同时乱设。
调整 series:圆环、间距与标签
为什么需要:需求是圆环图,并且扇区之间有白色圆角间隔。怎么想:对照示例改 name、type、radius、itemStyle、label、labelLine,不需要的高亮和标签先关掉。怎么做:配置骨架如下:
javascript
const option = {
title: {
left: '',
top: ''
},
series: [
{
name: '', // 名字,tooltip 会用到
type: '', // 类型
radius: '', // 半径
itemStyle: {}, // 区域样式
emphasis: {}, // 高亮样式
label: {}, // 文本
labelLine: {}, // 文本引导线
avoidLabelOverlap: '', // 避免重叠
data: [] // 数据
}
],
color: [] // 调色盘
}关键设置:type: 'pie';radius: ['55%', '70%'],数组第一项是内半径,第二项是外半径;itemStyle 中 borderRadius: 15、borderColor: '#fff'、borderWidth: 2;label.show: false、labelLine.show: false。转写稿中验证过:label 控制扇区文字,labelLine 控制引导线,不关掉会多出文字和线;avoidLabelOverlap 是防止标签重叠策略,圆环图不设置也可以。易错点:radius 写成两个值才是圆环;borderWidth 是扇区间隔粗细;emphasis 不需要高亮放大时可注释掉。
处理 salaryData 与 color
为什么需要:接口数据不能直接给饼图用,必须转成 { value, name }。怎么想:label 对应 name,g_count + b_count 对应 value,即女生和男生数量求和。怎么做:
javascript
data: salaryData.map(v => {
return {
value: v.g_count + v.b_count,
name: v.label
}
})调色盘与 series 同级:color: ['#fda224', '#5097ff', '#3abcfa', '#34d39a']。转写稿中鼠标悬浮第一项显示“1万以下 7”,正好验证 4 + 3 = 7。易错点:不要把 label 直接当 value;color 不要写进 series 内部;饼图 tooltip.trigger 用 'item'。
关键代码与提交
javascript
// 渲染薪资分布函数
function renderSalary(salaryData) {
// 初始化实例
const dom = document.querySelector('#salary')
const myChart = echarts.init(dom)
// 定义属性和数据
const option = {
title: {
text: '班级薪资分布',
left: 10,
top: 15
},
tooltip: {
trigger: 'item'
},
legend: {
bottom: '5%',
left: 'center'
},
series: [
{
name: '班级薪资分布',
type: 'pie',
radius: ['55%', '70%'],
itemStyle: {
borderRadius: 15,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: false
},
labelLine: {
show: false
},
data: salaryData.map(v => {
return {
value: v.g_count + v.b_count,
name: v.label
}
})
}
],
color: ['#fda224', '#5097ff', '#3abcfa', '#34d39a']
}
myChart.setOption(option)
}
// 首页-统计数据
async function getData() {
const res = await axios({
url: '/dashboard',
})
const { overview, year, salaryData, groupData, provinceData } = res.data
renderOverview(overview)
renderYearSalary(year)
renderSalary(salaryData)
}完成后提交:
bash
git add .
git commit -m"数据看板-薪资分布"易错点:提交前确认 #salary 容器存在、salaryData 已解构传入;如果图例数量不对,先检查接口返回和 map 结果。
需求与实现思路

为什么需要:数据看板里要展示“每组薪资”对比,横轴是组内成员,纵轴是薪资,同一成员要同时看到“期望薪资”和“实际薪资”,所以用双柱状图。
怎么想:仍然沿用“找示例 → 整合到项目 → 调整配置”的流程。先找柱状图示例,再把两组柱子拼到一个 series 里。
怎么做:打开 ECharts 示例页,选基础柱状图,再结合多柱示例,在 series 中放两个对象,分别表示期望薪资和实际薪资。
易错点:不要一上来手写全部配置,先让示例跑起来,再按需求改 grid、坐标轴、颜色和数据。
整合到项目:封装渲染函数
为什么需要:首页 getData 里会拿到多块数据,每组薪资数据是 groupData,需要单独封装渲染函数,避免所有逻辑堆在 getData 里。
怎么想:转写稿中先打印 groupData,控制台看到它是一个对象,键是 1 到 8 组;默认先渲染第一组,所以取 groupData[1]。
怎么做:定义 renderGroupSalary(groupData),初始化 #lines 容器,准备 option,最后 myChart.setOption(option);在 getData 中解构出 groupData 并调用该函数。
易错点:对象键是数字时不能写 groupData.1,必须用中括号:groupData[1]。转写稿里也是先打印确认结构,再写 groupData[1].map(...)。
调整提示框、坐标轴与网格
为什么需要:默认示例的提示框、轴线和分割线样式与设计稿不一致。
怎么想:提示框加 tooltip: {} 就能出现;X 轴轴线用 axisLine.lineStyle 改颜色和虚线;Y 轴横向分割线用 splitLine.lineStyle 改虚线;网格位置按需求给的 left:70、top:30、right:30、bottom:50 调。
怎么做:在 option 中加:
javascript
tooltip: {},
grid: { left: 70, top: 30, right: 30, bottom: 50 },
xAxis: {
axisLine: { lineStyle: { color: '#ccc', type: 'dashed' } },
axisLabel: { color: '#999' }
},
yAxis: {
splitLine: { lineStyle: { type: 'dashed' } }
}易错点:axisLabel 的文字颜色默认会和轴线颜色一致,所以要单独设置 color:'#999'。转写稿调试时先只改 xAxis.data,发现文字变了但柱子数据没变,是因为 series 还没接数据;继续改 series 后才完整。
两组柱子的渐变色
为什么需要:设计稿给了标准色,直接用标准色才能和看板统一。
怎么想:每组两根柱子,series 里放两个对象;每根柱子用线性渐变 itemStyle.color。
怎么做:第一个系列 name:'期望薪资',数据取 hope_salary,渐变从 #34D39A 到 rgba(52,211,154,0.2);第二个系列 name:'实际薪资',数据取 salary,渐变从 #499FEE 到 rgba(73,159,238,0.2)。
易错点:xAxis.data 用 groupData[1].map(v => v.name),两个系列分别用 map(v => v.hope_salary) 和 map(v => v.salary),不要取错字段。
点击按钮切换每组数据
为什么需要:页面上有分组按钮,点击后要切换高亮并换一组薪资数据。
怎么想:按钮点击后做三件事:切换高亮、取对应组数组、调用 setOption 重新渲染。
怎么做:给 #btns 绑定点击事件,先移除旧的 .btn-blue,再给当前按钮加上;用 e.target.innerText 作为组名取 groupData[index],更新 option.xAxis.data、option.series[0].data、option.series[1].data,最后 myChart.setOption(option)。
易错点:修改了 option 后必须再调用 setOption,图形才会更新;按钮高亮不要只加不移除,否则会出现多个高亮。
关键代码
javascript
// 渲染每组薪资函数
function renderGroupSalary(groupData) {
// 初始化实例
const dom = document.querySelector('#lines')
const myChart = echarts.init(dom)
const option = {
tooltip: {},
grid: {
left: 70,
top: 30,
right: 30,
bottom: 50
},
xAxis: {
type: 'category',
data: groupData[1].map(v => v.name),
axisLine: {
lineStyle: {
color: '#ccc',
type: 'dashed'
}
},
axisLabel: {
color: '#999'
}
},
yAxis: {
type: 'value',
splitLine: {
lineStyle: {
type: 'dashed'
}
}
},
series: [
{
name: '期望薪资',
data: groupData[1].map(v => v.hope_salary),
type: 'bar',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: '#34D39A'
}, {
offset: 1, color: 'rgba(52,211,154,0.2)'
}],
global: false
}
}
},
{
name: '实际薪资',
data: groupData[1].map(v => v.salary),
type: 'bar',
itemStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: '#499FEE'
}, {
offset: 1, color: 'rgba(73,159,238,0.2)'
}],
global: false
}
}
}
]
}
myChart.setOption(option)
const btns = document.querySelector('#btns')
btns.addEventListener('click', (e) => {
if (e.target.classList.contains('btn')) {
btns.querySelector('.btn-blue').classList.remove('btn-blue')
e.target.classList.add('btn-blue')
const index = e.target.innerText
const data = groupData[index]
option.xAxis.data = data.map(v => v.name)
option.series[0].data = data.map(v => v.hope_salary)
option.series[1].data = data.map(v => v.salary)
myChart.setOption(option)
}
})
}
async function getData() {
const res = await axios({
url: '/dashboard',
})
const { overview, year, salaryData, groupData, provinceData } = res.data
renderOverview(overview)
renderYearSalary(year)
renderSalary(salaryData)
renderGroupSalary(groupData)
}提交记录
bash
git add .
git commit -m"数据看板-每组薪资"课件图示








代码示例
1. 薪资分布(圆环饼图)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>数据看板 - 薪资分布</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
/* 容器必须有宽高,否则图表渲染不出来 */
#salary { width: 600px; height: 400px; }
</style>
</head>
<body>
<div id="salary"></div>
<script>
// 模拟接口返回的 salaryData(真实项目里来自 /dashboard 接口)
const salaryData = [
{ label: '1万以下', g_count: 4, b_count: 3 },
{ label: '1万-1.5万', g_count: 12, b_count: 12 },
{ label: '1.5万-2万', g_count: 9, b_count: 5 },
{ label: '2万以上', g_count: 3, b_count: 1 }
]
// 渲染薪资分布函数
function renderSalary(salaryData) {
// 初始化实例
const dom = document.querySelector('#salary')
const myChart = echarts.init(dom)
// 定义属性和数据
const option = {
title: {
text: '班级薪资分布',
left: 10,
top: 15,
textStyle: {
fontSize: 16 // 主标题文字大小
}
},
tooltip: {
trigger: 'item'
},
legend: {
// 离容器下侧的距离
bottom: '5%',
left: 'center'
},
series: [
{
name: '班级薪资分布', // 名字,tooltip 会用到
type: 'pie', // type=pie 饼图
// 数组第一项是内半径,第二项是外半径 → 圆环图
radius: ['55%', '70%'],
// 图形样式
itemStyle: {
borderRadius: 15, // 圆角
borderColor: '#fff',
borderWidth: 2
},
// 饼状图说明文本
label: {
show: false
},
// 说明文本的指引线
labelLine: {
show: false
},
// 圆环图不需要防止标签重叠策略
// avoidLabelOverlap: false,
data: salaryData.map(v => {
return {
value: v.g_count + v.b_count, // 男生 + 女生
name: v.label
}
})
}
],
// 调色盘
color: ['#fda224', '#5097ff', '#3abcfa', '#34d39a']
}
// 根据属性和数据绘制图表
myChart.setOption(option)
}
renderSalary(salaryData)
</script>
</body>
</html>关键行与易错点
echarts.init(dom)之前 DOM 必须已存在;容器 CSS 没给宽高时图表是 0×0,页面上什么都看不到。radius: ['55%', '70%']是数组 → 圆环图,第一项是内圆半径、第二项是外圆半径;写成单个值就退化成实心饼图。label/labelLine要写成{ show: false }这个对象形式,不能把这两个配置项整个删掉,删掉后会回退到默认值(文字和引导线又出现了)。data用map把接口数据转成{ value, name }:value是g_count + b_count(男女求和),name取label,这样 legend 和 tooltip 才能显示“1万以下”这类文案。series.name决定 tooltip 里显示的名字,要写成和标题一致的“班级薪资分布”。color调色盘的顺序与data的顺序一一对应,四个薪资区间四个颜色。itemStyle里的borderRadius / borderColor / borderWidth是画扇形之间那道白色缝隙和圆角的关键,三者配合才有“分段圆环”的效果。
2. 每组薪资(双柱状图 + 切换高亮)
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>数据看板 - 每组薪资</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
#lines { width: 700px; height: 400px; }
#btns { margin: 10px 0; }
.btn {
width: 44px; height: 28px; margin-right: 8px;
border: 1px solid #ccc; background: #fff; color: #333;
border-radius: 4px; cursor: pointer;
}
.btn-blue { background: #499fee; border-color: #499fee; color: #fff; }
</style>
</head>
<body>
<div id="lines"></div>
<div id="btns">
<button class="btn btn-blue">1</button>
<button class="btn">2</button>
<button class="btn">3</button>
</div>
<script>
// 模拟接口返回的 groupData(键为组号 1~8,此处只演示 3 组)
const groupData = {
1: [
{ name: '龚军', hope_salary: 12000, salary: 11000 },
{ name: '郝涛', hope_salary: 15000, salary: 13000 },
{ name: '张三', hope_salary: 10000, salary: 9000 },
{ name: '李四', hope_salary: 18000, salary: 16000 }
],
2: [
{ name: '王五', hope_salary: 9000, salary: 8000 },
{ name: '赵六', hope_salary: 14000, salary: 12000 },
{ name: '孙七', hope_salary: 11000, salary: 10000 }
],
3: [
{ name: '周八', hope_salary: 13000, salary: 12500 },
{ name: '吴九', hope_salary: 16000, salary: 14000 }
]
}
// 渲染每组薪资函数
function renderGroupSalary(groupData) {
// 初始化实例
const dom = document.querySelector('#lines')
const myChart = echarts.init(dom)
// 定义选项和数据
const option = {
// 显示提示框
tooltip: {},
// 绘图网格
grid: {
left: 70,
top: 30,
right: 30,
bottom: 50
},
xAxis: {
type: 'category',
// 默认渲染第一组的数据
data: groupData[1].map(v => v.name),
// 线的类型、颜色
axisLine: {
lineStyle: {
color: '#ccc',
type: 'dashed'
}
},
// 坐标轴刻度标签的相关设置
axisLabel: {
color: '#999' // 文字颜色单独设置,默认跟随轴线颜色
}
},
yAxis: {
type: 'value',
// 分割线的类型
splitLine: {
lineStyle: {
type: 'dashed'
}
}
},
// series 里设置多个图形,就会渲染多个图形
series: [
{
name: '期望薪资',
data: groupData[1].map(v => v.hope_salary),
type: 'bar',
itemStyle: {
color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: '#34D39A' }, // 0% 处的颜色
{ offset: 1, color: 'rgba(52,211,154,0.2)' } // 100% 处的颜色
],
global: false
}
}
},
{
name: '实际薪资',
data: groupData[1].map(v => v.salary),
type: 'bar',
itemStyle: {
color: {
type: 'linear',
x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: '#499FEE' }, // 0% 处的颜色
{ offset: 1, color: 'rgba(73,159,238,0.2)' } // 100% 处的颜色
],
global: false
}
}
}
]
}
// 基于选项和数据绘制图表
myChart.setOption(option)
// 高亮切换
const btns = document.querySelector('#btns')
btns.addEventListener('click', (e) => {
if (e.target.classList.contains('btn')) {
btns.querySelector('.btn-blue').classList.remove('btn-blue')
e.target.classList.add('btn-blue')
// 数据切换:按钮文字就是组号
const index = e.target.innerText
const data = groupData[index]
option.xAxis.data = data.map(v => v.name)
option.series[0].data = data.map(v => v.hope_salary)
option.series[1].data = data.map(v => v.salary)
// 重新渲染
myChart.setOption(option)
}
})
}
renderGroupSalary(groupData)
</script>
</body>
</html>关键行与易错点
series里放两个对象就是两根柱子;只放一个就是单柱。xAxis.data与series[i].data的长度必须一致,否则柱子会错位或缺失。- 渐变柱用
type: 'linear'的配置对象,x:0, y:0, x2:0, y2:1表示从上到下渐变;colorStops的offset是 0~1 的百分比。 - 轴线虚线是
xAxis.axisLine.lineStyle.type: 'dashed';文字颜色axisLabel.color要单独设,默认跟随轴线颜色,只改axisLine的话文字也会跟着变色。 - 横向分割线是
yAxis.splitLine.lineStyle.type,别和axisLine混淆(axisLine是竖直的那根轴线)。 - 点击切换用事件委托挂在
#btns上,先判断e.target.classList.contains('btn'),避免点到容器本身;切换高亮要先remove('btn-blue')再给当前按钮add。 - 按钮文字
e.target.innerText是字符串,groupData[index]能取到数字键的数据(对象数字键本质是字符串),但如果是数组就不能这么写。 - 改完
option之后必须再调一次myChart.setOption(option)才会重绘,只改对象不会自动刷新。
3. 整合入口:getData 拉数据 → 解构 → 分别渲染
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>数据看板 - 整合</title>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<style>
#salary { width: 520px; height: 360px; }
#lines { width: 700px; height: 360px; }
#btns { margin: 10px 0; }
.btn {
width: 44px; height: 28px; margin-right: 8px;
border: 1px solid #ccc; background: #fff; color: #333;
border-radius: 4px; cursor: pointer;
}
.btn-blue { background: #499fee; border-color: #499fee; color: #fff; }
</style>
</head>
<body>
<div id="salary"></div>
<div id="lines"></div>
<div id="btns">
<button class="btn btn-blue">1</button>
<button class="btn">2</button>
<button class="btn">3</button>
</div>
<script>
// 模拟接口返回的完整数据(真实项目由 axios 请求 /dashboard 得到)
const mockRes = {
data: {
overview: {},
year: [],
salaryData: [
{ label: '1万以下', g_count: 4, b_count: 3 },
{ label: '1万-1.5万', g_count: 12, b_count: 12 },
{ label: '1.5万-2万', g_count: 9, b_count: 5 },
{ label: '2万以上', g_count: 3, b_count: 1 }
],
groupData: {
1: [
{ name: '龚军', hope_salary: 12000, salary: 11000 },
{ name: '郝涛', hope_salary: 15000, salary: 13000 },
{ name: '张三', hope_salary: 10000, salary: 9000 }
],
2: [
{ name: '王五', hope_salary: 9000, salary: 8000 },
{ name: '赵六', hope_salary: 14000, salary: 12000 }
],
3: [
{ name: '周八', hope_salary: 13000, salary: 12500 }
]
},
provinceData: []
}
}
// 渲染薪资分布(饼图)
function renderSalary(salaryData) {
const myChart = echarts.init(document.querySelector('#salary'))
myChart.setOption({
title: { text: '班级薪资分布', left: 10, top: 15 },
tooltip: { trigger: 'item' },
legend: { bottom: '5%', left: 'center' },
series: [{
name: '班级薪资分布',
type: 'pie',
radius: ['55%', '70%'],
itemStyle: { borderRadius: 15, borderColor: '#fff', borderWidth: 2 },
label: { show: false },
labelLine: { show: false },
data: salaryData.map(v => ({ value: v.g_count + v.b_count, name: v.label }))
}],
color: ['#fda224', '#5097ff', '#3abcfa', '#34d39a']
})
}
// 渲染每组薪资(柱状图)
function renderGroupSalary(groupData) {
const myChart = echarts.init(document.querySelector('#lines'))
const option = {
tooltip: {},
grid: { left: 70, top: 30, right: 30, bottom: 50 },
xAxis: {
type: 'category',
data: groupData[1].map(v => v.name),
axisLine: { lineStyle: { color: '#ccc', type: 'dashed' } },
axisLabel: { color: '#999' }
},
yAxis: {
type: 'value',
splitLine: { lineStyle: { type: 'dashed' } }
},
series: [
{
name: '期望薪资',
type: 'bar',
data: groupData[1].map(v => v.hope_salary),
itemStyle: {
color: {
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: '#34D39A' },
{ offset: 1, color: 'rgba(52,211,154,0.2)' }
],
global: false
}
}
},
{
name: '实际薪资',
type: 'bar',
data: groupData[1].map(v => v.salary),
itemStyle: {
color: {
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: '#499FEE' },
{ offset: 1, color: 'rgba(73,159,238,0.2)' }
],
global: false
}
}
}
]
}
myChart.setOption(option)
// 切换高亮 + 重新渲染
const btns = document.querySelector('#btns')
btns.addEventListener('click', (e) => {
if (e.target.classList.contains('btn')) {
btns.querySelector('.btn-blue').classList.remove('btn-blue')
e.target.classList.add('btn-blue')
const index = e.target.innerText
const data = groupData[index]
option.xAxis.data = data.map(v => v.name)
option.series[0].data = data.map(v => v.hope_salary)
option.series[1].data = data.map(v => v.salary)
myChart.setOption(option)
}
})
}
// 首页-统计数据
async function getData() {
// 真实项目:const res = await axios({ url: '/dashboard' })
const res = await Promise.resolve(mockRes)
// 通过解构语法,简化数据取值
const { overview, year, salaryData, groupData, provinceData } = res.data
// 调用函数-渲染薪资分布
renderSalary(salaryData)
// 调用函数-渲染每组薪资
renderGroupSalary(groupData)
}
getData()
</script>
</body>
</html>关键行与易错点
- 一次请求拿回所有图表数据,再用解构
const { overview, year, salaryData, groupData, provinceData } = res.data取出,各渲染函数只接收自己那一份,互不干扰。 - 每个图表一个 DOM 对应一个 echarts 实例,
echarts.init写在各渲染函数内部,不要在外面 init 一次然后到处复用。 - 渲染函数里不要再用
console.log打印数据,调试完就删掉,避免上线后控制台一堆输出。 getData()是async函数,DOM 结构必须在脚本执行前就存在(脚本放</body>前),否则querySelector('#salary')拿到null,echarts.init(null)直接报错。- 真实项目里换成
axios后,注意响应拦截器已经把数据剥了一层还是没剥,res.data的层级要按实际接口返回调整。 - 每完成一个图表就
git add . && git commit -m"数据看板-薪资分布",分步提交便于回滚。
常见错误
1. 鼠标悬浮饼图,提示框显示的是 series 或英文示例名
- 现象:tooltip 里出现
series、Search Engine之类字样,与图例中的"1万以下"对不上。 - 原因:
series.name仍是从示例里抄来的值,tooltip 取的是series.name而非title.text。 - 修法:把
series.name改成与业务一致的文案(如"班级薪资分布"),改完悬浮验证一次。
2. 饼图扇区显示 NaN 或整块饼只有一种颜色
- 现象:图形能出来但比例全错,或者控制台报
value相关错误。 - 原因:直接把接口返回的
salaryData({label, g_count, b_count})塞给了series.data,ECharts 需要的是{value, name}。 - 修法:先
console.log看清字段,再map成{ value: g_count + b_count, name: label };转换完打印一次结果数组确认。
3. 圆环中间冒出文字、外部带引导线
- 现象:设计稿是干净的圆环,实际渲染出扇区名称文字和折线。
- 原因:
label/labelLine默认show为true,把属性整行删掉等于恢复默认显示。 - 修法:保留属性并显式写
show: false;同理emphasis若不需要,也不能删,要显式处理。
4. 点击"第 2 组"按钮,高亮变了但柱子没变(或图表空白)
- 现象:按钮样式切换正常,图形数据纹丝不动。
- 原因:只改了按钮 class,没改
option里的数据;或改了series.data却忘了重新setOption;或只更新了series没更新xAxis.data,导致横轴还是上一组的人名。 - 修法:切换时同步更新
option.xAxis.data、option.series[0].data、option.series[1].data三处,最后统一myChart.setOption(option)。
5. 按钮高亮错乱,多个按钮同时变蓝 / 第一次点击无反应
- 现象:连点几次后出现两个蓝按钮,或者点按钮内部文字时事件不触发。
- 原因:只给新按钮加了类,没有先清掉旧的;事件委托时用
e.target命中了按钮内部的子元素。 - 修法:先用
querySelector('.btn-blue')移除旧类再加新类;判断条件用e.target.classList.contains('btn')做兜底,取数据时用按钮上的稳定标识(如dataset)而非innerText。
6. 取 groupData[1] 报 Cannot read properties of undefined
- 现象:页面白屏或图表区空白,控制台报 map 相关错误。
- 原因:接口返回的
groupData是对象{1: [...], 2: [...]},用groupData[0]取不到;或解构时字段名拼错(groupData写成groupdata)。 - 修法:先打印整个
res.data确认层级与键名;数字键只能用中括号取值,groupData.1是语法错误。
7. 柱状图渐变不生效,柱子是纯色或颜色上下颠倒
- 现象:
itemStyle.color写了字符串,或渐变方向反了(顶部浅底部深)。 - 原因:
color传了字符串而非 linear 对象;或x2/y2写反。 - 修法:用
{ type:'linear', x:0, y:0, x2:0, y2:1, colorStops:[...] },y2:1表示自上而下,0% 放深色、100% 放透明浅色。
练习
1. 圆环中心文字 给薪资分布饼图加上"鼠标移入某扇区时,圆环中心显示该扇区的名称与人数,移出后文字消失"的效果。要求移出后中心区域不残留任何文字(提示:需要同时处理 emphasis.label 和默认 label,并注意 position 设置)。
2. 动态生成分组按钮 现在 1~8 组的按钮是写死在 HTML 里的。改为根据 groupData 的键动态生成按钮,并在渲染完成后默认选中第 1 组。验证点:按钮数量与数据组数一致,点击任意按钮图表都能正确切换。
3. 重复点击拦截 改造切换逻辑:当用户点击的已经是当前高亮按钮时,不再调用 setOption。用 console.count() 或控制台输出证明重复点击确实被拦截了。
4. 折线图自定义提示框 把折线图的 tooltip 改成自定义 formatter,一次显示该月的"期望薪资 / 实际薪资"两行内容。验证点:悬浮任意月份,提示框只出现一条,且两条数据都齐全、单位正确。
5. 窗口自适应 页面缩放或窗口大小变化时,让三个图表都能自动适配容器宽度。要求说明:为什么不能只在 init 时传死宽高,以及 resize 应该监听在哪个对象上、何时需要调用。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 案例-折线图提示框组件完成 | 02:01 |
| 2 | 案例-薪资分布图表整合 | 07:28 |
| 3 | 案例-薪资分布修改配置1 | 13:18 |
| 4 | 案例-薪资分布-修改配置2 | 10:52 |
| 5 | 案例-每组薪资整合示例 | 06:59 |
| 6 | 案例-每组薪资-轴线样式完成 | 04:54 |
| 7 | 案例-每组薪资-x轴数据及文字样式 | 07:09 |
小结
- "找示例 → 整合 → 调配置"是 ECharts 所有图表的通用落地路径,示例代码只是起点,交付物是配置项,别指望抄完就能用。
- 改配置要有顺序:先改一眼能看到的(title / legend / color),再改结构(series 数量与类型),最后改数据。一次改一堆,出问题时无从定位。
- 设计稿给的数值要照抄,不要凭感觉试。grid、radius、颜色值这类参数,UI 给了就用;文档查不到含义的属性,就去控制台改值看效果,这是最快的验证方式。
- 后端数据结构几乎从不等于 ECharts 期望结构,
map转换是常态。转换之前必须先打印原始数据,看清字段名和嵌套层级。 - 图表更新不重新 init,改完 option 再
setOption即可;同一个 DOM 上重复init会产生多实例,导致渲染异常。 show类属性默认多为 true,想关闭必须显式写 false。删掉属性不等于关闭,反而会恢复默认显示——这是圆环图出现多余文字和引导线的高发原因。- 交互的本质是"改数据 → 重渲染",样式切换与图表数据要分开维护。按钮高亮和数据更新是两件事,任何一件漏掉,都会出现"看起来点了但没反应"的现象。