# 数据可视化142
**Repository Path**: wang-zhi-you/data-visualization-142
## Basic Information
- **Project Name**: 数据可视化142
- **Description**: No description available
- **Primary Language**: Unknown
- **License**: Not specified
- **Default Branch**: master
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2021-12-05
- **Last Updated**: 2021-12-05
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
## 项目介绍123
- 应对现在数据可视化的趋势,越来越多企业需要在很多场景(营销数据,生产数据,用户数据)下使用,可视化图表来展示体现数据,让数据更加直观,数据特点更加突出。
- 项目以黑马班级管理为背景,功能包括学生信息录入,每次成绩录入;并制作可视化看板。
- 我们以班主任老师的角色注册账号,并登录系统。
- 为了方便开发,登录后点击页面顶部的“点我初始化数据”按钮,即可为该账号随机增加56名同学(8个小组,每组7人),并为每位学生模拟了3次考试成绩。
- 后续,可以在学员管理中,增删改学员信息,也可以录入或修改成绩。
## 重要的三个地址
- **接口文档地址**:https://docs.apipost.cn/preview/ebfa24f6d27e4f89/cf3af015f5ca6674
- **接口根路径**:http://www.itcbc.com:8000
- **线上演示地址**:http://www.itcbc.com:8888/login.html
## 资源说明
- 仓库地址:https://gitee.com/laotang1234/data-visualization-211025
- master 默认模板
- dev 包括完整功能
```bash
# 克隆master分支
git clone git@gitee.com:laotang1234/data-visualization-211025.git
或
git clone https://gitee.com/laotang1234/data-visualization-211025.git
# 克隆dev分支
git clone -b dev git@gitee.com:laotang1234/data-visualization-211025.git
或
git clone -b dev https://gitee.com/laotang1234/data-visualization-211025.git
```
## 课程目标
- 实战Ajax在项目中的应用
- 掌握echarts的基本使用
- 增强对数据的处理能力,增强编程能力
## Echarts-介绍
> ECharts,一个使用 JavaScript 实现的开源可视化库,可以流畅的运行在 PC 和移动设备上,兼容当前绝大部分浏览器(IE8/9/10/11,Chrome,Firefox,Safari等),底层依赖矢量图形库 [ZRender](https://github.com/ecomfe/zrender),提供直观,交互丰富,可高度个性化定制的数据可视化图表。
大白话:
- 是一个JS插件
- 性能好可流畅运行PC与移动设备
- 兼容主流浏览器
- 提供很多常用图表,且可**定制**。
## Echarts-体验
官方教程:[五分钟上手ECharts](https://echarts.apache.org/handbook/zh/get-started/)
自己步骤:
- 下载echarts https://echarts.apache.org/zh/download.html
- 引入echarts `dist/echarts.min.js`
- 准备一个具备大小(宽高)的 DOM
```html
```
- 初始化echart实例
```js
var myChart = echarts.init(document.getElementById('main'));
```
- 指定图表的配置项和数据 (根据文档提供示例找到option)
```js
var option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line'
}]
};
```
- 使用刚指定的配置项和数据显示图表
```js
myChart.setOption(option);
```
## Echarts-基础配置
> 需要了解的主要配置:`series` `xAxis` `yAxis` `grid` `tooltip` `title` `legend` `color`
- series
- 系列列表。每个系列通过 `type` 决定自己的图表类型
- 大白话:图标数据,指定什么类型的图标,可以多个图表重叠。
- xAxis:直角坐标系 grid 中的 x 轴
- yAxis:直角坐标系 grid 中的 y 轴
- grid:直角坐标系内绘图网格
- title:标题组件
- tooltip:提示框组件
- legend:图例组件
- color:调色盘颜色列表
演示代码:
```js
var option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
},
yAxis: {
type: 'value'
},
series: [{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line',
name:'线形图'
},
{
data: [22, 333, 111, 222, 444, 666, 777],
type: 'bar',
name:'饼状图'
}],
grid: {
show: true
},
title: {
text: '标题'
},
tooltip: {
padding: 20
},
legend: {
data: ['线形图']
},
color: ['red','green']
};
```
## Echarts-饼图
### 步骤分析
1. 封装好函数,为后续传入真实数据做准备
2. 初始化echarts
3. 设置配置项,空的 option 即可
4. 创建图表
5. 查找官方示例
6. 按需求,自定义配置图表
### 第一步:echarts基本步骤
```js
function pieChart() {
let myChart = echarts.init(document.querySelector('.pie'));
let option = {};
myChart.setOption(option);
}
```
### 第二步:参照官方示例
(官方示例:https://echarts.apache.org/examples/zh/editor.html?c=pie-roseType-simple)
- 只留下series系列数据配置,其他全部删除。
### 第三步:自定义配置
- 增加标题,标题颜色 #6d767e
- 增加鼠标移入提示。(比如:“各地学员分布 北京市 12人 占比6.8%”)
- 系列数据
- 修改 name 为 '各地学员分布'
- 饼图,内圈半径 10%,外圈半径 60%
- 居中显示
- 面积模式
- 扇形轮廓圆角(4px)
完成后的配置项如下:
```js
let option = {
tooltip: {
// {a} 表示series中的name
// {b} 表示数据中的series.data中的name
// {c} 表示每一项的值
// {d} 表示百分比
formatter: '{a}
{b} {c}人 占比{d}%'
},
title: {
text: '籍贯 Hometown',
textStyle: {
color: '#6d767e' // 标题演示
},
},
series: [
{
name: '各地学员分布',
type: 'pie', // pie 表示饼图
radius: ['10%', '65%'], // 内外圈的半径
center: ['50%', '50%'], // 中心点
roseType: 'area', // area表示面积模式,radius表示半径模式
itemStyle: { // 每一项的设置
borderRadius: 4, // 扇形边缘圆角设置
},
data: [
{ value: 40, name: '北京' },
{ value: 38, name: '山东' },
{ value: 32, name: '上海' },
{ value: 30, name: '江苏' },
{ value: 28, name: '河北' },
{ value: 26, name: '山西' },
{ value: 22, name: '河南' },
{ value: 18, name: '辽宁' }
]
}
]
};
```
## Echarts-折线图
### 步骤分析
1. 封装好函数,为后续传入真实数据做准备
2. 初始化echarts
3. 设置配置项,空的option 即可
4. 创建图表
5. 查找官方示例
6. 按需求,自定义配置图表
### 第一步:echarts基本步骤
```js
function lineChart() {
let myChart = echarts.init(document.querySelector('.line'));
let option = {};
myChart.setOption(option);
}
```
### 第二步:参照官方示例
(官方示例:https://echarts.apache.org/examples/zh/editor.html?c=area-simple)
- tooltip -- 输入移入的提示
- title -- 标题
- xAxis -- x轴
- yAxis -- y轴
- dataZoom -- 数据缩放组件
- series -- 系列数据
以上配置项留下,其他删除
### 第三步:自定义配置
- 将官方示例中除了option之外的其他代码删除,并自己添加X轴数据和series中的数据。
- 系列数据
- 增加一条线
- 修改 name 为 '期望薪资' 和 '实际薪资'
- 线的拐点为平滑拐点
- 线条和X轴对齐位置,无特殊标记 `symbol: 'none'`
- 分析数据缩放组件 `dataZoom`
- 增加标题,标题颜色 #6d767e
- 分析tooltip(官方示例已带)。
- 增加图例,距离顶部20px。
- 分析坐标轴留白策略 `boundaryGap`
完成后的配置项 option 如下:
```js
let option = {
// 图例
legend: {
top: 20,
},
// 鼠标移入的提示
tooltip: {
trigger: 'axis', // 轴触发
position: function (pt) {
// pt是一个数组,pt[0]表示横坐标位置,'10%'表示Y轴方向始终保持距离顶部10%的距离
// 所以意思是,提示框的位置会跟随鼠标左右移动,但纵向上的位置始终保持不变。
return [pt[0], '10%'];
}
},
// 标题
title: {
text: '薪资 Salary',
textStyle: {
color: '#6d767e'
}
},
xAxis: {
type: 'category',
boundaryGap: false, // x轴两边的留白策略,false表示不留空白
data: ['张三', '李四', '张飞', '赵云', '狗哥', '张三', '李四', '张飞', '赵云', '狗哥', '张三', '李四', '张飞', '赵云', '狗哥', '张三', '李四', '张飞', '赵云', '狗哥']
},
yAxis: {
type: 'value',
// Y轴类型为value,则留白策略指的是对数据的延伸。
// 比如,图表中的数据最大值是17000,则Y轴最大数字大约是 17000 + 17000 * 50%
boundaryGap: [0, '50%'],
},
// 数据缩放组件
dataZoom: [
// {
// type: 'inside', // 将拖动的条内置到轴里面,看不见了,但是可以拖动
// start: 0,
// end: 10
// },
{
type: 'slider', // 拖动条显示到轴的外面(默认就是slider类型)
start: 0, // 拖动滑块起始位置(这是一个百分比)
end: 15 // 拖动滑块结束位置(这是一个百分比)
}
],
// 数据部分
series: [
{
name: '期望薪资',
type: 'line',
smooth: true, // 表示使用平滑曲线
symbol: 'none', // 线上拐点位置的样式,none表示没有;也可以是实心圆、空心圆、方块.....
itemStyle: { // 单独控制这条线的颜色
color: '#ee6666'
},
data: [8300, 9600, 15000, 17000, 12000, 8300, 9600, 15000, 17000, 12000, 8300, 9600, 15000, 17000, 12000, 8300, 9600, 15000, 17000, 12000]
},
{
name: '实际薪资',
type: 'line',
smooth: true,
symbol: 'none',
itemStyle: { // 单独控制这条线的颜色
color: '#5470c6'
},
data: [9600, 15000, 17000, 12000, 8300, 9600, 15000, 17000, 12000, 8300, 9600, 15000, 17000, 12000, 8300, 9600, 15000, 17000, 12000, 13000]
}
]
};
```
## Echarts-柱状图
### 步骤分析
1. 封装好函数,为后续传入真实数据做准备
2. 初始化echarts
3. 设置配置项,空的option 即可
4. 创建图表
5. 查找官方示例
6. 按需求,自定义配置图表
### 第一步:echarts基本步骤
```js
function barChart() {
let myChart = echarts.init(document.querySelector('.barChart'));
let option = {}
myChart.setOption(option);
}
```
### 第二步:参照官方示例
(官方示例:https://echarts.apache.org/examples/zh/editor.html?c=mix-line-bar)
- tooltip 提示组件
- legend 图例
- xAxis x轴
- yAxis y轴
- series 系列数据
以上几个配置项留下,其他删除。
### 第三步:自定义配置
- 修改X轴及series中的数据
```js
['1组', '2组', '3组', '4组', '5组', '6组', '7组']
[83, 57, 90, 78, 66, 76, 77, 87, 69, 92, 88, 78]
[2, 1, 3, 4, 2, 5, 2, 2, 4, 1, 6, 2]
[3, 2, 1, 5, 1, 2, 3, 4, 5, 2, 2, 4]
[3, 2, 1, 5, 1, 2, 3, 4, 5, 2, 2, 4]
```
- 多个Y轴
- 第一个y轴(**索引0**)表示平均分,范围0~100,根据数字10,将Y轴分为10份
- 第二个y轴(**索引1**)表示人数,范围0~10(根据班级情况而定),根据数字1,将y轴分为10份。
- 系列数据
- 增加至 4 组数据,并修改每组 name
- 修改每个柱子的宽度为 15px
- 让平均分使用第一个Y轴(`yAxisIndex: 0`),让人数使用第二个Y轴(`yAxisIndex: 1`)
- 调整网格(图表的宽高)
- 上下 30px,左右 7%
- 分析tooltip(官方示例已带)
```js
let option = {
// 网格(整个图表区域设置)
grid: {
top: 30,
bottom: 30,
left: '7%',
right: '7%'
},
// 鼠标移入的提示
tooltip: {
trigger: 'axis', // 触发方式,axis表示轴触发,item表示每一项
axisPointer: { // 坐标轴指示器配置项
// 十字准星指示器,其他选项有 line、shadow、none(这里配合x轴的设置,组成的十字准星)
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
// 图例
legend: {},
// X轴
xAxis: [
{
type: 'category',
data: ['1组', '2组', '3组', '4组', '5组', '6组', '7组'],
axisPointer: { // 坐标轴指示器为阴影,配合tooltip中的设置,组成十字准星
type: 'shadow'
}
}
],
// Y轴
yAxis: [
{
type: 'value',
min: 0, // y轴数据最小值
max: 100, // y轴数据最大值
interval: 10, // step步长,把y轴的数据分成多少份
axisLabel: { // Y轴文字设置
formatter: '{value}分', // Y轴文字
}
},
{
type: 'value',
min: 0,
max: 10,
interval: 1,
axisLabel: {
formatter: '{value}人'
}
}
],
// 数据部分(4组数据)
series: [
{
name: '平均分',
type: 'bar',
data: [83, 57, 90, 78, 66, 76, 77, 87, 69, 92, 88, 78],
barWidth: '15',
},
{
name: '低于60分人数',
type: 'bar',
data: [2, 1, 3, 4, 2, 5, 2, 2, 4, 1, 6, 2],
barWidth: '15',
yAxisIndex: 1, // Y轴索引,1表示使用第2个Y轴
},
{
name: '60到80分之间',
type: 'bar',
yAxisIndex: 1, // Y轴索引,1表示使用第2个Y轴
barWidth: '15',
data: [1, 4, 2, 4, 5, 2, 1, 3, 3, 2, 2, 4]
}
,
{
name: '高于80分人数',
type: 'bar',
yAxisIndex: 1, // Y轴索引,1表示使用第2个Y轴
barWidth: '15',
data: [3, 2, 1, 5, 1, 2, 3, 4, 5, 2, 2, 4]
}
]
};
```
## Echarts社区
> 社区就是一些,活跃的echart使用者,交流和贡献定制好的图表的地方。
在这里可以找到一些基于echart的高度定制好的图表,相当于基于jquery开发的插件,这里是基于echarts开发的第三方的图表。
社区示例:https://www.makeapie.com/explore.html

## Echarts-使用社区的示例
项目中使用的社区示例地址:https://www.makeapie.com/editor.html?c=xD4a1EBnvW
重点:
- 使用社区示例,必须要查看示例引入了哪些外部js文件。
实现步骤:
- 第一需要下载china.js提供中国地图的js文件
- 导入后,直接使用社区提供的配置即可。
- 自行修改
- 将背景色改为 白色
- 将 视觉映射组件(`visualMap`)中的 `show` 改为 `false`
- 其他自行自愿修改。
必须知道的结论:
- 哪些数据和哪些数据是对应的,必须一致
- 哪些数据能多,能错
- 哪些数据不能多,不能错
## 插件介绍
### 表单验证插件
- 表单验证插件有很多,不同的框架中选择的也不一样。
- 案例中,表单验证使用的是 [bootstrapValidator](https://gitee.com/psccja/bootstrapvalidator) 插件。(链接挂的是码云仓库地址)
- 网上有很多关于它的介绍,比如[这里](https://www.zhuangyan.cn/BootstrapValidator-guide/) 和 [这里](https://www.yisu.com/zixun/153358.html)。
- 经过整理,总结它的使用步骤如下:
使用步骤:
1. 先引入bootstrap.css 、jquery.js 和 bootstrap.js
2. 引入 bootstrapValidator.css 和 bootstrapValidator.js
3. 编写验证代码,为了方便,可以封装进一个函数
```js
// 比如,验证一个用户名和密码
function test() {
return {
fields: {
username: { // 这里username是 input 的name属性值,表示对这个输入框进行验证
validators: {
notEmpty: { //不能为空
message: '用户名不能为空.'
},
stringLength: { //检测长度
min: 2,
max: 15,
message: '用户名需要2~15个字符'
}
}
},
password: {
validators: {
notEmpty: {
message: '密码不能为空'
},
stringLength: { //检测长度
min: 6,
max: 15,
message: '密码需要6~15个字符'
}
}
}
}
}
}
```
4. 使用插件语法,监听表单提交事件,并使用验证
```js
// 语法:
// $('表单').bootstrapValidator(上面的验证函数()).on('success.form.bv', function (e) {}
// 比如,注册
$('.register form').bootstrapValidator(test()).on('success.form.bv', function (e) {
e.preventDefault();
// 通过验证,这里的代码将会执行。我们将Ajax请求的代码放到这里即可
});
```
### 提示框插件
- 提示框插件有很多,不同的框架中选择的也不一样。
- 案例中,提示框使用的是 [toastr](https://codeseven.github.io/toastr/demo.html) 插件。(挂的是GitHub链接,可能打不开)
- 总结它的使用步骤如下:
使用步骤:
1. 加载 toastr.css 和 toastr.js 文件
2. 全局配置。为方便,我们将下面的配置放到 assets/utils/toastr.js 中,使用时,加载这个配置文件即可
```js
toastr.options = {
// "closeButton": false,
// "debug": false,
// "newestOnTop": false,
// "progressBar": false,
"positionClass": "toast-top-right", // 提示框位置,这里填类名
// "preventDuplicates": false,
// "onclick": null,
"showDuration": "300", // 提示框渐显所用时间
"hideDuration": "300", // 提示框隐藏渐隐时间
"timeOut": "2000", // 提示框持续时间
// "extendedTimeOut": "1000",
// "showEasing": "swing",
// "hideEasing": "linear",
// "showMethod": "fadeIn",
// "hideMethod": "fadeOut"
}
```
3. 调用方法,直接使用
```js
toastr.info('提示信息'); // 普通提示
toastr.success('提示信息'); // 成功提示
toastr.warning('提示信息'); // 警告提示
toastr.error('提示信息'); // 错误提示
```
## 配置Axios项目根路径
- 目前echarts图表中的数据都是假数据
- 如果获取真数据就需要调用接口
- 这就需要至少要完成注册、登录、初始化数据等接口的调用才行
- 项目中接口根路径是相同的,见前文 [重要的三个地址](##重要的三个地址)
- 所以,在 `assets/utils` 文件夹,创建axios的配置文件,取名 `request.js` 。代码如下:
```js
axios.defaults.baseURL = 'http://www.itcbc.com:8000';
```
> 这里之所以放到 assets/utils 文件夹中,是因为 utils 在编程中常用来放工具函数。而request是请求的意思。
## 注册账号
### 切换登录和注册的盒子
登录和注册同在 login.html 中,因为定位的原因重叠在一起了。我们可以通过JS实现切换两个盒子
```js
// 切换两个盒子
$('.box a').on('click', function () {
$(this).parents('.box').hide().siblings('.box').show();
})
```
### 表单验证
- 表单验证,见上文的 [插件介绍](##插件介绍) 中的 [表单验证插件](###表单验证插件) 。(这里的示例可以直接当做注册和登录的验证代码)
### 完成注册
当表单验证通过后,根据接口文档,获取输入框的账号和密码,Ajax提交账号和密码
```js
$('.register form').bootstrapValidator(register()).on('success.form.bv', function (e) {
e.preventDefault();
// 通过验证,这里的代码将会执行。我们将Ajax请求的代码放到这里即可
let data = $(this).serialize();
// console.log(data);
axios.post('/api/register', data).then(({ data: res }) => {
// console.log(res);
if (res.code === 0) { // 我们只考虑成功的情况即可,失败的情况后续统一使用拦截器处理
toastr.success(res.message); // 使用插件提示消息
$('.register input').val(''); // 清空输入框
$('.register').hide().next().show(); // 切换至登录的盒子
}
})
});
```
## 登录功能
- 因为前文已经完成了注册,所以这里的登录功能极为简单
- 问题是,登录如果成功,该做什么?
- 将服务器响应的token存储到本地存储(关于token的说明见下文的 [JWT身份认证](##JWT身份认证))
- 跳转到 index.html 页面 (页面跳转,只考虑两个html之间的相对关系,不考虑js在哪里)
登录的代码如下:
```js
$('.login form').bootstrapValidator(register()).on('success.form.bv', function (e) {
e.preventDefault();
//提交逻辑
// console.log(222);
let data = $(this).serialize();
// console.log(data);
axios.post('/api/login', data).then(({ data: res }) => {
// console.log(res);
if (res.code === 0) {
localStorage.setItem('token', res.token);
location.href = './index.html'
}
})
});
```
## JWT身份认证
### 什么是jwt身份认证
在前后端分离模式的开发中,服务器如何知道来访者的身份呢?
- 在登录后,服务器会响应给用户一个 令牌 (token)
- 令牌中会包括该用户的id等唯一标识
- 浏览器收到令牌后,自己保存
- 下次请求其他接口时,(在请求头中)携带这个令牌去请求
- 这样服务器就知道来访者的身份了,服务器就会为该用户开发接口的访问权限,并处理该用户的数据

> 这样,就明白为什么登录后,要将token保存到本地存储中了。
### 全局配置请求头
由于除了登录和注册接口外,其他所有接口都需要身份认证(都需要我们提供令牌),所以我们可以在 `request.js` 中,全局配置请求头。
```js
axios.defaults.headers.common['Authorization'] = localStorage.getItem('token');
```
### 利用令牌控制页面的访问权限
浏览器端,可以通过合理使用令牌,控制页面的访问权限。
比如,用户默认只能访问登录页,如果不登录就不能访问首页,怎么做?

**第一个判断**:判断本地存储是否有token
```html
```
> 上述判断只能判断token有没有,但不能判断token的真假,所以需要发送Ajax请求,根据服务器响应结果再次判断
**第二个判断**:根据服务器响应结果,判断token是否是假token或者过期的token
- 如果token值是正确的,是没有过期的,则服务器响应 `code===0`
- 如果token是错误的获取过期的,则服务器响应 `code===1 && message==='身份认证失败'`
```js
// request.js 中,使用响应拦截器,拦截响应结果进行判断
// 如果响应结果中 code === 1 && message === '身份认证失败' 则表示浏览器使用了无效的token
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
return response;
}, function (error) {
// 对响应错误做点什么
if (error.response) {
if (error.response.data.message === '身份认证失败') {
localStorage.removeItem('token');
location.href = './login.html'
}
}
return Promise.reject(error);
});
```
## 统一处理错误提示
在上述响应拦截器的基础之上,顺便添加响应错误提示。
所有接口响应的结果有两种:
- 响应状态码 小于 400,并且 `code===1`,比如登录账号密码错误。这样的响应进入响应拦截器中的第一个函数。
- 响应状态码 大于等于 400,并且 `code === 1` ,比如身份认证失败。这样的响应进入响应拦截器中的第二个函数。
所以分别提示
```diff
// request.js 中,使用响应拦截器,拦截响应结果进行判断
// 如果响应结果中 code === 1 && message === '身份认证失败' 则表示浏览器使用了无效的token
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 对响应数据做点什么
+ if (response.data.code === 1) {
+ toastr.warning(response.data.message)
+ }
return response;
}, function (error) {
// 对响应错误做点什么
if (error.response) {
if (error.response.data.message === '身份认证失败') {
localStorage.removeItem('token');
location.href = './login.html'
- }
+ } else {
+ toastr.error(error.response.data.message);
+ }
}
return Promise.reject(error);
});
```
## 退出登录
一般来说,退出需要做的事和登录后做的事刚好相反。
- 登录后,在本地存储了token;退出时,移除这个token
- 登录后,跳转到了 index.html 页面;退出时,跳转到 login.html
```js
// ------------------------ 退出登录 -------------------
$('.logout a').on('click', function () {
if (!confirm('确定要退出登录吗?')) return;
localStorage.removeItem('token');
location.href = './login.html';
})
```
## 初始化数据
为了减少手动录入数据的时间,特别设计了此接口。调用此接口将为你随机添加56名同学,并分为8个组。并为每位同学随机添加了三次成绩。
```js
// ------------------------ 初始化数据 -------------------
$('.init').on('click', function () {
axios.get('/init/data').then(({ data: res }) => {
if (res.code === 0) {
toastr.success(res.message);
}
})
})
```
## 班级概况
- 查阅接口文档,发现服务器已经将数据计算整理完毕,提供了现成的接口。
- 所以我们前端开发者,只需要调用接口,并将数据展示到页面中即可。
```js
// 获取班级概况数据
axios.get('/student/overview').then(({ data: res }) => {
// console.log(res);
let { code, data } = res;
if (code === 0) {
$('.overview .total').text(data.total);
$('.overview .avgAge').text(data.avgAge);
$('.overview .avgSalary').text(data.avgSalary);
$('.overview .proportion').text(data.proportion);
}
})
```
## 柱状图使用接口数据
- 这个接口和班级概况一样,服务器已经将数据整理完毕。
- 所以我们前端开发者,只需要调用接口获取数据,并将数据传递给 echarts 即可。
- 不过,接口需要一个 batch 参数(考试的次数),所以需要先将下拉菜单处理一下。
```js
// 下拉菜单
$('.bar .btn').on('click', function () {
$(this).next('ul').toggle();
})
// 点击 “第n次成绩” 按钮,获取该次考试成绩
$('#batch li').on('click', function () {
// 取得当前li元素在兄弟间的位置,即索引,加1后,刚好当做考试次数
let batch = $(this).index() + 1;
$(this).parent().hide(); // 让ul列表隐藏,这行也可以省略,即不隐藏
axios.get('/score/batch', { params: { batch } }).then(({ data: res }) => {
let { data, code } = res;
if (code === 0) {
// console.log(data);
barChart(data);
}
})
})
// 页面加载后,触发第一个li的单击事件
$('#batch li').eq(0).trigger('click');
```
创建柱状图的函数,设置形参接收数据。并把数据应用到X轴及series中。
```diff
// 形参,直接解构赋值
+function barChart({ avgScore, group, gt60, gt80, lt60 }) {
...... 省略其他代码
let option = {
xAxis: {
type: 'category',
+ data: group, // X轴使用组名 1组 2组 3组 ......
},
series: [
{
+ data: avgScore, // 第一个柱子,使用平均分
type: 'bar',
barWidth: '15%',
name: '平均分',
yAxisIndex: 0,
},
{
+ data: lt60, // 第二个柱子,使用小于60分人数数据
type: 'bar',
barWidth: '15%',
name: '低于60分人数',
yAxisIndex: 1,
},
{
+ data: gt60, // 第三个柱子,使用大于60小于80分人数数据
type: 'bar',
barWidth: '15%',
name: '60~80分人数',
yAxisIndex: 1,
},
{
+ data: gt80, // 第四个柱子,使用大于80分人数数据
type: 'bar',
barWidth: '15%',
name: '80分以上人数',
yAxisIndex: 1,
}
]
}
}
- barChart(); // 这里就不要再调用函数了
```
## 折线图使用接口数据
> 为了断链数据处理能力。所以并没有提供 “返回折线图数据” 的接口。我们需要调用获取学生接口,然后自己处理数据。
调用“获取学生”接口,然后处理
```js
// 获取学员信息 ==> 1、提取期望薪资和实际薪资,做折线图; 2、提取地区,做饼图; 3、提取地区和经纬度,做地图
axios.get('/student/list').then(({ data: res }) => {
let { code, data } = res;
if (code === 0) {
// console.log(data);
// 折线图所需数据,检查折线图需要的数据格式
// (x轴需要一个数组,放姓名)
// (两条线分别需要一个数组,放期望薪资和实际薪资)
let lineData = {
xAxis: [],
salary: [],
truesalary: []
};
// 遍历data,将所需的数据取出,放到前面定义好的数组中
data.forEach(item => {
lineData.xAxis.push(item.name);
lineData.salary.push(item.salary);
lineData.truesalary.push(item.truesalary);
});
// 遍历结束,得到我们所需的数据
// 数据处理好,调用图表函数
lineChart(lineData);
}
})
```
折线图函数 lineChart 中,设置形参接收数据并使用。
```diff
+function lineChart({ xAxis, salary, truesalary }) {
let myChart = echarts.init($('.line')[0]);
let option = {
...... 其他配置项省略
xAxis: {
type: 'category',
boundaryGap: false,
+ data: xAxis
},
// 数据部分
series: [
{
name: '期望薪资',
type: 'line',
smooth: true, // 表示使用平滑曲线
symbol: 'none', // 线上拐点位置的样式,none表示没有;也可以是实心圆、空心圆、方块.....
itemStyle: { // 单独控制这条线的颜色
color: '#ee6666'
},
+ data: salary
},
{
name: '实际薪资',
type: 'line',
smooth: true,
symbol: 'none',
itemStyle: { // 单独控制这条线的颜色
color: '#5470c6'
},
+ data: truesalary
}
]
};
myChart.setOption(option);
}
- lineChart(); // 这里不要再调用函数了
```
## 地图使用接口数据
地图中使用的数据,只有地名和经纬度。这些数据在 “获取学生”接口中都有,但还是需要我们自己整理。锻炼数据处理能力。
由于前面已经调用过获取学生接口了,所以不用重新发送请求获取数据了,只需要处理即可。
```js
// 分析地图需要什么格式的数据。
// 需要两组数据
// 第一组数据
let chinaGeoCoordMap = {
'北京市': [116.4551, 40.2539],
'内蒙古': [110.3467, 41.4899],
"吉林": [125.8154, 44.2584],
// ......
}
// 第二组数据
let chinaDatas = [
[{
name: '黑龙江11',
value: 0
}], [{
name: '内蒙古',
value: 0
}],
// ......
]
```
> 上述数据,用的是省的名字,实际上全部用市的名字或者县的名字都可以。只要对应上即可。
>
> **我们案例中用县的名字。**
当请求到学生数据之后
```diff
// 获取学员信息 ==> 1、提取期望薪资和实际薪资,做折线图; 2、提取地区,做饼图; 3、提取地区和经纬度,做地图
axios.get('/student/list').then(({ data: res }) => {
let { code, data } = res;
if (code === 0) {
// console.log(data);
// 折线图所需数据,检查折线图需要的数据格式
// (x轴需要一个数组,放姓名)
// (两条线分别需要一个数组,放期望薪资和实际薪资)
let lineData = {
xAxis: [],
salary: [],
truesalary: []
};
+ // 地图,第一组数据
+ let chinaGeoCoordMap = { '北京市': [116.4551, 40.2539] };
+ // 地图,第二组数据
+ let chinaDatas = []
// 遍历data,将所需的数据取出,放到前面定义好的数组中
data.forEach(item => {
lineData.xAxis.push(item.name);
lineData.salary.push(item.salary);
lineData.truesalary.push(item.truesalary);
+ chinaGeoCoordMap[item.county] = [item.jing, item.wei];
+ chinaDatas.push([{name: item.county, value: 0}]);
});
// 遍历结束,得到我们所需的数据
// 数据处理好,调用图表函数
lineChart(lineData);
+ mapChart(chinaGeoCoordMap, chinaDatas);
}
})
```
地图函数中使用数据:
```diff
+function mapChart(chinaGeoCoordMap, chinaDatas) {
- var chinaGeoCoordMap = {.......}
- var chinaDatas = [......]
let myChart = echarts.init($('.map')[0]);
var convertData = function (data) {
var res = [];
for (var i = 0; i < data.length; i++) {
var dataItem = data[i];
var fromCoord = chinaGeoCoordMap[dataItem[0].name];
+ var toCoord = [116.4551, 40.2539]; // 目标点 经纬度(北京市)
if (fromCoord && toCoord) {
res.push([{
coord: fromCoord,
value: dataItem[0].value
}, {
coord: toCoord,
}]);
}
}
return res;
};
var series = [];
+ [['北京市', chinaDatas]].forEach(function (item, i) { // 写好北京市,目标点
// 其他代码省略,无需修改
}
- mapChart(); // 这里不要再调用函数了
```
## 饼图使用接口数据
饼图使用的数据格式如下
```js
data: [
{ name: '河北省', value: 12 },
{ name: '广东省', value: 12 },
{ name: '上海市', value: 12 },
{ name: '江西省', value: 12 },
// ......
]
```
饼图使用的数据,也需要通过 获取学生 接口得到,并需要自行处理。
```diff
// 获取学员信息 ==> 1、提取期望薪资和实际薪资,做折线图; 2、提取地区,做饼图; 3、提取地区和经纬度,做地图
axios.get('/student/list').then(({ data: res }) => {
let { code, data } = res;
if (code === 0) {
// console.log(data);
// 折线图所需数据,检查折线图需要的数据格式
// (x轴需要一个数组,放姓名)
// (两条线分别需要一个数组,放期望薪资和实际薪资)
let lineData = {
xAxis: [],
salary: [],
truesalary: []
};
// 地图,第一组数据
let chinaGeoCoordMap = { '北京市': [116.4551, 40.2539] };
// 地图,第二组数据
let chinaDatas = []
+ // 饼图数据
+ let pieData = [];
// 遍历data,将所需的数据取出,放到前面定义好的数组中
data.forEach(item => {
lineData.xAxis.push(item.name);
lineData.salary.push(item.salary);
lineData.truesalary.push(item.truesalary);
chinaGeoCoordMap[item.county] = [item.jing, item.wei];
chinaDatas.push([{name: item.county, value: 0}]);
+ let i;
+ if ((i = pieData.findIndex(v => v.name === item.province)) >= 0) {
+ pieData[i].value++;
+ } else {
+ pieData.push({ name: item.province, value: 1 });
+ }
});
// 遍历结束,得到我们所需的数据
// 数据处理好,调用图表函数
lineChart(lineData);
mapChart(chinaGeoCoordMap, chinaDatas);
+ pieChart(pieData);
}
})
```
饼图函数,接收数据并使用:
```diff
+function pieChart(pieData) {
let myChart = echarts.init($('.pie')[0]);
let option = {
series: [
{
name: '各地人员分布',
type: 'pie', // pie 表示饼图
radius: ['10%', '65%'], // 内外圈的半径
center: ['50%', '50%'], // 中心点
roseType: 'area', // area表示面积模式,radius表示半径模式
itemStyle: { // 每一项的设置
borderRadius: 4, // 扇形边缘圆角设置
},
+ data: pieData
}
]
};
}
```
## 成绩管理
> 后续,就是我们在Ajax课程中学习的知识点了。也就是数据的增删改查等等操作。
### 获取成绩并展示
这个功能有对应的接口,而且得到的数据不需要处理,直接可以使用。**认真阅读接口文档**。
```js
// -------------- 获取分数,展示列表 ------------
function renderScore() {
axios.get('/score/list').then(({ data: res }) => {
// console.log(res);
let { code, data } = res;
if (code === 0) {
let arr = [];
for (let key in data) {
arr.push(`
${key} |
${data[key].name} |
${data[key].score[0]} |
${data[key].score[1]} |
${data[key].score[2]} |
${data[key].score[3]} |
${data[key].score[4]} |
`);
}
$('tbody').html(arr.join(''))
}
})
}
renderScore();
```
### 可编辑的表格效果
添加和修改成绩,并没有使用弹出层等效果。而是采用可编辑的表格。
可编辑的表格,需要用到的技术就是DOM操作,没有其他。
思路如下:
- 点击单元格 `td.score` ,获取单元格的值。
- 创建input
- 设置input的样式和td的样式一致,或者自行修改样式。
- 将 单元格的值 设置为 input 的 value
- 将 input 放入 td 中,并获取焦点
后续补充,输入框中按回车,表示确定,失去焦点表示取消
- 给input注册 keyup 和 blur 事件。
完整代码:
```js
// 可编辑的表格
$('tbody').on('click', '.score', function () {
let td = $(this);
if (td.children().length > 0) return; // 判断如果已经有input了,就不要在重复添加input了
let text = td.text(); // 保存td中的分数
td.html(''); // 设置td的内容为空
let input = $(''); // 创建input
input // 设置input的样式
.css('color', td.css('color')) // 设置input的color和td的color相同
.css('background-color', td.css('background-color'))
.css('font-size', td.css('font-size'))
.css('width', '100%')
.css('height', '100%')
.css('outline', 'none')
.css('border', 'none')
.val(text) // 将原来td中的值,设置为input的value
.appendTo(td) // 将input添加到td中
.focus(); // 让input获取焦点,即有光标闪烁效果
// 为input注册 keyup 事件
input.on('keyup', function (e) {
if (e.keyCode === 13) {
// console.log('按下回车键,这里调用接口,修改或录入成绩');
}
})
// 为input注册blur事件
input.on('blur', function () {
td.html(text); // 失去焦点后,将原来td的值设置回去
})
})
```
### 修改或录入成绩
接口分析:
- 需要学员id 参数
- 需要batch次数 参数
- 需要score成绩 参数
所以,在循环遍历数据时,将需要的 **学员id** 和 **batch**次数,用自定义属性 data-xx 存储。
```js
arr.push(`
${key} |
${data[key].name} |
${data[key].score[0]} |
${data[key].score[1]} |
${data[key].score[2]} |
${data[key].score[3]} |
${data[key].score[4]} |
```
当按下回车键后,获取接口所需参数,调用接口发送请求,进行成绩的修改和录入
```js
input.on('keyup', function (e) {
if (e.keyCode === 13) {
// console.log('按下回车键,这里调用接口,修改或录入成绩');
let score = $(this).val();
if (isNaN(score) || score < 0 || score > 100) {
return toastr.warning('请输入0~100之间的数字');
}
let stu_id = td.data('id');
let batch = td.data('batch');
// console.log(score, stu_id, batch);
axios.post('/score/entry', { score, stu_id, batch }).then(({ data: res }) => {
if (res.code === 0) {
toastr.success(res.message);
td.html(score) // 修改成功后,将新的成绩 放到 td 中
}
})
}
})
```
## 学员管理
### 获取数据展示
这也是一个常规操作,按照接口文档获取数据,然后循环遍历到表格中即可。
```js
// 获取学员数据
function renderStudent() {
axios.get('/student/list').then(({ data: res }) => {
// console.log(res);
if (res.code === 0) {
let arr = [];
res.data.forEach(item => {
arr.push(`
${item.id} |
${item.name} |
${item.age} |
${item.sex} |
${item.group} |
${item.phone} |
${item.salary} |
${item.truesalary} |
${item.province}${item.city}${item.county} |
|
`);
});
$('tbody').html(arr.join(''))
}
})
}
renderStudent();
```
### 添加 -- 分析
- 添加使用了 bootstrap4 的[模态框](https://v4.bootcss.com/docs/components/modal/)(弹出框),这个只需按 bootstrap 的语法写即可。
- 表单中有“省市县”联动效果。当然也有对应的接口。
- 开始时,先获取所有的省,放到第一个下拉框中
- 当“省”切换的时候,获取对应的市,并放入第二个下拉框中。
- 当“市”切换的时候,获取对应的县,并放入第三个下拉框中
- 需要有表单验证,这个安装表单验证插件的语法写即可。
- 一切准备就绪,提交表单数据,完成添加即可。
### 添加 -- 省市县联动
```js
// --------------------- 获取省 ---------------------
axios.get('/geo/province').then(({ data: res }) => {
// console.log(res);
let arr = [''];
res.forEach(item => {
arr.push(``)
});
$('select[name=province]').html(arr.join(''))
})
// --------------------- 省切换的时候,选择市 ---------------------
$('select[name=province]').on('change', function () {
// 重置县
$('select[name=county]').html(``);
let pname = $(this).val();
axios.get('/geo/city', { params: { pname } }).then(({ data: res }) => {
// console.log(res);
let arr = [''];
res.forEach(item => {
arr.push(``)
});
$('select[name=city]').html(arr.join(''))
})
})
// --------------------- 市切换的时候,选择县 ---------------------
$('select[name=city]').on('change', function () {
let pname = $(this).parents('.col-sm-3').prev().find('select').val();
let cname = $(this).val();
axios.get('/geo/county', { params: { pname, cname } }).then(({ data: res }) => {
// console.log(res);
let arr = [''];
res.forEach(item => {
arr.push(``)
});
$('select[name=county]').html(arr.join(''))
})
})
```
### 添加 -- 表单验证
```js
function student() {
return {
fields: {
name: {
validators: {
notEmpty: {
message: '姓名不能为空',
},
stringLength: {
min: 2,
max: 10,
message: '姓名长度2~10位'
}
}
},
age: {
validators: {
notEmpty: {
message: '年龄不能为空',
},
greaterThan: {
value: 18,
message: '年龄不能小于18岁'
},
lessThan: {
value: 60,
message: '年龄不能超过60岁'
}
}
},
sex: {
validators: {
choice: {
min: 1,
max: 1,
message: '请选择性别'
}
}
},
group: {
validators: {
notEmpty: {
message: '组号不能为空',
},
regexp: {
regexp: /^\d{1,2}$/,
message: '请选择有效的组号'
}
}
},
phone: {
validators: {
notEmpty: {
message: '手机号不能为空',
},
regexp: {
regexp: /^1\d{10}$/,
message: '请填写有效的手机号'
}
}
},
salary: {
validators: {
notEmpty: {
message: '实际薪资不能为空',
},
greaterThan: {
value: 800,
message: '期望薪资不能低于800'
},
lessThan: {
value: 100000,
message: '期望薪资不能高于100000'
}
}
},
truesalary: {
validators: {
notEmpty: {
message: '实际薪资不能为空',
},
greaterThan: {
value: 800,
message: '实际薪资不能低于800'
},
lessThan: {
value: 100000,
message: '实际薪资不能高于100000'
}
}
},
province: {
validators: {
notEmpty: {
message: '省份必填',
},
}
},
city: {
validators: {
notEmpty: {
message: '市必填',
},
}
},
county: {
validators: {
notEmpty: {
message: '县必填',
},
}
},
}
}
}
```
### 添加 -- 完成添加
```js
// 添加学生
$('.add-form').bootstrapValidator(student()).on('success.form.bv', function (e) {
e.preventDefault();
//提交逻辑
// console.log(222);
let data = $(this).serialize();
// console.log(data);
axios.post('/student/add', data).then(({ data: res }) => {
// console.log(res);
if (res.code === 0) {
toastr.success(res.message);
renderStudent();
$('#addModal').modal('hide') // 这个是关闭模态框(弹出框)的意思
}
})
});
```
### 修改 -- 分析
- 修改中,也会用到省市县联动。但是不用再写代码了,前面添加的时候已经写过了。
- 修改时,表单(每个输入框)不能为空,必须设置好默认的value值,**这个操作叫做数据回填。所有的修改操作都需要数据回填**
- 点击 修改 按钮
- 发送请求,获取该学员的信息。
- 得到数据后,设置每个输入框的value值。下拉框和单项按钮需设置选中状态。
- 修改接口,需要学员id,所以在修改表单中,加入 ``
- 最后,按照接口文档,完成修改即可。
### 修改 -- 数据回填
遍历数据时,用 data-id 属性,存储该学员的 id
```diff
arr.push(`
${item.id} |
${item.name} |
${item.age} |
${item.sex} |
${item.group} |
${item.phone} |
${item.salary} |
${item.truesalary} |
${item.province}${item.city}${item.county} |
+
|
`);
```
JS代码如下:
```js
// 修改学员 --- 数据回填
$('tbody').on('click', '.update', function () {
let id = $(this).data('id');
axios.get('/student/one', { params: { id } }).then(({ data: res }) => {
if (res.code === 0) {
// console.log(res.data);
let { name, age, sex, group, phone, id, salary, truesalary, province, city, county } = res.data;
$('#updateModal input[name=id]').val(id);
$('#updateModal input[name=name]').val(name);
$('#updateModal input[name=age]').val(age);
$('#updateModal input[name=sex][value=' + sex + ']').prop('checked', true);
$('#updateModal input[name=phone]').val(phone);
$('#updateModal input[name=salary]').val(salary);
$('#updateModal input[name=truesalary]').val(truesalary);
$('#updateModal select[name=group]').children('[value=' + group + ']').prop('selected', true);
$('#updateModal select[name=province]').children('[value=' + province + ']').prop('selected', true);
$('#updateModal select[name=city]').html(``);
$('#updateModal select[name=county]').html(``);
$('#updateModal').modal('show'); // 这行的意思是让模态框显示
}
})
})
```
### 修改 -- 完成修改
```js
// 修改学员 --- 确认修改
$('.update-form').bootstrapValidator(student()).on('success.form.bv', function (e) {
e.preventDefault();
//提交逻辑
// console.log(222);
let data = $(this).serialize();
// console.log(data);
axios.put('/student/update', data).then(({ data: res }) => {
// console.log(res);
if (res.code === 0) {
toastr.success(res.message);
renderStudent();
$('#updateModal').modal('hide')
}
})
});
```
### 删除操作
遍历数据时,用 data-id 属性,存储该学员的 id
```diff
arr.push(`
${item.id} |
${item.name} |
${item.age} |
${item.sex} |
${item.group} |
${item.phone} |
${item.salary} |
${item.truesalary} |
${item.province}${item.city}${item.county} |
+
|
`);
```
JS代码如下:
```js
// 删除学员
$('tbody').on('click', '.del', function () {
if (!confirm('你确定要删除吗?')) return;
let id = $(this).data('id');
axios.delete('/student/delete', { params: { id } }).then(({ data: res }) => {
if (res.code === 0) toastr.success(res.message); renderStudent();
})
})
```