内容管理系统(content management system,CMS)
应对现在数据可视化的趋势,越来越多企业需要在很多场景(营销数据,生产数据,用户数据)下使用,可视化图表来展示体现数据,让数据更加直观,数据特点更加突出。
项目以黑马班级管理为背景,功能包括学生信息录入,每次成绩录入;并制作可视化看板。
我们以班主任老师的角色注册账号,并登录系统。
为了方便开发,登录后点击页面顶部的“点我初始化数据”按钮,即可为该账号随机增加56名同学(8个小组,每组7人),并为每位学生模拟了3次考试成绩。
后续,可以在学员管理中,增删改学员信息,也可以录入或修改成绩。
bootstrap axios jquery jwt echart toastr localstorage NProgress h5 es6
登录 注册 退出登录 学员信息概览 学员信息crud 省市级联 成绩信息crud .
新建一个 js文件 ./assets/js/common.js
设置 axios 的基地址
设置了拦截器 显示 加载中
在 login.html 来引入 NProgress 中文件
// 设置基地址
axios.defaults.baseURL = 'http://www.itcbc.com:8000';
// 拦截器 设置 显示加载中!!
// 添加请求拦截器
axios.interceptors.request.use(
function (config) {
// 显示加载中
NProgress.start();
// 在发送请求之前做些什么
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
NProgress.done();
// 对响应数据做点什么
return response;
},
function (error) {
NProgress.done();
// 对响应错误做点什么
return Promise.reject(error);
}
);
新建 assets\js\login.js
在里面来发送post请求测试以上代
axios.post('/api/login', {
username: 'laowan123',
password: 'laowan123',
});
在拦截器中 错误的提示信息处理
// 添加响应拦截器
axios.interceptors.response.use(
function (response) {
console.log(1111);
// HTTP状态码 正常 !! 触发这个函数
NProgress.done();
// 对响应数据做点什么
return response;
},
function (error) {
// HTTP状态码 不正常 !! 触发这个函数
console.log("==============");
// console.dir(error.response.data.message);
toastr.error(error.response.data.message);
console.log("==============");
NProgress.done();
// 对响应错误做点什么
return Promise.reject(error);
}
);
由于存在很多个接口都需要携带token,当没有携带token而去访问接口数据的时候,后端会返回401状态码
,此时我们可以在拦截器中统一处理
axios.interceptors.response.use(
function (response) {
console.log(1111);
// HTTP状态码 正常 !! 触发这个函数
NProgress.done();
// 对响应数据做点什么
return response;
},
function (error) {
// HTTP状态码 不正常 !! 触发这个函数
console.log("==============");
console.dir(error);
toastr.error(error.response.data.message);
// 判断一下当前的http状态码 是不是 401 如果是 跳转回登录页面!
if (error.response.status === 401) {
// 未登录
window.top.location.href="./login.html"
}
console.log("==============");
NProgress.done();
// 对响应错误做点什么
return Promise.reject(error);
}
);
拦截器中 自动的携带token到请求头那里
axios.interceptors.request.use(
function (config) {
// 在发送请求之前做些什么
// 显示加载中
NProgress.start();
// 项目中 有一些接口不需要用到token 登录和注册
if (localStorage.getItem('token')) {
// 在请求头中 添加一个token
config.headers.Authorization = localStorage.getItem('token');
}
return config;
},
function (error) {
// 对请求错误做些什么
return Promise.reject(error);
}
);
以后请求私有数据不需要手动携带token
axios
.get('/init/data')
axios
.get('/student/overview', {
})
点击 登录 和 注册 进行表单的切换显示
给 登录 和 注册 里面的 a标签 添加一个类名
获取 登录 和 注册 的盒子 以及它里面里面的a标签
分布绑定点击事件来实现 登录和注册的盒子切换显示
//#region 获取dom元素
const registerBox = document.querySelector('.register'); // 注册 盒子
const loginBox = document.querySelector('.login'); // 登录 盒子
const registerA = document.querySelector('.register-a'); // 注册 a 标签
const loginA = document.querySelector('.login-a'); // 登录 a 标签
//#endregion
//#region 给登录和注册的a标签都绑定点击事件
registerA.addEventListener('click', function () {
// 隐藏注册 显示登录
registerBox.style.display = 'none';
loginBox.style.display = 'block';
});
loginA.addEventListener('click', function () {
// 隐藏登录 显示注册
loginBox.style.display = 'none';
registerBox.style.display = 'block';
});
//#endregion
获取表单数据 发送axios请求完成登录
form
标签添加 属性 autocomplete="off"
判断后端返回业务码 是否等于 0 0 表示正常!!
把登录后的 token
存在 本地存储中
显示 登录成功 提示框 toastr
。 在 common.js
中来设置 弹出层的样式
toastr.options = {
"positionClass": "toast-center-center",
}
// toastr.success("Are you the six fingered man?")
// toastr.info("Are you the six fingered man?")
// toastr.warning("Are you the six fingered man?")
toastr.error("Are you the six fingered man?")
一会后(1.5s) 跳转到 首页
建议 路径 location.href = "./index.html"
在js中写要跳转的页面,参照物是当前的html 不是js文件!
打开页面的方式 选择 open with live server
<link rel="stylesheet" href="./assets/lib/NProgress/NProgress.css">
<script src="./assets/lib/NProgress/NProgress.js"></script>
<script src="./assets/js/common.js"></script>
<script src="./assets/js/index.js"></script>
token
该功能在正常的项目中是不存在,
我们实现它 只是为了让项目 和线上运行的一致 锻炼代码能力!
打开页面的时候 默认就显示一个菜单
/* 选择了标签ul 身上必须有 show class */
.nav ul.show{
display: block;
}
<ul class="show">
<li><a class="active" href="./dashboard.html" target="fm">图表概览</a></li>
</ul>
点击 一级标题 控制内容 切换显示
const topAList = document.querySelectorAll('.nav>li>a '); // 一级菜单
topAList.forEach((aDom) => {
aDom.addEventListener('click', function () {
this.nextElementSibling.classList.toggle('show');
});
});
点击 二级标题 自身选中 右侧内容切换显示
获取标签来绑定点击事件
const subAList = document.querySelectorAll('.nav ul a '); // 二级菜单
先找到以前添加过选中类的 a标签, 移除掉选中效果
给当前被点击的a标签 添加 选中 效果
subAList.forEach((aDom) =>
aDom.addEventListener('click', function () {
// 先找到以前添加过选中类的 a标签, 移除掉选中效果
document.querySelector('.nav ul a.active').classList.remove("active");
// 给当前被点击的a标签 添加 选中 效果
this.classList.add("active");
})
);
4.点击左侧 超链接,右侧自动切换页面 标签 iframe标签和a标签
登录限制处理
注意要引入哪些资源
<!-- 引入进度条的样式 -->
<link rel="stylesheet" href="./assets/lib/NProgress/NProgress.css">
<!-- 引入进度条 -->
<script src="./assets/lib/NProgress/NProgress.js"></script>
<!-- 引入公共的 js -->
<script src="./assets/js/common.js"></script>
<!-- 当前页面自己的js -->
<script src="./assets/js/dashboard.js"></script>
身份认证失败
让页面跳转回登录页面。同学姓名
、期望薪资
、实际薪资
给菜单绑定点击事件 切换显示 成绩菜单
toggleBatchBtn.addEventListener('click', function () {
// show 负责 显示标签 display:block;
batch.classList.toggle("show");
})
点击 成绩菜单 获取到它对应的成绩的次数
修改了样式
/* 该死前辈 留坑!!! 页面暂时只有一个ul!! */
ul {
list-style: none;
font-size : 12px;
position : absolute;
right : 20px;
top : -5px;
background-color: #f1f1f1;
/* width : 90px; */
border-radius: 3px;
z-index : 1;
display: none;
justify-content: space-evenly;
height: 40px;
width: 343px;
}
.show{
display: flex;
}
.bar ul li {
box-shadow: #f1f1f1 1px 1px 4px 0px;
flex:1;
line-height: 40px;
text-align: center;
}
.bar ul li a {
text-decoration: none;
color: #6d767e;
display: block;
height: 100%;
}
修改了html
<ul id="batch">
<li><a data-index="1" href="javascript:;">第1次成绩</a></li>
<li><a data-index="2" href="javascript:;">第2次成绩</a></li>
<li><a data-index="3" href="javascript:;">第3次成绩</a></li>
<li><a data-index="4" href="javascript:;">第4次成绩</a></li>
<li><a data-index="5" href="javascript:;">第5次成绩</a></li>
</ul>
添加了点击事件
batch.addEventListener("click",function (event) {
const index = event.target.dataset.index;
console.log(index);
})
把次数 发送给后端 来获取 分组成绩
使用 柱状图 来 显示 分组成绩 图表
//#region 获取dom元素
const tbody = document.querySelector('tbody');
//#endregion
//#region 获取成绩数据 和显示成绩
function getScore() {
axios.get('/score/list').then((result) => {
const data = result.data.data;
let html = '';
// 对象是可以遍历
for (const key in data) {
html += `
<tr>
<th scope="row">${key}</th>
<td>${data[key].name}</td>
<td class="score">${data[key].score[0]}</td>
<td class="score">${data[key].score[1]}</td>
<td class="score">${data[key].score[2]}</td>
<td class="score">${data[key].score[3]}</td>
<td class="score">${data[key].score[4]}</td>
</tr>
`;
}
tbody.innerHTML = html;
});
}
getScore();
//#endregion
给目标元素先绑定好 双击(短时间内快速按两下鼠标)事件
div.addEventListener("dblclick",function () {
console.log("双击");
})
tbody.addEventListener('dblclick', function (event) {
// 目标元素 要是 td标签 同时身上有类名 score
const td = event.target;
if (td.tagName === 'TD' && td.className === 'score') {
console.log('可以被修改');
}
});
在对应的 td
中 显示 出来 输入框
input
标签input
标签td
找到它里面 的 input
让它显示出来用户在输入框中数据的填写,触发 输入框 失去焦点
事件
失去焦点事件 普通的标签没有!!
失去焦点事件 给input标签添加 不能给tbody添加
获取用户最新的输入,拼接成参数 发送给后端,完成数据的修改
打开页面,发送请求 ,获取数据 ,渲染页面
留个大家发挥
点击 新增学员 显示模态框 (利用了bootstrap框架)
方法一 让模态框显示
$("#addModal").modal("show");
方法二 让模态框显示
<button
data-bs-toggle="modal"
data-bs-target="#addModal"
>
添加学员
</button>
初始化 新增表单中 城市下拉列表 - 重点把握
先显示真实的省份信息
找一下接口 发送网络请求 获取数据 渲染到对应的 下拉列表中
// 显示省份
axios.get('/geo/province').then((result) => {
const provinces = result.data;
let html = "<option value=''>--省--</option>";
html += provinces
.map((value) => `<option value="${value}" >${value}</option>`)
.join('');
addFormProvince.innerHTML = html;
});
省 下拉列表 切换的时候 加载对应 市信息
// 绑定省份下拉列表 切换事件 - 渲染 城市下拉列表
addFormProvince.addEventListener('change', function () {
// 输出你选中的 省份
// console.log(addFormProvince.value);
// 根据选中的省份获取城市信息
// axios.get("/geo/city", { params: { pname: addFormProvince.value } })
axios.get(`/geo/city?pname=${addFormProvince.value}`).then((result) => {
const citys = result.data;
let html = "<option value=''>--市--</option>";
html += citys
.map((value) => `<option value="${value}" >${value}</option>`)
.join('');
addFormCity.innerHTML = html;
});
});
市 下拉列表切换的时候 加载对应 区或者县 信息
// 绑定 城市 下拉列表 渲染 区 数据
addFormCity.addEventListener('change', function () {
axios
.get(
`/geo/county?pname=${addFormProvince.value}&cname=${addFormCity.value}`
)
.then((result) => {
const countys = result.data;
let html = "<option value=''>--县--</option>";
html += countys
.map((value) => `<option value="${value}" >${value}</option>`)
.join('');
addFormCounty.innerHTML = html;
});
});
重置表单
原生的html的按钮 type=reset 已经帮我们重置了很多表单组件。但是对于下拉列表,需要我们手动写代码去重置 下拉列表
<form action="">
<input type="text" name="username" />
<input type="text" name="password" />
<button type="reset">重置</button>
</form>
给表单绑定 重置事件
addForm.addEventListener('reset', function () {
// 重置下拉列表
});
事件触发,重置下拉列表的代码 完成
addFormCity.innerHTML = "<option value=''>--市--</option>";
addFormCounty.innerHTML = "<option value=''>--县--</option>";
点击 确认添加
submit
触发确认修改
按钮需要了解的主要配置:
series
xAxis
yAxis
grid
tooltip
title
legend
color
type
决定自己的图表类型演示代码:
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']
};
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)
完成后的配置项如下:
let option = {
tooltip: {
// {a} 表示series中的name
// {b} 表示数据中的series.data中的name
// {c} 表示每一项的值
// {d} 表示百分比
formatter: '{a} <br />{b} <strong>{c}</strong>人 占比{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: '辽宁' }
]
}
]
};
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)
以上配置项留下,其他删除
将官方示例中除了option之外的其他代码删除,并自己添加X轴数据和series中的数据。
系列数据
symbol: 'none'
分析数据缩放组件 dataZoom
增加标题,标题颜色 #6d767e
分析tooltip(官方示例已带)。
增加图例,距离顶部20px。
分析坐标轴留白策略 boundaryGap
完成后的配置项 option 如下:
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]
}
]
};
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)
以上几个配置项留下,其他删除。
修改X轴及series中的数据
['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轴
系列数据
yAxisIndex: 0
),让人数使用第二个Y轴(yAxisIndex: 1
)调整网格(图表的宽高)
分析tooltip(官方示例已带)
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]
}
]
};
社区就是一些,活跃的echart使用者,交流和贡献定制好的图表的地方。
在这里可以找到一些基于echart的高度定制好的图表,相当于基于jquery开发的插件,这里是基于echarts开发的第三方的图表。
社区示例:https://www.makeapie.com/explore.html
项目中使用的社区示例地址:https://www.makeapie.com/editor.html?c=xD4a1EBnvW
重点:
实现步骤:
visualMap
)中的 show
改为 false
必须知道的结论:
按下 #
json web token
可以实现 页中页 效果
父子页面不共享js和css等静态资源
iframe标签经常和a标签相搭配。 a标签的target 属性 要等于 iframe标签的name属性
现在前端项目中 很不推荐使用 iframe
-面试题
子页面 想要控制自己跳转 location.href
子页面 想要控制 父页面跳转 window.top.location.href
注册
功能此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。