1.导包
<!-- 1.引入 ECharts 文件 -->
<script src="./echarts/echarts.min.js"></script>
2.HTML结构:一行代码
<!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
3.js初始化
所有的图表使用步骤都是固定的,不同的图表仅仅只是配置项和数据不同而已
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 1.引入 ECharts 文件 -->
<script src="./echarts/echarts.min.js"></script>
</head>
<body>
<!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#main'));
// 3.2 指定图表的配置项和数据
let option = {
title: {
text: 'ECharts 入门示例'
},
tooltip: {},
legend: {
data: ['销量']
},
xAxis: {
data: ["衬衫", "羊毛衫", "雪纺衫", "裤子", "高跟鞋", "袜子"]
},
yAxis: {},
series: [{
name: '销量',
type: 'bar',
data: [5, 20, 36, 10, 10, 20]
}]
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
</script>
</body>
</html>
1.1-项目学习目标介绍
-
1.硬实力(技术)
-
1.掌握ajax在实际项目中的应用
-
2.掌握echarts图表制作的基本使用
-
3.增强对数据的处理能力,增强编程能力
-
-
2.软实力(经验)
-
1.初步了解项目开发流程
-
2.培养项目思路分析能力
-
-
3.本项目重难点
-
1.token工作流程(重要)
-
2.axios拦截器配置全局token(重要)
-
3.图表制作(重要)
-
-
4.本项目非重点
-
1.布局(不重要,用的是bootstrap+jquery)
-
2.插件(不重要,因为后面vue项目根本就不用)
-
-
5.(可选)git仓库使用
-
大家可以把自己练习代码上传到远程仓库,熟悉git一些命令
-
-
6.课程要求:至少敲两遍
-
第一遍:课堂跟着老师敲
-
第二遍:每天晚自习课后作业,就是独立完成今天代码(建议不要看老师代码,可以适当参考老师思路注释,能自己写思路最好。有bug尽量自己断点解决)
-
1.2-项目全局目录与布局结构介绍
1.4-[可选]将本地项目上传到远程仓库
-
前一天学习远程仓库 : 新建空的远程仓库(适用于全新项目)
-
(1)先在码云创建远程仓库
-
(2)git clone : 远程仓库克隆到本地
-
-
CMS : 不是全新文件,而是里面已经有很多文件了( 本地项目上传到远程仓库 )
-
(1)本地项目
-
git init
: 新建git本地仓库 -
git add
. : 添加到暂存区 -
git commit -m"描述信息''
: 确认存档
-
-
(2)在码云新建一个远程仓库
-
注意点
: 新建仓库名字一定要和本地文件名一致,而且是空的
-
-
(3)将本地项目 上传到 git远程仓库
-
第一个命名,将本地仓库的地址与远程仓库地址进行关联
-
git remote add origin 仓库地址
-
-
第二个命令,本地文件推送到服务器
-
git push -u origin master
-
-
-
02-登录注册
1.1-注册功能
(1)收集表单数据
(2)校验数据 : 账号在2-30个字符 密码在6-30个字符
(3)发送数据 : ajax请求
(4)处理数据 : 成功跳转登录页 失败提示用户原因
/*点击注册按钮业务
(1)收集表单数据
(2)校验数据 : 账号在2-30个字符 密码在6-30个字符
(3)发送数据 : ajax请求
(4)处理数据 : 成功跳转登录页 失败提示用户原因
*/
document.querySelector('#btn-register').addEventListener('click',async function(e) {
// (0)阻止表单默认提交行为
e.preventDefault()
// (1)收集表单数据
let username = document.querySelector('[name="username"]').value
let password = document.querySelector('[name="password"]').value
// (2)校验数据
if (username.length < 2 || username.length > 30) {
Toast.fail('账号在2-30个字符')
} else if (password.length < 6 || password.length > 30) {
Toast.fail('密码在6-30个字符')
} else {
// (3)发送数据:ajax请求
try{
const { data:res } = await axios.post('/register',{username,password})
console.log(res)
//提示框
Toast.success('注册成功')
//(4)成功:跳转登录页
location.href = './login.html'
}catch(err){
//(5)失败:提示用户原因
Toast.fail(err.response.data.message)
}
}
})
1.2-登录功能
(1)收集表单数据
(2)校验数据 : 账号在2-30个字符 密码在6-30个字符
(3)发送数据 : ajax请求
(4)处理数据 : 成功跳转首页 失败提示用户原因
/*点击登录按钮业务
(1)收集表单数据
(2)校验数据 : 账号在2-30个字符 密码在6-30个字符
(3)发送数据 : ajax请求
(4)处理数据 : 成功跳转首页 失败提示用户原因
*/
document.querySelector('#btn-login').addEventListener('click',async function(e) {
// (0)阻止表单默认提交行为
e.preventDefault()
// (1)收集表单数据
let username = document.querySelector('[name="username"]').value
let password = document.querySelector('[name="password"]').value
// (2)校验数据
if (username.length < 2 || username.length > 30) {
Toast.fail('账号在2-30个字符')
} else if (password.length < 6 || password.length > 30) {
Toast.fail('密码在6-30个字符')
} else {
// (3)发送数据:ajax请求
try{
const { data:res } = await axios.post('/login',{username,password})
console.log(res)
//提示框
Toast.success('注册成功')
//(4)成功:跳转登录页
location.href = './index.html'
}catch(err){
//(5)失败:提示用户原因
Toast.fail(err.response.data.message)
}
}
})
03-首页
-
首页需求分析
-
(1)页面一加载,ajax请求图表数据
-
(2)将服务器返回的数据渲染到图表
-
(3)退出登录
-
-
首页核心技术点
-
token工作原理
-
axios拦截器使用
-
echarts图表绘制
-
/*首页需求分析
1.页面一加载,ajax请求图表数据
2.将服务器返回的数据渲染到图表
3.退出登录
*/
// 1.页面一加载,ajax请求图表数据
window.addEventListener('load',async function(){
const {data:res} = await axios.get('/dashboard')
console.log(res)
})
-
当我们尝试在首页发送ajax请求的时候,此时会出现经典的401响应码错误
-
要想理解401的含义,首先我们要了解什么是token
-
==04-Token实现JWT身份认证==
1.1-Token介绍
-
1.为什么要有token?
-
默认情况下,HTTP是一个无状态协议,也就是说任何浏览器都可以访问服务器,但是服务器并不能知道浏览器到底是属于哪个用户的。
-
-
2.token是什么 : 身份令牌
-
token是一串经过加密之后的字符串
,相当于是用户一种身份认证令牌。类似于古代的腰牌,现代的工牌。 见到这个牌子,服务器才知道你是自己人,才会把数据响应给你。-
jwt全称JSON WEB Token:它是一个后端加密并转换生成的一个字符串, 里面存储着本次登录的用户相关信息 (需要登录, 输入正确的账号和密码 换取)
-
-
-
3.token应用场景
-
免登录功能 : 用户在输入账号、密码登录之后,只需要将服务器返回的token存入到本地。之后用户进入网站只需要取出token发给服务器。 服务器就知道你是哪一个用户了,从而避免每一次进入网站都需要用户重新登录一次。
-
权限控制:有的网页需要做一个权限控制,你只有登录了才可以访问。没有登录就会自动跳转到登录页。这个时候只需要判断用户的token是否有效即可。例如订单列表页,如果用户token有效,说明用户已经登录了就可以成功跳转。如果用户token无效,说明用户没有登录,就跳转到登录页让用户先登录。
-
1.2-Token工作流程
-
1.token工作流程介绍
token工作流程分为三个步骤
第一步: 用户发送登录请求
第二步:服务器响应token,客户端将token存储在本地
第三步: 登录之后的所有请求,用户都需要在请求头中发送token
1.3-代码实现Token工作流程
Token核心业务主要由三个部分组成
(1)获取token : 在登录接口获取token
(2)存储token : 使用localStorage.setItem() 存储token
(3)使用token : 在发送ajax的请求头中发送token
-
1.登录请求获取token
-
2.使用localStorage存储token
-
3.使用token : 在ajax的请求头中发送token
1.4-退出登录功能
-
index.html页面
/*
(1)删除token
(2)跳转登录页
*/
document.querySelector('#logout').addEventListener('click',function(){
if ( confirm('确定要退出登录吗?') ) {
//(1) 销毁token
localStorage.removeItem('token')
//(2) 跳转到登录页
location.href = './login.html'
}
})
05-axios拦截器使用
1.1-拦截器工作流程
-
axios拦截器官网:axios中文文档|axios中文网 | axios
-
把官网axios拦截器代码放入 ./libs/request.js文件中 由于每一个html文件都导入了这个js, 所以我们的拦截器可以对任何页面都生效 // 添加请求拦截器 axios.interceptors.request.use(function (config) { // 在发送请求之前做些什么 return config; }, function (error) { // 对请求错误做些什么 return Promise.reject(error); }); // 添加响应拦截器 axios.interceptors.response.use(function (response) { // 对响应数据做点什么 return response; }, function (error) { // 对响应错误做点什么 return Promise.reject(error); });
1.2-请求拦截器
请求拦截器应用场景:可以给所有的axios请求设置请求头
(1)不使用拦截器: 我们需要手动给页面每一个axios都设置请求头
(2)使用拦截器: 只需要在拦截器中给axios设置请求头即可(因为所有的axios请求都会先进入请求拦截器)
axios.interceptors.request.use(
// config:请求报文信息
function (config) {
// 在发送请求之前做些什么
// 如果有token,就在请求头中添加token
if (localStorage.getItem('token')) {
config.headers.Authorization = localStorage.getItem('token')
}
return config
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error)
}
)
1.3-响应拦截器
响应拦截器应用:
(1)拦截401,如果遇到401错误就可以直接跳转登录页
(2)对请求错误统一处理:弹出提示框
axios.interceptors.response.use(
function (response) {
// 对响应数据做点什么
return response
},
function (error) {
// 对响应错误做点什么
// 如果是401错误,说明token没有 或 过期,就跳转登录页
if( error.response.status === 401 ){
alert('请先登录')
location.href = './login.html'
}else{//其他的错误,就弹出提示框
Toast.fail(error.response.data.message)
}
return Promise.reject(error)
}
)
06-ECharts数据可视化
数据可视化:
1.借助图形手段, 清晰传达信息的表现方式
2.把枯燥的数字数据, 转换成图形, 数据特点更突出
Echarts官网(第三方可视化图表库):Apache ECharts
echart.js下载地址:https://cdn.jsdelivr.net/npm/echarts@5.3.3/dist/echarts.min.js
==1.1-ECharts快速入门==
-
eCharts官方文档传送门:Apache ECharts
-
eCharts是前端开发中一个比较复杂的插件,但同时也是最容易学习的一个插件(它的官方文档写的非常的好)
-
==1.1.1-eCharts所有的图表制作固定为三个步骤==
==1.1.2.eCharts各种酷炫图表制作只需要两个步骤==
-
对比法
-
(1)通过删减代码观察图表变化 (得知某个选项的作用)
-
(2)对比其他图表,将其他图表代码复制粘贴(某个功能其他图表有,自己的图表没有)
-
(3)对比其他图表,其他图表没有的代码,我也删除(观察某个属性的意思)
-
-
1.点击首页顶部的案例页面,里面有各种酷炫的图表
-
2.修改配置项,根据需求定制你的图表
1.1.3-查询配置项
如果实在找不到想要的效果,可以查询echarts配置项手册,这里面有所有的配值
1.2-ECharts折线图制作
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 1.引入 ECharts 文件 -->
<script src="./echarts/echarts.min.js"></script>
</head>
<body>
<!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#main'));
option = {
// 1.标题
title: {
text: '2021全学科薪资走势',
top: 15,
left: 10,
textStyle: {
color: '#6d767e',
fontSize: 16
}
},
// 2.鼠标移入提示
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%']
}
},
// 3.x轴数据: 必须
xAxis: {
type: 'category', // 类别
data: [
"1月",
"2月",
"3月",
"4月",
"5月",
"6月",
]
},
// 4.y轴数据:必须
yAxis: {
type: 'value',
boundaryGap: [0, '50%'] // Y轴留白,会影响最大值,最小值
},
// 5.线条样式
series: [
{
type: 'line',
smooth: true, //设置折线圆角
symbolSize: 10, // 小圆圈大小
data: [820, 932, 901, 934, 1290, 1330],
//线条颜色
lineStyle: {
width: 6,
color: '#4281ef'
},
//内容区域颜色
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0, color: '#499FEE' // 0% 处的颜色
}, {
offset: 0.8, color: 'rgba(255, 255, 255, 0.2)' // 80% 处的颜色
}, {
offset: 1, color: 'rgba(255, 255, 255, 0)' // 100% 处的颜色
}
])
},
}
]
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
</body>
</html>
1.3-ECharts环形图制作
-
1.在官方文档找到符合需求条件的环形图
-
2.修改配置项,根据需求实现环形图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 1.引入 ECharts 文件 -->
<script src="./echarts/echarts.min.js"></script>
</head>
<body>
<!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#main'));
// 3.2 指定图表的配置项和数据
let option = {
title: {
text: '班级薪资分布',
top: 15,
left: 10,
textStyle: {
fontSize: 16
}
},
// 1.鼠标移入提示
tooltip: {
trigger: 'item'
},
// 2.顶部提示
legend: {
bottom: '6%',
left: 'center'
},
// 3.图形颜色
color: ['#FDA224', '#5097FF', '#3ABCFA', '#34D39A'],
// 4.内容
series: [
{
name: '班级新资分布', // 系列名称,用于tooltip的显示
type: 'pie', // 图形类型'pie'固定代表饼状图
radius: ['40%', '70%'], // 饼图的半径(内圈, 外圈) 表示外半径为可视区尺寸(容器高宽中较小一项)的 20%
avoidLabelOverlap: true, // 是否启用防止标签重叠策略,默认开启,在标签拥挤重叠的情况下会挪动各个标签的位置,防止标签间的重叠。
itemStyle: { // 图形样式
borderRadius: 10, // 表示内圆角半径和外圆角半径
borderColor: '#fff', // 图形的描边颜色
borderWidth: 2 // 描边线宽(为0无描边)
},
label: { // 饼图图形上的文本标签(默认上来显示的, 鼠标移入是另外配置)
show: false, // 隐藏
position: 'center' // 让他们出现在中心
},
emphasis: { // 高亮状态的扇区和标签样式
label: { // 文本标签控制
show: true, // 显示
fontSize: '40', // 字体大小
fontWeight: 'bold' // 字体粗度
}
},
labelLine: { // 标签的视觉引导线配置(就是解释每块图形的文本标签连线)
show: false
},
data: [ // 系列数据
{ value: 1048, name: '1万以下' },
{ value: 735, name: '1-1.5万' },
{ value: 580, name: '1.5万-2万' },
{ value: 484, name: '2万以上' },
]
}
]
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 1.引入 ECharts 文件 -->
<script src="./echarts/echarts.min.js"></script>
</head>
<body>
<!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#main'));
// 3.2 指定图表的配置项和数据
let option = {
// 1.标题
title: [
{
text: '男女薪资分布',
left: 10,
top: 10,
textStyle: {
fontSize: 16,
},
},
{
text: '男生',
left: '50%',
top: '50%',
textAlign: 'center',
textStyle: {
fontSize: 12,
},
},
{
text: '女生',
left: '50%',
top: '90%',
textAlign: 'center',
textStyle: {
fontSize: 12,
},
},
],
// 2.鼠标移入提示
tooltip: {
trigger: 'item'
},
// 3.颜色
color: ['#FDA224', '#5097FF', '#3ABCFA', '#34D39A'],
// 4.内容
series: [
{
type: 'pie',
radius: 50,
radius: ['20%', '30%'],
center: ['50%', '30%'],
datasetIndex: 1,
data: [
{ name: '1万以下', value: 4 },
{ name: '1万-1.5万', value: 3 },
{ name: '1.5万-2万以下', value: 2 },
{ name: '2万以上', value: 3 },
]
},
{
type: 'pie',
radius: 50,
radius: ['20%', '30%'],
center: ['50%', '75%'],
datasetIndex: 2,
data: [{ name: '1万以下', value: 4 },
{ name: '1万-1.5万', value: 3 },
{ name: '1.5万-2万以下', value: 2 },
{ name: '2万以上', value: 3 },]
}
]
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
</script>
</body>
</html>
1.4-ECharts柱状图制作
-
1.在官方文档找到符合需求条件的柱状图
-
2.修改配置项,根据需求实现柱状图
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 1.引入 ECharts 文件 -->
<script src="./echarts/echarts.min.js"></script>
</head>
<body>
<!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 1000px;height:400px;"></div>
<script type="text/javascript">
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#main'));
// 3.2 指定图表的配置项和数据
let option = {
// 0.标题
title: {
text: '班级每组工资',
top: 10,
left: 50,
},
// 1.网格(类似于padding)
grid: {
top: 60, bottom: 30, left: '7%', right: '7%'
},
// 2.鼠标移入提示
tooltip: {
trigger: 'item',
},
// 3.x轴
xAxis: [
{
type: 'category',
data: ['张三', '李四', '王五', '小明', '小红', '小赵'],
axisLine: {
lineStyle: {
color: '#ccc',
type: 'dashed'
}
},
axisLabel: {
color: '#999'
}
}
],
// 4.y轴
yAxis: [
{
type: 'value',
splitLine: {
lineStyle: {
type: 'dashed'
}
}
}
],
// 5.颜色
color: [new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0, color: '#34D39A' // 0% 处的颜色
}, {
offset: 1, color: 'rgba(52,211,154,0.2)' // 100% 处的颜色
}
]), new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0, color: '#499FEE' // 0% 处的颜色
}, {
offset: 1, color: 'rgba(73,159,238,0.2)' // 100% 处的颜色
}
])],
// 6.内容
series: [
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
name: '期望薪资'
},
{
data: [120, 200, 150, 80, 70, 110, 130],
type: 'bar',
name: '就业薪资'
}
]
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
</script>
</body>
</html>
1.5-ECharts地图制作
-
1.官方案例没有这样的地图, 可以去社区的看下
-
直接复制下面代码即可,不要从文档里面复制
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<!-- 1.引入 ECharts 文件 -->
<script src="./echarts/echarts.min.js"></script>
<!-- 中国地图 -->
<script src="./echarts/china.js"></script>
</head>
<body>
<!-- 2.为ECharts准备一个具备大小(宽高)的Dom -->
<div id="main" style="width: 600px;height:400px;"></div>
<script type="text/javascript">
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#main'));
// 3.2 指定图表的配置项和数据
const dataList = [
{
"name": "上海",
"value": 3
},
{
"name": "陕西省",
"value": 3
},
{
"name": "江苏省",
"value": 2
},
{
"name": "内蒙古自治区",
"value": 3
},
{
"name": "北京",
"value": 11
},
{
"name": "贵州省",
"value": 4
},
{
"name": "西藏自治区",
"value": 3
},
{
"name": "广东省",
"value": 4
},
{
"name": "天津",
"value": 3
},
{
"name": "黑龙江省",
"value": 1
},
{
"name": "云南省",
"value": 3
},
{
"name": "福建省",
"value": 3
},
{
"name": "四川省",
"value": 2
},
{
"name": "新疆维吾尔自治区",
"value": 3
},
{
"name": "甘肃省",
"value": 3
},
{
"name": "辽宁省",
"value": 5
},
{
"name": "安徽省",
"value": 3
},
{
"name": "吉林省",
"value": 2
},
{
"name": "浙江省",
"value": 1
},
{
"name": "山西省",
"value": 2
},
{
"name": "青海省",
"value": 1
},
{
"name": "广西壮族自治区",
"value": 3
},
{
"name": "宁夏回族自治区",
"value": 2
},
{
"name": "湖北省",
"value": 4
},
{
"name": "河北省",
"value": 1
},
{
"name": "湖南省",
"value": 3
},
{
"name": "江西省",
"value": 1
},
{
"name": "河南省",
"value": 1
}
]
// 数据设置
dataList.forEach((item) => {
// 数据里名字和上面的名字有点不太一样, 需要把多余的文字去掉(替换成空字符串)
item.name = item.name.replace(/省|回族自治区|吾尔自治区|壮族自治区|特别行政区|自治区/g, '')
})
let option = {
title: {
text: '籍贯分布',
top: 10,
left: 10,
textStyle: {
fontSize: 16,
},
},
tooltip: {
trigger: 'item',
formatter: '{b}: {c} 位学员',
borderColor: 'transparent',
backgroundColor: 'rgba(0,0,0,0.5)',
textStyle: {
color: '#fff',
},
},
visualMap: {
min: 0,
max: 6,
left: 'left',
bottom: '20',
text: ['6', '0'],
inRange: {
color: ['#ffffff', '#0075F0'],
},
show: true,
left: 40,
},
geo: { // 地理坐标系组件
map: 'china',
roam: false,
zoom: 1.0,
label: {
normal: {
show: true,
fontSize: '10',
color: 'rgba(0,0,0,0.7)',
},
},
itemStyle: {
normal: {
borderColor: 'rgba(0, 0, 0, 0.2)',
color: '#e0ffff',
},
emphasis: {
areaColor: '#34D39A',
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowBlur: 20,
borderWidth: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
series: [
{
name: '籍贯分布',
type: 'map',
geoIndex: 0,
data: dataList,
},
],
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
</script>
</body>
</html>
07-CMS项目图表制作思路
图表页面思路分析: 从左往右,从上往下依次加载图表数据
1.班级概览
2.折线图:学科薪资走势
3.饼图:班级薪资走势
4.柱状图:班级每组薪资,点击切换不同小组薪资
5.男女薪资
6.籍贯分布
// 1.页面一加载,ajax请求图表数据
window.addEventListener('load',async function(){
const {data:res} = await axios({
url:'/dashboard',
method:'get',
headers: {
Authorization: localStorage.getItem('token')
},
})
console.log(res)
//渲染图表
setOverview(res.data.overview)
setSubjectSalary(res.data.year)
setClassSalary(res.data.salaryData)
setGroupSalary(res.data.groupData)
setGenderSalary(res.data.salaryData)
setProvinc(res.data.provinceData)
})
// 2.将服务器返回的数据渲染到图表
// 2.1 数据概览
const setOverview = data=>{
}
// 2.2 学科薪资
const setSubjectSalary = data=>{
}
// 2.3 班级薪资
const setClassSalary = data=>{
}
// 2.4 班级每组薪资
const setGroupSalary = data=>{
// 2.5 点击小组切换按钮:事件委托
// 事件写在这个函数里面好处: (1)肯定要先加载薪资,然后才能点 (2)与图表在同一个作用域方便操作
document.querySelector('#btns').addEventListener('click',function(e){
console.log(e.target.innerText);
})
}
// 2.6 男女薪资分布
const setGenderSalary = data=>{
}
// 2.7 籍贯分布
const setProvinc = data=>{
}
1.1-班级概览
/* 1.班级概览 */
axios({
url: '/student/overview',
method: 'get'
}).then(res => {
console.log(res)
let data = res.data.data
// 班级人数,期望薪资,评价年龄,男女比例;
document.querySelector(".total").innerHTML = data.total
document.querySelector(".avgSalary").innerHTML = data.avgSalary
document.querySelector(".avgAge").innerHTML = data.avgAge
document.querySelector(".proportion").innerHTML = data.proportion
})
1.2-学科薪资分布
// 2.2 学科薪资
const setSubjectSalary = data => {
//设置x轴和y轴数据
let xData = data.map(obj => obj.month) // ['1月', '2月', ...]
let yData = data.map(obj => obj.salary) // [19300, 22300, ...]
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#line'));
option = {
// 1.标题
title: {
text: '2021全学科薪资走势',
top: 15,
left: 10,
textStyle: {
color: '#6d767e',
fontSize: 16
}
},
// 2.鼠标移入提示
tooltip: {
trigger: 'axis',
position: function (pt) {
return [pt[0], '10%']
}
},
// 3.x轴数据: 必须
xAxis: {
type: 'category', // 类别
data: xData
},
// 4.y轴数据:必须
yAxis: {
type: 'value',
boundaryGap: [0, '50%'] // Y轴留白,会影响最大值,最小值
},
// 5.线条样式
series: [
{
type: 'line',
smooth: true, //设置折线圆角
symbolSize: 10, // 小圆圈大小
data: yData,
//线条颜色
lineStyle: {
width: 6,
color: '#4281ef'
},
//内容区域颜色
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0, color: '#499FEE' // 0% 处的颜色
}, {
offset: 0.8, color: 'rgba(255, 255, 255, 0.2)' // 80% 处的颜色
}, {
offset: 1, color: 'rgba(255, 255, 255, 0)' // 100% 处的颜色
}
])
},
}
]
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
1.3-班级薪资分布
-
核心代码:把薪资数组映射成图表对应的数据
-
data = data.map(obj => { return { value: obj.g_count + obj.b_count, name: obj.label } }) // 2.3 班级薪资 const setClassSalary = data => { //把data转换为图表的数据格式 data = data.map(obj => { return { value: obj.g_count + obj.b_count, name: obj.label } }) // 3.1 基于准备好的dom,初始化echarts实例 let myChart = echarts.init(document.querySelector('#salary')); // 3.2 指定图表的配置项和数据 let option = { title: { text: '班级薪资分布', top: 15, left: 10, textStyle: { fontSize: 16 } }, // 1.鼠标移入提示 tooltip: { trigger: 'item' }, // 2.顶部提示 legend: { bottom: '6%', left: 'center' }, // 3.图形颜色 color: ['#FDA224', '#5097FF', '#3ABCFA', '#34D39A'], // 4.内容 series: [ { name: '班级新资分布', // 系列名称,用于tooltip的显示 type: 'pie', // 图形类型'pie'固定代表饼状图 radius: ['40%', '70%'], // 饼图的半径(内圈, 外圈) 表示外半径为可视区尺寸(容器高宽中较小一项)的 20% avoidLabelOverlap: true, // 是否启用防止标签重叠策略,默认开启,在标签拥挤重叠的情况下会挪动各个标签的位置,防止标签间的重叠。 itemStyle: { // 图形样式 borderRadius: 10, // 表示内圆角半径和外圆角半径 borderColor: '#fff', // 图形的描边颜色 borderWidth: 2 // 描边线宽(为0无描边) }, label: { // 饼图图形上的文本标签(默认上来显示的, 鼠标移入是另外配置) show: false, // 隐藏 position: 'center' // 让他们出现在中心 }, emphasis: { // 高亮状态的扇区和标签样式 label: { // 文本标签控制 show: true, // 显示 fontSize: '40', // 字体大小 fontWeight: 'bold' // 字体粗度 } }, labelLine: { // 标签的视觉引导线配置(就是解释每块图形的文本标签连线) show: false }, data: data } ] } // 3.3使用刚指定的配置项和数据显示图表。 myChart.setOption(option) }
1.4-班级每组薪资
// 2.4 班级每组薪资
const setGroupSalary = data => {
//默认显示第一组数据: 注意data是个对象, 后台返回此对象key从1开始(不是数组下标)
const hope_salary = data[1].map(obj => obj.hope_salary)
const salary = data[1].map(obj => obj.salary)
const names = data[1].map(obj => obj.name)
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#lines'));
// 3.2 指定图表的配置项和数据
let option = {
// 0.标题
title: {
text: '班级每组工资',
top: 10,
left: 50,
},
// 1.网格(类似于padding)
grid: {
top: 60, bottom: 30, left: '7%', right: '7%'
},
// 2.鼠标移入提示
tooltip: {
trigger: 'item',
},
// 3.x轴
xAxis: [
{
type: 'category',
data: names,
axisLine: {
lineStyle: {
color: '#ccc',
type: 'dashed'
}
},
axisLabel: {
color: '#999'
}
}
],
// 4.y轴
yAxis: [
{
type: 'value',
splitLine: {
lineStyle: {
type: 'dashed'
}
}
}
],
// 5.颜色
color: [new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0, color: '#34D39A' // 0% 处的颜色
}, {
offset: 1, color: 'rgba(52,211,154,0.2)' // 100% 处的颜色
}
]), new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0, color: '#499FEE' // 0% 处的颜色
}, {
offset: 1, color: 'rgba(73,159,238,0.2)' // 100% 处的颜色
}
])],
// 6.内容
series: [
{
data: hope_salary,
type: 'bar',
name: '期望薪资'
},
{
data: salary,
type: 'bar',
name: '就业薪资'
}
]
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
}
1.5-点击小组切换薪资
// 2.5 点击小组切换按钮:事件委托
document.querySelector('#btns').addEventListener('click', function (e) {
//(1)判断点击的是不是按钮
if (e.target.classList.contains('btn')) {
console.log(e.target.innerText);
// (2)排他修改样式
document.querySelector('.btn-blue').classList.remove('btn-blue')
e.target.classList.add('btn-blue')
// (3)获取点击的group
const group = e.target.innerText
// (4)切换数据
option.xAxis.data = data[group].map((item) => item.name);
option.series[0].data = data[group].map((item) => item.hope_salary);
option.series[1].data = data[group].map((item) => item.salary);
myChart.setOption(option)
}
})
1.6-男女薪资分布
// 2.6 男女薪资分布
const setGenderSalary = data => {
// 获取男 女 数据
const bData = data.map((item) => ({ name: item.label, value: item.b_count }))
const gData = data.map((item) => ({ name: item.label, value: item.g_count }))
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#gender'));
// 3.2 指定图表的配置项和数据
let option = {
// 1.标题
title: [
{
text: '男女薪资分布',
left: 10,
top: 10,
textStyle: {
fontSize: 16,
},
},
{
text: '男生',
left: '50%',
top: '50%',
textAlign: 'center',
textStyle: {
fontSize: 12,
},
},
{
text: '女生',
left: '50%',
top: '90%',
textAlign: 'center',
textStyle: {
fontSize: 12,
},
},
],
// 2.鼠标移入提示
tooltip: {
trigger: 'item'
},
// 3.颜色
color: ['#FDA224', '#5097FF', '#3ABCFA', '#34D39A'],
// 4.内容
series: [
{
type: 'pie',
radius: 50,
radius: ['20%', '30%'],
center: ['50%', '30%'],
datasetIndex: 1,
data: bData
},
{
type: 'pie',
radius: 50,
radius: ['20%', '30%'],
center: ['50%', '75%'],
datasetIndex: 2,
data: gData
}
]
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option)
}
1.7-籍贯分布
// 2.7 籍贯分布
const setProvinc = data => {
// 3.1 基于准备好的dom,初始化echarts实例
let myChart = echarts.init(document.querySelector('#map'));
// 3.2 指定图表的配置项和数据
const dataList = data
// 数据设置
dataList.forEach((item) => {
// 数据里名字和上面的名字有点不太一样, 需要把多余的文字去掉(替换成空字符串)
item.name = item.name.replace(/省|回族自治区|吾尔自治区|壮族自治区|特别行政区|自治区/g, '')
})
let option = {
title: {
text: '籍贯分布',
top: 10,
left: 10,
textStyle: {
fontSize: 16,
},
},
tooltip: {
trigger: 'item',
formatter: '{b}: {c} 位学员',
borderColor: 'transparent',
backgroundColor: 'rgba(0,0,0,0.5)',
textStyle: {
color: '#fff',
},
},
visualMap: {
min: 0,
max: 6,
left: 'left',
bottom: '20',
text: ['6', '0'],
inRange: {
color: ['#ffffff', '#0075F0'],
},
show: true,
left: 40,
},
geo: { // 地理坐标系组件
map: 'china',
roam: false,
zoom: 1.0,
label: {
normal: {
show: true,
fontSize: '10',
color: 'rgba(0,0,0,0.7)',
},
},
itemStyle: {
normal: {
borderColor: 'rgba(0, 0, 0, 0.2)',
color: '#e0ffff',
},
emphasis: {
areaColor: '#34D39A',
shadowOffsetX: 0,
shadowOffsetY: 0,
shadowBlur: 20,
borderWidth: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)',
},
},
},
series: [
{
name: '籍贯分布',
type: 'map',
geoIndex: 0,
data: dataList,
},
],
}
// 3.3使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
}
08-学员信息管理
1.经典的增删改查业务
2.省市县三级联动
1.查询业务
(1)发送ajax获取数据
(2)渲染数据
2.删除业务
(1)渲染删除按钮的时候添加data-id
(2)注册委托事件: 给父元素注册事件,判断是不是点击删除按钮
(3)获取按钮上的id
(4)发送删除ajax
(5)刷新列表
3.添加学员
(1)弹出模态框
(2)省市县三级联动
4.确认新增学员
(1)收集表单数据
(2)发送ajax请求
(3)刷新列表 并 清空表单
5.编辑学员
(1)获取按钮上的di
(2)发送ajax获取学员详细信息
(3)弹出模态框
(4)把数据渲染到模态框的表单
6.确认编辑学员
(1)收集表单数据
(2)发送ajax请求
(3)刷新列表 并 清空表单
1.1-查询学员信息
-
给编辑按钮和删除按钮添加data-id, 后面点击的时候就知道是哪一个学员了
/*
1.查询业务
(1)发送ajax获取数据
(2)渲染数据
*/
// 1.查询学员信息
async function getStudentList() {
// (1)ajax获取学员数据
const {data:res} = await axios.get('/students')
console.log(res)
// (2)渲染响应数据
document.querySelector('.list').innerHTML = res.data.map(item =>`
<tr>
<td>${item.name}</td>
<td>${item.age}</td>
<td>${item.gender === 0 ? '男' : '女'}</td>
<td>第${item.group}组</td>
<td>${item.hope_salary}</td>
<td>${item.salary}</td>
<td>${item.province}${item.city}${item.area}</td>
<td>
<a href="javascript:;" class="text-success mr-3"><i class="bi bi-pen" data-id="${item.id}"></i></a>
<a href="javascript:;" class="text-danger"><i class="bi bi-trash" data-id="${item.id}"></i></a>
</td>
</tr>
`).join('')
}
//调用函数
getStudentList()
1.2-删除学员信息
-
先给所有删除按钮绑定点击事件
-
因为要区分点击的是哪个, 所以先给每个按钮绑定数据的id
<a href="javascript:;" class="text-danger" ><i class="bi bi-trash" data-id="${item.id}"></i></a>
-
事件委托,:给父元素绑定点击事件, 点击获取到id属性的值
// 2. 删除学生功能
// 事件委托:给父元素注册事件, 判断是不是委托的子元素
document.querySelector('.list').addEventListener('click', e => {
// 判断是不是删除按钮
if (e.target.classList.contains('bi-trash')) {
let id = e.target.dataset.id
console.log(id)
}
})
调用删除的接口后, 再调用请求列表方法刷新即可
// 2. 删除学生功能
// 事件委托:给父元素注册事件, 判断是不是委托的子元素
document.querySelector('.list').addEventListener('click', async function(e) {
// (1)判断是不是删除按钮
if (e.target.classList.contains('bi-trash')) {
// (2)获取id
let id = e.target.dataset.id
console.log(id)
// (3)发送删除ajax (注意接口文档是路径参数)
const {data:res} = await axios.delete(`/students/${id}`)
console.log(res)
Toast.info('删除成功')//提示框
// (4)重新刷新列表
getStudentList()
}
})
1.3-添加学员模态框
本案例重点是:
(1)添加学员业务逻辑 : 收集表单数据 + 发送数据
(2)省市县三级联动
本案例非重点: bootstrap模态框,实际开发不用。不需要过多了解
-
先找到相关的标签, 使用bootstrap5自带的弹窗和js功能, bootstrap模态框文档
// 3.添加学员
document.querySelector('#openModal').addEventListener('click', function(e) {
// 1.模态框弹窗
// (1)弹出模态框
new bootstrap.Modal(document.querySelector('#modal')).show()
// (2)清空表单
document.querySelector('form').reset()
// (3)设置标题
document.querySelector('.modal-title').innerHTML = '添加学员'
})
1.4-省市县三级联动(难点)
// 3.添加学员
document.querySelector('#openModal').addEventListener('click', function (e) {
// 1.模态框弹窗
// (1)弹出模态框
new bootstrap.Modal(document.querySelector('#modal')).show()
// (2)清空表单
document.querySelector('form').reset()
// (3)设置标题
document.querySelector('.modal-title').innerHTML = '添加学员'
// (4)加载省数据
getProvince()
})
/* 省市县三级联动 : 省市县必须要依次选择
(1)加载省条件 : 点击添加学员,出现弹窗后。默认加载省
(2)加载市条件 : 用户选择了某一个省
(3)加载县条件 : 用户选择了某一个市
*/
const getProvince = async () => {
// 获取省市县select选择框(简化后面代码)
const ps = document.querySelector('[name=province]')
const cs = document.querySelector('[name=city]')
const as = document.querySelector('[name=area]')
// (1)默认加载省
// 获取 省数据
const { data: res } = await axios.get('/api/province')
// 渲染 省数据
ps.innerHTML = '<option value="">--省份--</option>' + res.data.map((item) => `<option value="${item}">${item}</option>`).join('')
// 省发生变化,就把市 和 县默认清空
cs.value = ''
as.value = ''
//(2)加载市: 用户选择了某一个省
ps.addEventListener('change', async () => {
// 获取 市数据
const { data: res } = await axios.get('/api/city',{params: { pname: ps.value }})
// 渲染 市数据
cs.innerHTML = `<option value="">--城市--</option>` + res.data.map((item) => `<option value="${item}">${item}</option>`).join('')
// 市发生变化 就把 县默认情况
as.value = ''
})
//(3)加载县: 用户选择了某一个市
cs.addEventListener('change', async () => {
// 获取 县数据
const { data: res } = await axios.get('/api/area',{params: { pname: ps.value,cname: cs.value }})
// 渲染 县数据
as.innerHTML = `<option value="">--地区--</option>` + res.data.map((item) => `<option value="${item}">${item}</option>`).join('')
})
}
1.5-点击确认新增学员(难点)
-
技术点: formdata一键获取表单所有参数
-
FormData有两种使用方式 第一种: 一般用于文件上传 (1)先创建一个空的FormData: let fd = new FormData() (2)使用append()添加参数: fd.append('参数名',参数值) 第二种: 一键获取form表单所有参数 let fd = new FormData( form元素 ) (1)参数必须是form标签,其他标签会报错。 (2)fd底层自动帮我们遍历form里面的每一个表单, 将name属性作为参数名,value作为参数值
-
-
疑惑点解答
: 为什么formdata一键获取了表单所有的参数,还需要通过forEach遍历,将formdata里面的参数全部遍历到 对象中呢?-
原因解答
:formdata会自动修改请求头为文件数据,而这个接口根本就没有文件。因此不能直接把formdata丢到data中发送-
如果这个接口有文件数据,就不需要遍历啦。直接把formdata传给服务器即可
-
-
/* 4.确认添加 : 发送ajax请求 */
document.querySelector('#submit').addEventListener('click', async function (e) {
//0.表单按钮需要阻止默认跳转
e.preventDefault()
//(1)获取表单参数
/* FormData有两种使用方式
第一种: 一般用于文件上传
(1)先创建一个空的FormData: let fd = new FormData()
(2)使用append()添加参数: fd.append('参数名',参数值)
第二种: 一键获取form表单所有参数
let fd = new FormData( form元素 )
(1)参数必须是form标签,其他标签会报错。
(2)fd底层自动帮我们遍历form里面的每一个表单, 将name属性作为参数名,value作为参数值
*/
let fd = new FormData(document.querySelector('#form'))
let data = {}
fd.forEach(function (value, key) {
data[key] = value
})
console.log(data)
/* 接口问题:有几个数据必须要转成数字,一般实际开发中前端传字符串后台接口自己处理 */
data.age = +data.age
data.hope_salary = +data.hope_salary
data.salary = +data.salary
data.gender = +data.gender
data.group = +data.group
//(2)ajax发送数据
const {data:res} = await axios.post('/students',data)
// 提示框
Toast.success('添加成功')
// 刷新列表
getStudentList()
// 清空表单
document.querySelector("#form").reset()
// 隐藏模态框(触发关闭按钮点击事件)
document.querySelector("#modal .btn-close").click()
})
1.7-修改学员信息弹窗(难点)
-
在事件委托中,判断用户点击的是不是编辑按钮,如果是编辑按钮就请求学员详细信息并渲染到表单
// 2. 删除学生功能
// 事件委托:给父元素注册事件, 判断是不是委托的子元素
document.querySelector('.list').addEventListener('click', async function (e) {
// (1)判断是不是删除按钮
if (e.target.classList.contains('bi-trash')) {
// (2)获取id
let id = e.target.dataset.id
console.log(id)
// (3)发送删除ajax (注意接口文档是路径参数)
const { data: res } = await axios.delete(`/students/${id}`)
console.log(res)
Toast.info('删除成功')//提示框
// (4)重新刷新列表
getStudentList()
}
// (1)判断是不是编辑按钮
if (e.target.classList.contains('bi-pen')) {
//(2)获取id
let id = e.target.dataset.id
//(3)发送ajax获取学员详细信息: 路径参数
const { data: res } = await axios.get(`/students/${id}`)
console.log(res)
//(4)将服务器返回的数据渲染到 模态框表单
// 弹出模态框
new bootstrap.Modal(document.querySelector('#modal')).show()
// 设置标题
document.querySelector('.modal-title').innerHTML = '修改学员'
// 设置表单数据
document.querySelector('[name="name"]').value = res.data.name
document.querySelector('[name="age"]').value = res.data.age
document.querySelector('[name="group"]').value = res.data.group
document.querySelector('[name="hope_salary"]').value = res.data.hope_salary
document.querySelector('[name="salary"]').value = res.data.salary
if (res.data.gender) {
document.querySelectorAll('[name="gender"]')[1].checked = true
} else {
document.querySelectorAll('[name="gender"]')[0].checked = true
}
const ps = document.querySelector('[name=province]')
const cs = document.querySelector('[name=city]')
const as = document.querySelector('[name=area]')
// 获取 省数据
const { data: res1 } = await axios.get('/api/province')
// 渲染 省数据
ps.innerHTML = '<option value="">--省份--</option>' + res1.data.map((item) => `<option value="${item}">${item}</option>`).join('')
// 设置省
ps.value = res.data.province
// 获取 市数据
const { data: res2 } = await axios.get('/api/city', { params: { pname: ps.value } })
// 渲染 市数据
cs.innerHTML = `<option value="">--城市--</option>` + res2.data.map((item) => `<option value="${item}">${item}</option>`).join('')
// 设置市
cs.value = res.data.city
// 获取 县数据
const { data: res3 } = await axios.get('/api/area', { params: { pname: ps.value, cname: cs.value } })
// 渲染 县数据
as.innerHTML = `<option value="">--地区--</option>` + res3.data.map((item) => `<option value="${item}">${item}</option>`).join('')
// 设置县
as.value = res.data.area
}
})
1.6-点击确认修改学员(难点)
-
编辑和新增用同一套表单, 可以先定义外部变量editId来保存正在编辑的id值, 也可以用它来判断是否处于编辑状态
-
新增按钮把此值置空
-
编辑按钮把正在编辑的id赋值给它
-
然后点击确定的时候可以用这个值做判断
-
-
1.声明全局变量存储编辑id
-
2.点击编辑的时候,存储编辑id
-
3.点击新增的时候,清空编辑id
-
4.点击确认的时候,通过editId判断是编辑学员还是新增学员
if (editId) {//编辑
const { data: res } = await axios.put(`/students/${editId}`, data)
console.log(res)
// 提示框
Toast.success('修改成功')
} else {
const { data: res } = await axios.post('/students', data)
console.log(res)
// 提示框
Toast.success('添加成功')
}