Skip to content

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

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

课件图示 ​

课件第19页:核心步骤: 数据看板-每组薪资 整合到项目 找示例 调整设置 切换效果

课件第20页:核心步骤: 数据看板-每组薪资 整合到项目 找示例 调整设置 切换效果 cons

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

课件第22页:核心步骤: 数据看板-每组薪资 整合到项目 找示例 调整设置 切换效果 xAxi

课件第23页:核心步骤: 数据看板-每组薪资 整合到项目 找示例 调整设置 切换效果 cons

课件第24页:核心步骤: 数据看板-每组薪资 整合到项目 找示例 调整设置 切换效果 高亮切换

课件第14页:核心步骤: 数据看板-薪资分布 整合到项目 找示例 调整设置

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

代码示例 ​

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案例-薪资分布修改配置113:18
4案例-薪资分布-修改配置210:52
5案例-每组薪资整合示例06:59
6案例-每组薪资-轴线样式完成04:54
7案例-每组薪资-x轴数据及文字样式07:09

小结 ​

  1. "找示例 → 整合 → 调配置"是 ECharts 所有图表的通用落地路径,示例代码只是起点,交付物是配置项,别指望抄完就能用。
  2. 改配置要有顺序:先改一眼能看到的(title / legend / color),再改结构(series 数量与类型),最后改数据。一次改一堆,出问题时无从定位。
  3. 设计稿给的数值要照抄,不要凭感觉试。grid、radius、颜色值这类参数,UI 给了就用;文档查不到含义的属性,就去控制台改值看效果,这是最快的验证方式。
  4. 后端数据结构几乎从不等于 ECharts 期望结构,map 转换是常态。转换之前必须先打印原始数据,看清字段名和嵌套层级。
  5. 图表更新不重新 init,改完 option 再 setOption 即可;同一个 DOM 上重复 init 会产生多实例,导致渲染异常。
  6. show 类属性默认多为 true,想关闭必须显式写 false。删掉属性不等于关闭,反而会恢复默认显示——这是圆环图出现多余文字和引导线的高发原因。
  7. 交互的本质是"改数据 → 重渲染",样式切换与图表数据要分开维护。按钮高亮和数据更新是两件事,任何一件漏掉,都会出现"看起来点了但没反应"的现象。