← 返回个人网站搭建

Project Update

贡献日历组件:统计网站更新频率

2026-07-05 · 个人网站搭建 · UI · 数据统计 · Astro

组件目标

贡献日历用于回答一个简单问题:

这个网站是不是在持续更新?

它模仿 GitHub Contributions 的视觉形式,用一年中的每一天作为一个小格子,颜色深浅表示当天发布了多少篇 post。

本站统计的 post 包括:

  • blog 文章
  • note 更新帖
  • project 更新帖

不统计分区本身,因为分区更像目录,不是内容更新。

数据来源

About 页面先读取所有已发布内容:

const allPosts = [
  ...(await getPublishedEntries('blog')),
  ...(await getPublishedEntries('study')),
  ...(await getPublishedEntries('projects')),
];

然后把每篇内容的日期归并到天:

const contributionCounts = allPosts.reduce((counts, post) => {
  const key = toDateKey(post.data.date);
  counts.set(key, (counts.get(key) ?? 0) + 1);
  return counts;
}, new Map());

最终得到:

[
  { date: '2026-07-05', count: 3 },
  { date: '2026-07-06', count: 1 },
]

为什么使用自然年

一开始贡献日历可以做成“最近一年滚动窗口”,但这样横轴最左边可能不是 1 月。

为了更符合直觉,本站改成当前自然年:

1月 -> 12月

这样你看到的就是今年从年初到年末的更新分布。

网格生成方法

每一天都有两个位置:

  • 第几周:决定横向位置。
  • 星期几:决定纵向位置。

计算方式:

const week = Math.floor((index + start.getDay()) / 7) + 1;
const day = date.getDay() + 1;

然后用 CSS Grid 放置:

<span
  style={`grid-column: ${day.week}; grid-row: ${day.day};`}
/>

颜色等级

本站把更新数量压缩为 0 到 4 五个等级:

level: Math.min(count, 4)

含义是:

  • 0:没有更新
  • 1:少量更新
  • 2:中等更新
  • 3:较多更新
  • 4:高频更新

这样即使某天更新很多篇,也不会破坏颜色尺度。

可访问性细节

每个小格子都有 titlearia-label

2026-07-05: 3 篇更新

鼠标悬停时可以看到当天更新数量,屏幕阅读器也能读出这一天的含义。

本站实践总结

贡献日历不是必要功能,但它很适合个人网站。

它把“我持续维护这个网站”这件事可视化了,也能反过来提醒自己保持记录习惯。