在Hexo-fluid的分类页中增加说明

在Hexo-fluid的分类页中增加说明

整了个文章专栏,想要在分类页面写个说明,本以为很简单,却发现需要修改的是主题页面文件,要改的东西挺多。基本上都是AI指导实现,仅做记录。

本篇文章内容涉及hexo-fluid主题相关代码内容,点击跳转仓库地址

实现

一.思路描述

 由于分类页面属于通过EJS模板文件读取相关数据列表然后构建的,并非从md等文件加载,因此只能考虑修改主题页面模板文件。

 于是找到分类页面的主题模板文件categories.ejs首先进行简要分析:

  1. 页面元数据设置
1
2
3
4
5
6
page.layout = 'categories'
page.title = theme.category.title || __('category.title')
page.subtitle = theme.category.subtitle || __('category.subtitle')
page.banner_img = theme.category.banner_img
page.banner_img_height = theme.category.banner_img_height
page.banner_mask_alpha = theme.category.banner_mask_alpha

这部分设置了页面的基本属性:

  • 使用 ‘categories’ 布局
  • 标题和副标题从主题配置中获取,如果未配置则使用国际化翻译
  • 设置横幅图片、高度和遮罩透明度
  1. 获取分类数据
1
2
var orderBy = theme.category.order_by || 'name'
var curCats = site.categories.find({ parent: { $exists: false } }).sort(orderBy).filter(cat => cat.length)

这部分:

  • 获取分类排序方式(默认为按名称排序)
  • 查询所有顶级分类(没有父分类的分类)
  • 按指定方式排序
  • 过滤掉空分类(cat.length 检查分类下是否有文章)
  1. 渲染分类列表
1
2
3
4
5
6
7
8
<%- partial('_partials/category-list', {
curCats: curCats,
params: {
orderBy: orderBy,
postLimit : theme.category.post_limit,
postOrderBy: theme.category.post_order_by || config.index_generator.order_by
}
}) %>

这部分:

  • 引入 _partials/category-list 子模板
  • 传递当前分类列表 (curCats)
  • 传递参数包括:
    • 分类排序方式
    • 文章数量限制
    • 文章排序方式(默认使用全局配置)

 分析我们需要实现功能中需要加入的内容:由于我们的专栏介绍内容可能较多,于是考虑从额外的数据文件中读取数据,然后渲染(并且设置相关样式)。

 从数据文件中读取数据采用hexo提供的source/_data数据文件读取相关文件数据的功能。

 设置样式考虑使用CSS,在主题配置中直接引用css是全局性的,由于我们不想要全局性的,因此选择直接写在模板文件中。

二.详细操作

  1. 数据文件: 在项目的source/_data文件夹下新建文件column_descriptions.yml并且写下如下配置(下面的就是示例):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
columns:
- name: "例子1"
subtitle: "例子1小标题"
icon: "icon-computer" #示例图标,详细需要配合图标库
description: "例子1描述"
color: "#1890ff" ##示例颜色

- name: "例子2"
subtitle: "例子2小标题"
icon: "icon-life" #示例图标,详细需要配合图标库
description: "例子2描述"
color: "#f5222d" #示例颜色

# 后门还可以根据需要按照示例格式增加内容
  1. 样式文件:在source/css文件夹下新建文件custom.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
/* 专栏容器 */
.columns-container {
display: grid;
gap: 1.8rem;
margin: 2.5rem 0;
}

/* 单个专栏样式 */
.teach-column-desc {
--column-color: #1890ff; /* 默认颜色 */
--column-color-rgb: 24, 144, 255; /* 默认RGB值 */
--column-hover-color: #40a9ff; /* 悬停颜色 */

padding: 1.8rem;
border-radius: 12px;
border-left: 4px solid var(--column-color);
background:
radial-gradient(circle at 90% 10%, rgba(var(--column-color-rgb), 0.03) 0%, transparent 20%),
linear-gradient(to bottom, rgba(var(--column-color-rgb), 0.02), rgba(var(--column-color-rgb), 0.01)),
#ffffff;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
transition:
transform 0.35s ease,
box-shadow 0.35s ease,
border-color 0.3s ease;
position: relative;
overflow: hidden;
}

.teach-column-desc:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1);
border-left-color: var(--column-hover-color);
}

/* 添加微妙的背景图案 */
.teach-column-desc::before {
content: "";
position: absolute;
top: -20px;
right: -20px;
width: 80px;
height: 80px;
opacity: 0.05;
background-color: var(--column-color);
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='currentColor' d='M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z'/%3E%3C/svg%3E");
mask-repeat: no-repeat;
z-index: -1;
}

.desc-header {
display: flex;
align-items: flex-start;
margin-bottom: 1.2rem;
}

.desc-header i.iconfont {
font-size: 2.2rem;
margin-right: 15px;
flex-shrink: 0;
color: var(--column-color);
transition: color 0.3s ease;
}

.teach-column-desc:hover .desc-header i.iconfont {
color: var(--column-hover-color);
}

.desc-header h3 {
margin: 0 0 5px 0;
font-size: 1.45rem;
font-weight: 600;
color: var(--column-color);
transition: color 0.3s ease;
}

.teach-column-desc:hover .desc-header h3 {
color: var(--column-hover-color);
}

.subtitle {
margin: 0;
font-size: 0.95rem;
color: #666;
font-style: italic;
}

.desc-content p {
margin: 0;
line-height: 1.85;
font-size: 1.08rem;
color: #333;
position: relative;
padding-left: 1.8rem;
}

.desc-content p::before {
content: "";
position: absolute;
left: 0;
top: 0.6em;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--column-color);
opacity: 0.7;
transition: background 0.3s ease;
}

.teach-column-desc:hover .desc-content p::before {
background: var(--column-hover-color);
}

.highlight {
color: var(--column-color);
font-weight: 600;
position: relative;
display: inline-block;
margin-right: 5px;
transition: color 0.3s ease;
}

.teach-column-desc:hover .highlight {
color: var(--column-hover-color);
}

.highlight::after {
content: "";
position: absolute;
bottom: -2px;
left: 0;
width: 100%;
height: 2px;
background: currentColor;
opacity: 0.3;
border-radius: 2px;
transition: opacity 0.3s ease;
}

.teach-column-desc:hover .highlight::after {
opacity: 0.4;
}

/* 响应式设计 */
@media (max-width: 768px) {
.columns-container {
gap: 1.3rem;
}

.teach-column-desc {
padding: 1.3rem;
}

.desc-header {
flex-direction: column;
align-items: flex-start;
}

.desc-header i.iconfont {
margin-bottom: 10px;
}
}

/* 为不支持 mask-image 的浏览器提供回退 */
@supports not (mask-image: none) {
.teach-column-desc::before {
background: none;
}
}
  1. 修改模板文件:打开themes/fluid/layout文件夹,找到categories.ejs文件并打开。
  • 在文件开头第一行换行插入对css引用:
1
<link rel="stylesheet" href="/css/custom.css">
  • 第一对<%%>中的末尾新增如下内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 加载专栏配置文件
const columnData = site.data.column_descriptions;

// 十六进制转RGB函数(用于CSS变量)
function hexToRgb(hex) {
hex = hex.replace('#', '');
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return [r, g, b];
}
  • 两对<%%>的中间新增如下内容:
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
<% if (columnData && columnData.columns && columnData.columns.length) { %>
<div class="columns-container">
<% columnData.columns.forEach(column => {
const color = column.color || '#1890ff';
const rgb = hexToRgb(color);
%>
<div class="teach-column-desc"
style="
--column-color: <%- color %>;
--column-color-rgb: <%= rgb.join(',') %>;
">
<div class="desc-header">
<i class="iconfont icon-<%= column.icon || 'info-circle-fill' %>"></i>
<div>
<h3><%= column.name %>专栏说明</h3>
<% if (column.subtitle) { %>
<p class="subtitle"><%= column.subtitle %></p>
<% } %>
</div>
</div>
<div class="desc-content">
<p>
<strong class="highlight"><%= column.name %></strong>
<%= column.description %>
</p>
</div>
</div>
<% }) %>
</div>
<% } %>
  • 最后完整内容如下:
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
<!-- 直接引入分类页专属 CSS -->
<link rel="stylesheet" href="/css/custom.css">
<%
page.layout = 'categories'
page.title = theme.category.title || __('category.title')
page.subtitle = theme.category.subtitle || __('category.subtitle')
page.banner_img = theme.category.banner_img
page.banner_img_height = theme.category.banner_img_height
page.banner_mask_alpha = theme.category.banner_mask_alpha
var orderBy = theme.category.order_by || 'name'
var curCats = site.categories.find({ parent: { $exists: false } }).sort(orderBy).filter(cat => cat.length)
// 加载专栏配置文件
const columnData = site.data.column_descriptions;

// 十六进制转RGB函数(用于CSS变量)
function hexToRgb(hex) {
hex = hex.replace('#', '');
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return [r, g, b];
}
%>

<!-- 动态加载专栏说明 -->
<% if (columnData && columnData.columns && columnData.columns.length) { %>
<div class="columns-container">
<% columnData.columns.forEach(column => {
const color = column.color || '#1890ff';
const rgb = hexToRgb(color);
%>
<div class="teach-column-desc"
style="
--column-color: <%- color %>;
--column-color-rgb: <%= rgb.join(',') %>;
">
<div class="desc-header">
<i class="iconfont icon-<%= column.icon || 'info-circle-fill' %>"></i>
<div>
<h3><%= column.name %>专栏说明</h3>
<% if (column.subtitle) { %>
<p class="subtitle"><%= column.subtitle %></p>
<% } %>
</div>
</div>
<div class="desc-content">
<p>
<strong class="highlight"><%= column.name %></strong>
<%= column.description %>
</p>
</div>
</div>
<% }) %>
</div>
<% } %>

<%- partial('_partials/category-list', {
curCats: curCats,
params: {
orderBy: orderBy,
postLimit : theme.category.post_limit,
postOrderBy: theme.category.post_order_by || config.index_generator.order_by
}
}) %>

后记:

 这些都是通过AI学习后进行的操作,可能中间有所不妥,还请多多指教,这篇主要用来记录。
 这些修改都是基于主题fluid1.9.8版本进行的操作,新或旧版可能无效,请见谅。
 以上全部内容遵循GNU 通用公共许可证 v3.0

 

鸣心/Write

在Hexo-fluid的分类页中增加说明
https://b.wihi.top/posts/8de7287f.html
作者
鸣心
发布于
2025年7月29日
许可协议