Next 主题微信朋友圈风格说说

前言

之前写过一篇『Next 主题添加说说』,当时苦于 copy 不来洪哥的说说瀑布流样式,尝试一周未果,遂草草写了个结构,能够显示发布时间、外部链接和图片,但没有设计图片的排版样式,导致电脑上看一些图片很大,再加上样式本身也没什么特点,看久了就腻了。这两天突然兴起,又想再去试试,结果还是弄不来瀑布流,于是把方向转到其他有特点的设计上,正巧打开了微信,看到朋友的动态,灵光一闪,为何不模仿微信朋友圈样式呢?在 chatgpt 的帮助下捣鼓了一下午,于是有了这篇文章。

教程

创建 hexo/themes/next/layout/essay.njk 文件并写入以下内容

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
    {##################}
{### ESSAY BLOCK ###}
{##################}


<style>
body {
background: #f3f3f3;
}
.post-header {
display: none;
}
.post-block:first-of-type {
padding-top: 60px;
}
@media (max-width: 767px) {
.post-block:first-of-type {
padding-top: 40px;
}
}
#waline blockquote { background: #fff; }
.cover { position: relative; max-width: 800px; margin: 0 auto; }
.cover img { width: 100%; height: 200px; object-fit: cover; }
.profile {
position: absolute; bottom: 10px; right: 20px;
display: flex; align-items: center; gap: 10px;
}
.profile span { color: #fff; font-weight: bold; font-size: 16px; }
.profile img { width: 50px; height: 50px; border-radius: 5px; }
.moments { max-width: 800px; margin: 20px auto; }
.card {
background: #fff; border-radius: 12px;
padding: 20px 25px; margin-bottom: 15px;
}
.card-header { display: flex; align-items: flex-start; gap: 10px; }
.card-header img { width: 50px; height: 50px; border-radius: 5px; }
.card-content { flex: 1; }
.card-content .name { font-weight: bold; color: #333; }
.card-content p { margin: 6px 0; color: #444; }
.images-container { display: grid; grid-template-columns: repeat(3, 1fr); gap: 6px; margin-top: 6px; }
.thumb { width: 100%; aspect-ratio: 1 / 1; overflow: hidden; border-radius: 8px; }
.thumb img { width: 100%; height: 100%; object-fit: cover; }
.card-footer { display: flex; justify-content: space-between; margin-top: 8px; font-size: 14px; color: #777; }
.card-footer button { background: none; border: none; color: #555; cursor: pointer; }
.card-footer button:hover { color: #007aff; }
.post-body *, .post-body *::before, .post-body *::after {
box-sizing: border-box;
}
</style>

<!-- 顶部背景 -->
{% set essay = site.data.essay %}
<div class="cover">
<img src="{{ essay.profile.cover }}" style="border-radius: 12px;">
<a href="/about"><div class="profile" style="border: none;">
<span>{{ config.author }}</span>
<img src="{{ theme.avatar.url }}" alt="头像" class="nofancybox" style="margin-bottom: 0;">
</div></a>
</div>

<!-- 动态列表 -->
<div class="moments">
{% for moment in essay.moments %}
<div class="card">
<div class="card-header">
<a href="/about" style="border: none;"><img src="{{ theme.avatar.url }}" alt="头像" class="nofancybox" style="margin-bottom: 0;"></a>
<div class="card-content">
<a href="/about"><div class="name">{{ config.author }}</div></a>
<p>{{ moment.text }}</p>
<div class="images-container">
{% for img in moment.images %}
<div class="thumb">
<img src="{{ img }}">
</div>
{% endfor %}
</div>
<div class="card-footer">
<time class="datetime" datetime="{{ date(moment.date, "YYYY/MM/DD") }}">{{ date(moment.date, "YYYY/MM/DD") }}</time>
<div>
<button class="essay-comment-btn" data-moment-text="{{ moment.text }}"><i class="fa-solid fa-message"></i></button>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>

<script src="{{ url_for('/js/essay.js') }}" data-pjax></script>

{######################}
{### END ESSAY BLOCK ###}
{######################}

修改 hexo/themes/next/layout/page.njk,添加 + 后的内容(不含 +)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{%- if page.type === 'categories' and not page.title %}
{{- __('title.category') + page_title_suffix }}
{%- elif page.type === 'tags' and not page.title %}
{{- __('title.tag') + page_title_suffix }}
{%- elif page.type === 'schedule' and not page.title %}
{{- __('title.schedule') + page_title_suffix }}
+{%- elif page.type === 'essay' and not page.title %}
+ {{- __('title.essay') + page_title_suffix }}
{%- else %}

...

{%- if page.type === 'tags' %}
{%- include '_partials/page/tags.njk' -%}
{% elif page.type === 'categories' %}
{%- include '_partials/page/categories.njk' -%}
{% elif page.type === 'schedule' %}
{%- include '_partials/page/schedule.njk' -%}
+{% elif page.type === 'essay' %}
+ {%- include 'essay.njk' -%}
{% else %}

以下为方便复制的版本,请分别复制并粘贴到对应位置

1
2
3
4
5
6
7
{%- elif page.type === 'essay' and not page.title %}
{{- __('title.essay') + page_title_suffix }}

...

{% elif page.type === 'essay' %}
{%- include 'essay.njk' -%}

创建 hexo/source/_data/essay.yml 并粘贴以下内容

1
2
3
4
5
6
7
8
9
10
11
12
13
profile:
cover: /images/essay-cover.webp

moments:
- text: 最新说说
date: 2025-09-06

- text: 之前的说说
date: 2025-09-01
images:
- /images/example1.webp
- /images/example2.webp
- /images/example3.webp

简单解释下参数

参数说明
cover头图,可填相对路径或绝对路径
text说说内容
date发布日期,格式:年-月-日,月日请采用两位数,比如「2025-09-06」而不是「2025-9-6」
images【可选】插入文章的图片,支持多张图片

发布新说说在 moments 下添加相同结构即可,注意缩进!

文件中说说的先后顺序与渲染出来的结果是一致的,即越写在上面的,在渲染结果中也就越排在上方。

创建 hexo/source/js/essay.js 并填入以下内容

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
(function() {
// ----------------------
// 相对时间功能
// ----------------------
function formatRelativeTime(dateStr) {
const now = new Date();
const past = new Date(dateStr);
const diff = (now - past) / 1000; // 秒

if (diff < 60) return `${Math.floor(diff)}秒前`;
if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
if (diff < 604800) return `${Math.floor(diff / 86400)}天前`;

const year = past.getFullYear();
const month = past.getMonth() + 1;
const day = past.getDate();
if (year !== now.getFullYear()) {
return `${year}${month}${day}日`;
}
return `${month}${day}日`;
}

function updateRelativeTime(container = document) {
container.querySelectorAll('.datetime').forEach(el => {
const timeStr = el.getAttribute('datetime');
if (timeStr) el.textContent = formatRelativeTime(timeStr);
});
}

// ----------------------
// 评论按钮跳转 Waline 功能
// ----------------------
function getWalineTextarea() {
return document.querySelector('#waline textarea') || document.querySelector('#waline input[type="text"]');
}

function bindCommentButtons(container = document) {
container.querySelectorAll('.essay-comment-btn').forEach(btn => {
btn.addEventListener('click', () => {
const momentText = btn.getAttribute('data-moment-text');
const textarea = getWalineTextarea();
if (!textarea) return;

// 滚动到评论框
textarea.scrollIntoView({ behavior: 'smooth', block: 'center' });

// 自动填充引用文本
textarea.value = `> ${momentText}\n\n`;
textarea.focus();
});
});
}

// ----------------------
// 初始化函数
// ----------------------
function init(container = document) {
updateRelativeTime(container);
bindCommentButtons(container);
}

// 页面首次加载
init(document);

// PJAX 切换后重新初始化
document.addEventListener('pjax:success', () => {
init(document);
});
})();

运行 hexo new page "essay" 创建 hexo/source/essay/index.md 文件,设置标题日期,type 要设置为 "essay"

1
2
3
title: 说说
date: 2025-09-06 11:18:28
type: "essay"

打开 _config.next.yml,在 menu 中添加

1
2
menu:
essay: /essay/ || fas fa-pen

打开 hexo/themes/next/languages/zh-CN.yml,在 menu 中添加

1
2
menu:
essay: 说说

大功告成!

需要注意的是,如果要使用评论功能,必须安装并使用 waline,其他评论系统请自行修改 essay.js 中的选择器。

效果展示