8.主题魔改:添加“待办清单”独立页面
AI-摘要
Tianli GPT
AI初始化中...
介绍自己 🙈
生成本文简介 👋
推荐相关文章 📖
前往主页 🏠
前往爱发电购买
8.主题魔改:添加“待办清单”独立页面
Prorise8.主题魔改:添加“待办清单”独立页面
本指南将引导您为博客添加一个独立的“待办清单”页面。这个页面采用双栏布局,可以清晰地分类和展示您的各种待办事项。
警告: 这是一项涉及多个文件修改的“魔改”操作。在开始前,强烈建议您备份整个
themes/anzhiyu
文件夹,以便在出现问题时可以随时恢复。
第一步:创建并编辑您的待办事项数据
我们首先来准备清单页面的“内容数据”。
- 创建数据文件 (
todolist.yml
)
- 在您博客根目录的
source/_data/
文件夹内,新建一个名为todolist.yml
的文件。如果_data
文件夹不存在,请手动创建。
编辑
todolist.yml
文件- 将下面的模板完整复制到
todolist.yml
文件中,然后根据注释修改为您自己的待办事项。
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# seat: 控制该分类显示在左栏(left)还是右栏(right)
# completed: 控制该事项是否已完成 (true 表示已完成,会显示对勾和删除线)
- class_name: "学习计划"
seat: left
todo_list:
- content: "深入学习 Vue 3 组合式 API"
completed: false
- content: "完成一个完整的 Node.js 项目"
completed: true
- class_name: "想看的书"
seat: left
todo_list:
- content: "《代码整洁之道》"
completed: true
- content: "《你不知道的JavaScript》"
completed: false
- class_name: "想去的地方"
seat: right
todo_list:
- content: "日本,京都"
completed: false
- content: "中国,新疆"
completed: false
- class_name: "想买的东西"
seat: right
todo_list:
- content: "一把舒适的人体工学椅"
completed: false
- content: "降噪耳机"
completed: true- 将下面的模板完整复制到
第二步:创建新的页面布局与样式
现在,我们需要为这个新页面类型创建专属的模板和CSS样式。
创建 Pug 模板文件
- 文件路径:
themes/anzhiyu/layout/includes/page/todolist.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
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270#todolist-box
- let todo_background = page.top_background
.author-content.author-content-item.todolist.single(style=`${todo_background ? `background: url(${todo_background}) top / cover no-repeat;` : ""}`)
.card-content
.author-content-item-tips Todo
span.author-content-item-title 待办清单
.content-bottom
.tips
i.fa-solid.fa-quote-left.fa-fw
span 耽误太多时间,事情可就做不完了
i.fa-solid.fa-quote-right.fa-fw
.banner-button-group
a.banner-button(onclick='pjax.loadUrl("/about/")')
i.anzhiyufont.anzhiyu-icon-arrow-circle-right(style='font-size: 1.5rem')
span.banner-button-text 我的更多
#todolist-filter
.filter-title
i.fa-solid.fa-filter
span 分类筛选
.filter-buttons
button.filter-btn.active(data-filter="all") 全部
- let categories = []
each i in site.data.todolist
- if(!categories.includes(i.class_type)) categories.push(i.class_type || '未分类')
each category in categories
button.filter-btn(data-filter=category)= category
#todolist-main.todolist-grid
each i in site.data.todolist
- const categoryClass = i.class_type ? `todo-category-${i.class_type}` : 'todo-category-undefined'
.todolist-item(class=categoryClass, data-category=i.class_type || '未分类')
h3.todolist-title
//- 移除了旧的图标
span= i.class_name
.task-count
- let completedCount = 0
each item in i.todo_list
- if(item.completed) completedCount++
span= completedCount + '/' + i.todo_list.length
ul.todolist-ul
each item in i.todo_list
//- 我们只需要设置 class,CSS 会自动创建复选框样式
- var listItemClass = item.completed ? 'todolist-li-done' : 'todolist-li'
li(class=listItemClass)
span= item.content
//- 移除了进度条
#todolist-pagination
.pagination-container
button#prev-page.page-btn(disabled)
i.fa-solid.fa-angle-left
#page-numbers
button#next-page.page-btn(disabled)
i.fa-solid.fa-angle-right
//- 下方是实现所有交互功能所需的完整JavaScript代码
script.
// 防止重复初始化的标志
let todolistInitialized = false;
// Todolist 初始化函数
function initTodolist() {
// 防止重复初始化
if (todolistInitialized) return;
// 检查必要的DOM元素是否存在
const todolistBox = document.getElementById('todolist-box');
if (!todolistBox) return;
// 等待CSS加载完成
const checkCSSLoaded = () => {
const testElement = document.querySelector('.filter-btn');
if (!testElement) return false;
const computedStyle = window.getComputedStyle(testElement);
return computedStyle.cursor === 'pointer';
};
// 如果CSS未加载完成,延迟初始化
if (!checkCSSLoaded()) {
setTimeout(initTodolist, 100);
return;
}
console.log('Todolist 开始初始化...');
// ========================================================================
// 筛选与分页功能
// ========================================================================
const filterBtns = document.querySelectorAll('.filter-btn');
const todoItems = document.querySelectorAll('.todolist-item');
const itemsPerPage = 6;
let currentPage = 1;
let filteredItems = Array.from(todoItems);
// 检查必要元素是否存在
if (filterBtns.length === 0 || todoItems.length === 0) {
console.warn('Todolist: 未找到必要的DOM元素');
return;
}
function applyFilter(filter) {
filteredItems = Array.from(todoItems);
if (filter !== 'all') {
filteredItems = filteredItems.filter(item => item.getAttribute('data-category') === filter);
}
todoItems.forEach(item => { item.style.display = 'none'; });
setupPagination();
goToPage(1);
}
function setupPagination() {
const pageCount = Math.ceil(filteredItems.length / itemsPerPage);
const pageNumbersContainer = document.getElementById('page-numbers');
const paginationContainer = document.getElementById('todolist-pagination');
if (!pageNumbersContainer || !paginationContainer) return;
pageNumbersContainer.innerHTML = '';
if (pageCount <= 1) {
paginationContainer.style.display = 'none';
showItems(filteredItems);
return;
}
paginationContainer.style.display = 'flex';
for (let i = 1; i <= pageCount; i++) {
const btn = document.createElement('button');
btn.classList.add('page-number');
if (i === 1) btn.classList.add('active');
btn.textContent = i;
btn.addEventListener('click', () => goToPage(i));
pageNumbersContainer.appendChild(btn);
}
const prevBtn = document.getElementById('prev-page');
const nextBtn = document.getElementById('next-page');
if (prevBtn) prevBtn.disabled = true;
if (nextBtn) nextBtn.disabled = pageCount <= 1;
}
// 分页按钮事件监听
const prevBtn = document.getElementById('prev-page');
const nextBtn = document.getElementById('next-page');
if (prevBtn) {
prevBtn.addEventListener('click', () => {
if (currentPage > 1) goToPage(currentPage - 1);
});
}
if (nextBtn) {
nextBtn.addEventListener('click', () => {
const pageCount = Math.ceil(filteredItems.length / itemsPerPage);
if (currentPage < pageCount) goToPage(currentPage + 1);
});
}
function goToPage(page) {
const startIndex = (page - 1) * itemsPerPage;
const endIndex = startIndex + itemsPerPage;
const itemsToShow = filteredItems.slice(startIndex, endIndex);
const pageButtons = document.querySelectorAll('.page-number');
pageButtons.forEach((btn, index) => btn.classList.toggle('active', index + 1 === page));
const prevBtn = document.getElementById('prev-page');
const nextBtn = document.getElementById('next-page');
if (prevBtn) prevBtn.disabled = page === 1;
if (nextBtn) nextBtn.disabled = page === Math.ceil(filteredItems.length / itemsPerPage);
todoItems.forEach(item => { item.style.display = 'none'; });
showItems(itemsToShow);
currentPage = page;
}
function showItems(items) {
items.forEach(item => { item.style.display = 'block'; });
}
// 筛选按钮事件监听
filterBtns.forEach(btn => {
btn.addEventListener('click', function() {
filterBtns.forEach(b => b.classList.remove('active'));
this.classList.add('active');
applyFilter(this.getAttribute('data-filter'));
});
});
// ========================================================================
// 清单项目的交互功能
// ========================================================================
const allListItems = document.querySelectorAll('.todolist-ul li');
allListItems.forEach(li => {
li.addEventListener('click', function() {
// 切换被点击项目的视觉状态
this.classList.toggle('todolist-li-done');
// 找到父级卡片以更新其计数器
const parentCard = this.closest('.todolist-item');
if (parentCard) {
// 在此卡片内重新计算任务数量
const tasksInCard = parentCard.querySelectorAll('.todolist-ul li');
const completedTasksInCard = parentCard.querySelectorAll('.todolist-ul li.todolist-li-done');
const totalCount = tasksInCard.length;
const completedCount = completedTasksInCard.length;
// 更新计数器文本
const counterSpan = parentCard.querySelector('.task-count span');
if (counterSpan) {
counterSpan.textContent = `${completedCount}/${totalCount}`;
}
}
});
});
// 初始化加载
setupPagination();
goToPage(1);
// 标记为已初始化
todolistInitialized = true;
console.log('Todolist 初始化完成');
}
// 多种初始化触发方式
// 1. DOM加载完成时
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTodolist);
} else {
// 如果DOM已经加载完成,立即初始化
setTimeout(initTodolist, 50);
}
// 2. PJAX完成时
document.addEventListener('pjax:complete', () => {
todolistInitialized = false; // 重置标志
setTimeout(initTodolist, 100); // 延迟初始化以确保DOM更新完成
});
// 3. CSS加载完成时
document.addEventListener('todolist-css-loaded', () => {
if (!todolistInitialized) {
setTimeout(initTodolist, 50);
}
});
// 4. 页面完全加载时(备用)
window.addEventListener('load', () => {
if (!todolistInitialized) {
setTimeout(initTodolist, 200);
}
});
// 5. 兼容性检查 - 如果其他方式都失败了,定期检查
let retryCount = 0;
const maxRetries = 10;
const retryInterval = setInterval(() => {
if (todolistInitialized || retryCount >= maxRetries) {
clearInterval(retryInterval);
return;
}
if (document.querySelector('#todolist-box')) {
initTodolist();
}
retryCount++;
}, 500);- 文件路径:
创建 CSS 样式文件
- 文件路径:
themes/anzhiyu/source/custom/css/todolist.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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327/* Todolist 现代化扁平设计 - v4.0 */
/* ==========================================================================
基础布局与容器
========================================================================== */
body[data-type="todolist"] #web_bg {
background: var(--anzhiyu-background);
}
body[data-type="todolist"] #page {
border: 0;
box-shadow: none ;
padding: 0 ;
background: transparent ;
}
body[data-type="todolist"] #page .page-title {
display: none;
}
#todolist-box {
margin: 0 auto;
max-width: 1200px;
padding: 0 15px;
}
/* 顶部内容区 */
.author-content.todolist {
margin-bottom: 20px;
}
.author-content .tips {
display: flex;
align-items: center;
gap: 0.5rem;
color: var(--anzhiyu-fontcolor);
opacity: 0.75;
}
.author-content .tips i {
color: var(--anzhiyu-theme);
font-size: 0.8rem;
}
/* ==========================================================================
筛选器
========================================================================== */
#todolist-filter {
margin: 20px 0;
padding: 15px;
background: var(--anzhiyu-card-bg);
border-radius: 12px;
box-shadow: var(--anzhiyu-shadow-border);
}
.filter-title {
display: flex;
align-items: center;
gap: 0.5rem;
margin-bottom: 12px;
color: var(--anzhiyu-fontcolor);
font-weight: 600;
}
.filter-title i {
color: var(--anzhiyu-theme);
}
.filter-buttons {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.filter-btn {
background: var(--anzhiyu-card-bg);
border: 1px solid var(--anzhiyu-card-border);
color: var(--anzhiyu-fontcolor);
border-radius: 8px; /* 更方正的圆角 */
padding: 6px 15px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 0.9rem;
/* 确保按钮可点击 */
pointer-events: auto;
user-select: none;
}
.filter-btn:hover {
transform: translateY(-2px);
border-color: var(--anzhiyu-theme);
background: var(--anzhiyu-card-bg);
}
.filter-btn.active {
background: var(--anzhiyu-theme);
color: white;
border-color: var(--anzhiyu-theme);
}
/* ==========================================================================
主体布局与卡片 (核心重构区域)
========================================================================== */
#todolist-main {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin: 20px 0;
}
/* 卡片容器 - 简化 */
.todolist-item {
background: var(--anzhiyu-card-bg);
border-radius: 12px;
padding: 20px 25px;
box-shadow: var(--anzhiyu-shadow-border);
transition: all 0.3s ease;
animation: fadeIn 0.5s ease forwards;
}
.todolist-item:hover {
transform: translateY(-5px);
box-shadow: var(--anzhiyu-shadow-main);
}
/* 标题样式 - 简化 */
h3.todolist-title {
margin: 0 0 1rem 0 ;
padding-bottom: 0.8rem;
font-size: 1.2rem;
color: var(--anzhiyu-fontcolor);
border-bottom: 1px solid var(--anzhiyu-card-border);
display: flex;
align-items: center;
justify-content: space-between;
}
/* 隐藏原标题图标 */
h3.todolist-title i {
display: none;
}
/* 任务计数器 */
.task-count {
background: var(--anzhiyu-secondbg);
color: var(--anzhiyu-fontcolor);
padding: 2px 8px;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 500;
}
/* 移除进度条 */
.progress-bar {
display: none;
}
/* ==========================================================================
清单列表项 (全新 Checkbox 样式)
========================================================================== */
.todolist-ul {
margin: 0;
padding: 0;
}
.todolist-ul li {
list-style: none;
padding: 10px 0;
margin: 0;
display: grid;
grid-template-columns: 20px auto; /* 复选框 | 文本 */
align-items: center;
gap: 15px;
cursor: pointer;
position: relative;
/* 确保列表项可点击 */
pointer-events: auto;
user-select: none;
}
/* 隐藏原列表图标 */
.todolist-ul li i {
display: none;
}
/* 任务文本 */
.todolist-ul li span {
color: var(--anzhiyu-fontcolor);
position: relative;
transition: color 0.3s ease;
line-height: 1.5;
}
/* 使用 ::before 伪元素创建自定义复选框的“框” */
.todolist-ul li::before {
content: "";
width: 16px;
height: 16px;
border: 2px solid var(--anzhiyu-fontcolor);
border-radius: 4px;
transition: all 0.3s ease;
position: relative;
}
/* 使用 ::after 伪元素创建自定义复选框的“勾” */
.todolist-ul li::after {
content: "";
position: absolute;
left: 6px;
top: 13px;
width: 4px;
height: 8px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
opacity: 0;
transition: opacity 0.3s ease;
}
/* ==========================================================================
已完成状态 (交互动画)
========================================================================== */
.todolist-ul li.todolist-li-done span {
color: var(--anzhiyu-secondtext);
}
/* 复选框(框)的完成状态 */
.todolist-ul li.todolist-li-done::before {
background: var(--anzhiyu-blue);
border-color: var(--anzhiyu-blue);
animation: check-box 0.3s ease;
}
/* 复选框(勾)的完成状态 */
.todolist-ul li.todolist-li-done::after {
opacity: 1;
}
/* 文本删除线动画 */
.todolist-ul li span::before {
content: "";
position: absolute;
top: 50%;
left: 0;
width: 0;
height: 2px;
background: var(--anzhiyu-secondtext);
transition: width 0.4s ease;
}
.todolist-ul li.todolist-li-done span::before {
width: 100%;
}
/* ==========================================================================
分页与响应式
========================================================================== */
#todolist-pagination {
display: flex;
justify-content: center;
margin: 30px 0;
}
.pagination-container {
display: flex;
align-items: center;
gap: 5px;
}
.page-btn, .page-number {
min-width: 36px;
height: 36px;
border: 1px solid var(--anzhiyu-card-border);
background: var(--anzhiyu-card-bg);
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
color: var(--anzhiyu-fontcolor);
}
.page-btn:hover:not(:disabled), .page-number:hover:not(.active) {
border-color: var(--anzhiyu-theme);
color: var(--anzhiyu-theme);
}
.page-number.active {
background: var(--anzhiyu-theme);
color: white;
border-color: var(--anzhiyu-theme);
}
.page-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}
#page-numbers {
display: flex;
gap: 5px;
}
@media screen and (max-width: 768px) {
#todolist-box {
margin: 0;
padding: 0 10px;
}
.todolist-item {
padding: 15px;
}
h3.todolist-title {
font-size: 1.1rem;
}
.filter-buttons {
overflow-x: auto;
padding-bottom: 5px;
flex-wrap: nowrap;
}
}
/* ==========================================================================
动画定义
========================================================================== */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes check-box {
0% { transform: scale(0.8); }
50% { transform: scale(1.1); }
100% { transform: scale(1); }
}- 文件路径:
第三步:让主题识别“待办清单”页面类型
打开主布局文件:
themes/anzhiyu/layout/page.pug
添加
when 'todolist'
:在case page.type
的逻辑判断中,添加一个新的分支,告诉主题当遇到type
为todolist
的页面时,应该使用我们刚刚创建的todolist.pug
模板来渲染。修改指引:
1
2
3
4
5
6when 'music'
include includes/page/music.pug
+ when 'todolist'
+ include includes/page/todolist.pug
default
include includes/page/default-page.pug(您只需在
when 'music'
代码块的下方,添加那两行以+
开头的代码即可)
第四步:创建并配置“待办清单”页面本身
执行创建页面命令
- (如果之前没创建过)在终端运行:
1
hexo new page todolist
修改页面 Front-matter
- 打开
source/todolist/index.md
文件。 - 确保其
type
为"todolist"
。
1
2
3
4
5
6
7
8
title: 待办清单
date: 2025-06-14 12:00:00
type: "todolist"
top_background: "/img/background.png" # 可选,页面顶部背景图
comments: false
aside: false- 打开
第五步:在主题配置中注入新样式
- 打开主题配置文件 (
themes/anzhiyu/_config.yml
)。 - 找到
inject:
配置项,在head:
列表中添加我们新建的CSS文件。
1 | inject: |
3**.菜单项更新**
1 | 个人: |
评论
隐私政策
✅ 你无需删除空行,直接评论以获取最佳展示效果