注意版本:框架 Hexo 7.3.0|主题 Butterfly 5.4.3

需求:构建一个文学列表栏目,根据不同类别(三行诗集、诗词歌赋等),每个栏目可以有多个作品展示(类似首页,每页展示10条,数据为标题、时间、描述、作者、图片等)。

具体效果可参照我的博客: ”文学 - 诗词歌赋“

注意事项

Front-matter 必须严格格式(md文件):

1
2
3
4
5
---
title: 春日偶得
type: poems
date: 2025-04-01
---

对于脚本的放置位置

  • 放在项目根 scripts(推荐用于全局逻辑)

  • 放在主题 scripts(推荐用于主题专属逻辑)

我是放在主题butterfly之下 themes/butterfly/scripts/custom/

对于模板文件的配置

三个地方必须完全一致:

  1. Front-matter: layout: custom/literature
  2. Generator: layout: ['custom/literature']
  3. 文件路径: layout/custom/literature.pug

如果没有加载到模板,会自动用 archive 模板(时间轴形式)

对于路径的配置

  • Generator 中的 basePath,必须和你的文件路径 完全一致!如果文件路径是 literature/poems/,但 basePathliterature/three-line-poems/ 会导致路由不匹配,找不到模板。

  • 路径不能有符号,如:我之前 literature/three-line-poems/ ,存在符号导致一直路由不到自定义模板中

其他文件异常

1
2
3
// Cannot read properties of undefined (reading 'length')
// 可以在报错的指定行,添加判断条件 && page.categories.data
if theme.post_meta.post.categories && page.categories && page.categories.data && page.categories.data.length > 0

项目结构

hexo-blog/
├── source/
│ ├── _posts/文学/
│ │ ├── 春日偶得.md ← 包含 type: three-line-poem
│ │ └── 夜读有感.md
│ └── literature/
│ └──poems/
│ └── index.md ← layout: custom/literature
├── themes/
│ └── butterfly/
│ ├── layout/custom/
│ │ └── literature.pug
│ ├── scripts/custom/
│ │ └──poems.js ← layout: custom/literature
│ ├── source/css/custom/
│ │ └──literature.css
└── _config.yml

前提条件

  1. 你已安装 Hexo 并创建了博客项目。

  2. 已安装 hexo-pagination 插件(用于分页逻辑):

    1
    npm install hexo-pagination --save
  3. 使用 Pug 作为模板引擎(默认是 EJS,需配置):

    1
    npm install hexo-renderer-pug --save
  4. _config.yml 中设置主题:

    1
    theme: butterfly

创建自定义页面

注意:框架会自动创建该文件。你也可以自己手动创建,指定相关信息。

创建页面文件(列表):在 source/literature/poems/index.md

1
2
3
4
5
6
7
---
title: 三行诗集
layout: custom/literature
pagination: true
per_page: 5
type: three-line-poem
---
  • layout: custom/literature → 指定使用自定义模板(模板所在位置:路径/文件名

  • per_page: 5 → 每页显示5首诗

  • type: three-line-poem → 自定义类型,用于筛选文章

准备文章数据

source/_posts/文学/春日偶得.md

1
2
3
4
5
6
7
8
9
10
11
---
title: 春日偶得
date: 2025-04-01
categories: [文学, 诗集]
tags: [三行情诗, 春日偶得]
type: three-line-poem
---

春风拂面不须酒,
落花随步即成诗。
心静自然天地宽。

source/_posts/文学/夜读有感.md

1
2
3
4
5
6
7
8
9
10
11
---
title: 夜读有感
date: 2025-04-02
categories: [文学, 诗集]
tags: [三行情诗, 夜读有感]
type: three-line-poem
---

灯下翻书夜已深,
字里行间见古人。
合卷方知月照襟。

所有你想在 /literature/poems 页面展示的诗,都加上 type: three-line-poem

路径可以随意,如: source/_posts/test/test.md

创建分页模板(pug)

在主题目录( themes/butterfly/layout/)下创建模板(模板一定要与 hexo-pagination 版本对应,否则会出错,目前是4.0.0)

themes/butterfly/layout/custom/literature.pug

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
extends ../includes/layout.pug

block content
style.
@import url('/css/custom/literature.css');

.poetry-page
.container
.poetry-header
h1= page.title || '诗集'

- var poemPosts = page.posts || [];
if poemPosts.length === 0
.no-poems
p 📖 暂无诗歌,敬请期待...
else
.poem-container
each post in poemPosts
.poem-card
.card-header
h3.poem-title= post.title
if post.author
p.poem-author 【#{post.author}】
.poem-meta
if post.date && post.address
span #{new Date(post.date).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })} • #{post.address}
else if post.date
span #{new Date(post.date).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' })}
else if post.address
span #{post.address}
if post.description
p.poem-description= post.description

.poem-body
.poem-content
-
var contentText = post.content || '';
var cleanText = contentText.replace(/<[^>]*>/g, '').replace(/\s*\n\s*/g, '\n').trim();
var lines = cleanText.split('\n').filter(line => line.trim() !== '');
if lines.length > 0
each line, i in lines
- var cleanLine = line.trim().replace(/^(《[^》]*》\s*)/, '');
if cleanLine
p.poem-line= cleanLine
else
p.poem-line(style='text-align: center; color: #999;') 无诗句内容

.card-footer
a.read-more(href=url_for(post.path)) 📖 阅读全文

if page.total > 1
.pagination-wrap
.pagination-title
| 第 #{page.current} 页,共 #{page.total} 页
.pagination
if page.prev && page.prev > 0
a.page-link.prev(href=url_for(page.prev_link))
span.page-nav &laquo; 上一页
else
span.page-link.disabled
span.page-nav &laquo; 上一页

- for (let i = 1; i <= page.total; i++)
if i === page.current
span.page-link.current= i
else
- var pageLink = i === 1 ? page.base : page.base + 'page/' + i;
a.page-link(href=url_for(pageLink))= i

if page.next && page.next > 0
a.page-link.next(href=url_for(page.next_link))
span.page-nav 下一页 &raquo;
else
span.page-link.disabled
span.page-nav 下一页 &raquo;

如果你希望更精确控制“诗句预览”,可以在生成器或模板中预处理。

每首诗显示为一张精美卡片:

┌──────────────────────────────────────┐
│ 《春日偶得》 │ ← 渐变标题栏
│ 余一叶知秋尽 │
│ 己亥年正月 • 老家 ├
│ 春日散步偶得小诗,心随景动。 │ ← 描述(斜体灰色)
│ │
│ 春风拂面不须酒, │ ← 第一行(橙色)
│ 落花随步即成诗。 │ ← 第二行(蓝色)
│ 心静自然天地宽。 │ ← 第三行(绿色加粗)
├──────────────────────────────────────┤
│ 📖 阅读全文 │ ← 右下角按钮
└──────────────────────────────────────┘

  • 网格自适应(PC 3列,平板 2列,手机 1列)
  • 鼠标悬停轻微上浮 + 阴影加深
  • 点击标题或“阅读全文”进入详情页
  • 分页导航 + 页码跳转

如果显示:未提取到诗句内容

可能问题:你的Generator 与 模板中的数据不对应,获取数据的属性不同,或者缺少文章

添加样式(CSS)

在主题目录( themes/butterfly/source/)下创建样式 themes\butterfly\source\css\custom\literature.css

你可以根据自己的需求,随意更改样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
.poetry-page {
padding: 2rem 0;
background: #f8f9fa;
}

.poetry-header {
text-align: center;
margin: 2rem 0;
padding: 1.5rem;
}

.poetry-header h1 {
font-size: 2.2rem;
color: #2c3e50;
margin: 0;
font-weight: 600;
}

.poem-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 2rem;
padding: 0 2rem;
max-width: 1200px;
margin: 0 auto;
}

@media (max-width: 768px) {
.poem-grid {
grid-template-columns: 1fr;
padding: 0 1rem;
}
}

.poem-card {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
overflow: hidden;
transition: all 0.3s ease;
border: 1px solid #eee;
}

.poem-card:hover {
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}

.card-header {
padding: 1.5rem 1.5rem 1rem 1.5rem;
border-bottom: 1px solid #f0f0f0;
}

.poem-title {
font-size: 1.3rem;
font-weight: 600;
margin: 0 0 0.5rem 0;
color: #2c3e50;
line-height: 1.4;
}

.poem-meta {
display: flex;
flex-wrap: wrap;
gap: 1rem;
font-size: 0.85rem;
color: #7f8c8d;
}

.poem-body {
padding: 1.5rem;
}

.poem-content {
margin: 1rem 0;
}

.poem-line {
text-align: center;
font-size: 1.1rem;
line-height: 1.8;
margin: 0.4rem 0;
padding: 0.3rem 0;
font-family: "STKaiti", "KaiTi", serif;
}

.poem-line:nth-child(1) {
color: #e74c3c;
}

.poem-line:nth-child(2) {
color: #3498db;
}

.poem-line:nth-child(3) {
color: #27ae60;
font-weight: 500;
}

.card-footer {
padding: 1rem 1.5rem;
background: #f8f9fa;
text-align: right;
border-top: 1px solid #eee;
}

.read-more {
color: #5d8aa8;
text-decoration: none;
font-size: 0.9rem;
font-weight: 500;
}

.read-more:hover {
color: #2c5aa0;
text-decoration: underline;
}

.no-poems {
text-align: center;
padding: 3rem 2rem;
color: #666;
font-size: 1.1rem;
}

然后在模板中引入即可: @import url('/css/custom/literature.css');

添加评论(giscus)

添加 hexo 默认提供的评论样板,在自定义模板下面添加:

1
2
3
4
// giscus 评论系统(从配置中读取参数)
if page.comments !== false && theme.comments.use
- var commentsJsLoad = true
!=partial('../includes/third-party/comments/index', {}, {cache: true})

添加护眼模式(CSS)

在主题目录( themes/butterfly/source/)下创建样式 themes\butterfly\source\css\custom\dark_mode.css

你可以根据自己的需求,随意更改样式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
.poetry-page {
padding: 2.5rem 0;
background: var(--card-bg);
}

.poetry-header h1 {
color: var(--font-color);
}

.poem-container {
column-count: 2;
column-gap: 3rem;
padding: 0 4rem;
max-width: 1400px;
margin: 0 auto;
}

.poem-card {
break-inside: avoid;
background: var(--card-bg);
border-radius: 18px;
box-shadow: var(--card-box-shadow);
border: 1px solid var(--line-color);
margin-bottom: 2.5rem;
}

.poem-card:hover {
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.12);
}

.card-header {
padding: 2rem;
border-bottom: 1px solid var(--line-color);
}

.poem-title {
font-size: 1.5rem;
font-weight: 600;
margin: 0 0 1rem 0;
color: var(--font-color);
text-align: center;
}

.poem-description {
font-style: italic;
color: var(--font-second-color);
margin: 0 0 1.2rem 0;
padding: 1rem;
background: var(--code-bg);
/* 使用代码背景色作为描述背景 */
border-radius: 10px;
border-left: 4px solid var(--theme-color);
text-align: center;
}

.poem-author {
text-align: center;
color: var(--font-color);
font-weight: 500;
margin: 0.8rem 0 0.5rem 0;
}

.poem-meta {
text-align: right;
color: var(--font-second-color);
margin: 0;
}

.poem-line {
text-align: center;
font-size: 1.2rem;
line-height: 1.8;
margin: 0.6rem 0;
color: var(--font-color);
}

.card-footer {
padding: 1.2rem 2rem;
background: var(--card-bg);
text-align: right;
border-top: 1px solid var(--line-color);
}

.read-more {
color: var(--theme-color);
text-decoration: none;
font-weight: 500;
padding: 0.5rem 1.2rem;
border: 1px solid var(--theme-color);
border-radius: 25px;
}

.read-more:hover {
background: var(--theme-color);
color: var(--white);
}

.pagination-wrap {
text-align: center;
margin: 4rem 0 3rem 0;
padding: 2.5rem 0;
background: var(--card-bg);
border-top: 1px solid var(--line-color);
border-bottom: 1px solid var(--line-color);
}

.pagination {
background: var(--card-bg);
box-shadow: var(--card-box-shadow);
}

.page-link {
border: 2px solid var(--line-color);
color: var(--font-color);
background: var(--card-bg);
}

.page-link:hover:not(.disabled):not(.current) {
background: var(--second-bg);
border-color: var(--theme-color);
color: var(--theme-color);
}

.page-link.current {
background: var(--theme-color);
color: var(--white);
border-color: var(--theme-color);
}

.page-link.disabled {
color: var(--font-third-color);
background: var(--card-bg);
border-color: var(--line-color);
}

1、在模板中引入该样式: @import url('/css/custom/dark_mode.css');

2、自定义模板必须 正确继承 Butterfly 的 layout,这样才能获得护眼模式的 CSS 和 JS 支持(extends ../includes/layout.pug

注入分页数据(Generator)

你需要在主题的脚本或插件中为该页面生成分页器。在主题的 scripts/custom 目录创建 three-line-poem.js

注意: 此处的 register generators名称 “three-line-poem”,必须保持唯一,否则会与其他冲突而不起效果。

插件的使用 参照 Git 文档,配置错误会导致加载不到模板,一直使用默认的。

你可以随意在 processedPosts 中添加任意返回值,然后在pug模板中获取展示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
// themes/butterfly/scripts/three-line-poem-pagination.js
const pagination = require('hexo-pagination');

hexo.extend.generator.register('three-line-poem', function(locals) {

const { config } = this;
const { posts } = locals;

// 筛选 type: poems 的文章
const poemPosts = posts
.sort('-date')
.filter(post => post.type === 'three-line-poem');

const processedPosts = poemPosts.map(post => {
return {
title: post.title,
author: post.author,
date: post.date,
content: post.content,
description: post.description,
}
});

const basePath = 'literature/poems/';

const paginated = pagination(basePath, processedPosts, {
perPage: config.theme.per_page || 10,
layout: ["custom/literature"], // ← 和你的模板路径一致 , 'index', 'archive'
format: 'page/%d/',
data: {
title: "三行情诗",
tag: "三行情诗",
}
});

return paginated;
});

这个脚本会在生成时为 /literature/poems/ 创建分页,并注入 paginator 对象到模板上下文。

在脚本中加 console.log, 验证脚本是否被加载

在主题配置(themes/butterfly/_config.yml)中添加:per_page: 10,这样你可以统一控制每页数量。

生成并预览

1
2
3
4
5
6
7
8
9
10
# 执行
hexo clean & hexo g & hexo s
# 运行生成后,检查输出文件:
# public/literature/poems/index.html
# public/literature/poems/page/2/index.html
hexo g --debug > debug.log 2>&1

# 预览
http://localhost:4000/literature/poems/
http://localhost:4000/literature/poems/page/2/
  • 访问 /literature/poems/ 显示第一页诗歌列表(每页10首)。

  • 底部有“上一页 / 页码 / 下一页”导航。

  • 支持输入页码跳转。

  • 每首诗标题可点击进入详情页。

  • 响应式、语义化 Pug 结构。

image-20250920181655896

隐藏文章

如果需要在首页或其他栏目隐藏,可以使用 插件 hexo-hide-posts,支持在特定标签显示或隐藏指定文章。

详细教程参考 插件Github说明博客

如我的配置,其他地方隐藏,只在 “文学-三行诗集” 栏目显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
hide_posts:
enable: true # 是否启用 hexo-hide-posts
filter: hidden # 隐藏文章的 front-matter 标识,也可以改成其他你喜欢的名字
noindex: false # 为隐藏的文章添加 noindex meta 标签,阻止搜索引擎收录
# 设置白名单,白名单中的 generator 可以访问隐藏文章
# 常见的 generators 有:index, tag, category, archive, sitemap, feed, etc.
# allowlist_generators: ['*']
allowlist_generators: ['three-line-poem']

# 设置黑名单,黑名单中的 generator 不可以访问隐藏文章
# 如果同时设置了黑名单和白名单,白名单的优先级更高
# blocklist_generators: ['*']
blocklist_generators: ['index', 'tag', 'archive']