主题
黑马就业数据平台(后端接口)(一)
本页对应 黑马就业数据平台(后端接口),共 8 段视频,约 1 小时 13 分。
学习目标
- 能独立从远程仓库克隆项目,切换到指定开发分支,并验证页面可正常运行。
- 能按 ECharts 官方文档完成图表快速上手:引入库、准备 DOM 容器、初始化实例并调用
setOption渲染图表。 - 能查阅配置项手册,完成
title与legend配置,实现标题显隐与文本修改、图例位置调整及名称同步。 - 能配置
grid与xAxis,调整绘图网格位置,并使用map从数据数组中提取 X 轴类目。 - 能说明请求拦截器与响应拦截器的职责,并在代码中实现统一携带 token、响应数据剥离及 404/401 错误处理。
- 能识别数据看板中的重复逻辑并抽取为可复用函数,用于薪资走势图表的数据处理与渲染。
知识点
先明确目标:为什么要把渲染逻辑抽成函数
为什么需要: 首页不是只有一个数字区域,后面还会接折线图、柱状图、地图等区域。如果所有渲染代码都堆在 getData 里,函数会越来越长,改一个区域容易碰到另一个区域,排查问题也很费劲。本节的目标就是“每一个图表对应一个函数”,先把首页顶部数据区域的渲染抽成 renderOverview。
怎么想: 把 getData 的职责收窄:它只负责发请求、解构数据、调用各个渲染函数;具体“怎么把数据写到页面上”交给各自的渲染函数。这样后续加图表时,只需要继续增加 renderXxx(data),再在 getData 里传对应数据即可。
怎么做: 先写 renderOverview(overview),参数就是顶部统计所需的数据对象;函数内部遍历这个对象,把每个值写到对应类名的元素里。然后再回到 getData 中调用它。
易错点: 不要一边抽函数一边改业务逻辑,先把原来能跑的渲染代码原样搬进函数,确认页面效果不变,再继续下一步。转写稿里也反复强调“笔记”和“查文档”的重要性,函数抽取同样要保留可对照的结构,不能凭感觉重写。
实现 renderOverview:按数据键名批量写 DOM
为什么需要: 顶部统计通常是一组结构相似的数据,如果一个个手写 querySelector 再赋值,代码重复且容易漏字段。用 Object.keys(overview).forEach(...) 可以按数据对象的键统一处理。
怎么想: 约定“数据对象的 key”和“页面元素的 class 名”对应。比如数据里某个 key 叫 xxx,页面上就有一个 class="xxx" 的元素。这样函数不需要关心具体有多少项,只负责遍历写入。
怎么做: 使用模板字符串拼出类选择器,把 overview[key] 写入 innerText。代码里保留了 // console.log(key),调试时可以先打开它,确认遍历出来的 key 是否和页面类名一致。
javascript
// 渲染顶部数据的函数
function renderOverview(overview) {
Object.keys(overview).forEach(key => {
// console.log(key)
document.querySelector(`.${key}`).innerText = overview[key]
})
}易错点: 第一,key 的大小写、拼写必须和页面 class 完全一致,否则 querySelector 可能拿到 null,再访问 innerText 就会报错。第二,innerText 是覆盖写入,确认目标元素里没有需要保留的子结构。第三,调试完记得把 console.log(key) 注释掉,避免控制台噪音。
在 getData 中解构并传递数据
为什么需要: 接口返回的数据通常不止一个区域用。getData 负责统一请求,再把不同数据分发给不同渲染函数,避免每个函数内部重复发请求。
怎么想: 先拿到响应,再按接口结构解构出 overview、year、salaryData、groupData、provinceData。本节只先调用顶部数据函数 renderOverview(overview),其他数据留给后续图表函数使用。
怎么做: 保持 getData 为 async,用 await axios({ url: '/dashboard' }) 请求;按本节笔记使用 res.data 解构;最后调用 renderOverview(overview),并在文件末尾执行 getData()。
javascript
// 首页-统计数据
async function getData() {
// 使用拦截器的写法(只保留核心逻辑即可)
const res = await axios({
url: '/dashboard',
})
// 通过解构语法,简化数据取值
const { overview, year, salaryData, groupData, provinceData } = res.data
// 调用函数-渲染顶部数据
renderOverview(overview)
}
getData()易错点: 解构字段名必须和接口返回一致;如果项目里响应拦截器已经做过“数据剥离”(转写稿里说的“玻璃”应纠正为“剥离”),那么 res 的层级要按项目约定调整,不能盲目照抄 res.data。另外,renderOverview 必须在 await 拿到数据之后调用,不能提到请求前面。最后别忘了调用 getData(),否则页面不会发请求。
调试与提交记录
调试过程: 先确认 getData 能拿到数据,再打开 console.log(key) 核对遍历键名;如果某个数字没更新,优先检查对应 class 是否存在、拼写是否一致。页面效果确认无误后,再注释调试语句。转写稿里提到的“图立组件”应纠正为“图例组件”,“位置项”应纠正为“配置项”,这类术语在后续图表配置中也要注意,不要因为同音字记错文档关键词。
提交记录: 完成本节改动后,按笔记提交:
bash
git add .
git commit -m"数据看板-函数抽取"易错点: 提交前先确认只包含本节相关改动;提交信息保持简洁明确,方便后续回看“函数抽取”这一节点。
需求与实现思路

为什么需要:首页看板要展示“薪资走势”,让月度薪资变化一眼可见,而不是只看表格数字。怎么想:这类图表不手写 SVG,先用 ECharts 找现成折线图示例,再复制到项目里改配置。怎么做:核心流程是“找示例 → 整合到项目 → 查配置项手册逐项调整”。转写稿里反复强调:这个阶段不是背配置,而是知道有 title、grid、xAxis、yAxis、series、tooltip 这些组件,然后去文档查。易错点:不要一上来自己造图表;先让示例跑起来,再逐项改,改一项看一项。
找示例并整合到项目
为什么需要:ECharts 示例已经给出完整 option 结构,能减少从零试错。怎么想:在示例页找折线图,复制 option,把示例数据换成接口里的 year。怎么做:页面准备 #line 容器,echarts.init(dom) 创建实例,最后 myChart.setOption(option)。易错点:容器要有宽高;init 的 DOM 必须存在;示例里的 data 是假数据,必须换成 year.map(v => v.month) 和 year.map(v => v.salary)。
调整标题、网格与Y轴
为什么需要:示例默认标题、绘图区、分割线不一定符合看板布局。怎么想:按视觉层级先改标题,再让绘图区避开标题,最后弱化 Y 轴分割线。怎么做:title.text 写“2022年薪资走势”,left/top 调位置;grid.top 给绘图区留空间;yAxis.splitLine.lineStyle.type 设为 dashed。易错点:left/top 是相对容器;grid.top 太小会压标题;yAxis.type 用 value 表示连续数值轴。
调整X轴与数据映射
为什么需要:X 轴要显示月份,而不是示例里的星期。怎么想:xAxis.data 是类目数组,接口 year 里每月一条数据,用 map 取 month。怎么做:xAxis.type 用 category,axisLine.lineStyle 调 type/color,data: year.map(v => v.month)。易错点:改 axisLine.lineStyle.color 后文字也会一起变色,这是笔记里特别提醒的点;map 返回的必须是数组。
配置折线系列与渐变色
为什么需要:默认折线太细、颜色普通,区域填充能让走势更突出。怎么想:series 里同时控制数据、点大小、线宽、线色和区域色。怎么做:data: year.map(v => v.salary),symbolSize: 10,lineStyle.width: 8,smooth: true;线色用水平线性渐变,区域色用垂直线性渐变。渐变色关键是起点 x,y 和终点 x2,y2:垂直渐变写 x:0,y:0,x2:0,y2:1,水平渐变写 x2:1。

javascript
{
type: 'linear',
x: 0, y: 0, // 起始点
x2: 0, y2: 1, // 结束点,从起点指向结束点就是方向
colorStops: [
{ offset: 0, color: 'red' },
{ offset: 1, color: 'blue' }
],
global: false
}易错点:坐标原点在左上角,Y 轴从上往下是 0→1;offset 是 0 到 1;tooltip.trigger 用 axis 才能按坐标轴触发。
完整渲染函数与提交
javascript
function renderYearSalary(year) {
const dom = document.querySelector('#line')
const myChart = echarts.init(dom)
const option = {
title: {
text: '2022年薪资走势',
left: '12',
top: '15'
},
grid: { top: '20%' },
yAxis: {
type: 'value',
splitLine: { lineStyle: { type: 'dashed' } }
},
xAxis: {
type: 'category',
axisLine: {
lineStyle: { type: 'dashed', color: '#ccc' }
},
data: year.map(v => v.month)
},
series: [{
data: year.map(v => v.salary),
symbolSize: 10,
type: 'line',
smooth: true,
lineStyle: {
width: 8,
color: {
type: 'linear', x: 0, y: 0, x2: 1, y2: 0,
colorStops: [
{ offset: 0, color: '#479dee' },
{ offset: 1, color: '#5c75f0' }
],
global: false
}
},
areaStyle: {
color: {
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
colorStops: [
{ offset: 0, color: '#b2d7f7' },
{ offset: 1, color: 'rgba(255,255,255,0)' }
],
global: false
}
}
}],
tooltip: { show: true, trigger: 'axis' }
}
myChart.setOption(option)
}
async function getData() {
const res = await axios({ url: '/dashboard' })
const { overview, year, salaryData, groupData, provinceData } = res.data
renderOverview(overview)
renderYearSalary(year)
}bash
git add .
git commit -m"数据看板-薪资走势"课件图示








代码示例
1. index.html — 页面结构 + 图表容器
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>数据看板</title>
<style>
body { margin: 0; background: #f5f7fa; font-family: 'Microsoft YaHei', sans-serif; }
.dashboard { display: flex; flex-wrap: wrap; gap: 20px; padding: 20px; }
/* 顶部统计数据:每个盒子一个 class,class 名 = overview 的 key */
.overview-item {
flex: 1;
min-width: 160px;
height: 90px;
background: #fff;
border-radius: 8px;
text-align: center;
line-height: 90px;
font-size: 22px;
color: #479dee;
box-shadow: 0 2px 8px rgba(0, 0, 0, .06);
}
/* 折线图容器:必须有宽高,否则图表渲染不出来 */
#line { width: 100%; height: 420px; background: #fff; border-radius: 8px; }
</style>
</head>
<body>
<div class="dashboard">
<!-- 这里的 class 必须和接口返回 overview 的 key 一一对应 -->
<div class="overview-item salary"></div>
<div class="overview-item student"></div>
<div class="overview-item age"></div>
<div class="overview-item group"></div>
<!-- 薪资走势图表容器 -->
<div id="line"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/echarts@5/dist/echarts.min.js"></script>
<script src="./index.js"></script>
</body>
</html>关键行与易错点
.overview-item上挂的salary / student / age / group就是overview对象的 key,renderOverview里靠document.querySelector('.' + key)精准命中,key 与 class 名必须完全一致,拼错不会报错,只是数据静默渲染不上去。#line容器必须给宽高(这里是width:100%; height:420px),ECharts 按容器尺寸绘制,没有高度时图表是一片空白,这是最常见的“代码没错但没图”的原因。echarts的<script>要放在index.js之前,否则echarts is not defined。
2. index.js — 抽取 renderOverview / renderYearSalary
javascript
// 渲染顶部数据的函数
function renderOverview(overview) {
Object.keys(overview).forEach(key => {
// console.log(key)
document.querySelector(`.${key}`).innerText = overview[key]
})
}
// 渲染薪资走势
function renderYearSalary(year) {
// console.log(year)
// 初始化ECharts实例
const dom = document.querySelector('#line')
const myChart = echarts.init(dom)
// 定义选项和数据
const option = {
// 标题
title: {
text: '2022年薪资走势',
// left,top分别设置,距离容器左侧和顶部的距离
left: '12',
top: '15'
},
// 绘图网络
grid: {
top: '20%'
},
// y轴
yAxis: {
// 坐标轴类型 value,连续数据
type: 'value',
// 调整分割线为虚线
splitLine: {
lineStyle: {
type: 'dashed'
}
}
},
// x轴
xAxis: {
// 坐标轴类型,类目轴
type: 'category',
// 坐标轴轴线相关设置
axisLine: {
// 线段样式
lineStyle: {
// 线的类型
type: 'dashed',
// 线的颜色,修改之后,文字也会一起变色
color: '#ccc'
}
},
// 数据
data: year.map(v => v.month)
},
// 系列图表
series: [
{
// 数据
data: year.map(v => v.salary),
// 标记的大小
symbolSize: 10,
// 线的样式
lineStyle: {
// 线宽
width: 8,
// 颜色(横向渐变:从左往右)
color: {
type: 'linear',
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [{
offset: 0, color: '#479dee' // 0% 处的颜色
}, {
offset: 1, color: '#5c75f0' // 100% 处的颜色
}],
global: false // 缺省为 false
}
},
// 折线图
type: 'line',
// 平滑曲线,true开启
smooth: true,
// 区域填充样式(纵向渐变:从上往下)
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: '#b2d7f7' // 0% 处的颜色
}, {
offset: 1, color: 'rgba(255,255,255,0)' // 100% 处的颜色
}],
global: false // 缺省为 false
}
}
}
],
// 提示框
tooltip: {
show: true, // false隐藏
// 触发方式坐标轴
trigger: 'axis'
}
}
// 基于选项和数据绘制图表
myChart.setOption(option)
}关键行与易错点
Object.keys(overview).forEach(...)一次遍历拿到所有字段名,不用为每个数字写一遍 DOM 操作,新增字段只改 HTML 就行。year.map(v => v.month)/year.map(v => v.salary):year是数组,字段名要和接口返回保持一致;写成year.month会得到undefined,图表直接空白。- 渐变四个参数
x/y/x2/y2取值 0–1 是相对容器包围盒的百分比,方向 = 从起点指向终点:lineStyle用x2:1, y2:0→ 横向(左→右),线的颜色从左到右渐变;areaStyle用x2:0, y2:1→ 纵向(上→下),填充从上到下由浅蓝过渡到透明。 写反了就会出现“渐变方向不对/整块实色”。
colorStops的offset必须是0和1(0% 与 100%),中间可以继续加0.5之类;global: false保持缺省即可,设成true时四个坐标会变成绝对像素。title.left / grid.top用百分比或像素字符串都行,但grid.top:'20%'是为了给标题让位,不设会导致标题压在图表上。- 同一个 DOM 反复调用
echarts.init会创建多个实例,后续更新应复用实例调setOption,否则会出现重复渲染、内存占用变大。
3. index.js — getData 调用并传递数据
javascript
// 首页-统计数据
async function getData() {
// 使用拦截器的写法(只保留核心逻辑即可)
const res = await axios({
url: '/dashboard',
})
// 通过解构语法,简化数据取值
const { overview, year, salaryData, groupData, provinceData } = res.data
// 调用函数-渲染顶部数据
renderOverview(overview)
// 调用函数-渲染薪资走势
renderYearSalary(year)
}
getData()本地没有 /dashboard 接口时,把上面 axios 那一行换成下面的模拟数据即可直接跑通页面:
javascript
// 本地联调:模拟接口返回
const mockData = {
overview: { salary: '¥12000', student: '320', age: '25', group: '8' },
year: [
{ month: '1月', salary: 8000 },
{ month: '2月', salary: 9200 },
{ month: '3月', salary: 8800 },
{ month: '4月', salary: 10500 },
{ month: '5月', salary: 11800 },
{ month: '6月', salary: 12600 }
]
}
async function getData() {
const res = { data: mockData } // 等价于 axios 返回的结构
const { overview, year } = res.data // 解构取值
renderOverview(overview) // 传参:顶部数据
renderYearSalary(year) // 传参:薪资走势数据
}
getData()关键行与易错点
- 抽取函数的意义:
getData只负责“取数据 + 分发”,每个区域的渲染各由一个函数负责,后续加柱状图、地图只需再加renderGroupSalary、renderProvince并在getData里补一行调用。 const { overview, year, salaryData, groupData, provinceData } = res.data用的是解构,字段名必须和接口返回一致;写成res.overview会拿到undefined。- 传参要传解构后的数据(
year),不是整个res或res.data,否则year.map会报is not a function。 axios返回的是被包装过的响应对象,真正的业务数据在res.data里(若项目里配了响应拦截器做了数据剥离,则直接取res,两种写法不要混用)。async函数别忘了await,也别忘最后调用getData(),否则整页数据不渲染、也不报错。
常见错误
现象:图表区域空白,控制台无报错。
原因:renderYearSalary已抽取但getData中未调用或未传year。
修法:检查getData解构后是否调用renderYearSalary(year),并在函数内打印参数确认。现象:
echarts.init(dom)报错或图表不显示。
原因:选择器写错、DOM 尚未生成,或容器没有宽高。
修法:确认document.querySelector('#line')能取到元素;先有容器再初始化;给容器设置明确宽高。现象:渐变色方向与预期相反,或区域填充变成横向。
原因:x/y/x2/y2起止点配反,未按“原点左上、x 向右、y 向下”判断方向。
修法:纵向渐变用x:0,y:0,x2:0,y2:1;横向渐变用x:0,y:0,x2:1,y2:0,改完刷新验证。现象:图例点击后不联动,或图例文字改后消失。
原因:legend.data与series.name不一致,图例找不到对应系列。
修法:保持legend.data与series.name同名;改图例文字时同步改系列名称。现象:X 轴显示
[object Object]或数据错位。
原因:直接把对象数组赋给xAxis.data,未提取字段。
修法:使用data.map(v => v.month)/map(v => v.name)提取类目字段后再赋值。现象:拦截器加完后请求失败,或页面拿到的仍是包装数据。
原因:请求拦截器漏return config;响应拦截器漏return res.data。
修法:请求拦截器末尾返回config;响应拦截器成功分支返回剥离后的数据。现象:克隆后代码不是最新开发内容,或提交到错误分支。
原因:克隆后停留在默认分支,未切到开发分支;误建本地同名分支。
修法:git branch -a查看远端分支,正确checkout远端开发分支后再开发。
练习
- 在
getData中新增一个图表渲染函数调用,要求数据通过参数传入,并说明如果不传参会出现什么现象。 - 将薪资走势折线图改为横向渐变线条、纵向渐变面积,写出关键配置点并说明方向判断依据。
- 修改图例文字为“月薪”,要求图例仍可点击联动,列出需要同步修改的配置项。
- 给 X 轴传入
year数组,要求轴标签显示月份而不是对象,写出数据处理表达式。 - 模拟一次响应拦截器改造:让调用处直接拿到业务数据,并说明漏写返回值会导致什么问题。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 1反馈 | 22:50 |
| 2 | 2昨日回顾 | 13:52 |
| 3 | 3今日内容介绍 | 00:51 |
| 4 | 4上午回顾 | 05:35 |
| 5 | 克隆远程仓库代码 | 04:01 |
| 6 | echarts快速上手 | 10:27 |
| 7 | 基础配置-标题及图例组件 | 10:40 |
| 8 | 基础配置-网格及x轴组件 | 05:32 |
小结
- 图表渲染函数应“一图一函数”,数据由
getData统一获取后通过参数传入。 - ECharts 图表能否显示,先查容器、选择器、宽高,再查
setOption是否执行。 - 渐变色关键在起止点方向,改颜色前先确认
x/y/x2/y2。 - 图例、系列、X 轴数据字段之间要保持命名和结构一致。
- 拦截器属于公共逻辑,请求侧要返回配置,响应侧要返回剥离后的数据。
- Git 克隆后先确认分支,再开发;推送前先拉取,避免基于错误分支操作。
- 当前阶段以查文档、改配置、对照笔记为主,遇到单点问题及时提问并记录。