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:锁定环境依赖。
{
  "scripts": { "start": "waline" },
  "dependencies": { "@waline/vercel": "latest" }
}
  • api/index.js:Serverless 函数入口。

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

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

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

{
  "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

# 评论系统全局配置
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操作前请务必备份)。
将原内容替换为以下“绝对居中版”结构:

//- 使用 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,确保切换页面时页脚数据不消失。

/* 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 文件夹下存放的文件路径一致。

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

3. 强制刷新与生效

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

在终端执行:

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?

文章作者: I-Meet
本文链接:
版权声明: 本站所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 I-Meet
科技 comments hugging face hexo
喜欢就支持一下吧