这篇文章是本站的说明书 + 命令速查表。
上半部分给「来看页面的人」:每一屏是什么、怎么点、有什么隐藏操作;
下半部分给「来改这个站的人」:写作、校验、发布、后端配置的全部命令。

更深的实现细节(为什么这么做、踩过哪些坑)集中在仓库 docs/ 目录,索引用 docs/README.md。

一、一句话认识这个站

项 值
站点名 Biko 的个人小站
技术栈 Hexo 8.1 + Butterfly 5.7 + 一批自研脚本
两套托管 biko-duckyo.github.io(GitHub Pages,纯静态)/biko-duckyo.pages.dev(Cloudflare Pages,带后端)
写作方式 本地 npm run publish "提交说明",推上去由云端构建

⚠️ 两套托管能力不同:/calendar/ 的在线记录依赖 Cloudflare Pages Functions,
在 GitHub Pages 上是只读的(接口恒 404,前端会自动降级,不会报错)。

二、左侧导航:每个入口是干什么的

入口 路径 说明
首页 / 首屏是一整套 Home Hub(鲸鱼背景 + 猫耳时钟 + 建站时长);下滚进入文章流,回顶重现;下滚超过 12px 顶栏吸附出现
迹录 /calendar/ 工作计划历(53×7 格子墙)。点格子记一条,同一天可反复追加、不覆盖;卡片里能改能删
文章 › 时间轴 / 标签 / 分类 /archives/、/tags/、/categories/ Butterfly 原生三件套
赋闲 › 相册 /galleryGroup/ 加密相册,需要输入暗号才能进
赋闲 › 电影 /movies/ 观影记录
赋闲 › 音乐 /music/ 大屏演出视图,和左下角小播放器共用同一个 APlayer 实例(放哪边都同步)
追番 /bangumis/ 追番进度
书斋 /books/ 文献阅读工作台(本站的隐藏大件,见 3.4)
自述 /HTML/resume/index.html 个人简历页
友链 /link/ 友链墙,卡片由外挂标签 {% link %} 生成
butterfly主题 外链 主题 GitHub 仓库

三、页面级的隐藏交互

3.1 全局(每一页都有)

操作 效果
右键空白处 弹出毛玻璃(Acrylic)自定义菜单:后退 / 前进 / 刷新 / 回首页 · 随便逛逛 / 切换模式 / 繁简切换
左侧音符按钮 展开 / 收起网易云播放器(默认不自动播放,点了才播;播放列表 2120966270)
齿轮 → 明暗按钮 切换昼夜(带星球旋转动画),偏好存 localStorage.theme
顶栏天气胶囊 和风天气实况,走本站同源代理;定位被拒时回退默认城市(南京)
左下角小猫 点一下叫出「小黑」AI 面板
Ctrl/⌘ + K 或顶栏放大镜 站内搜索
长文章右侧 目录(TOC)+ 阅读进度百分比

3.2 首页

  • 文章轮播(Swiper):首页顶部的文章轮播,已在 _config.butterfly.yml 的 swiper.enable 打开,样式与脚本均走本地 source/swiper/。卡片不是自动取最新文章,而是由文章的 front-matter swiper_index 决定:加了才会进轮播,数字越小越靠前。想下线就把它设回 false。
  • 分类磁贴:文章列表上方的分类入口卡。
  • 文章封面:本地渐变为底 + Unsplash 真图增强(同一关键词全站只消耗 1 次 API 配额);拿不到图时静默回退渐变,不会空白。

3.3 「小黑」AI 面板(/nanaly/)

站点里的 AI 助手,功能比普通聊天窗多:身份设定、知识库、工作区、文件与视觉(看图)、联网研究、任务、语音(朗读与识别)、韵律分析,还支持语音唤醒。

3.4 文献阅读工作台(/books/)

本站最重的一个自研页面,等于一个「本地优先」的论文阅读器:

能力 说明
打开文献 拖入本地 PDF;或粘 DOI(自动查 Crossref)、PDF 直链;配了 Unpaywall 邮箱还会找开放获取全文
长文不卡 按需渲染,只画附近几页,滚过去自动补、离开就释放内存,几百页也不用等转圈
快速定位 左栏「目录」读 PDF 自带书签,「搜索」在全文找词并跳页,页码可直接点进去
边读边标 划选文字 → 高亮 / 划线 / 删除线 / 批注。标注存的是文本锚点而不是坐标,缩放、改窗口、下次打开都不会丢
中英对照 「设置」里填模型接口,按段落生成对照;划词后也能只翻一小段;结果存本地,重复段落自动跳过
图区超清 按 g 或点左栏「图区放大」,插图会自动框选,点一下按 4x~16x 重新渲染矢量(不是把屏幕位图拉大);也能拖一个矩形只放大那一小块
导出 笔记导 Markdown(front-matter 已写好 categories: 知识进阶之路,丢进 source/_posts/ 就能发)/导出长图/单页转图片
数据在哪 PDF 副本、标注、笔记、API Key 全部存在浏览器 IndexedDB,不经过任何服务器;换设备用「导出当前 JSON」迁移

快捷键:j / k 按屏滚动,n / p 翻页,+ / - 缩放,Ctrl/⌘ + F 全文搜索,g 图区放大,Esc 关浮层。

3.5 工作计划历(/calendar/)

操作 行为
点今天的格子 弹出输入卡,写一条(已有记录会列在上方)
再点今天 纯追加,不覆盖,各自带时间戳
点过去的格子 不弹输入框,只让悬停卡常驻(只读)
点未来的格子 禁用,提示「还没到这天」
卡片里 每条记录都能改、能删(不用再补一条「更正」)
键盘 Tab 进格子墙,← → ↑ ↓ 在格子间移动,Enter 记录 / 修改
分档着色 同一天条数 0 / 1–2 / 3–4 / 5–7 / ≥8 → 五档由淡到浓

首次写入需要在页面底部口令行填一次口令(只存在本机 localStorage)。数据存 Cloudflare KV,键 worklog:v1;单条上限 200 字,单日上限 50 条。

四、写作:从新建到发布

4.1 新建一篇

1
2
npx hexo new "文章标题"        # 生成 source/_posts/文章标题.md
npx hexo new page "关于" # 生成独立页面 source/关于/index.md

front-matter 模板:

1
2
3
4
5
6
7
8
9
---
title: 文章标题
date: 2026-10-15 20:00:00
updated: 2026-10-15
tags: [标签A, 标签B]
categories: [Hexo 教程]
description: 一句话摘要(首页预览优先用它,见 index_post_content.method: 2)
swiper_index: 1 # 可选:开启轮播后进 Swiper 轮播
---

⚠️ 编码警告:source/_posts/ 下的 Markdown 必须是 UTF-8(无 BOM)。
用 PowerShell 的 Get-Content / Set-Content 直接改中文文件,会按 GBK 读进来再以 UTF-8 写回,
中文被截成乱码,构建出来的文章标题就变成「鍗氬宸ョ▼鍖」这种样子。
改文章请用编辑器(VS Code 右下角确认是 UTF-8),或让脚本显式以 UTF-8 写入。

4.2 本地预览

1
2
3
4
npm run server      # ≈ hexo server → http://localhost:4000(纯静态,后端接口不执行)

npm run cf:preview # ≈ wrangler pages dev public → http://localhost:8788
# ⚠️ 只有这个能真跑 functions/,用来在部署前验证 /api/*

4.3 一键发布

1
2
3
npm run publish:check            # 预演:构建 + 列改动,不提交不推送
npm run publish "内容:新增 xx" # 构建校验 → git add/commit → 推送到所有 remote
npm run publish:fast "提交说明" # 跳过本地构建(只改文案/样式时用)

tools/publish.mjs 干四件事:构建校验 → 列改动 → 提交 → 逐个推送远程(origin / gitee 各推一次,互不阻塞)。推送成功后由 Cloudflare Pages 自动构建部署。

注意 npm run publish 用的是 git add -A,别在仓库根放临时文件,否则会被一起提交,还得再补一次删除提交。

4.4 Git 与双通道部署

1
2
3
npm run deploy          # hexo deploy → 推 public/ 产物到 GitHub Pages 产物仓
npm run deploy:cf # hexo clean + generate + wrangler 直传 → Cloudflare Pages(含 Functions)
npm run deploy:cf:fast # 跳过生成,直接上传当前 public/(只改了功能时快得多)
命令 推到哪 结果
npm run deploy GitHub 产物仓 更新 biko-duckyo.github.io(无后端,/calendar/ 只读)
npm run deploy:cf Cloudflare Pages 更新 biko-duckyo.pages.dev(有后端,日历可写)
npm run publish 源码仓(origin / gitee) 云端自动构建;日常该用的就是这条

五、校验与测试(改完必跑)

1
2
3
4
5
6
7
npm run check          # 一条命令跑完全部:books 语法 + 三套回归测试 + vendor 完整性
npm run books:check # source/books/js/*.js 语法 + U+FFFD / mojibake 编码检查
npm test # 自定义脚本回归(jsdom,19 项:Pjax 监听器不泄漏等)
npm run test:worklog # 工作计划历(jsdom + 后端纯函数,103 项)
npm run test:cover # 文章封面链路(16 项,含「前端不含密钥」防线)
npm run vendor:check # 本地库 sha256 与 tools/vendor-manifest.json 是否一致
npm run vendor:sync # 同步缺失的本地库(升级版本:改完清单再 --force)
脚本 兜住的坑
tools/tests/custom-scripts.test.mjs 每次 pjax:complete 都新增监听器 / 定时器 → 长会话越用越卡(不报错,只能靠测试发现)
tools/tests/worklog.test.mjs 只读降级被误判、今天可写 / 历史只读 / 未来禁用三条语义、同日追加不覆盖
tools/tests/auto-cover.test.mjs 固定关键词只发 1 次请求、失败回滚原封面、源码 / 生成器 / 配置里不含任何密钥痕迹
tools/books-check-syntax.js books 下的 JS 是零构建直出,坏一个字符要打开浏览器才知道;顺带查 GBK 乱码残留

CI(.github/workflows/ci.yml)在每次 push / PR 都会跑:语法检查 → 回归测试 → vendor 校验 → 构建 → 断言关键产物(index.html / 404.html / sitemap.xml / atom.xml / robots.txt / 本地 pjax)。

六、动主题和样式

1
2
npm run build     # hexo generate → public/
npm run clean # hexo clean(清 public/ 与缓存)

主要配置位:

文件 改什么
_config.yml 站点名、URL、permalink、搜索、Live2D、deploy、sitemap / feed,以及 swiper / 分类磁贴 / 双栏布局插件
_config.butterfly.yml 菜单、代码块、封面、TOC、页脚、inject(所有自定义 CSS / JS 的挂载点)、CDN.option(本地库覆盖)、cover_generator
source/css/*.css 自研样式(custom.css、custom_nav.css、home_hub.css、music_panel.css、worklog_calendar.css……)
source/js/custom/*.js 自研脚本,逐个在 inject.bottom 里挂载
scripts/*.js Hexo 构建期脚本(自动 require,无需配置):gen-random-posts.js、gen-cover-config.js、link.js

主题装在 node_modules/(不在 themes/),不要直接改主题文件——升级就会丢。
要改结构就用 inject 注入等价 HTML,本站的自定义顶栏、右键菜单都是这么做的。

三个必守约定(动手前先看)

  1. 自定义脚本必须 Pjax 安全:凡注册了全局监听器 / 定时器的脚本,都要提供成对的卸载逻辑,否则每换一页就多累积一份。参考 home_hub.js 的 teardownHub(),机理与实测见 docs/pjax-safety.md。
  2. 关键第三方库走本地:pjax / fancybox / instant.page / snackbar / 不蒜子已本地化(jsDelivr 在国内会被 DNS 污染;pjax 一挂,整站就退化成整页跳转)。手改 source/**/vendor/ 却不跑 vendor:sync,sha256 会对不上,CI 直接失败。
  3. 密钥永不进前端:任何 API Key 都只能放服务端环境变量,前端走同源代理。见第八节。

七、数据维护

7.1 历史上的今天(瘦身 60%)

1
2
node tools/history-slim.mjs --dry-run   # 先看能省多少(原始 4.34 MB → 1.74 MB)
node tools/history-slim.mjs # 执行,幂等;原文备份到 .history-backup/

7.2 文章封面配置

1
node scripts/gen-cover-config.js        # 构建期自动跑;产物 public/js/custom/cover.config.json

改完 _config.butterfly.yml 的 cover_generator 段落必须重新 hexo generate —— 配置是构建期产物,改源码文件不会自动同步。想强制刷新已缓存的封面,在浏览器控制台执行:

1
2
Object.keys(localStorage).filter(function (k) { return k.indexOf('biko-cover:') === 0 })
.forEach(function (k) { localStorage.removeItem(k) })

7.3 第三方库本地化

1
2
npm run vendor:sync              # 下载缺失文件(升级:npm run vendor:sync -- --force)
npm run vendor:check # 只校验 sha256,不下载

升级流程:改 tools/vendor-sync.mjs 里的 URL(版本号)→ npm run vendor:sync -- --force → npm run build 验证 → 提交。

八、后端:Cloudflare Pages Functions

本站的 /api/* 全部是 Cloudflare Pages Functions(functions/ 目录),GitHub Pages 不会执行(返回 404),
前端会先探测再静默降级——这就是「双环境部署」的由来。

接口 方法 作用 是否需口令
/api/worklog/list GET 工作记录全量 否(公开可读)
/api/worklog/add POST 追加一条(同日不覆盖) 是(WORKLOG_TOKEN)
/api/worklog/remove DELETE 删除该日若干条 / 指定内容 / 清空该日 是(WORKLOG_TOKEN)
/api/weather/city、now、air、warning GET 和风天气代理(子路径白名单) 否
/api/cover/random GET Unsplash 随机图代理,只回 { ok, data: { url } } 否(同站来源校验)

8.1 环境变量与绑定

每个 Pages 项目要配(生产 / 预览是两套独立变量,必须各配一次):

名称 类型 用途
WORKLOG_TOKEN Secret 写入工作记录的口令
WORKLOG_KV KV 绑定 工作记录存储(键 worklog:v1)
QWEATHER_KEY Secret 和风天气 Key
QWEATHER_HOST 文本 和风 API Host,强烈建议填 https://api.qweather.com
UNSPLASH_KEY Secret Unsplash Access Key(文章封面)

命令行写入 secret(在仓库根目录执行):

1
2
'你的Key' | npx wrangler pages secret put UNSPLASH_KEY --project-name=biko-duckyo
'你的Key' | npx wrangler pages secret put UNSPLASH_KEY --project-name=biko-duckyo --env=preview

也可以在控制台配:项目 → Settings → Variables and Secrets(类型选 Secret)/ KV namespace bindings。

⚠️ 为什么 Key 绝对不能写进配置文件:前端产物是公开的。
Unsplash 的演示 Key 不带鉴权头也能调用(?client_id=),一泄漏就会被抄走,50 次 / 小时的配额秒光;
和风天气是按调用量计费的。本站在历史上两处明文 Key 都已改成同源代理,
细节见 docs/cover-key.md、docs/weather-proxy.md。

8.2 本地联调后端

在仓库根建 .dev.vars(已被 .gitignore 忽略,只对本地 pages dev 生效,不会被 pages deploy 带上线):

1
2
3
4
WORKLOG_TOKEN=本地测试口令
QWEATHER_KEY=你的和风Key
QWEATHER_HOST=https://api.qweather.com
UNSPLASH_KEY=你的UnsplashKey
1
2
3
4
5
6
7
8
9
npm run cf:preview      # 启动后控制台会打印绑定;看到 WORKLOG_TOKEN ("(hidden)") 说明读到了

curl http://localhost:8788/api/worklog/list
# → {"ok":true,"data":{}}

curl -X POST http://localhost:8788/api/worklog/add ^
-H "authorization: Bearer 本地测试口令" ^
-H "content-type: application/json" ^
-d "{\"date\":\"2026-10-15\",\"text\":\"本地测试\"}"

⚠️ Windows 下 curl -d 发中文会被按 GBK 编码,写进去就是乱码 —— remove 接口就是为清理这类脏数据补上的。
本地测中文请用 --data-binary "@body.json"(文件存 UTF-8),或者干脆在页面上点。
本地 KV 数据落在 .wrangler/state/,删掉即重置。

8.3 撤销一把已泄漏的 Key

服务 操作
Unsplash 后台 → Applications → 删除 / 重置 Access Key → wrangler pages secret put 写新值 → 重新部署
和风天气 控制台 → 项目管理 → 删除旧 Key → 换成新的 QWEATHER_KEY
工作记录口令 wrangler pages secret put WORKLOG_TOKEN ... 覆盖;旧口令立即失效(本机存的那份会 401,重填即可)

九、全部指令速查

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
# ── 开发 ───────────────────────────────────────────────
npm install # 装依赖(Node >= 20.19.0,见 .nvmrc)
npm run vendor:sync # 首次克隆后必跑一次(拉本地库)
npm run server # 静态预览 http://localhost:4000
npm run cf:preview # 带后端的预览 http://localhost:8788
npm run build # hexo generate → public/
npm run clean # hexo clean(清 public/ 与缓存)
npx hexo new "标题" # 新建文章
npx hexo new page "页面名" # 新建独立页面

# ── 校验 / 测试 ────────────────────────────────────────
npm run check # 全部校验(改完就跑这条)
npm test # 自定义脚本回归(19 项)
npm run test:worklog # 工作计划历回归(103 项)
npm run test:cover # 文章封面链路(16 项)
npm run books:check # books 语法 + 编码检查
npm run vendor:check # 本地库 sha256 校验

# ── 发布 ──────────────────────────────────────────────
npm run publish:check # 预演,不改远端
npm run publish "提交说明" # 构建 → 提交 → 推送所有远程(日常用这条)
npm run publish:fast "说明" # 跳过本地构建
npm run deploy # hexo deploy → GitHub Pages
npm run deploy:cf # 清缓存 → 生成 → wrangler 直传 Cloudflare Pages
npm run deploy:cf:fast # 只上传当前 public/

# ── Cloudflare 运维 ────────────────────────────────────
npx wrangler login # 授权(一次即可)
npx wrangler pages project list # 看账号下的项目名
npx wrangler pages project create biko-duckyo # 首次建项目
npx wrangler pages deploy public --project-name=biko-duckyo
npx wrangler pages secret put UNSPLASH_KEY --project-name=biko-duckyo
npx wrangler pages dev public --compatibility-date=2025-01-01 --kv WORKLOG_KV

# ── 数据维护 ──────────────────────────────────────────
node tools/history-slim.mjs --dry-run # 历史上的今天:先看能省多少
node tools/history-slim.mjs # 执行瘦身(幂等)
node scripts/gen-cover-config.js # 重新生成封面配置
node tools/publish.mjs "提交说明" # 不经 npm 直接调发布脚本
node tools/books-check-syntax.js # 直接跑 books 语法检查

9.1 目录地图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
superBrain/
├── _config.yml # Hexo 站点配置
├── _config.butterfly.yml # 主题配置(inject / CDN 本地化 / cover_generator)
├── source/
│ ├── _posts/ # 文章(UTF-8 无 BOM!)
│ ├── css/ js/ # 自研样式与脚本(vendor/ 下是本地化的第三方库)
│ ├── books/ music/ calendar/ galleryGroup/ # layout:false 的独立页
│ ├── history/json/ # 「历史上的今天」数据(已瘦身)
│ └── nanaly/ # 「小黑」AI 面板
├── scripts/ # Hexo 构建期脚本(自动 require)
├── functions/api/ # Cloudflare Pages Functions(worklog / weather / cover)
├── tools/ # 本地工具链:publish / vendor-sync / history-slim / tests
├── docs/ # 工程文档(索引 docs/README.md)
└── .github/workflows/ci.yml # CI:语法 + 回归测试 + vendor + 构建 + 产物断言

十、出问题先看这里

现象 原因 / 处理
改了开关 / 配置没生效 忘了 hexo generate(配置是构建期产物)
全站封面长得一样 cover_generator.unsplash_enable 没开,或没配 UNSPLASH_KEY,或改完 secret 没重新部署
封面全是渐变 代理拿不到图(未配 Key / 跨站 / 上游限流)。设计如此:静默回退,不清缓存也会在 24h 后自动重试
/calendar/ 写不进去 在 GitHub Pages 上(Functions 不执行)→ 只读;或口令错了 → 页面底部重填
日历格子一片灰 KV 绑定丢了或接口 404,页面顶部提示条会写原因
天气一直显示南京 浏览器定位被拒;或 QWEATHER_HOST 用的还是 devapi.qweather.com(它没有 GeoAPI,恒 404)
某段中文变成乱码字符 文件被 GBK 覆写损坏 → npm run books:check 会指出行号
站点越用越卡 大概率是 Pjax 监听器泄漏,跑 npm test 看断言
推送后线上没变 Cloudflare 会自动构建;GitHub Pages 那套要单独跑 npm run deploy

最后一句话:动这个站之前先跑一句 npm run check 摸底,改完再跑 npm run publish "说明" ——
本地的构建校验已经把「推上去才发现坏了」挡在门口了。