目标
leetcode和github都有活跃度的热力图,本文的目标就是搭建一个类似leetcode的热力图组件
格子布局
为简化组件复杂度,将热力图组件中月份单独拆分为子组件。
渲染上需要考虑以下问题
问题一,本月有多少天,决定渲染多少个格子
使用dayjs,获取当前月month最后一天的日期,这样就能够获得本月的天数了
const days = dayjs(month).endOf('month');
const dayCount = days.date();
问题二,本月第一天是星期几,从哪一个格子开始渲染
使用dayjs获取当前月份月初是星期几
// 周日会返回0
const startDayOfWeek = dayjs(month).startOf('month').day() || 7;
问题三,从上到下,从左到右的布局,以及怎么指定开始的格子位置
- 使用grid布局,指定行数=7,指定元素流动方向为从上到下
- 当前为星期四,就用三个空白格子占位
格子颜色
问题四,由浅入深的格子颜色怎么显示
每一个格子的渲染时都会接收以下数据,level表示程度
export interface HeatMapData {
level: number;
duration: number;
record: { category: string; content: string }[];
}
// key是日期,值是热力图每一格的数据
export type LogData = Record<string, HeatMapData>;
指定5个level,由浅入深的颜色,渲染时根据level动态为每个格子增加对应的class
.color-level-0 {
background-color: #ebedf0;
}
.color-level-1 {
background-color: #ace7ae;
}
.color-level-2 {
background-color: #69c16e;
}
.color-level-3 {
background-color: #549f57;
}
.color-level-4 {
background-color: #386c3e;
}
鼠标悬浮展示内容
根据每个格子接收到的数据,拼接而成,比较简单
export interface HeatMapData {
level: number;
duration: number;
record: { category: string; content: string }[];
}
// key是日期,值是热力图每一格的数据
export type LogData = Record<string, HeatMapData>;
代码
热力图组件代码
component/heat-map/index.ts,约定类型
export interface HeatMapData {
level: number;
duration: number;
record: { category: string; content: string }[];
}
// key是日期,值是热力图每一格的数据
export type LogData = Record<string, HeatMapData>;
component/heat-map/month.vue,渲染当前月份格子
<template>
<div class="flex flex-col gap-3">
<div class="inline-grid gap-1 grid-rows-7 grid-flow-col">
<div v-for="item in emptyGridList" :key="item" />
<template v-for="item in gridCount" :key="item">
<el-tooltip v-if="logData[item]" effect="light">
<template #content>
<div class="text-3.5">
<div>{{ `${item},${logData[item].duration}min` }}</div>
<div v-for="recordItem in logData[item].record" :key="recordItem.category">
{{ `${recordItem.category} : ${recordItem.content}` }}
</div>
</div>
</template>
<div class="rd-1 w-4 h-4 gap-1" :class="getBgClass(item)" />
</el-tooltip>
<div v-else class="rd-1 w-4 h-4 gap-1 color-level-0" />
</template>
</div>
<div>{{ month }}月</div>
</div>
</template>
<script setup lang="ts">
import { dayjs } from 'element-plus';
import type { LogData } from '.';
const { month } = defineProps<{
month: string;
}>();
// 本月有几天
const days = dayjs(month).endOf('month');
const dayCount = days.date();
const gridCount = Array.from({ length: dayCount }, (item, index) => {
const day = String(index + 1).padStart(2, 0);
const date = `${month}-${day}`;
return date;
});
const logData = inject('logData') as LogData;
// 本月第一天是周几
const startDayOfWeek = dayjs(month).startOf('month').day() || 7;
const emptyGridList = Array.from({ length: startDayOfWeek - 1 }, (item, index) => `empty-${index}`);
function getBgClass(date: string) {
const level = logData[date]?.level;
return `color-level-${level}`;
}
</script>
<style scoped>
.color-level-0 {
background-color: #ebedf0;
}
.color-level-1 {
background-color: #ace7ae;
}
.color-level-2 {
background-color: #69c16e;
}
.color-level-3 {
background-color: #549f57;
}
.color-level-4 {
background-color: #386c3e;
}
</style>
component/heat-map/index.vue,渲染本年的所有格子
<template>
<div class="flex text-3.5 mt-10">
<div class="mr-4">
<div v-for="item in daysInWeek" :key="item">
{{ item }}
</div>
</div>
<div class="flex gap-4">
<Month v-for="item in monthList" :key="item.key" :month="item.month" />
</div>
</div>
</template>
<script setup lang="ts">
import Month from './month.vue';
const props = defineProps<{
year: string;
}>();
const daysInWeek = ['周一', '周二', '周三', '周四', '周五', '周六', '周日'];
const monthList = computed(() => {
return Array.from({ length: 12 }, (item, index) => {
const monthStr = `${index + 1}`.padStart(2, 0);
return {
key: `${props.year}-month-${index}`,
month: `${props.year}-${monthStr}`,
};
});
});
</script>
<style scoped>
</style>
页面中使用
<template>
<HeatMap :year="year" :log-data="logData" />
</template>
logData格式如下:
export interface HeatMapData {
level: number;
duration: number;
record: { category: string; content: string }[];
}
// key是日期,值是热力图每一格的数据
export type LogData = Record<string, HeatMapData>;
效果
完成搭建!