实现功能:
标签筛选 Waline 评论区 显示每条 moment 评论数量
Step 1 - 添加 moments 页面模板
layouts/moments/list.html
html
html
1{{ define "main" }}
2
3 <!-- 如果需要引入 moments.css,请保持路径一致或根据自己项目结构调整 -->
4 {{ $css := resources.Get "css/extended/moments.css" | minify | fingerprint }}
5 <link
6 crossorigin="anonymous"
7 href="{{ $css.RelPermalink }}"
8 integrity="{{ $css.Data.Integrity }}"
9 rel="stylesheet"
10 />
11
12 {{ $dateformat := .Params.DateFormat }}
13
14 <article class="post-single">
15 <header class="page-header">
16 <!-- 可根据需求添加页面标题、描述等 -->
17 <!-- <h1>{{ .Title }}</h1> -->
18 </header>
19
20 <div class="tags-filter">
21 <ul>
22 <li><a href="#" class="tag-filter all-tags">全部</a></li> <!-- "全部"选项 -->
23 {{ $tags := slice }} <!-- 用于存储所有标签 -->
24
25 {{ range .Pages }}
26 {{ range .Params.tags }}
27 {{ if not (in $tags .) }}
28 {{ $tags = $tags | append . }}
29 {{ end }}
30 {{ end }}
31 {{ end }}
32
33 <!-- 按字母顺序排序标签 -->
34 {{ $tags = $tags | sort }}
35
36 {{ range $tags }}
37 <li><a href="#" class="tag-filter">{{ . }}</a></li> <!-- 标签项 -->
38 {{ end }}
39 </ul>
40 </div>
41
42 <div class="post-content">
43 <div class="moments-list">
44 {{ range .Pages }}
45 {{ if .Content }}
46 <!-- 卡片容器 -->
47 <div class="moment-card">
48 <!-- 头部:头像 + 作者名 -->
49 <div class="moment-header">
50 <div class="left-content">
51 <img
52 src="{{ site.Params.label.avatar }}"
53 alt="{{ site.Params.author }}"
54 class="moment-avatar"
55 >
56 <span class="moment-author">
57 {{ site.Params.author }}
58 </span>
59 </div>
60 </div>
61
62 <!-- 动态主体内容(Hugo 渲染后的 .Content) -->
63 <div class="moment-body">
64 <div class="moment-content-wrapper">
65 {{ .Content | safeHTML }}
66 </div>
67 <div class="moment-loading">
68 <div class="loading-spinner"></div>
69 <div class="skeleton-content">
70 <div class="skeleton-line" style="width: 90%"></div>
71 <div class="skeleton-line" style="width: 75%"></div>
72 <div class="skeleton-line" style="width: 60%"></div>
73 </div>
74 </div>
75 <div class="moment-error" style="display: none;">
76 <span>图片加载失败</span>
77 <button onclick="retryLoad(this)">重试</button>
78 </div>
79 </div>
80
81 <!-- 标签(如果有) -->
82 {{ if .Params.tags }}
83 <div class="moment-tags">
84 {{ range $tag := .Params.tags }}
85 <span class="moment-tag">{{ $tag }}</span>
86 {{ end }}
87 </div>
88 {{ end }}
89
90 <!-- 底部:时间 + 评论按钮 -->
91 <div class="moment-bottom">
92 <div class="moment-time">
93 <span>
94 {{ .Param "date" | time.Format (default site.Params.DateFormat $dateformat) }}
95 </span>
96 </div>
97 <!-- 如果没有 hideComment 参数,则显示评论按钮 -->
98 {{ if not (.Param "hideComment") }}
99 <button
100 class="moment-comment-btn"
101 onclick="showComment(this)"
102 data-slug="{{ .Param "slug" }}"
103 data-path="{{ .Param "slug" }}"
104 >
105 <!-- 评论图标 SVG -->
106 <svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M281.535354 387.361616c-31.806061 0-57.664646 26.763636-57.664647 59.733333 0 32.969697 25.858586 59.733333 57.664647 59.733334s57.664646-26.763636 57.664646-59.733334c0-33.09899-25.858586-59.733333-57.664646-59.733333z m230.529292 0c-31.806061 0-57.664646 26.763636-57.664646 59.733333 0 32.969697 25.729293 59.733333 57.664646 59.733334 31.806061 0 57.535354-26.763636 57.535354-59.733334 0-33.09899-25.858586-59.733333-57.535354-59.733333z m230.4 0c-31.806061 0-57.664646 26.763636-57.664646 59.733333 0 32.969697 25.858586 59.733333 57.664646 59.733334s57.664646-26.763636 57.664647-59.733334c-0.129293-33.09899-25.858586-59.733333-57.664647-59.733333z m115.2-270.222222H166.335354c-63.612121 0-115.2 53.527273-115.2 119.59596v390.981818c0 65.939394 52.751515 126.836364 117.785858 126.836363h175.579798c30.513131 32.581818 157.220202 149.979798 157.220202 149.979798 5.559596 5.818182 14.739394 5.818182 20.29899 0 0 0 92.832323-91.410101 153.212121-149.979798h179.717172c65.034343 0 117.785859-60.89697 117.785859-126.836363V236.606061c0.129293-65.939394-51.458586-119.466667-115.070708-119.466667z m57.535354 510.577778c0 32.969697-27.668687 67.620202-60.250505 67.620202H678.335354c-21.462626 0-40.727273 21.979798-40.727273 21.979798l-124.121212 114.941414-124.121212-114.941414s-23.660606-21.979798-43.830303-21.979798H168.921212c-32.581818 0-60.250505-34.650505-60.250505-67.620202V236.606061c0-32.969697 25.729293-59.733333 57.664647-59.733334h691.329292c31.806061 0 57.535354 26.763636 57.535354 59.733334v391.111111z m0 0"></path></svg>
107 <!-- 评论按钮文字 -->
108 <span class="comment-text">评论 ({{ .Params.comments_count | default "0" }})</span>
109 </button>
110 {{ end }}
111 </div>
112
113 <!-- 评论容器:点击按钮后会在这里渲染 Waline 评论 -->
114 <div
115 class="waline-container"
116 id="waline-{{ .Param "slug" }}"
117 data-path="{{ .Param "slug" }}"
118 ></div>
119 </div>
120 {{ end }}
121 {{ end }}
122 </div>
123 </div>
124 </article>
125
126 <!-- JavaScript 代码 -->
127 <script>
128 document.addEventListener('DOMContentLoaded', function() {
129 // 图片懒加载和预加载处理
130 const imageObserver = new IntersectionObserver((entries, observer) => {
131 entries.forEach(entry => {
132 if (entry.isIntersecting) {
133 const img = entry.target;
134 const wrapper = img.closest('.moment-content-wrapper');
135 const loadingEl = wrapper.nextElementSibling;
136 const errorEl = loadingEl.nextElementSibling;
137
138 // 显示加载状态
139 loadingEl.style.display = 'flex';
140 errorEl.style.display = 'none';
141
142 // 创建新的Image对象用于预加载
143 const tempImg = new Image();
144 tempImg.onload = function() {
145 img.src = img.dataset.src;
146 img.classList.add('loaded');
147 loadingEl.style.display = 'none';
148 observer.unobserve(img);
149 };
150 tempImg.onerror = function() {
151 loadingEl.style.display = 'none';
152 errorEl.style.display = 'block';
153 };
154 tempImg.src = img.dataset.src;
155 }
156 });
157 }, {
158 rootMargin: '50px 0px',
159 threshold: 0.1
160 });
161
162 // 处理所有图片元素
163 document.querySelectorAll('.moment-content-wrapper').forEach(wrapper => {
164 const loadingEl = wrapper.nextElementSibling;
165 const images = wrapper.querySelectorAll('img');
166
167 if (images.length === 0) {
168 loadingEl.style.display = 'none';
169 return;
170 }
171
172 let loadedImages = 0;
173 images.forEach(img => {
174 if (img.src) {
175 img.dataset.src = img.src;
176 img.src = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; // 透明占位图
177 imageObserver.observe(img);
178
179 // 监听图片加载完成
180 img.onload = () => {
181 loadedImages++;
182 if (loadedImages === images.length) {
183 loadingEl.style.display = 'none';
184 }
185 };
186 }
187 });
188 });
189
190 // 重试加载功能
191 window.retryLoad = function(button) {
192 const errorEl = button.closest('.moment-error');
193 const loadingEl = errorEl.previousElementSibling;
194 const wrapper = loadingEl.previousElementSibling;
195 const img = wrapper.querySelector('img');
196
197 errorEl.style.display = 'none';
198 loadingEl.style.display = 'flex';
199
200 const tempImg = new Image();
201 tempImg.onload = function() {
202 img.src = img.dataset.src;
203 img.classList.add('loaded');
204 loadingEl.style.display = 'none';
205 };
206 tempImg.onerror = function() {
207 loadingEl.style.display = 'none';
208 errorEl.style.display = 'block';
209 };
210 tempImg.src = img.dataset.src;
211 };
212
213 // 新增分页相关变量
214 let currentPage = 1;
215 const pageSize = {{ .Site.Params.moments.pageSize | default 8 }};
216 let filteredMoments = [];
217 let isLoading = false;
218 let isAllLoaded = false;
219 let loadingTimeout = null;
220 const loadingHint = document.createElement('div');
221 loadingHint.className = 'scroll-hint-container';
222 loadingHint.innerHTML = '<div class="loading-hint">加载更多...</div>';
223 document.querySelector('.moments-list').after(loadingHint);
224
225 // 显示分页内容的方法
226 function displayMoments() {
227 const end = currentPage * pageSize;
228 const totalItems = filteredMoments.length;
229
230 // 优化显示逻辑,只处理新增的内容
231 const start = (currentPage - 1) * pageSize;
232 filteredMoments.slice(start, end).forEach(moment => {
233 moment.style.display = 'block';
234 // 触发图片懒加载重新检查
235 moment.querySelectorAll('img[data-src]').forEach(img => {
236 imageObserver.observe(img);
237 });
238 });
239
240 // 更新底部提示
241 const hasMore = end < totalItems;
242 isAllLoaded = !hasMore;
243
244 if (totalItems === 0) {
245 loadingHint.innerHTML = '<div class="end-divider">暂无内容</div>';
246 } else if (isAllLoaded) {
247 loadingHint.innerHTML = '<div class="end-divider">———— · 已到底部 · ————</div>';
248 } else {
249 loadingHint.innerHTML = '<div class="loading-hint">加载更多...</div>';
250 }
251 loadingHint.style.display = 'block';
252 }
253
254 // 优化的滚动事件处理
255 function checkScroll() {
256 if (isLoading || isAllLoaded || filteredMoments.length === 0) return;
257
258 const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
259 const threshold = 200; // 增加阈值,提前开始加载
260 const end = currentPage * pageSize;
261
262 if (end < filteredMoments.length && scrollTop + clientHeight >= scrollHeight - threshold) {
263 isLoading = true;
264 loadingHint.innerHTML = '<div class="loading-hint"><div class="loading-spinner"></div>加载中...</div>';
265
266 // 清除之前的超时
267 if (loadingTimeout) {
268 clearTimeout(loadingTimeout);
269 }
270
271 // 使用 requestAnimationFrame 和防抖优化性能
272 requestAnimationFrame(() => {
273 loadingTimeout = setTimeout(() => {
274 currentPage++;
275 displayMoments();
276 isLoading = false;
277 loadingTimeout = null;
278 }, 300);
279 });
280 }
281 }
282
283 // 点击标签时筛选逻辑
284 const tags = document.querySelectorAll('.tag-filter'); // 获取所有标签
285 const moments = document.querySelectorAll('.moment-card'); // 获取所有 moment 卡片
286 const allTags = document.querySelector('.all-tags'); // 获取"全部"按钮
287 const momentTags = document.querySelectorAll('.moment-tag'); // 获取所有卡片内的标签
288
289 // 默认选中"全部"标签
290 if (allTags) {
291 allTags.classList.add('selected');
292 }
293
294 // 点击标签时进行筛选
295 tags.forEach(tag => {
296 tag.addEventListener('click', function(e) {
297 e.preventDefault();
298 const selectedTag = tag.textContent.trim();
299
300 filteredMoments = Array.from(moments).filter(moment => {
301 const momentTags = moment.querySelectorAll('.moment-tag');
302 return selectedTag === '全部' ||
303 Array.from(momentTags).some(t => t.textContent === selectedTag);
304 });
305
306 currentPage = 1;
307 displayMoments();
308 window.scrollTo(0, 0); // 筛选后回到顶部
309
310 tags.forEach(t => t.classList.remove('selected'));
311 tag.classList.add('selected');
312 });
313 });
314
315 // 为卡片内的标签添加点击事件
316 momentTags.forEach(tag => {
317 tag.style.cursor = 'pointer';
318 tag.addEventListener('click', function() {
319 const tagText = this.textContent.trim();
320 // 找到对应的顶部标签并触发点击
321 tags.forEach(headerTag => {
322 if (headerTag.textContent.trim() === tagText) {
323 headerTag.click();
324 }
325 });
326 });
327 });
328
329 // 初始加载
330 filteredMoments = Array.from(moments);
331 displayMoments();
332 // 确保提示容器正确插入
333 const existingHint = document.querySelector('.scroll-hint-container');
334 if (!existingHint) {
335 const hintContainer = document.createElement('div');
336 hintContainer.className = 'scroll-hint-container';
337 document.querySelector('.moments-list').after(hintContainer);
338 }
339 window.addEventListener('scroll', checkScroll);
340 });
341 </script>
342
343 <!-- 在页面底部引入 Waline 评论脚本并初始化 -->
344 <script type="module">
345 import { init } from 'https://unpkg.com/@waline/client@v3/dist/waline.js';
346
347 const walineParams = {
348 /* 这里根据你自己的 Waline 配置进行调整 */
349 serverURL: '{{ .Site.Params.waline.serverURL }}',
350 lang: '{{ .Site.Params.waline.lang | default "zh-CN" }}',
351 visitor: '{{ .Site.Params.waline.visitor | default "匿名者" }}',
352 emoji: [
353 {{- range .Site.Params.waline.emoji }}
354 '{{ . }}',
355 {{- end }}
356 ],
357 requiredMeta: [
358 {{- range .Site.Params.waline.requiredMeta }}
359 '{{ . }}',
360 {{- end }}
361 ],
362 locale: {
363 admin: '{{ .Site.Params.waline.locale.admin | default "作者本人" }}',
364 placeholder: '{{ .Site.Params.waline.locale.placeholder | default "🍗所以我配有一条评论吗!" }}',
365 },
366 dark: '{{ .Site.Params.waline.dark | default "html.dark" }}',
367 };
368
369 // 点击"添加评论"按钮时,显示对应卡片下的评论区
370 window.showComment = function(element) {
371 const slug = element.getAttribute('data-slug');
372 const path = element.getAttribute('data-path');
373 const commentElement = document.getElementById('waline-' + slug);
374
375 // 如果已激活则清空
376 if (commentElement.classList.contains('active')) {
377 commentElement.classList.remove('active');
378 commentElement.innerHTML = '';
379 return;
380 }
381
382 // 移除其它所有已激活评论区
383 const allComments = document.querySelectorAll('.waline-container');
384 allComments.forEach(el => {
385 el.classList.remove('active');
386 el.innerHTML = '';
387 });
388
389 // 激活当前评论区
390 commentElement.classList.add('active');
391
392 // 初始化 Waline
393 init({
394 el: commentElement,
395 serverURL: walineParams.serverURL,
396 lang: walineParams.lang,
397 visitor: walineParams.visitor,
398 emoji: walineParams.emoji,
399 requiredMeta: walineParams.requiredMeta,
400 locale: walineParams.locale,
401 path: path,
402 dark: walineParams.dark,
403 });
404 }
405 </script>
406
407 <!-- 获取评论数 -->
408 <script>
409 document.addEventListener('DOMContentLoaded', function() {
410 // 获取所有评论按钮
411 const commentBtns = document.querySelectorAll('.moment-comment-btn');
412
413 // 确保有找到评论按钮
414 if (commentBtns.length > 0) {
415 commentBtns.forEach(button => {
416 const slug = button.getAttribute('data-slug'); // 获取按钮对应的 slug
417 const commentText = button.querySelector('.comment-text'); // 获取按钮中的评论文本
418 const serverURL = '{{ .Site.Params.waline.serverURL }}'; // 获取 Waline 服务器地址
419
420 // 输出调试信息,查看是否有多个按钮
421 console.log(`Processing button with slug: ${slug}`);
422
423 if (slug && commentText) {
424 // 假设你有一个获取评论数量的 API 或接口
425 fetch(`${serverURL}/api/comment?type=count&url=${slug}`)
426 .then(response => response.json())
427 .then(data => {
428 if (commentText) {
429 // 从 API 返回的数据中提取评论数
430 const commentCount = data.data && data.data[0] ? data.data[0] : 0; // 如果没有数据或评论数,默认为 0
431
432 // 输出调试信息,查看评论数
433 console.log(`Fetched comment count: ${commentCount}`);
434
435 // 更新评论按钮上的评论数量
436 commentText.textContent = `评论 (${commentCount})`; // 更新评论数
437 }
438 })
439 .catch(error => {
440 console.error('Error fetching comment count:', error);
441 if (commentText) {
442 // 如果 API 请求失败,保持评论数为 0
443 commentText.textContent = `评论 (0)`;
444 }
445 });
446 } else {
447 console.error('Slug or commentText missing for button:', button);
448 }
449 });
450 } else {
451 console.error('No comment buttons found');
452 }
453 });
454 </script>
455
456{{ end }}
Step2 - Build options
content/mements/_index.md
text
text
1---
2title: 瞬间
3build:
4 render: always
5cascade:
6 - build:
7 list: local
8 publishResources: false
9 render: never
10menu: main
11weight: 30
12---
Step3 - 修改页面样式
assets/css/extended/moments.css
css
css
1/*
2 全局:亮色模式下的默认值
3 说明:PaperMod 会在 <html> 或 <body> 上添加 .dark 类切换暗色,
4 所以这里只需定义默认(亮色)和暗色时的变量即可。
5*/
6:root {
7 --card-bg: #fff;
8 --card-text: #333;
9 --tag-bg: #f2f4f5;
10 --tag-text: #333;
11 --comment-btn-color: #999;
12 --time-color: #999;
13 --tag-filter-bg: #fff;
14 --tag-filter-text: #333;
15 --tag-filter-hover-bg: #55ac68;
16 --gradient-mask: linear-gradient(180deg, rgba(255, 255, 255, 0), #ffffff 100%);
17}
18
19/* 暗色模式下的变量 */
20.dark {
21 --card-bg: rgb(46, 46, 51);
22 --card-text: rgb(196, 196, 197); /* 对比度够高的浅色文字 */
23 --tag-bg: rgb(65, 66, 68); /* 比卡片再浅一些,或自己喜欢的颜色 */
24 --tag-text: #eee;
25 --comment-btn-color: #aaa;
26 --time-color: #ccc;
27 --tag-filter-bg: #555;
28 --tag-filter-text: #ddd;
29 --tag-filter-hover-bg: #55ac68;
30 --gradient-mask: linear-gradient(180deg, rgba(46, 46, 51, 0), rgb(46, 46, 51) 100%);
31 --tag-filter-shadow: 0 2px 6px rgba(0, 0, 0, 0.15); /* 新增暗色阴影变量 */
32}
33
34/* 单列容器:依旧保持 vertical 布局 */
35.moments-list {
36 display: flex;
37 flex-direction: column;
38 gap: 1.5rem;
39 margin-top: 1.5rem;
40}
41
42/* 卡片:使用CSS变量 */
43.moment-card {
44 width: 100%;
45 max-width: 800px; /* 每张卡片最多800px */
46 margin: 0 auto; /* 居中 */
47 position: relative;
48 background: var(--card-bg);
49 color: var(--card-text);
50 border-radius: 8px;
51 padding: 1rem;
52 box-shadow: 0 2px 8px rgba(0,0,0,0.06);
53 display: none; /* 初始隐藏,由JS控制显示 */
54 flex-direction: column;
55 justify-content: space-between;
56}
57
58.moment-card.hidden {
59 display: none; /* 隐藏元素并且不占据空间 */
60}
61
62/* 头像 & 作者 */
63.moment-header {
64 position: relative;
65 display: flex; /* 水平排列头像、昵称和按钮 */
66 align-items: center; /* 垂直居中 */
67 justify-content: space-between; /* 头像和昵称左对齐,按钮右对齐 */
68}
69
70.moment-header .left-content {
71 display: flex;
72 align-items: center;
73 gap: 1rem; /* 固定间距,控制头像和昵称之间的距离 */
74}
75
76.moment-avatar{
77 width: 40px;
78 height: 40px;
79 object-fit: cover; /* 保证图片不变形 */
80 border-radius: 4px;
81 transition: transform 1s ease; /* 平滑的旋转过渡效果 */
82 cursor: pointer; /* 鼠标悬停时显示为可点击 */
83}
84
85.moment-avatar:active {
86 transform: rotate(360deg); /* 点击时旋转一圈 */
87}
88
89.moment-author {
90 font-weight: 600;
91 font-size: 16px;
92 /* 跟随卡片文字颜色 */
93 color: var(--card-text);
94}
95
96/* 主体内容 */
97.moment-body {
98 font-size: 16px;
99 color: var(--card-text);
100 padding: 0;
101 line-height: 1.5;
102}
103
104.moment-body p {
105 margin-bottom: 0.6rem; /* 默认值可能是 1rem,改为 0.75rem 或更小 */
106 line-height: 1.6; /* 确保行间距仍然易读 */
107}
108
109.moment-body ol {
110 padding-left: 1rem; /* 调整左侧内边距,避免序号超出范围 */
111 margin-left: 0; /* 确保整体左对齐 */
112 list-style-position: inside; /* 确保序号在内容外部显示 */
113}
114
115.image-row {
116 display: grid;
117 gap: 0.4rem; /* 默认图片之间的间距 */
118 margin: 1rem 0; /* 上下与其他内容的间距 */
119}
120
121.image-row-1col {
122 grid-template-columns: repeat(1, 1fr); /* 单列布局 */
123}
124
125.image-row-2col {
126 grid-template-columns: repeat(2, 1fr); /* 双列布局 */
127 gap: 0.4rem; /* 自定义两列间距 */
128}
129
130.image-row-3col {
131 grid-template-columns: repeat(3, 1fr); /* 三列布局 */
132 gap: 0.2rem; /* 自定义三列间距 */
133}
134
135.image-row img {
136 width: 100%; /* 图片宽度占满格子 */
137 aspect-ratio: 1 / 1; /* 强制为正方形 */
138 object-fit: cover; /* 裁切图片内容,居中显示 */
139 display: block; /* 避免周围多余间隙 */
140 border-radius: 2px; /* 可选:增加圆角 */
141}
142
143/* 标签 */
144.moment-tags {
145 margin-bottom: 1rem;
146}
147
148.moment-tag {
149 display: inline-block;
150 padding: 0.3em 0.6em;
151 margin-right: 0.5em;
152 margin-top: 1.2em;
153 background: var(--tag-bg);
154 color: var(--tag-text);
155 font-size: 14px;
156 border-radius: 4px;
157}
158
159/* 底部:时间 + 评论按钮 */
160.moment-bottom {
161 display: flex;
162 align-items: center;
163 justify-content: space-between;
164}
165
166.moment-time span {
167 font-size: 14px;
168 color: var(--time-color); /* 比主体更淡一点 */
169}
170
171/* 评论按钮 */
172.moment-comment-btn {
173 background: none;
174 border: none;
175 cursor: pointer;
176 padding: 0;
177 display: inline-flex;
178 align-items: center;
179 /* 使用变量 color */
180 color: var(--comment-btn-color);
181 transition: color 0.2s;
182 font-size: 14px;
183}
184
185.moment-comment-btn:hover {
186 /* 可在暗色下也进行不同程度的 hover 颜色变化 */
187 color: var(--card-text);
188}
189
190.moment-comment-btn svg {
191 width: 18px;
192 height: 18px;
193 fill: currentColor;
194}
195
196.moment-comment-btn .comment-text {
197 margin-left: 0.3em;
198}
199
200/* 评论容器激活时 */
201.waline-container.active {
202 display: grid; /* 使用 flex 布局确保子元素正确排列 */
203 margin-top: 1rem;
204 padding-top: 1rem;
205 border-top: 1px dashed rgb(223, 223, 223);
206 font-weight: normal;
207 box-sizing: border-box;
208}
209
210.waline-container .wl-avatar {
211 display: flex;
212 justify-content: center;
213 align-items: center;
214}
215
216.waline-container .wl-avatar img {
217 display: flex;
218 justify-content: center;
219 align-items: center;
220 width: 100%;
221 height: 100%;
222 border-radius: 50%;
223}
224
225.waline-container .wl-cards .wl-user .wl-user-avatar {
226 margin-top: 1em;
227 margin-right: 0.25em;
228}
229
230.waline-container .wl-cards .wl-user .verified-icon{
231 display: none;
232}
233
234/* 顶部筛选标签样式 */
235.tags-filter {
236 margin-bottom: 20px;
237}
238
239.tags-filter ul {
240 list-style: none;
241 padding: 0;
242 display: flex;
243 flex-wrap: wrap;
244 gap: 10px;
245}
246
247.tags-filter li {
248 margin: 0;
249}
250
251.tags-filter .tag-filter {
252 text-decoration: none;
253 color: var(--tag-filter-text);
254 cursor: pointer;
255 padding: 8px 16px;
256 background-color: var(--tag-filter-bg);
257 border-radius: 20px;
258 font-size: 14px;
259 transition: all 0.3s ease;
260 font-weight: normal;
261 box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05); /* 缩小垂直偏移,降低透明度 */
262}
263
264.tag-filter.selected {
265 background-color: var(--tag-filter-hover-bg);
266 color: #fff;
267 font-weight: bold;
268 box-shadow: 0 2px 4px rgba(85, 172, 104, 0.1); /* 减少扩散半径和透明度 */
269}
270
271.tag-filter:hover {
272 background-color: var(--tag-filter-hover-bg);
273 color: #fff;
274 transform: translateY(-1px);
275 box-shadow: 0 2px 6px rgba(85, 172, 104, 0.15); /* 保持微移效果但降低阴影强度 */
276}
277
278go.moment-loading {
279 display: none;
280 flex-direction: column;
281 align-items: center;
282 justify-content: center;
283 padding: 1rem;
284 gap: 0.5rem;
285}
286
287.loading-spinner {
288 width: 24px;
289 height: 24px;
290 border: 2px solid var(--card-text);
291 border-top-color: transparent;
292 border-radius: 50%;
293 animation: spin 1s linear infinite;
294}
295
296@keyframes spin {
297 to { transform: rotate(360deg); }
298}
299
300.loading-hint {
301 text-align: center;
302 padding: 0.5rem 0;
303 color: var(--card-text);
304 margin: 0 auto;
305 max-width: 600px;
306 opacity: 0.7;
307 font-size: 0.9em;
308 transition: opacity 0.3s ease;
309}
310
311.loading-hint[style*="block"],
312.end-divider {
313 display: block !important;
314}
315
316.end-hint {
317 text-align: center;
318 padding: 2rem 0;
319 position: relative;
320}
321
322.end-divider {
323 color: #dcdcdc;
324 font-size: 0.9em;
325 text-align: center;
326 width: 100%;
327 padding: 2rem 0;
328 margin: 0 auto;
329}
330
331/* 修复提示容器样式(新增) */
332.scroll-hint-container {
333 width: 100%;
334 max-width: 800px;
335 margin: 0 auto;
336 padding: 1rem;
337 font-size: 0.95em;
338}
339
340.skeleton-content {
341 width: 100%;
342 padding: 0.5rem 0;
343}
344
345.skeleton-line {
346 height: 16px;
347 background: linear-gradient(90deg, var(--card-bg) 25%, rgba(128, 128, 128, 0.1) 50%, var(--card-bg) 75%);
348 background-size: 200% 100%;
349 animation: loading 1.5s infinite;
350 border-radius: 4px;
351 margin-bottom: 0.5rem;
352}
353
354@keyframes loading {
355 0% { background-position: 200% 0; }
356 100% { background-position: -200% 0; }
357}
Step 4 - 添加 Moments
在layouts/moments/
文件夹下添加 markdown 文件,每个 md 文件就是一条 moments。注意每个文件都需要包含以下 frontmatter:
text
text
1---
2date:
3slug: {{date:YYMMDD-HHmmss}}
4tags:
5---
- date:即每条 moment 的发布时间,显示在卡片左下角
- slug:waline 评论区的唯一标识,注意每条的 slug 必须唯一,所以我直接使用精确到时分的时间自动生成
- tags:可添加多个,添加后会自动显示在上方的筛选区域