Lumin Blog 归档页:时间线 + 分页
📌 概述
归档页采用纯平铺时间线设计,所有文章按时间倒序排列,左侧竖线 + 圆点构成视觉时间轴。无需点击折叠即可浏览全部文章,同时提供前端分页功能,避免文章过多时页面过长。页面底部附带分类云和标签云,方便快速导航。
🎯 一、功能说明
| 属性 | 说明 |
|---|---|
| 展示方式 | 纯平铺时间线,文章按时间倒序排列 |
| 时间线 | 左侧竖线 + 每篇文章圆点,hover 时圆点放大发光 |
| 文章项 | 日期 + 标题 + 分类标签 |
| 分页 | JS 前端分页,默认每页 30 篇 |
| 分页配置 | hugo.toml 中 params.archives.pageSize |
| 底部 | 分类云 + 标签云 |
| 移动端 | 隐藏分类标签,标题允许两行换行 |
改造前后对比
| 特性 | 改造前 | 改造后 |
|---|---|---|
| 展示方式 | 年份折叠 → 月份折叠 → 文章(需点击2次) | 纯平铺时间线,所有文章直接可见 |
| 时间线 | 左侧竖线 + 年份/月份大圆点 | 左侧竖线 + 每篇文章小圆点 |
| 分页 | 无(全部平铺) | JS 前端分页,每页可配置 |
| 底部 | 无 | 分类云 + 标签云 |
🏗️ 二、代码结构
2.1 HTML 模板
定义在 layouts/_default/archives.html 中:
1{{ define "main" }}
2{{ $sections := .Site.Params.mainSections | default (slice "life" "tech" "smart") }}
3{{ $allPages := where .Site.RegularPages "Section" "in" $sections }}
4{{ $allPages = where $allPages "Params.excludeFromList" "!=" true }}
5{{ $allPages = $allPages.ByDate.Reverse }}
6{{ $pageSize := .Site.Params.archives.pageSize | default 30 }}
7
8<div class="list-page archives-page">
9 <header class="list-header archives-header">
10 <h1 class="list-title">归档</h1>
11 <div class="list-count">共 {{ len $allPages }} 篇文章</div>
12 </header>
13
14 <div class="archives-timeline" id="archives-timeline"
15 data-page-size="{{ $pageSize }}"
16 data-total="{{ len $allPages }}">
17 {{ range $idx, $page := $allPages }}
18 <article class="archive-item" data-index="{{ $idx }}">
19 <div class="archive-item-dot"></div>
20 <div class="archive-item-line"></div>
21 <time class="archive-item-date">{{ $page.Date.Format "2006-01-02" }}</time>
22 <div class="archive-item-body">
23 <a href="{{ $page.Permalink }}" class="archive-item-title">{{ $page.Title }}</a>
24 </div>
25 <div class="archive-item-footer">
26 {{ with $page.Params.categories }}
27 {{ range first 1 . }}
28 <a href="/{{ . }}/" class="archive-item-cat">
29 {{ partial "category-display-name.html" (dict "Site" $.Site "Slug" .) }}
30 </a>
31 {{ end }}
32 {{ end }}
33 </div>
34 </article>
35 {{ end }}
36 </div>
37
38 <div class="archives-pagination" id="archives-pagination"></div>
39
40 <!-- 底部分类云 + 标签云 -->
41 <div class="archives-bottom">...</div>
42</div>
43{{ end }}
关键设计点:
data-page-size和data-total传递给 JS 分页模块data-index标记每篇文章的序号,JS 据此控制显隐- 文章来源通过
mainSections过滤,排除excludeFromList的页面 - 分类显示使用
category-display-name.htmlpartial 支持中文映射
2.2 JavaScript 分页
定义在 assets/js/main.js 的 ArchivePagination IIFE 模块中:
1var ArchivePagination = (function() {
2 var container, paginationEl;
3 var pageSize = 30;
4 var currentPage = 1;
5 var totalPages = 1;
6
7 function goToPage(page) {
8 if (page < 1 || page > totalPages) return;
9 currentPage = page;
10 var items = container.querySelectorAll('.archive-item');
11 var start = (page - 1) * pageSize;
12 var end = start + pageSize;
13
14 for (var i = 0; i < items.length; i++) {
15 if (i >= start && i < end) {
16 items[i].removeAttribute('data-hidden');
17 } else {
18 items[i].setAttribute('data-hidden', '');
19 }
20 }
21
22 renderPagination();
23 window.scrollTo({ top: container.offsetTop - 100, behavior: 'smooth' });
24 }
25
26 function renderPagination() {
27 if (!paginationEl || totalPages <= 1) {
28 if (paginationEl) paginationEl.innerHTML = '';
29 return;
30 }
31
32 var html = '';
33 html += '<button class="page-btn' + (currentPage === 1 ? ' disabled' : '') +
34 '" data-page="' + (currentPage - 1) + '">‹</button>';
35
36 var pages = getVisiblePages(currentPage, totalPages);
37 for (var i = 0; i < pages.length; i++) {
38 if (pages[i] === '...') {
39 html += '<span class="page-ellipsis">…</span>';
40 } else {
41 html += '<button class="page-btn' + (pages[i] === currentPage ? ' active' : '') +
42 '" data-page="' + pages[i] + '">' + pages[i] + '</button>';
43 }
44 }
45
46 html += '<button class="page-btn' + (currentPage === totalPages ? ' disabled' : '') +
47 '" data-page="' + (currentPage + 1) + '">›</button>';
48 html += '<span class="page-info">' + currentPage + ' / ' + totalPages + '</span>';
49
50 paginationEl.innerHTML = html;
51 }
52
53 function getVisiblePages(current, total) {
54 if (total <= 7) {
55 var arr = [];
56 for (var i = 1; i <= total; i++) arr.push(i);
57 return arr;
58 }
59 if (current <= 3) return [1, 2, 3, 4, '...', total];
60 if (current >= total - 2) return [1, '...', total - 3, total - 2, total - 1, total];
61 return [1, '...', current - 1, current, current + 1, '...', total];
62 }
63
64 function init() {
65 container = document.getElementById('archives-timeline');
66 paginationEl = document.getElementById('archives-pagination');
67 if (!container || !paginationEl) return;
68
69 pageSize = parseInt(container.getAttribute('data-page-size')) || 30;
70 var total = parseInt(container.getAttribute('data-total')) || 0;
71 totalPages = Math.ceil(total / pageSize);
72
73 if (totalPages <= 1) { paginationEl.innerHTML = ''; return; }
74
75 paginationEl.addEventListener('click', function(e) {
76 var btn = e.target.closest('.page-btn');
77 if (!btn || btn.classList.contains('disabled') || btn.classList.contains('active')) return;
78 var page = parseInt(btn.getAttribute('data-page'));
79 if (page) goToPage(page);
80 });
81
82 goToPage(1);
83 }
84
85 return { init: init };
86})();
分页策略:
- 采用前端 JS 分页而非 Hugo 内置分页,因为归档页是单页模板(非 section list)
- 所有文章 DOM 都渲染到页面,JS 通过
data-hidden属性控制显隐 - 切换页面后自动滚动到列表顶部(
scrollTo+behavior: 'smooth') - 页码过多时自动省略中间页码(
1 ... 4 5 6 ... 10) - 文章不足一页时不显示分页器
2.3 分页器页码算法
1function getVisiblePages(current, total) {
2 if (total <= 7) return [1, 2, ..., total]; // 7页以内全部显示
3 if (current <= 3) return [1, 2, 3, 4, '...', total]; // 前部
4 if (current >= total-2) return [1, '...', total-3, total-2, total-1, total]; // 后部
5 return [1, '...', current-1, current, current+1, '...', total]; // 中部
6}
示例(共 10 页):
| 当前页 | 显示 |
|---|---|
| 1 | 1 2 3 4 ... 10 |
| 3 | 1 2 3 4 ... 10 |
| 5 | 1 ... 4 5 6 ... 10 |
| 8 | 1 ... 7 8 9 10 |
| 10 | 1 ... 7 8 9 10 |
🎨 三、样式设计
3.1 时间线
1.archives-timeline {
2 position: relative;
3 padding-left: 24px;
4}
5
6.archives-timeline::before {
7 content: '';
8 position: absolute;
9 left: 7px;
10 top: 4px;
11 bottom: 4px;
12 width: 2px;
13 background: linear-gradient(180deg, var(--accent-color) 0%, rgba(59,130,246,0.15) 100%);
14 border-radius: 2px;
15}
竖线使用渐变色,从顶部强调色渐变到底部淡蓝色,视觉上引导用户从新到旧浏览。
3.2 文章项
1.archive-item {
2 display: flex;
3 align-items: center;
4 gap: 16px;
5 padding: 12px 16px;
6 position: relative;
7 border-radius: 10px;
8 transition: all 0.25s ease;
9}
10
11.archive-item:hover {
12 background: var(--bg-secondary);
13 transform: translateX(4px);
14}
hover 时文章行向右微移 4px + 背景变色,提供交互反馈。
3.3 时间线圆点
1.archive-item-dot {
2 position: absolute;
3 left: -21px;
4 top: 50%;
5 transform: translateY(-50%);
6 width: 8px;
7 height: 8px;
8 background: var(--bg-card);
9 border: 2px solid var(--accent-color);
10 border-radius: 50%;
11 z-index: 2;
12 transition: all 0.25s ease;
13}
14
15.archive-item:hover .archive-item-dot {
16 background: var(--accent-color);
17 box-shadow: 0 0 8px rgba(59,130,246,0.4);
18 transform: translateY(-50%) scale(1.3);
19}
默认空心圆点(白底蓝边),hover 时填充蓝色 + 发光 + 放大 1.3 倍。
3.4 分类标签
1.archive-item-cat {
2 font-size: 0.7rem;
3 padding: 2px 9px;
4 border-radius: 6px;
5 background: linear-gradient(135deg, #e0f2fe, #dbeafe);
6 color: #0369a1;
7 border: 1px solid rgba(3,105,161,0.12);
8}
9
10.archive-item-cat:hover {
11 background: var(--accent-color);
12 color: #fff;
13}
14
15[data-theme="dark"] .archive-item-cat {
16 background: rgba(59,130,246,0.12);
17 color: rgba(96,165,250,0.9);
18}
3.5 分页器
1.archives-pagination .page-btn {
2 min-width: 36px;
3 height: 36px;
4 border: 1px solid var(--border-light);
5 border-radius: 8px;
6 background: var(--bg-card);
7 color: var(--text-secondary);
8 font-size: 0.85rem;
9 cursor: pointer;
10 transition: all 0.2s ease;
11}
12
13.archives-pagination .page-btn.active {
14 background: var(--accent-color);
15 color: #fff;
16 border-color: var(--accent-color);
17 box-shadow: 0 2px 8px rgba(59,130,246,0.3);
18}
19
20.archives-pagination .page-btn.disabled {
21 opacity: 0.4;
22 cursor: not-allowed;
23 pointer-events: none;
24}
3.6 移动端适配
1@media (max-width: 600px) {
2 .archive-item-title {
3 white-space: normal;
4 -webkit-line-clamp: 2; /* 标题允许两行 */
5 display: -webkit-box;
6 }
7 .archive-item-footer {
8 display: none; /* 隐藏分类标签 */
9 }
10 .archives-pagination .page-info {
11 display: none; /* 隐藏页码信息 */
12 }
13}
⚙️ 四、配置方法
4.1 分页大小
在 hugo.toml 中配置:
1[params.archives]
2 pageSize = 30 # 每页显示文章数,默认 30
修改后重新构建站点即可生效。
4.2 文章来源
归档页显示的文章由 mainSections 控制:
1[params]
2 mainSections = ["life", "tech", "smart"]
不在 mainSections 中的内容类型(如 demo、moments)不会出现在归档页。
4.3 排除特定文章
在文章的 Front Matter 中设置:
1title: "不想出现在归档页的文章"
2excludeFromList: true
4.4 分类显示名称
如果分类 slug 是英文但需要显示中文,使用 category-display-name.html partial 或在 hugo.toml 中配置 categoryLabels 映射。
🐛 五、常见问题排查
Q1: 分页器不显示
- 检查文章总数是否超过
pageSize,不足一页时不显示分页器 - 打开浏览器控制台,确认
ArchivePagination模块已初始化 - 检查
#archives-paginationDOM 是否存在
Q2: 切换页面后没有滚动到顶部
- 检查
archives-timeline容器的offsetTop是否正确 - 如果页面有固定导航栏,
scrollTo中减去了 100px 偏移量
Q3: 某些文章没有出现在归档页
- 检查文章的
Section是否在mainSections中 - 检查文章是否设置了
excludeFromList: true - 检查文章是否为草稿(
draft: true)
Q4: 分类标签显示为英文 slug
- 检查
category-display-name.htmlpartial 是否存在 - 检查
hugo.toml中categoryLabels映射是否配置
Q5: 移动端分类标签被隐藏了
这是设计意图。移动端屏幕宽度有限,隐藏分类标签让标题有更多空间。如需显示,修改 CSS:
1@media (max-width: 600px) {
2 .archive-item-footer {
3 display: flex; /* 改为显示 */
4 }
5}
📁 六、相关文件索引
| 文件 | 作用 |
|---|---|
layouts/_default/archives.html | 归档页模板(时间线 + 分页容器 + 分类/标签云) |
assets/js/main.js ArchivePagination 模块 | 前端分页逻辑 |
assets/css/main.css | 时间线 + 文章项 + 分页器样式 |
hugo.toml [params.archives] | 分页大小配置 |
hugo.toml [params] mainSections | 文章来源配置 |
content/pages/archives.md | 归档页内容文件 |
留言评论
期待你的想法评论加载中