这篇文章是本站的说明书 + 命令速查表。
上半部分给「来看页面的人」:每一屏是什么、怎么点、有什么隐藏操作;
下半部分给「来改这个站的人」:写作、校验、发布、后端配置的全部命令。
更深的实现细节(为什么这么做、踩过哪些坑)集中在仓库 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 "文章标题" npx hexo new page "关于"
|
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 ---
|
⚠️ 编码警告: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
npm run cf:preview
|
4.3 一键发布
1 2 3
| npm run publish:check npm run publish "内容:新增 xx" 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 npm run deploy:cf npm run deploy:cf:fast
|
| 命令 |
推到哪 |
结果 |
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 npm run books:check npm test npm run test:worklog npm run test:cover npm run vendor:check npm run vendor:sync
|
| 脚本 |
兜住的坑 |
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 npm run clean
|
主要配置位:
| 文件 |
改什么 |
_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,本站的自定义顶栏、右键菜单都是这么做的。
三个必守约定(动手前先看)
- 自定义脚本必须 Pjax 安全:凡注册了全局监听器 / 定时器的脚本,都要提供成对的卸载逻辑,否则每换一页就多累积一份。参考
home_hub.js 的 teardownHub(),机理与实测见 docs/pjax-safety.md。
- 关键第三方库走本地:pjax / fancybox / instant.page / snackbar / 不蒜子已本地化(jsDelivr 在国内会被 DNS 污染;pjax 一挂,整站就退化成整页跳转)。手改
source/**/vendor/ 却不跑 vendor:sync,sha256 会对不上,CI 直接失败。
- 密钥永不进前端:任何 API Key 都只能放服务端环境变量,前端走同源代理。见第八节。
七、数据维护
7.1 历史上的今天(瘦身 60%)
1 2
| node tools/history-slim.mjs --dry-run node tools/history-slim.mjs
|
7.2 文章封面配置
1
| node scripts/gen-cover-config.js
|
改完 _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:check
|
升级流程:改 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
curl http://localhost:8788/api/worklog/list
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 npm run vendor:sync npm run server npm run cf:preview npm run build npm run clean npx hexo new "标题" npx hexo new page "页面名"
npm run check npm test npm run test:worklog npm run test:cover npm run books:check npm run vendor:check
npm run publish:check npm run publish "提交说明" npm run publish:fast "说明" npm run deploy npm run deploy:cf npm run deploy:cf:fast
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 "提交说明" node tools/books-check-syntax.js
|
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 "说明" ——
本地的构建校验已经把「推上去才发现坏了」挡在门口了。