以下笔记来源:编程导航
分析
有三种基本图表可以选择:
- 基础日历图:https://echarts.apache.org/examples/zh/editor.html?c=calendar-simple
- 日历热力图:https://echarts.apache.org/examples/zh/editor.html?c=calendar-heatmap
跟上一个图的区别就是鼠标放上去可以展示具体的热力值,热力值越高,图块的颜色越深。
当我们只需要实现签到功能的时候,不涉及热力数值的区分(只有 0 和 1 签到 / 未签到的区别)。
官方生成数据的循环代码如下:
for (let time = date; time <= end; time += dayTime) {
data.push([
echarts.time.format(time, '{yyyy}-{MM}-{dd}', false),
Math.floor(Math.random() * 10000)
]);
}
得到的数据是一个二维数组,每个元素表示一个日期和对应的数值(也正是后端需要返回的结构):
[
['2017-01-01', 3456],
['2017-01-02', 8975],
...
]
调整热力值的范围,从而控制颜色深浅。还支持调整颜色:
visualMap: {
show: false,
min: 0,
max: 1,
inRange: {
color: ['#efefef', 'lightgreen'] // 颜色从灰色到浅绿色
},
},
实现
安装 ECharts:https://echarts.apache.org/zh/index.html
和 React ECharts 可视化库:https://github.com/hustcc/echarts-for-react
npm install --save echarts
npm install --save echarts-for-react
安装失败的话,在命令后加 --force
。
封装日历图组件:
1)参考 React ECharts 的 官方文档 来使用 ECharts 组件,把 Demo 代码复制到新建的组件文件中。
2)定义签到日期数组变量,将数组转换为图表需要的数据。其中,对日期的处理需要用到 dayjs 库:
tsx
复制代码
// 签到日期列表([1, 200],表示第 1 和第 200 天有签到记录)
const [dataList, setDataList] = useState<number[]>([]);
// 计算图表需要的数据
const year = new Date().getFullYear();
const optionsData = dataList.map((dayOfYear, index) => {
// 计算日期字符串
const dateStr = dayjs(`${year}-01-01`)
.add(dayOfYear - 1, "day")
.format("YYYY-MM-DD");
return [dateStr, 1];
});
4)参考 Echarts 的官方 Demo 开发前端日历图:https://echarts.apache.org/examples/zh/editor.html?c=calendar-simple
先在 Demo 页面里调整好效果,得到 options 选项。
💡 小技巧:可以通过配置项或者询问 AI 得到需要的配置
import React, {useEffect, useState} from "react";
import ReactECharts from "echarts-for-react";
import "./index.css";
import dayjs from "dayjs";
import {getUserSignInRecordUsingGet} from "@/api/userController";
import {message} from "antd";
/**
* 日历图组件
* @constructor
*/
export default function CalendarChart() {
// 签到日期列表([1, 200],表示第 1 和第 200 天有签到记录)
const [dataList, setDataList] = useState<number[]>([1, 200]);
const fetchDataList = async () => {
try {
// 请求后端获取数据
const res = await getUserSignInRecordUsingGet({
year
});
setDataList(res.data);
} catch (e) {
message.error(`获取刷题签到记录失败: ${e.message}`);
}
}
useEffect(() => {
fetchDataList();
}, []);
// 计算图表需要的数据
// 当前年份
const year = new Date().getFullYear();
const optionsData = dataList.map((dayOfYear, index) => {
// 计算日期字符串
const dateStr = dayjs(`${year}-01-01`)
.add(dayOfYear - 1, "day")
.format("YYYY-MM-DD");
return [dateStr, 1];
});
// 图表配置
const options = {
visualMap: {
show: false,
min: 0,
max: 1,
inRange: {
// 颜色从灰色到浅绿色
color: ["#efefef", "lightgreen"],
},
},
calendar: {
range: year,
left: 20,
// 单元格自动宽度,高度为 16 像素
cellSize: ['auto', 16],
yearLabel: {
position: "top",
formatter: `${year} 年刷题记录`,
}
},
series: {
type: "heatmap",
coordinateSystem: "calendar",
data: optionsData,
},
};
return <ReactECharts option={options} />;
}
执行签到:
这里单独封装了一个钩子,因为进入详情页面时才会执行自动签到,而详情页面是在服务端渲染(本项目),获取不到登录态,所以我们需要单独封装一个钩子,在客户端额外发送请求来执行签到。
import { useEffect, useState } from "react";
import { message } from "antd";
import { addUserSignInUsingPost } from "@/api/userController";
/**
* 添加用户签到记录钩子
*/
const useAddUserSignInRecord = () => {
const [loading, setLoading] = useState(false);
// 请求后端执行签到
const doFetch = async () => {
setLoading(true);
try {
await addUserSignInUsingPost();
} catch (e) {
message.error("添加刷题签到记录失败," + e.message);
} finally {
setLoading(false);
}
};
useEffect(() => {
doFetch();
}, []);
return { loading };
};
export default useAddUserSignInRecord;
该钩子需要在客户端组件中执行,因为用到了 useEffect 防止重复请求、并且还需要获取到用户登录态。
todo: 签到成功,可以保存到 LocalStorage 等位置,防止每次刷题都重复发送签到请求。