卡片列表(cardlist)
lib/content/cardlist.ts 是一个 markdown-it 插件:把 ::: cardlist 容器包裹的 Markdown 表格重渲染成卡片网格,数据仍然写在表格里,方便编辑:
::: cardlist
| 名称 | 加入方式 | 二维码 |
| :-------: | :---------------------: | :-------------------------------------------------------------------------: |
| NKUwiki组 | :qq:[1108024910](链接) | <QrCode src="https://qm.qq.com/q/xxxx" label="NKUwiki项目组" :size="128" /> |
| 某社团 | :wechat: 公众号私信加群 | |
:::实际渲染效果见兴趣群汇总——那里每张卡片都来自这样一个表格(站内目前用的都是群组卡片模式,美食卡片模式暂无实际用例)。
表头(列名)决定渲染方式
| 列名 | 行为 |
|---|---|
| 第一列 | 卡片标题 |
头像 | 卡片头像,写 、链接或裸路径均可 |
加入方式 / 群号 | 渲染为联系区,支持图标短码与链接;群组模式还会自动从单元格提取 QQ 号 |
二维码 | 写 <QrCode> 组件,见下文 |
备注 | 渲染为卡片上的徽章 |
文章 / 详情 | 填一个链接(如 [查看详情](/pages/Xxx/)),整张卡片可点击跳转;该列本身不显示在卡片上 |
菜品 | 美食卡评分,写四段数字如 4-3-4-4,自动标注菜品/菜量/环境/总体 |
评价 / 主要工作 | 渲染为描述段落 |
| 其他列名 | 渲染为通用的「列名: 内容」字段,空单元格自动跳过 |
表头里出现「群号 / 加入方式 / 二维码」时整表按群组卡片渲染(带头像与联系区),否则按美食卡片渲染(带菜品评分),无需手动指定。
单元格内的配套组件
以下三个组件都设计在 cardlist 单元格里使用,各写一行即可:
| 组件 | 写法 | 参数 |
|---|---|---|
| QrCode | <QrCode src="https://qm.qq.com/q/xxxx" label="群名称" :size="128" /> | src 必填;label 悬停大图标题;size 卡片图标尺寸(px),默认 128 |
| HoverMedia | <HoverMedia kind="image" src="/img/03/03/xx.png" label="负责人微信" /> | src 必填;kind 为 qr(默认,显示「扫码」)或 image;label 悬停提示,qr 默认「扫码加入」;size 预览尺寸(px),默认 200 |
| CopyContact | <CopyContact value="123456789" /> | value 必填,点击复制到剪贴板并提示「已复制」 |
图标短码
单元格中写 :名称: 会替换为对应图标,当前支持 :qq:、:wechat:、:feishu:、:mail:、:link:;需要更多图标时在 lib/content/cardlist.ts 的 cardIcons 中追加(需 import 对应的 @iconify-icons/ri/* 图标)。
群头像生成(GroupAvatar)
「头像」列缺省时,cardlist 会为每个卡片自动生成 <GroupAvatar>:候选头像按「手动 avatar 字段 → 按 QQ 号请求 p.qlogo.cn 群头像」排列,加载失败自动顺延,全部失败显示站点图标。组件本身一般不手写。
应用卡片(AppCards)
「图标 + 名称 + 一句话简介」的紧凑卡片网格,数据写在 links 属性里:
<AppCards :links="[
{ text: '菜鸟', desc: '查快递物流与取件码', link: '/pages/Delivery' },
{ text: '学习通', desc: '签到、网课、作业与考试' }
]" />渲染效果(写 link 的整卡可点,不写则只有卡片本身):
| 字段 | 说明 |
|---|---|
text | 应用名称,最多两行,超出省略 |
icon | 图标图片,写站内路径(如 /img/04/xx.png,图片放 docs/public/ 下,自动拼 base)或外链均可;缺省时显示名称首字的方块 |
desc | 一句话简介,默认不限行数 |
link | 整卡跳转链接:站内路径当前页跳转,http(s) 外链新窗口打开,其余协议一律忽略 |
可选 props:width(列宽下限,默认 12em)、textLines(名称行数上限,默认 2)、descLines(简介行数上限,默认不限制)。
注意 links 是 Vue 属性值,[文字](链接) 这类 Markdown 语法不会被渲染,跳转请使用 link 字段。
图片画廊(Gallery)
<Gallery> 容器包裹若干 Markdown 图片,按各图的原始宽高比排成等高行、逐行铺满正文宽度:
<Gallery :row-height="220" :gap="8">



</Gallery>| 属性 | 默认值 | 说明 |
|---|---|---|
row-height | 220 | 每行的目标高度(px),行内图片按原始比例缩放到统一行高 |
gap | 8 | 图片间距(px),行内与行间共用 |
实现要点:
- 起始标签、图片与结束标签之间各保留一个空行,否则容器内不被当作 Markdown 解析,图片不显示
- 图片在全部加载完成前按原始尺寸平铺(同时是无 JS 时的降级形态),加载完成后由
ResizeObserver触发重排 - 布局算法是纯函数
lib/content/gallery.ts(贪心分行、行内等高铺满、末行不拉伸),单测在tests/gallery.test.ts
页面内实际渲染效果:






友情链接(FriendLinks)
lib/content/flink.ts 是一个 markdown-it 插件:把 md 里的 <flink> 数据块解析成 props 注入 FriendLinks 组件(数据与样式分离,友链数据直接在 md 中编辑)。每行一个字段,空行分隔条目:
<flink>
- name: 站点名称
image: /img/10/10/头像.png
url: https://example.com/
description: 一句话介绍
tag: 兄弟院校
qr: true
</flink>name 与 url 必填,image(头像)、description、tag(徽章)、qr(微信公众号,卡片不可点、悬停显二维码)、preview(悬停查看图片)可选;url 只接受站内相对路径与 http(s) 外链,其余协议的条目静默丢弃。完整用例见友情链接页。
分享图下载(DownloadPageImage)
空组件,渲染一组分享按钮:点击后用 share-image.ts(modern-screenshot)把当前页面截图拼上站点二维码生成分享长图并下载。直接写:
<div class="section" style="text-align:center;">
<DownloadPageImage />
</div>全站搜索(WikiSearch)
导航栏放大镜或 Ctrl + K(macOS Cmd + K)呼出。接线方式:
- 索引由
lib/search/search.ts在构建期扫描全部文章生成,经 data loadersearch.data.ts懒加载(不进首屏主包); - 分词与摘要高亮是双端共用的纯函数
lib/search/searchCore.ts——MiniSearch 默认分词不识别中文,自建分词按词命中并标注章节。
阅读增强(Nolebase)
导航栏右侧的「阅读增强」菜单由 @nolebase/vitepress-plugin-enhanced-readabilities 提供,WikiLayout 挂载两个入口:桌面端 NolebaseEnhancedReadabilitiesMenu(#nav-bar-content-after)与移动端 NolebaseEnhancedReadabilitiesScreenMenu(#nav-screen-content-after)。提供两类能力:
- 布局切换:调整页面宽度,四种模式——默认(Original)、全屏宽度(FullWidth)、仅侧边栏宽度可调、两者宽度均可调,后两种附带内容/页面最大宽度的滑杆;适合大表格与卡片页临时放宽正文
- 聚光灯:以光标为中心高亮当前阅读区域,有「下方遮罩(Under)」与「侧边竖线(Aside)」两种样式,Aside 的竖线在
theme/styles/nolebase.css里调成品牌色
站点侧的定制:桌面端聚光灯默认开启——主题入口只在 localStorage 无记录时写入默认值,用户改动后完全尊重其选择;触屏设备插件自身禁用聚光灯。设置均持久化在 localStorage,插件主题适配样式集中在 theme/styles/nolebase.css。
文章元信息与作者(ArticleMeta / ArticleAuthors)
ArticleMeta 在构建期由 config.mts 的 article-title core rule 注入正文最前(防重复插入有 wikiTitleInserted 标记),渲染分类面包屑、标题、日期、字数与阅读时间、标签;页尾「本文作者」由 WikiLayout 挂载 ArticleAuthors 渲染,作者来自 frontmatter 的 author 字段,经 lib/data/authors.ts 的 collectAuthors 与站点侧成员表(docs/.vitepress/data/members.ts)补全头像与联系方式。字段写法见贡献指南。
页面历史(GitHistory)
WikiLayout 挂载 GitHistory(frontmatter hideChangelog: true 可单篇隐藏)。数据链路:docs/.vitepress/scripts/gen-history.mjs 用 git log --follow 生成 history.json(dev/build 前自动执行、带缓存)→ transformPageData 按页注入各自的提交记录(全量数据不进主题入口包)→ contributors-mapping.json 把邮箱/姓名映射到 GitHub 用户名。
评论与在线编辑(Twikoo / webObsidian)
参考 QUT wiki 预留的两个前端接入接口,组件已就位。评论后端已上线(官方版 Twikoo 2.0.13,自建部署,接入 https://nkustudy.top/api/twikoo/ 反向代理),地址写在仓库根 docs/.env 的 VITE_TWIKOO_ENV_ID,随仓库分发、无需额外配置即可构建出带评论的站点;在线编辑平台尚未部署,未设置环境变量前整体不渲染、零开销。
| 接口 | 组件 | 环境变量 | 出现位置 |
|---|---|---|---|
| 评论 | TwikooComments | VITE_TWIKOO_ENV_ID | 正文之后、页面历史之前(doc-after) |
| 在线编辑 | OnlineEdit | VITE_EDITOR_URL | 作者胶囊下方(doc-footer-before) |
VITE_TWIKOO_ENV_ID:Twikoo 后端地址(腾讯云 Cloudbase 环境 ID,或自建后端的完整 URL,如https://twikoo.example.com)。twikoo包经动态import()拆成独立 chunk,仅评论区出现时下载VITE_EDITOR_URL:webObsidian 在线编辑平台的部署地址(如https://edit.example.com)。深链格式为平台地址/note/+ 逐段编码的文章相对路径(相对docs/),平台的 vault 根需对应仓库的docs/- 单篇关闭:frontmatter 写
comments: false(隐藏该页评论区)或edit: false(隐藏该页按钮);layout: page/layout: home的页面(首页、地图)不显示评论 - CI 的
static.yml不再用 secrets 透传这两个变量:secret 未配置时会注入空串,而process.env里的VITE_变量优先级高于.env文件,空串会把已配置的地址覆盖掉、功能被静默关闭。需要保密的值才走 secret,且必须先确认已填写 - 本地开发想在
docs/.env之外覆盖,可在docs/.env.local写入同名变量
公告横条(AnnouncementBar)
固定在视口顶部的全站公告,配置在站点侧 docs/.vitepress/data/announcement.ts:
export const announcement = {
message: '', // 公告文字;空字符串表示隐藏(默认)
background: '', // 可选,留空用主题色底
color: '', // 可选,留空白色文字
}接线方式:config.mts 经 vite.define 以 __ANNOUNCEMENT__ 双重序列化注入(对象配置需 JSON.stringify(JSON.stringify(...)),包内 lib/data/announcement.ts 解析,未注入返回 null);横条通过 VitePress 内置的 --vp-layout-top-height 约定让下方区域让位——CSS 给 40px 兜底值,组件挂载后用 ResizeObserver 实测高度覆盖,适配文字换行。修改后需重新构建(pnpm dev 需重启)。
- 关闭记忆:配置里可加
id(缺省取 message)与lifetime('session'本次会话不再显示 /'once'永久不再显示;不填则关闭不记忆)。用户关闭后按 id 记录,发新公告换一个 id 就会重新弹出。
校园地图(MapView)
校园地图由 MapView.vue 渲染(上游地图项目整体移植,不参与 ESLint),基于高德 JS API,覆盖八里台、津南两校区 17 类点位,支持分类筛选、详情卡、直达链接、关联文章跳转与导航。组件不内置数据:点位、分类、校区与高德密钥都在站点侧 docs/.vitepress/data/map-data.js,经 data prop 注入(格式示例见包内 map-data.example.js)。
新增或修改点位(BUILDINGS)
地图上的每个标点对应 BUILDINGS 数组里的一条数据:
{
"id": "n_landmark_01", // 唯一标识,不要与其他点位重复
"name": "南开大学主楼", // 标点与列表里显示的名称
"category": "landmark", // 分类 key,必须是 CATEGORY_CONFIG 里已有的
"campusId": "n", // 所属校区:"n" 八里台 / "j" 津南
"coord": [117.171274, 39.102101], // 坐标 [经度, 纬度]
"desc": "八里台校区标志性主楼……", // 详情卡上的介绍文字
"photos": ["/map/pic/xxx.png"], // 详情卡图片,可选,文件放 docs/public/map/pic/
"article": "/pages/BalitaiCampus/#主楼" // 关联文章,可选,见「点位关联文章」
}| 字段 | 说明 |
|---|---|
id | 唯一标识。惯例是「校区前缀 + 分类 + 序号」,如 n_canteen_02、j_library_01 |
name | 标点气泡、左侧列表与详情卡显示的名称 |
category | 决定标点图标与颜色,可用取值见可用的 category |
campusId | "n" 八里台校区、"j" 津南校区 |
coord | [经度, 纬度]。用站内拾取工具点选获取(GCJ02 坐标,不要用 GPS 原始坐标或百度坐标,会有偏移)。导航外链与步行路线都会自动换算 |
desc | 点击标点后详情卡里的介绍,一两句话即可 |
photos | 详情卡图片数组,可选。图片放进 docs/public/map/pic/,路径以 /map/pic/ 开头 |
article | 关联文章,可选,用法见点位关联文章 |
用站内工具拾取坐标,连数据一起生成
打开校园地图并进入拾取模式,或者在地图页按 Shift+P、点页头右侧的十字准星图标,都会打开拾取面板:
- 点击地图任意位置取坐标,拖动紫色标记可以微调(GCJ02,与数据文件同一坐标系,可直接用);
- 填上名称、分类、校区(描述、图片、关联文章选填),工具会自动算出不撞号的
id; - 面板底部实时生成上面那种格式的完整数据条,点「复制数据条」贴进
map-data.js的BUILDINGS数组即可。
同一位置 25 米内已有其它点位时会先列出来,避免重复标注。粘到数组最末(] 之前)时记得把最后一行的逗号去掉。
可用的 category
点位的 category 必须是 CATEGORY_CONFIG 里已有的 key,当前共 17 个。每个分类有自己的标点颜色,地图标点、左侧列表图标、详情卡图标共用同一套色:
| key | 显示名 | 标点颜色 | 典型用途 |
|---|---|---|---|
landmark | 地标建筑 | #F43F5E | 主楼、马蹄湖、纪念雕塑等标志性地点 |
teaching | 教学楼 | #3B82F6 | 公共教学楼、电教中心 |
college | 学院楼 | #6366F1 | 各学院的教学科研楼(伯苓楼、省身楼等) |
admin | 行政楼 | #64748B | 机关办公楼、出版社 |
library | 图书馆 | #8B5CF6 | 各校区图书馆 |
dormitory | 学生宿舍 | #10B981 | 学生公寓区 |
canteen | 食堂 | #F59E0B | 各食堂 |
sports | 运动场馆 | #06B6D4 | 田径场、体育馆、游泳馆 |
theater | 剧场 | #D946EF | 音乐厅、演出与活动场地 |
hospital | 校医院 | #DC2626 | 校区医院 |
gate | 校门 | #14B8A6 | 各校门 |
busstation | 校车站 | #0EA5E9 | 校内巴士站点 |
transit | 轨道交通 | #4F46E5 | 地铁站 |
express | 快递点 | #EAB308 | 快递驿站 |
shop | 商铺 | #EC4899 | 校内超市、门市 |
food | 附近美食 | #F97316 | 校外餐馆(如西南村) |
tool | 维修点 | #A18063 | 打印、维修等服务点 |
颜色都在 map-data.js 的 CATEGORY_CONFIG 里,改那一行的 color 即可,无需动组件。
都不合适时,需要在 map-data.js 里新增分类——文件头注释写明了要同步修改的三处配置;不熟悉的话也可以请管理员代为添加。
底图不显示高德自带的建筑名
高德底图默认会画出接口下发的 POI 点(「某某楼」「某商店」这类建筑名),和我们的标点重名且互相遮挡。站点已通过 MAP_DISPLAY 关掉:
export const MAP_DISPLAY = {
showPoi: false, // 去掉 point 要素,底图只留底色 / 道路 / 建筑轮廓
showLabels: true, // 保留道路名等文字标注
}想恢复高德自带的建筑名,把 showPoi 改成 true 即可;showLabels 改成 false 会连道路名一起隐藏。
缩放分档与校区边界
地图上的标点不是一次全画出来的,而是按缩放分三档累加显示(规则在 packages/wiki-map/lod.js):
| 档位 | 缩放级别(相对各校区默认值) | 显示内容 |
|---|---|---|
| 1 档 | 低于默认值两档及以上 | 收起分类标点,每个校区只留一枚写着校区名的大标点(当前浏览的实色、另一个淡化) |
| 2 档 | 默认值附近 | 1 档点位 + 教学楼、食堂、行政楼、轨道交通 |
| 3 档 | 高于默认值 | 全部点位 |
每个点位归在哪一档由 category 决定:landmark、gate、library 是 1 档;teaching、canteen、admin、transit 是 2 档;其余落到 3 档。某条数据想单独调整,就在该点位上加一个 tier: 1|2|3,数据里的值优先于分类兜底——比如把某个学院楼提到 1 档。
阈值是相对各校区 CAMPUS_CONFIG 里的 zoom 算的(八里台 17、津南 16),写成绝对值会让津南一进页面就掉进 1 档。
改了分类映射或给点位加了 tier 之后,跑一次 node scripts/check-lod.mjs:它把每个校区、每一档的点位数和累计可见数打成表。分档出错在地图上只表现为「某个点忽然不见了」,肉眼很难发现,看数字一眼就能判断(比如 2 档从 32 掉到 8,多半是映射写错了)。
校区轮廓(地图上那圈虚线)来自 docs/.vitepress/data/campus-boundary.js,由 scripts/gen-boundary.mjs 从 OpenStreetMap 拉取后转成 GCJ02 生成,不要手改——边界要调整就去 OSM 改图形再重跑脚本。格式是 { 校区 key: [环, 环...] },经 map-data.js 以 POLYGONS 名字导出给组件;缺这个导出时地图照常工作,只是不画轮廓、1 档标点退回校区配置的坐标。
边界数据来自 © OpenStreetMap contributors,按 ODbL 1.0 授权使用。
小屏与低内存设备的降级
桌面端的地图是两层叠成的:下层是高德底图(只留底色、道路与建筑轮廓),上层是另一个地图实例,专门画「校外 POI」,两层视野逐帧对齐。两层意味着两套瓦片请求与两块 canvas,而它换来的只是「校外也显示高德原生 POI」这件锦上添花的事,所以:
- 视口 ≤1024px(手机、竖屏平板),或
navigator.deviceMemory < 4(认不出来时按够用处理,Firefox / Safari 都是这种)时,上层不建,地图退化成单层。这时底图会把point要素加回来:校外的 POI 名称改由底图自己提供,代价是城内可能出现与自定义标点重名的标注; - 这个判定只在页面初始化时做一次。运行中把桌面窗口拖窄不会切换形态——重建地图的代价远大于这点收益,而且拖动窗口时闪一下比不做更糟。
顺带一句:MAP_DISPLAY.showPoi 只管上层衬底要不要 point,单层降级时它不生效(那时必须留着,否则校外一片空白)。
直达链接与分享
地图支持把「某个点位」写进地址,方便在聊天里直接发一栋楼,例如 /map?id=n_landmark_01:
- 打开带
?id=的链接会自动切到该点位所属校区再选中它、弹出详情卡(跨校区也能一步到位); - 在地图上点任意点位,地址栏会同步成这条链接。写法是
replaceState,不占用「后退」——否则每点一个点都要多按一次返回;关闭详情卡或切换校区时参数自动去掉; - 详情卡底部有「复制链接」按钮,点一下就能拿到当前点位的直达地址,不用手动划地址栏;复制结果就地显示在按钮上(「链接已复制」/「复制失败」);
id就是BUILDINGS里那条数据的id。改id会让已经发出去的旧链接失效,所以id定下来后尽量别动;- 参数对不上(拼错、点位被删)时静默忽略并清掉参数,不会报错。
和坐标拾取模式可共存:?id=n_landmark_01&pick=1 会既选中点位、又打开拾取面板。
点位关联文章
详情卡可以带一个「查看相关文章」按钮,点击跳转到站内对应文章;链接带锚点时会直接定位到文章里的那一章。在点位数据里加 article 字段即可:
"article": "/pages/BalitaiCampus/#主楼"- 链接写文章的
permalink;#后面填文章里的某个##章节标题(与标题文字一致),不写#则打开文章开头 - 没写
article字段的点位不显示按钮 - 写「校园建筑」类文章时,建议章节标题与地图点位名称对应,方便这样关联
内容增强语法(注释 / 马克笔 / 代码块标题 / 仓库卡片 / 轮播 / 步骤条 / 时间线)
以下语法在任何文章正文中可用,构建期由主题包 lib/markdown/ 下的规则处理:
行内注释:正文里写
{^注释内容}(^ 后不要紧跟数字),渲染为一个※小气泡,悬停或点按显示注释,适合名词解释与补充说明;示例:南开大学※简称「南开」,位于天津市创建于 1919 年。
隐秘文本:写
!!内容!!让部分文字默认被「黑幕」遮住,悬停才显示——适合防剧透、卖关子、制造阅读趣味。后缀分别决定触发方式(.hover缺省 /.click点击切换、点页面其他处收起)与视觉效果(.mask缺省黑幕 /.blur模糊),两者可自由组合,四种写法的实际效果:!!文字!!—— 黑幕 + 悬停显示:你知道吗,鲁迅曾说过:"我没说过这句话!"!!文字!!{.click}—— 黑幕 + 点击切换:点一下 → 点击之后才显形!!文字!!{.blur}—— 模糊 + 悬停看清:悬停看看 → 这行字是模糊的!!文字!!{.blur .click}—— 模糊 + 点击:要点击 → 先模糊,点一下才显形
加粗与马克笔:
**文字**与==文字==是同一套视觉——共用同一条「底部色带」与同一个色值,区别只剩字重与有没有描线动画。三种写法的实际效果:**文字**—— 粗体 + 色带(静态):重点标注==文字==—— 色带 + 自左向右的描线动画,不加粗:重点标注==**文字**==—— 粗体 + 色带,组合只画一层:重点标注
组合写法要马克笔在外(
==**文字**==)。反过来写成**==文字**==时,markdown-it 的定界符配对不跨越容器边界,**会退化成字面文本显示出来,请避免。适合考试重点、必背内容。色带的颜色与厚度各由一个令牌控制,都在主题包
theme/styles/tokens.css:--wiki-emphasis-band(颜色,浅色 / 深色各一档)与--wiki-emphasis-band-height(厚度)。改这两处即可同时调整以上全部写法,不必逐处找。马克笔末尾写
{.配色}可换颜色,内置三种(色值取自上游 plume 主题):==文字=={.tip}—— 提示(绿):重点标注==文字=={.caution}—— 注意(黄):重点标注==文字=={.warning}—— 警告(红):重点标注
其余类名落回默认色带。三色为实色(上游即如此),深浅色模式各有一档。内容里可以正常嵌套加粗、行内代码等标记。
混排示例:本站使用了 VitePress 构建,校园地图 支持 3D 视角,期末占位 请以教务处通知为准。
代码块标题:围栏信息串里在语言名后写
title="文件名",代码块顶部会显示标题栏(标题必须带引号,语言名不能省)。写法:
```ts title="config.mts"
export default {}
```渲染效果(下面代码块顶部即是标题栏):
export default {
// 顶部显示 config.mts,读者一眼知道这段代码属于哪个文件
}- 仓库卡片:写
<RepoCard repo="owner/repo" />渲染 GitHub 仓库卡片——星标 / Fork(超千显示1.2k)、主语言色点、License、Public/Archived 可见性徽章。组织仓库默认显示全名owner/repo,个人仓库只显示仓库名,写fullname属性可强制显示全名。结果缓存 24 小时,避免触发 GitHub 接口限流;请求失败时降级为一行链接。写法:
<RepoCard repo="NKUwiki/NKUwiki" />渲染效果(本项目仓库属于组织,默认显示全名):
NKUwiki/NKUwiki换成个人仓库,默认只显示仓库名(需要全名时加 fullname):
- 轮播:把多张图片包进
<WikiSwiper>与</WikiSwiper>之间即成横滑轮播(移动端手势可用,桌面悬停显示左右按钮)。注意组件标签与图片之间要留空行,否则图片不会被当作 Markdown 解析。写法:
<WikiSwiper>


</WikiSwiper>渲染效果(可横向滑动,桌面也可按住拖动):



- 步骤条:
::: steps容器把里面的有序/无序列表渲染成带圆点编号与竖向连线的步骤条,适合写「一步一步操作」的教程。容器内就是普通 Markdown 列表,编号由 CSS 自动生成(无序列表也会依次编号)。写法:
::: steps
1. 打开设置页面
点击右上角头像 → 设置
2. 修改昵称
输入新昵称后保存
:::- 时间线:
::: timeline容器把无序列表渲染成带时间点与连线的时间轴,适合展示发展沿革、版本历程。每个条目第一行是标题,同段紧随的下一行可写条目属性(空格分隔的key="value"):time(时间文字)、type(配色,可选info/tip/success/warning/danger/caution/important)、color(自定义色点与连线颜色)、line(连线样式solid/dashed/dotted)、card(card="true"卡片化该条目)、placement(left/right)。属性行之后空一行再写正文。容器可带属性:horizontal(横向排列)、card(全部卡片化)、placement="left|right"、line="solid|dashed|dotted"。写法:
::: timeline
- 建站立项
time="2024-09" type="tip"
首页与首批词条上线。
- 全站改版
time="2025-03" line="dashed" color="#a855f7"
迁移至新主题,新增校园地图。
:::两种容器的实际渲染效果:
- 打开设置页面 点击右上角头像,进入「设置」。
- 修改昵称 输入新昵称后点击保存。
- 完成 刷新页面即可看到新昵称。
建站立项
首页与首批词条上线。
2024-09
全站改版
迁移至新主题,新增校园地图与图片预览。
2025-03
持续建设中
欢迎通过下方贡献指南投稿新词条。
图片防布局跳动(CLS)
正文中的本地图片(/img/… 与相对路径)在构建期自动读取宽高并写入 <img> 的 width/height 属性,图片加载完成前浏览器即可预留空间,页面不再跳动。手动写了尺寸的、远程图片不参与。
图片预览(Lightbox)
点击文章正文里的任意图片即全屏放大查看:同一页有多张图时可用左右按钮或键盘 ←/→ 切换(右上显示「当前 / 总数」);滚轮以鼠标位置为中心缩放、拖拽平移、双击在原始/2.5 倍间切换;Esc 或点击遮罩关闭;图片的 alt 文字会作为题注显示在底部。实现为 theme/components/ImagePreview.vue(参考 QUT-WiKi 的查看器交互),随 WikiLayout 全站挂载,无需在文章里写任何标记。头像、仓库卡片图标等非内容图不参与预览。
交互动效
站点的动效不是逐处零散添加的,而是共用一套令牌(主题包 theme/styles/tokens.css)与三条硬性约定——这样全站动效「像是同一个人调的」,需要调整或降级时也只动一处。
令牌
| 类别 | 变量 | 说明 |
|---|---|---|
| 时长 | --wiki-duration-fast / --wiki-duration / --wiki-duration-collapse / --wiki-duration-slow / --wiki-duration-drawer / --wiki-duration-page | 依次对应微交互 120ms、色彩与背景 160ms、分类折叠 200ms、面板与容器 280ms、抽屉移出 360ms、跨页转场 260ms |
| 缓动 | --wiki-ease-out / --wiki-ease-in-out / --wiki-ease-spring | 入场用 ease-out(默认),往复用 ease-in-out,小元素落位用 spring |
| 编排 | --wiki-stagger | 首页各区块依次入场的间隔(60ms)。这是节奏而非单个动效时长,但同样随降级归零 |
| 位移 | --wiki-lift | 卡片悬停的统一抬升量(2px) |
三条约定
- 只引用令牌:新增动效一律取上面的变量,不硬编码毫秒数——
base.css的prefers-reduced-motion块靠把这些变量压成0s实现全局降级,硬编码的时长会绕过它; - 优先只动
opacity与transform:这两者只走合成阶段、不触发重排。确需动布局属性的(侧栏让位、分类折叠)单独配更短的时长,控制总帧数; - CSS 默认可见、JS 才加隐藏:需要 JS 参与的动效(滚动渐显、图片淡入)都遵循此约定,SSR 输出、禁用 JS、hydrate 完成前内容一律直接显示,不会白屏。
动效清单
| 位置 | 动效 | 实现要点 |
|---|---|---|
| 跨页导航 | 正文淡入 + 轻微上移 | composables/pageTransition.ts。首次进站、带 #hash 的跳转、首页均不播——首页有自己的错落入场,再叠一层整体淡入会把编排压平 |
| 首页 | hero 与各栏区块错落淡入 | styles/home.css,纯 CSS animation;区块本就在首屏内,「进入视口才显示」没有意义 |
| 侧栏收起 / 展开 | 抽屉式滑出滑入 | 覆盖默认主题的 display: none 硬切;visibility 延迟到动画播完再切隐藏,保留「折叠项不在 Tab 焦点序列」的原生行为 |
| 侧栏分类 | 展开 / 折叠高度过渡 | grid-template-rows: 1fr → 0fr。这是布局动画(展开要重排该分类下全部条目),因此比 slow 档更短 |
| 右侧大纲 | 当前章节的指示条与文字渐变 | VitePress 默认主题已内置过渡,这里只把时长与曲线统一到项目令牌 |
| 正文图片 | 加载完成淡入 | composables/imageFade.ts,与构建期注入的宽高配合,不产生布局跳动;加载失败也摘除,不留破图 |
| 图片预览 | 遮罩淡入 + 内容放大到位 | components/ImagePreview.vue。缩放施加在舞台容器而非 img 上——后者的 transform 由拖拽 / 缩放逻辑接管着 |
| 长列表 | 进入视口逐条渐显 | composables/reveal.ts,用于时间线与页面历史等带 data-reveal 的元素,只表演一次 |
| 分类页筛选 | 条目位置过渡(FLIP) | ArticleIndex.vue:读 / 写严格分离避免强制同步布局,只对视口内条目做动画;新出现的条目走淡入 |
| 列表与搜索框 | 开合缩放 | styles/search.css:背板淡入,面板从略小略高的位置落到原位 |
| 深浅色切换 | 滑块弹性落位、日月图标交换、全页颜色过渡 | WikiLayout.vue + styles/base.css。整页颜色过渡只在用户点击切换时启用(到时摘除类),平时不挂 transition,不影响 hover 等日常交互;只覆盖大面积容器,不扩大到全站 |
| 卡片 / 按钮 | 悬停抬升、按下轻微收缩 | 统一走 --wiki-lift 与 --wiki-duration-fast |
| 正文表格 / 链接 | 行悬停高亮、链接品牌色扫过 | styles/article.css。链接扫过用 background-size 过渡而非改下划线——后者可能影响行高 |
| 文章元信息 | 字数与阅读时长计数 | components/ArticleMeta.vue:SSR 输出真实值(hydrate 不 mismatch),挂载后从 0 计数;reduced-motion 下直接落终值 |
| 页面顶部 | 公告横条滑入 | 关闭仍是即时——横条高度参与 --vp-layout-top-height,导航栏 / 侧栏 / 本地导航都按它让位,做离场动画要额外解决与这些元素的高度联动 |
| 标题锚点 | 平滑滚动 + 抵达高亮 | composables/headingHighlight.ts。在主题模块顶层注册,比 VitePress 自带的锚点跳转监听更早,才能接管它的瞬时跳转 |
降级与克制
prefers-reduced-motion: reduce 下所有时长令牌归零,动效即时完成;悬浮球的回顶交互退化为瞬时跳转。系统开启「减少动态效果」后无需站内额外设置。
有意不做的:视差滚动、装饰性循环动画、大面积滚动驱动动画——与内容站的气质和移动端性能都冲突。
全局水印与打印样式
- 全局水印:全站页面平铺斜向站名水印(
theme/components/WaterMark.vue,参考南理工手册站样式),SVG 平铺生成、亮暗色两套随主题切换,pointer-events: none不影响任何交互;frontmatter 设watermark: false可对单页关闭。 - 打印样式:打印/导出 PDF 时自动隐藏导航、侧栏、目录大纲、页脚导航与所有浮层(公告条/悬浮球/图片预览/水印),正文占满页宽只留内容与版权声明;暗色模式打印时自动切回浅色,打印结束恢复。文章页右侧「本页目录」面板顶部有打印本页按钮(
theme/components/PrintButton.vue)一键唤起。
悬浮球(BackTop)
右下角常驻悬浮球:环形进度实时展示整页阅读进度,滚动中环心显示「百分比%」,静止约 1 秒后淡回箭头图标,点击平滑回到顶部。滚动超过一屏后才出现(theme/components/BackTop.vue,交互对齐 plume 主题的 VPBackToTop);打印时不显示,prefers-reduced-motion 用户点击改为瞬时回顶。
首页与专题页(WikiHome / ArticleIndex)
首页(WikiHome)、分类/标签/归档专题页(ArticleIndex)与「关于我们」的成员墙(MemberGrid)由站点侧组件渲染,直接消费 data loader(catalog.data.ts 等)——数据来自 lib/content/catalog.ts 与 category.ts 对 docs/ 编号目录的构建期扫描,侧栏、分类、标签、归档与搜索索引全自动生成,无需手工维护。首页「活动」栏读取 docs/activity/(不生成独立文章页,字段说明见其目录说明)。
一键构建脚本(build.ps1)
仓库根目录的 build.ps1(PowerShell 5.1+)把本地的「构建 → 预览」收成一条命令:
./build.ps1 # 自动在 5000-60000 范围内挑选空闲端口
./build.ps1 -Port 5173 # 指定端口,跳过随机挑选流程:
| 步骤 | 说明 |
|---|---|
| 安装依赖 | 仓库里没有 node_modules 时执行 pnpm install --frozen-lockfile |
| 构建站点 | pnpm build,含页面更新时间生成与 Markdown 死链校验 |
| 挑选端口 | 随机选一个空闲端口(默认在 5000–60000 范围内),避免和其他程序冲突导致预览起不来 |
| 启动预览 | 用 vitepress preview 把刚构建的产物跑在本地服务器上,浏览器打开即可查看,Ctrl+C 退出 |
资源外链替换(replaceAssets)
图片、字体等静态资源目前放在 docs/public 下随站点一起部署。将来要迁到对象存储 / CDN 时,不必改动任何文章里的路径——构建期统一把产物里的本地资源地址改写到外部域名即可(lib/vite/replace-assets.ts):
# 本地照常构建,资源仍是本地路径
pnpm build
# 带上这一条,产物里的本地资源地址全部改写到该域名
WIKI_ASSETS_BASE=https://cdn.example.com pnpm build未设置该变量时插件空转,产物逐字节不变,所以可以放心常驻在配置里。迁移步骤:
- 把
docs/public整个目录上传到对象存储,配上可公开访问的域名; - 用该域名作为
WIKI_ASSETS_BASE重新构建并部署; - 回滚只需去掉这个变量重新构建——正文里写的仍是本地路径,不需要改动。
覆盖范围与已知限制:
| 项目 | 说明 |
|---|---|
| 支持写法 | HTML 的 src/href、CSS 的 url()、JS 里的资源字符串,只要带引号且是站内绝对路径 |
| 不改动源文件 | 只改构建产物,Markdown 与组件里继续写本地路径 |
| 路径形式 | 只认以 / 开头的站内绝对路径;http(s)://、//、data: 开头的不会被重复处理 |
| 不支持 | 拼接的路径('/img/' + 'a.png')、srcset 的多候选写法——需要写成完整字符串 |
| 需手工处理 | docs/public 下原样拷贝、不进打包产物的文件(如 manifest.webmanifest)不会经过插件 |
为什么要两个钩子
VitePress 是先打出 bundle、再用 SSR 渲染页面,最终 HTML 的 <head>(favicon、字体 preload)是在 Vite 的 generateBundle 之后才拼出来的,单个 Vite 插件够不到。因此插件要同时挂 vite.plugins(管 JS/CSS)与 VitePress 的 transformHtml(管 HTML),两者配合才没有漏网。
致谢
本篇的体例参考了 青理 Wiki 的站点功能说明,并结合本站实际情况重写。
评论