Hexo 博客折腾记:从零部署 Waline 到完美复刻 Argon 极客页脚

前言
生命不息,折腾不止。为了给博客加上评论功能,我选择了 Waline。本以为是“一键部署”,结果踩了无数 Vercel 配置与 NPM 版本的坑。
搞定评论后,又顺手把 Butterfly 主题的页脚魔改成了 Argon 风格(显示服务器 IP、访客信息、精准运行时间)。
这是一篇保姆级避坑指南,记录了从后端搭建到前端 UI 深度魔改的全过程。


🛠 第一阶段:Waline 评论系统 —— 后端基石

Waline 的运行依赖于:LeanCloud (存储) + GitHub (托管) + Vercel (算力)

1. 注册数据库 (LeanCloud)

  1. 访问 LeanCloud 官网(推荐使用国际版,免备案且稳定)。
  2. 创建应用:点击“创建应用”,建议起名为 blog-comment
  3. 获取凭证:进入 应用 -> 设置 -> 应用凭证

    请务必记下: App IDApp KeyMaster Key。这是后续 Vercel 连接数据库的“钥匙”。

2. 构建代码仓库 (GitHub)

我们不使用一键部署,而是手动创建最纯净的代码结构,这能有效避免 Vercel 的路径识别报错。

  1. 在 GitHub 新建仓库 waline-server
  2. 手动创建并提交以下三个核心文件:
  • package.json:锁定环境依赖。
1
2
3
4
{
"scripts": { "start": "waline" },
"dependencies": { "@waline/vercel": "latest" }
}
  • api/index.js:Serverless 函数入口。

    ⚠️ 避坑准则:入口文件必须放在 api 文件夹内,否则 Vercel 无法将其识别为后端服务。

1
2
const { Waline } = require('@waline/vercel');
module.exports = Waline({ env: 'vercel' });
  • vercel.json:重定向逻辑。

    ⚠️ 避坑准则:没有它,访问域名会报 404 或直接暴露源码。

1
2
3
4
{
"version": 2,
"rewrites": [{ "source": "/(.*)", "destination": "/api/index.js" }]
}

3. 服务上线 (Vercel)

  1. 使用 GitHub 账号 登录 Vercel
  2. Import Project:导入刚才创建的 waline-server
  3. 配置环境变量:在 Environment Variables 中填入:
  • LEAN_ID / LEAN_KEY / LEAN_MASTER_KEY
  1. 点击 Deploy。完成后你将获得一个 serverURL 地址,建议绑定自己的子域名。

🎨 第二阶段:Butterfly 主题原生接入

服务端搞定后,回到 Hexo 修改 _config.butterfly.yml

1
2
3
4
5
6
7
8
9
10
11
12
# 评论系统全局配置
comments:
use: Waline
count: true

# Waline 专属配置
waline:
serverURL: https://your-waline-url.vercel.app # 填入你的 Vercel 地址
pageview: true # 统计文章阅读量
# 安全设置:强制要求填写昵称和邮箱
meta: ['nick', 'mail']
requiredMeta: ['nick', 'mail']

🚀 第三阶段:复刻 Argon 硬核页脚 (深度魔改)

Butterfly 原生页脚略显单调,魔改版将实现:透明背景、全员居中、实时访客/服务器 IP 解析

1. 结构重组:修改 Pug 模版

修改 themes/butterfly/layout/includes/footer.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
//- 使用 Flex 布局实现绝对居中,背景透明适配各种主题色
#footer-wrap(style="background: transparent; color: #fff; text-align: center; padding: 20px 10px; display: flex; flex-direction: column; align-items: center; justify-content: center;")

.argon-footer(style="font-family: -apple-system, sans-serif; font-size: 14px; line-height: 1.8; width: 100%;")

//- 行1:服务器位置与运营商信息
#server-info(style="margin: 5px 0;")
| 现在为您提供服务的服务器是
span#cf-country 检测中...
|
img#cf-flag(src="https://raw.githubusercontent.com/hampusborgos/country-flags/main/svg/us.svg", alt="国旗", style="width: 16px; height: 16px; display:none; vertical-align: text-bottom; margin: 0 4px;")
| 城市:
span#cf-city ...
| ,IP:
span#cf-ip ...
| ,运营商:
span#cf-provider Hugging Face

//- 行2:访客地理位置解析
#user-info(style="margin: 5px 0;")
| 您来自
span#user-country 检测中...
|
img#user-flag(src="", alt="国旗", style="width: 16px; height: 16px; display:none; vertical-align: text-bottom; margin: 0 4px;")
| 城市:
span#user-city ...
| ,IP:
span#user-ip ...
| ,运营商:
span#user-org ...

//- 行3:精准稳定运行时间
.time-container(style="margin: 5px 0;")
| 本博客已稳定运行
span#timeid1.time-element(style="color: #ffd700; font-weight: bold; margin: 0 3px;") 0
| 天
span#timeid2.time-element(style="color: #ffd700; font-weight: bold; margin: 0 3px;") 0
| 小时
span#timeid3.time-element(style="color: #ffd700; font-weight: bold; margin: 0 3px;") 0
| 分
span#timeid4.time-element(style="color: #ffd700; font-weight: bold; margin: 0 3px;") 0
| 秒

//- 行4:致敬 Butterfly
div(style="margin-top: 5px;")
| Theme
a(href="https://butterfly.js.org/", target="_blank", style="font-weight:bold; color: #fff; text-decoration: none; border-bottom: 1px dashed rgba(255,255,255,0.5);") Butterfly

2. 注入灵魂:增强版 JS 逻辑

新建或修改 source/js/custom.js

技术亮点

  1. 阿里云 DoH:替换被墙的 Google DNS,确保国内直连访客数据不报错。
  2. IP 数字净化:针对 CloudFront 等 CDN 返回的别名网址,正则提取真实数字 IP。
  3. PJAX 适配:监听 pjax:complete,确保切换页面时页脚数据不消失。
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
/* source/js/custom.js */
function runArgonWidget() {
// 1. 动态计时器
const boomDate = new Date("2026/01/01 00:00:00"); // 👈 改为你自己的建站时间
function updateTime() {
try {
const now = new Date();
const diff = now - boomDate;
if (diff < 0) return;
document.getElementById("timeid1").innerText = Math.floor(diff / 86400000);
document.getElementById("timeid2").innerText = Math.floor((diff % 86400000) / 3600000);
document.getElementById("timeid3").innerText = Math.floor((diff % 3600000) / 60000);
document.getElementById("timeid4").innerText = Math.floor((diff % 60000) / 1000);
} catch (err) {}
}
if (window.argonInterval) clearInterval(window.argonInterval);
window.argonInterval = setInterval(updateTime, 1000);
updateTime();

// 2. 访客 IP 地理位置
const userCountry = document.getElementById('user-country');
if (userCountry) {
userCountry.innerText = "查询中...";
fetch('https://ipapi.co/json/')
.then(res => res.json())
.then(data => {
userCountry.innerText = data.country_name || "未知";
document.getElementById('user-city').innerText = data.city || "...";
document.getElementById('user-ip').innerText = data.ip || "...";
document.getElementById('user-org').innerText = data.org || "运营商";
const flag = document.getElementById('user-flag');
if (data.country_code) {
flag.src = `https://raw.githubusercontent.com/hampusborgos/country-flags/main/svg/${data.country_code.toLowerCase()}.svg`;
flag.style.display = 'inline';
}
}).catch(() => { userCountry.innerText = "网络异常"; });
}

// 3. 服务器 IP (阿里云 DNS 解析版)
const cfCountry = document.getElementById('cf-country');
if (cfCountry && window.location.hostname !== 'localhost') {
cfCountry.innerText = "解析中...";
fetch(`https://dns.alidns.com/resolve?name=${window.location.hostname}&type=1`)
.then(res => res.json())
.then(data => {
if (data.Answer) {
const ipRecord = data.Answer.find(ans => ans.type === 1);
document.getElementById('cf-ip').innerText = ipRecord ? ipRecord.data : data.Answer[0].data;
cfCountry.innerText = "Global CDN";
document.getElementById('cf-flag').style.display = 'inline';
}
});
}
}

// 初始化运行
runArgonWidget();
// 兼容 PJAX 切换
document.addEventListener('pjax:complete', runArgonWidget);

第四阶段:激活脚本 —— 引入 custom.js

代码写完并不代表它会自动运行。Butterfly 主题需要通过配置文件手动“点火”,才能在浏览器中加载并执行你的自定义逻辑。

1. 修改主题配置文件

打开博客根目录下的 _config.butterfly.yml,搜索 inject 配置项。

2. 添加引用代码

bottom(即页面底部,</body> 标签之前)添加你的脚本路径。请确保这里的路径与你在 source 文件夹下存放的文件路径一致。

1
2
3
4
5
6
7
# Inject the logic to your site
inject:
head:
# 这里可以放一些需要在头部加载的 CSS
bottom:
# 引用复刻 Argon 页脚的灵魂脚本
- <script src="/js/custom.js"></script>

3. 强制刷新与生效

⚠️ 避坑准则:Hexo 的缓存非常顽固,修改完 JS 后必须执行“三连”操作,否则浏览器可能还在运行旧的代码。

在终端执行:

1
hexo clean && hexo g && hexo s

然后在浏览器中按下 Ctrl + F5(Windows)或 Cmd + Shift + R(Mac)进行强制刷新


📝 避坑终极避坑总结

  1. 缓存是大敌:修改完配置务必执行 hexo clean,否则你看到的永远是旧样式。
  2. DNS 策略:国内环境下必须使用阿里云/腾讯云的 DNS 接口,使用 Google DNS 会导致直连访客看到“DNS 错误”。
  3. 玄学位置:若开启了 iCloud 专用代理梯子,访客 IP 会显示为美国/日本,这是物理层面的代理转发,属正常现象。

结语:Hexo 的魅力就在于折腾。当看到页脚的跳动秒数与精准的服务器信息交织在一起时,这一天的汗水都化成了满满的成就感。


Would you like me to help you further with specific CSS styling for the footer elements?