echarts 实现签到记录日历组件
以下笔记来源:编程导航
分析
有三种基本图表可以选择:
- 基础日历图:https://echarts.apache.org/examples/zh/editor.html?c=calendar-simple
- 日历热力图:https://echarts.apache.org/examples/zh/editor.html?c=calendar-heatmap
跟上一个图的区别就是鼠标放上去可以展示具体的热力值,热力值越高,图块的颜色越深。
- 日历图:https://echarts.apache.org/examples/zh/editor.html?c=calendar-charts
当我们只需要实现签到功能的时候,不涉及热力数值的区分(只有 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 等位置,防止每次刷题都重复发送签到请求。