1. echarts
数据可视化:将数据转换为图形,数据特点更加突出
echarts:一个基于 JavaScript 的开源可视化图表库
echarts官网
1.1 echarts核心使用步骤
// 1. 基于准备好的dom,初始化echarts实例
const myChart = echarts.init(dom元素);
// 2. 指定图表的配置项和数据
const option = {
// ...
};
// 3. 使用刚指定的配置项和数据显示图表。
myChart.setOption(option);
1.2 折线图
// todo: 2022全学科薪资走势 折线图
// ? 折线粗细、点大小、x轴虚线、提示框(已解决)
function renderYear(year) {
// 省略大括号 {} 和 return 关键字(适用于单行表达式)
const months = year.map((item) => item.month);
const salarys = year.map((item) => item.salary);
// console.log(months, salarys);
const myChart = echarts.init(document.querySelector("#line"));
const option = {
// 标题组件
title: {
text: "2022全学科薪资走势",
// top、left..: 可设置title 组件离容器x侧的距离。
top: 20,
left: "2%",
},
// 直角坐标系内绘图网格
grid: {
top: "20%",
},
// 提示框组件
tooltip: {
// axis 坐标轴触发,主要在柱状图,折线图等会使用类目轴的图表中使用。
trigger: "axis",
},
// x轴
xAxis: {
// 坐标轴类型 value数值轴、category类目轴..
type: "category",
data: months,
// 坐标轴轴线
axisLine: {
lineStyle: {
color: "#9c9c9c",
// 线的类型:实线solid、虚线、点线dotted
type: "dashed",
},
},
// x轴在 grid 区域中的分隔线
/* splitLine: {
show: true,
lineStyle: {
type: "dashed",
},
}, */
},
// y轴
yAxis: {
type: "value",
// y轴在 grid 区域中的分隔线
splitLine: {
lineStyle: {
// 分隔线的类型
type: "dashed",
},
},
},
// 系列列表
series: [
{
data: salarys,
// 折线/面积图
type: "line",
smooth: true,
// 区域填充样式。设置后显示成区域面积图。
areaStyle: {
// 线性渐变,前四个参数分别是 x0, y0, x2, y2, 范围从 0 - 1,相当于在图形包围盒中的百分比,
// 如果 globalCoord 为 `true`,则该四个值是绝对的像素位置
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "#88BDF1", // 0% 处的颜色
},
{
offset: 1,
color: "rgba(255,255,255,0)", // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
// 线条样式
lineStyle: {
width: 7, // 线宽
color: {
type: "linear",
x: 0,
y: 0,
x2: 1,
y2: 0,
colorStops: [
{
offset: 0,
color: "#4897E7", // 0% 处的颜色
},
{
offset: 1,
color: "#5975EC", // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
// 标记的大小
symbolSize: 10,
},
],
};
myChart.setOption(option);
}
1.3 双柱状图
// todo:班级每组薪资 双柱状图
function renderGroupData(groupData) {
// 班级平均薪资默认渲染第一组的 后面点击更换组别 多次使用 → 封装函数
// console.log(groupData);
// console.log(groupData instanceof Object); // true
// console.log(groupData instanceof Array); // false
// groupData是对象 键是数字(1到8)
// ! 1. 直接封装函数后调用:这种方法简单直接,每次点击按钮时重新渲染整个图表。
// ! 虽然实现起来比较方便,但可能会带来性能问题,特别是在数据量较大或图表比较复杂的情况下。
// ! 2. 更改配置项数据,再次调用 myChart.setOption(option) 这种方法更高效,
// ! 因为 ECharts 提供了一些 API 来更新图表的数据,而不需要完全重新初始化图表。这通常比重新创建图表要快得多,尤其是在数据量大的情况下。
// * 对象的键可以是字符串或数字 groupData[1] 等价于 groupData["1"]
// function renderGroup(group) {
/* const getData = groupData[1].map((item) => {
const { name, hope_salary, salary } = item;
return [name, hope_salary, salary];
}); */
// console.log(getData);
const myChart = echarts.init(document.querySelector("#lines"));
const option = {
// legend: {},
tooltip: {},
dataset: {
source: [
["薪资", "期望薪资", "就业薪资"],
...groupData[1].map((item) => {
const { name, hope_salary, salary } = item;
return [name, hope_salary, salary];
}),
],
},
xAxis: {
type: "category",
axisLine: {
lineStyle: {
color: "#9c9c9c",
type: "dashed",
},
},
},
yAxis: {
splitLine: {
lineStyle: {
type: "dashed",
},
},
},
series: [
{
type: "bar",
itemStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "#3CD59E", // 0% 处的颜色
},
{
offset: 1,
color: "rgba(255,255,255,0.5)", // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
},
{
type: "bar",
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "#56B1FF", // 0% 处的颜色
},
{
offset: 1,
color: "rgba(255,255,255,0.5)", // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
],
// color: ["#68DEB4", "#77B7F2"],
};
myChart.setOption(option);
// }
// renderGroup(1);
document.querySelector("#btns").addEventListener("click", function (e) {
if (e.target.nodeName === "BUTTON") {
// console.log("button");
// console.log(e.target.innerText);
document.querySelector(".btn-blue").classList.remove("btn-blue");
e.target.classList.add("btn-blue");
// renderGroup(e.target.innerText);
// 更改配置项数据
option.dataset.source = [
["姓名", "期望薪资", "就业薪资"],
...groupData[e.target.innerText].map((item) => {
const { name, hope_salary, salary } = item;
return [name, hope_salary, salary];
}),
];
myChart.setOption(option);
}
});
}
1.4 饼状图
// todo: 班级薪资分布 饼图
function renderSalaryDataClass(salaryData) {
// 箭头函数省略return简写时,返回值若为对象{},需使用小括号()包裹
const data = salaryData.map((item) => ({
value: item.g_count + item.b_count,
name: item.label,
}));
/* const data = salaryData.map((item) => {
return { value: item.g_count + item.b_count, name: item.label };
}); */
// console.log(data);
const myChart = echarts.init(document.querySelector("#salary"));
const option = {
title: {
text: "班级薪资分布",
top: 15,
left: 15,
},
// 提示框
tooltip: {
trigger: "item",
},
// 图例组件
legend: {
bottom: "5%",
left: "center",
},
// 系列列表
series: [
{
name: "班级薪资分布",
type: "pie",
// 饼图大小
radius: ["50%", "70%"],
avoidLabelOverlap: false,
// 饼图小扇区的边框设置 圆角颜色宽度等
itemStyle: {
borderRadius: 10,
borderColor: "#fff",
borderWidth: 2,
},
// 饼图标签文字 默认为true 显示
label: {
show: false,
// 标签文字位置
// position: "center",
},
// 饼图标签文字与饼图的连接线
/* labelLine: {
show: false,
}, */
// 高亮状态的扇区和标签样式 (悬停时所处数据中间高亮)
/* emphasis: {
label: {
show: true,
fontSize: 40,
fontWeight: "bold",
},
}, */
data,
},
],
color: ["#FDA224", "#5097FF", "#3ABCFA", "#34D39A"],
};
myChart.setOption(option);
}
1.5 双饼图
// todo: 男女薪资分布 双饼图
function renderSalaryDataGender(salaryData) {
const myChart = echarts.init(document.querySelector("#gender"));
const option = {
title: [
{
text: "男女薪资分布",
left: 10,
top: 10,
textStyle: {
fontsize: 16,
},
},
{
text: "男生",
left: "45%",
top: "45%",
textStyle: {
fontsize: 12,
},
},
{
text: "女生",
left: "45%",
top: "88%",
textStyle: {
fontsize: 12,
},
},
],
tooltip: {
trigger: "item",
},
series: [
{
type: "pie",
radius: ["25%", "40%"],
// center 属性来控制每个饼图的位置
center: ["50%", "30%"],
avoidLabelOverlap: false,
label: {
show: true,
},
labelLine: {
show: true,
},
data: salaryData.map((item) => ({
value: item.b_count,
name: item.label,
})),
},
{
type: "pie",
radius: ["25%", "40%"],
center: ["50%", "70%"],
avoidLabelOverlap: false,
label: {
show: true,
},
labelLine: {
show: true,
},
data: salaryData.map((item) => ({
value: item.g_count,
name: item.label,
})),
},
],
color: ["#FDA224", "#5097FF", "#3ABCFA", "#34D39A"],
};
myChart.setOption(option);
}
2. 黑马就业数据平台
2.1 index.js
初始代码(不完整)
// 调用函数 判断是否有token
checkLogin();
// 渲染用户名
renderName(1);
// 退出登录
logout();
// ? 可以使用 async await 从后台只获取一次数据吗? 图表直接使用
// ? 外面套函数?
// * getRes 是一个异步函数,返回一个 Promise 对象。
// * 调用 getRes 时,如果不使用 await,会得到一个未解决的 Promise 对象。
// * 使用 await 可以等待 Promise 被解决,并将解决后的值赋给变量。
// * await 关键字只能在 async 函数内部使用。不能直接在 return 语句中使用 await
// ! 为了实现每次调用 getRes 函数时只向网络请求一次数据,并且能够根据传入的参数获取解构出来的一条数据,
// ! 可以使用一个闭包来缓存第一次请求的数据。这样,后续的调用将直接从缓存中获取数据,而不需要再次进行网络请求。
// const { groupData, overview, provinceData, salaryData, year } = res.data;
function outer() {
let cacheData = null;
return async function inner(name) {
if (!cacheData) {
const res = await axios.get("/dashboard");
cacheData = res.data;
}
return cacheData[name];
};
}
// * getRes === outer() === inner === inner(){}
const getRes = outer();
// todo:统计数据区域
(async function () {
// const token = localStorage.getItem("token");
// * common.js 已添加请求(统一设置token)和响应拦截器(统一处理token失效问题) → catch部分代码移除,try catch注释
// try {
/* const res = await axios.get("/dashboard", {
// * 请求头参数:校验是否登录
headers: { Authorization: token },
}); */
// const res = await axios.get("/dashboard");
// console.log(res);
// const { groupData, overview, provinceData, salaryData, year } = res.data;
// const { overview } = res.data;
const overview = await getRes("overview");
// console.log(overview);
// console.log(overview);
Object.keys(overview).forEach((key) => {
document.querySelector(`.${key}`).innerHTML = overview[key];
});
// } catch (error) {
// console.dir(error);
// console.log(error.response.status); // 401
// 401 token验证失败(token过期或被恶意篡改)
// todo:跳转到登录页面重新登陆,本地存储数据清除
/* if (error.response.status === 401) {
showToast("登陆失败,请重新登录");
localStorage.removeItem("username");
localStorage.removeItem("token");
// 延迟跳转登陆页面
setTimeout(() => {
location.href = "./login.html";
}, 1500);
} */
// }
})();
// * 多个图表需写入页面 → 立即执行函数,解决可能存在的变量名冲突问题
// todo: 柱状图 班级每组薪资
(async function () {
// const res = await axios.get("/dashboard");
// console.log(res.data);
// const { groupData } = res.data;
const groupData = await getRes("groupData");
const myChart = echarts.init(document.querySelector("#lines"));
const option = {
tooltip: {},
dataset: {
source: [
["薪资", "期望薪资", "就业薪资"],
...groupData[1].map((item) => {
const { name, hope_salary, salary } = item;
return [name, hope_salary, salary];
}),
],
},
xAxis: {
type: "category",
axisLine: {
lineStyle: {
color: "#9c9c9c",
type: "dashed",
},
},
},
yAxis: {
splitLine: {
lineStyle: {
type: "dashed",
},
},
},
series: [
{
type: "bar",
itemStyle: {
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "#3CD59E", // 0% 处的颜色
},
{
offset: 1,
color: "rgba(255,255,255,0.5)", // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
},
{
type: "bar",
color: {
type: "linear",
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [
{
offset: 0,
color: "#56B1FF", // 0% 处的颜色
},
{
offset: 1,
color: "rgba(255,255,255,0.5)", // 100% 处的颜色
},
],
global: false, // 缺省为 false
},
},
],
};
myChart.setOption(option);
document.querySelector("#btns").addEventListener("click", function (e) {
if (e.target.nodeName === "BUTTON") {
document.querySelector(".btn-blue").classList.remove("btn-blue");
e.target.classList.add("btn-blue");
// 更改配置项数据
option.dataset.source = [
["姓名", "期望薪资", "就业薪资"],
...groupData[e.target.innerText].map((item) => {
const { name, hope_salary, salary } = item;
return [name, hope_salary, salary];
}),
];
myChart.setOption(option);
}
});
})();
2.2 getData
向服务器请求数据 解构 调用函数 传参 (后四个函数在本章1.2-1.5)
// todo:统计数据区域
async function getData() {
const res = await axios.get("/dashboard");
const { groupData, overview, provinceData, salaryData, year } = res.data;
// console.log(salaryData);
renderOverview(overview);
renderGroupData(groupData);
renderYear(year);
renderSalaryDataClass(salaryData);
renderSalaryDataGender(salaryData);
}
getData();
function renderOverview(overview) {
Object.keys(overview).forEach((key) => {
document.querySelector(`.${key}`).innerHTML = overview[key];
});
}
2.3 student.js
学生信息渲染
// 调用函数 判断是否有token(登录校验)
checkLogin();
// 渲染用户名
renderName(1);
// 退出登录
logout();
// * 渲染学生信息 增删改之后需再次渲染 → 封装函数
// * 获取服务器学生列表 并渲染到页面
async function render() {
// 已设置请求拦截器
const res = await axios.get("/students");
const str = res.data
.map((item) => {
const {
id,
name,
age,
gender,
group,
hope_salary,
salary,
province,
city,
area,
} = item;
return `
<tr>
<td>${name}</td>
<td>${age}</td>
<td>${gender === 0 ? "男" : "女"}</td>
<td>第${group}组</td>
<td>${hope_salary}</td>
<td>${salary}</td>
<td>${province + city + area}</td>
<td>
<a href="javascript:;" class="text-success mr-3"><i class="bi bi-pen" data-id="${id}"></i></a>
<a href="javascript:;" class="text-danger"><i class="bi bi-trash" data-id="${id}"></i></a>
</td>
</tr>
`;
})
.join("");
document.querySelector(".list").innerHTML = str;
document.querySelector(".total").innerHTML = res.data.length;
}
render();