Typecho CF 添加归档总览页

本站基于 Typecho CF 搭建,默认主题没有归档总览页,无法按时间浏览所有文章。本文记录如何在项目里新增一个 /archive/ 归档页。

核心思路是:不走分页,直接查数据库拿全部已发布文章,这样无论多少篇,归档页都能完整显示。

同时为了避免同一篇文章出现两个不同的 URL(比如 /post/20.html 和 /archives/20/),文章链接用 buildPermalink 生成正确的 /post/xxx.html 格式。

一、新建页面文件

在 src/pages/ 下新建 archive.astro,直接用 Drizzle 查询全部已发布文章。

---
import Base from '@/layouts/Base.astro';
import { createContext } from '@/lib/context';
import { schema } from '@/db';
import { and, eq, desc } from 'drizzle-orm';
import { formatDate, buildPermalink } from '@/lib/content';

const ctx = await createContext(Astro.locals, Astro.request);
const { db, options, urls, user, isLoggedIn, pluginCtx } = ctx;

// 查全部已发布文章,不分页
const allPosts = await db.query.contents.findMany({
  where: and(
    eq(schema.contents.type, 'post'),
    eq(schema.contents.status, 'publish')
  ),
  orderBy: [desc(schema.contents.created)],
});

// 按「年 → 月」分组,并顺便算 permalink
const postsByYearMonth = {};
allPosts.forEach((post) => {
  const created = Number(post?.created);
  if (!created || isNaN(created)) return;
  const d = new Date(created * 1000);
  const year = String(d.getFullYear());
  const month = String(d.getMonth() + 1).padStart(2, '0');
  if (!postsByYearMonth[year]) postsByYearMonth[year] = {};
  if (!postsByYearMonth[year][month]) postsByYearMonth[year][month] = [];
  // 用 buildPermalink 生成正确的 /post/xxx.html 链接
  post.permalink = buildPermalink(
    { cid: post.cid, slug: post.slug, type: post.type, created: post.created },
    urls.siteUrl,
    options.permalinkPattern,
  );
  postsByYearMonth[year][month].push(post);
});

const sortedYears = Object.keys(postsByYearMonth).sort((a, b) => Number(b) - Number(a));

// 侧边栏和导航(单独加载)
const { loadSidebarAndNav } = await import('@/lib/sidebar');
const { sidebarData, pages } = await loadSidebarAndNav(
  ctx,
  db,
  urls.siteUrl,
  options.permalinkPattern,
  options.categoryPattern,
  options.pagePattern,
  options.cacheVersion,
);
---

二、折叠展示

文章列表用 <details> 和 <summary> 折叠,点击月份才展开。

{sortedYears.map((year) => (
  <section class="archive-year">
    <h4>{year} 年</h4>
    {Object.keys(postsByYearMonth[year])
      .sort((a, b) => Number(b) - Number(a))
      .map((month) => {
        const postsInMonth = postsByYearMonth[year][month];
        return (
          <details class="archive-month">
            <summary>
              {year} 年 {month} 月
              <span>({postsInMonth.length} 篇)</span>
            </summary>
            <ul class="archive-post-list">
              {postsInMonth.map((post) => (
                <li class="archive-post-row">
                  <span class="archive-post-date">
                    {formatDate(Number(post.created) || 0, 'm-d', options.timezone)}
                  </span>
                  <a href={post.permalink} class="archive-post-title">
                    {post.title || '无标题'}
                  </a>
                </li>
              ))}
            </ul>
          </details>
        );
      })}
  </section>
))}

注意:数据库原始行没有 permalink 字段,所以在分组时用 buildPermalink 生成,渲染时直接用 post.permalink。

三、行分隔与鼠标经过变色

在 archive.astro 的 </Base> 后加一段 <style>:

.archive-post-list {
  list-style: none;
  padding: 0;
  margin: 0;
}

.archive-post-row {
  display: flex;
  gap: 1rem;
  align-items: baseline;
  padding: 0.6rem 0.8rem;
  border-bottom: 1px solid #f0f0f0;
  transition: background-color 0.15s ease;
}
.archive-post-row:last-child {
  border-bottom: none;
}
.archive-post-row:hover {
  background-color: #f7f8fa;
}

.archive-post-date {
  color: #888;
  font-size: 0.9rem;
  flex-shrink: 0;
  min-width: 80px;
}

.archive-post-title {
  font-size: 1rem;
  text-decoration: none;
  color: #3354aa;
}
.archive-post-title:hover {
  text-decoration: underline;
}

样式只写在 archive.astro 内部,切换主题时不影响其他页面。

四、加入导航栏

在 src/pages/archive.astro 和 src/themes/typocho-theme-note/components/Archive.astro 的导航栏里加一行:

<a href={urls.siteUrl + '/archive/'}>归档</a>

五、为什么要直接查数据库

之前的做法是复用 prepareIndexData,但那个函数默认只返回最近 5 篇文章(受 options.pageSize 限制)。

结果就是:归档页只显示最近的分页内容,老文章(比如 2022 年的)被截断,看不到。

改成直接 db.query.contents.findMany 后,一次拿全部,彻底解决。

六、为什么要用 buildPermalink

项目里其实有两条路由都能打开同一篇文章:

  • /post/20.html:Typecho 后台配置的固定链接
  • /archives/20/:项目自带的按 cid 访问的路由

两条都能打开,但 URL 不同,搜索引擎会把它们当成两个页面,可能重复收录。

用 buildPermalink 生成正确的 /post/xxx.html 链接后,归档页里的文章链接和首页保持一致,从源头避免重复 URL。

七、最终效果

访问 https://hbw.me/archive/:

  • 按年份分组,最新的年份在最上面
  • 每个月折叠,点击展开文章列表
  • 每篇文章一行,日期列固定宽度
  • 鼠标经过整行变浅灰蓝
  • 所有文章都显示,不分页,不截断
  • 文章链接格式统一为 /post/xxx.html

八、注意事项

  • 归档页数据不依赖分页,文章再多也不会漏
  • 侧边栏和导航单独用 loadSidebarAndNav 加载,与首页保持一致
  • 文章链接用 buildPermalink 生成,与后台配置的固定链接格式一致
  • 改完后去 Typecho 后台改一下站点标题再改回来,触发缓存失效

标签: 主题

添加新评论