Skip to content

黑马就业数据平台(后端接口)(一) ​

本页对应 黑马就业数据平台(后端接口),共 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"数据看板-薪资走势"

课件图示 ​

课件第4页:核心步骤: 数据看板-薪资走势 整合到项目 找示例 调整设置 抽取函数 调用并传

课件第2页:核心步骤: 数据看板-函数抽取 调用并传递数据 抽取函数 async funct

课件第5页:核心步骤: 数据看板-薪资走势 整合到项目 找示例 调整设置 title:标题组

课件第6页:核心步骤: 数据看板-薪资走势 整合到项目 找示例 调整设置 xAxis:x轴

课件第7页:核心步骤: 数据看板-薪资走势 整合到项目 找示例 调整设置 const opt

课件第9页:核心步骤: 数据看板-薪资走势 整合到项目 找示例 调整设置 x=0,y=0 x

课件第10页:核心步骤: 数据看板-薪资走势 整合到项目 找示例 调整设置 // x0, y0

课件第12页:核心步骤: 数据看板-薪资走势 整合到项目 找示例 调整设置 const opt

代码示例 ​

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(),否则整页数据不渲染、也不报错。

常见错误 ​

  1. 现象:图表区域空白,控制台无报错。
    原因:renderYearSalary 已抽取但 getData 中未调用或未传 year。
    修法:检查 getData 解构后是否调用 renderYearSalary(year),并在函数内打印参数确认。

  2. 现象:echarts.init(dom) 报错或图表不显示。
    原因:选择器写错、DOM 尚未生成,或容器没有宽高。
    修法:确认 document.querySelector('#line') 能取到元素;先有容器再初始化;给容器设置明确宽高。

  3. 现象:渐变色方向与预期相反,或区域填充变成横向。
    原因:x/y/x2/y2 起止点配反,未按“原点左上、x 向右、y 向下”判断方向。
    修法:纵向渐变用 x:0,y:0,x2:0,y2:1;横向渐变用 x:0,y:0,x2:1,y2:0,改完刷新验证。

  4. 现象:图例点击后不联动,或图例文字改后消失。
    原因:legend.data 与 series.name 不一致,图例找不到对应系列。
    修法:保持 legend.data 与 series.name 同名;改图例文字时同步改系列名称。

  5. 现象:X 轴显示 [object Object] 或数据错位。
    原因:直接把对象数组赋给 xAxis.data,未提取字段。
    修法:使用 data.map(v => v.month) / map(v => v.name) 提取类目字段后再赋值。

  6. 现象:拦截器加完后请求失败,或页面拿到的仍是包装数据。
    原因:请求拦截器漏 return config;响应拦截器漏 return res.data。
    修法:请求拦截器末尾返回 config;响应拦截器成功分支返回剥离后的数据。

  7. 现象:克隆后代码不是最新开发内容,或提交到错误分支。
    原因:克隆后停留在默认分支,未切到开发分支;误建本地同名分支。
    修法:git branch -a 查看远端分支,正确 checkout 远端开发分支后再开发。

练习 ​

  1. 在 getData 中新增一个图表渲染函数调用,要求数据通过参数传入,并说明如果不传参会出现什么现象。
  2. 将薪资走势折线图改为横向渐变线条、纵向渐变面积,写出关键配置点并说明方向判断依据。
  3. 修改图例文字为“月薪”,要求图例仍可点击联动,列出需要同步修改的配置项。
  4. 给 X 轴传入 year 数组,要求轴标签显示月份而不是对象,写出数据处理表达式。
  5. 模拟一次响应拦截器改造:让调用处直接拿到业务数据,并说明漏写返回值会导致什么问题。

原视频对照 ​

#视频时长
11反馈22:50
22昨日回顾13:52
33今日内容介绍00:51
44上午回顾05:35
5克隆远程仓库代码04:01
6echarts快速上手10:27
7基础配置-标题及图例组件10:40
8基础配置-网格及x轴组件05:32

小结 ​

  1. 图表渲染函数应“一图一函数”,数据由 getData 统一获取后通过参数传入。
  2. ECharts 图表能否显示,先查容器、选择器、宽高,再查 setOption 是否执行。
  3. 渐变色关键在起止点方向,改颜色前先确认 x/y/x2/y2。
  4. 图例、系列、X 轴数据字段之间要保持命名和结构一致。
  5. 拦截器属于公共逻辑,请求侧要返回配置,响应侧要返回剥离后的数据。
  6. Git 克隆后先确认分支,再开发;推送前先拉取,避免基于错误分支操作。
  7. 当前阶段以查文档、改配置、对照笔记为主,遇到单点问题及时提问并记录。