WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
+539
View File
@@ -0,0 +1,539 @@
<script setup lang="ts">
/**
* 首页线描插画。用内联 SVG 而不是位图:墨线取 currentColor、点缀取 --wk-gold,
* 深浅色模式自动跟随,任意缩放不糊,整套加起来只有几 KB。
*/
defineProps<{ name: string }>()
const gold = 'var(--wk-gold)'
/**
* 首屏全景图右半部分:九个客户端沿一段椭圆弧展开,连线从核心圆的边缘出发。
* 角度、落点与曲线控制点都在这里算,避免在模板里堆一串手写坐标。
*/
const spokes = [-72, -54, -36, -18, 0, 18, 36, 54, 72].map((deg) => {
const t = (deg * Math.PI) / 180
const cx = 760 + 380 * Math.cos(t)
const cy = 122 + 100 * Math.sin(t)
const sx = 760 + 46 * Math.cos(t)
const sy = 122 + 46 * Math.sin(t)
const ex = cx - 15
// 控制点按两端距离取,近的线不会拧成结,远的线自然接近直线
const h = (ex - sx) * 0.45
const f = (n: number) => n.toFixed(1)
return {
cx: Number(cx.toFixed(1)),
cy: Number(cy.toFixed(1)),
d: `M${f(sx)} ${f(sy)}C${f(sx + h)} ${f(sy)} ${f(ex - h)} ${f(cy)} ${f(ex)} ${f(cy)}`,
}
})
/** 24×24 图标统一的线条参数 */
const s = {
fill: 'none',
stroke: 'currentColor',
'stroke-width': 1.4,
'stroke-linecap': 'round',
'stroke-linejoin': 'round',
}
</script>
<template>
<!-- ============ 首屏全景图 ============
一张图讲完 WeKnora 与通用 RAG 示意图不一样的地方:
左边多源接入(文档 / 网页 / 音频 / 图片)收束成一次统一解析,
中间同一份内容并行写入四路索引(向量 / 关键词 / Wiki / 图谱),
右边同一套知识库与 Agent 再扇形展开成九种客户端。
版面规则:
1. 全图是「收束 → 展开 → 收束 → 展开」的节奏,观者顺着疏密变化走,不需要标注;
2. 连接一律走三次贝塞尔,控制点按两端距离取,不用直角折线;
3. 九个客户端落在同一段椭圆弧上(角度在 script 里算),保证间距均匀;
4. 笔触只有三档:主体 1.3、细节 1.1 且 opacity .45、连接线 opacity .3;
金色只给四处焦点:解析框、核心圆、每路索引各一个记号。 -->
<svg
v-if="name === 'flow'"
class="illus illus-flow"
viewBox="0 0 1200 240"
fill="none"
aria-hidden="true"
>
<!-- ---------- 左:四类来源 ---------- -->
<g transform="translate(66 45)" stroke="currentColor" stroke-linejoin="round">
<path stroke-width="1.3" d="M-8-13h10l6 6v20H-8z" />
<path stroke-width="1.3" d="M2-13v6h6" />
<path stroke-width="1.1" opacity="0.45" d="M-4 1h8M-4 6h5" />
</g>
<g transform="translate(66 100)" stroke="currentColor">
<circle stroke-width="1.3" cx="0" cy="0" r="11" />
<ellipse stroke-width="1.3" cx="0" cy="0" rx="4.6" ry="11" />
<path stroke-width="1.1" opacity="0.45" d="M-11 0h22" />
</g>
<g transform="translate(66 155)" stroke="currentColor" stroke-linecap="round">
<path stroke-width="1.3" d="M-9-3.5v7M-4.5-6v12M4.5-5.5v11M9-3v6" />
<path :stroke="gold" stroke-width="1.4" d="M0-8.5v17" />
</g>
<g transform="translate(66 210)" stroke="currentColor" stroke-linejoin="round">
<rect stroke-width="1.3" x="-11" y="-9" width="22" height="18" rx="2.5" />
<path stroke-width="1.1" opacity="0.5" d="M-7 6l5-6 3.5 4 3-3.5 4.5 5.5" />
<circle :fill="gold" stroke="none" cx="-5" cy="-3.5" r="1.8" />
</g>
<!-- 来源汇入解析 -->
<g stroke="currentColor" stroke-width="1.1" opacity="0.3">
<path d="M82 45C150 45 186 102 248 104" />
<path d="M82 100C150 100 190 114 248 116" />
<path d="M82 155C150 155 190 130 248 128" />
<path d="M82 210C150 210 186 140 248 140" />
</g>
<!-- ---------- 统一解析 ---------- -->
<rect x="248" y="82" width="64" height="80" rx="12" fill="none" :stroke="gold" stroke-width="1.4" />
<g stroke="currentColor" stroke-width="1.1" stroke-linecap="round" opacity="0.45">
<path d="M264 104h32M264 120h32M264 136h20" />
</g>
<!-- 解析分流到四路索引 -->
<g stroke="currentColor" stroke-width="1.1" opacity="0.3">
<path d="M312 104C368 104 402 45 462 45" />
<path d="M312 116C368 116 404 100 462 100" />
<path d="M312 128C368 128 404 155 462 155" />
<path d="M312 140C368 140 402 210 462 210" />
</g>
<!-- ---------- 四路索引 ---------- -->
<!-- 向量:均匀点阵 -->
<g :fill="gold" opacity="0.75">
<circle cx="470" cy="39" r="2" />
<circle cx="496" cy="39" r="2" />
<circle cx="522" cy="39" r="2" />
<circle cx="548" cy="39" r="2" />
<circle cx="574" cy="39" r="2" />
<circle cx="600" cy="39" r="2" />
<circle cx="470" cy="51" r="2" />
<circle cx="496" cy="51" r="2" />
<circle cx="522" cy="51" r="2" />
<circle cx="548" cy="51" r="2" />
<circle cx="574" cy="51" r="2" />
<circle cx="600" cy="51" r="2" />
</g>
<!-- 关键词:命中的词被标出来 -->
<g stroke="currentColor" stroke-width="1.1" stroke-linecap="round" opacity="0.45">
<path d="M470 94h130M534 106h66" />
</g>
<path d="M470 106h52" :stroke="gold" stroke-width="1.6" stroke-linecap="round" />
<!-- Wiki:互链的两页 -->
<g stroke="currentColor" stroke-width="1.3" stroke-linejoin="round">
<rect x="470" y="141" width="44" height="22" rx="4" />
<rect x="556" y="155" width="44" height="22" rx="4" />
</g>
<path d="M514 152C532 152 538 166 556 166" :stroke="gold" stroke-width="1.4" />
<!-- 图谱:实体与关系 -->
<g stroke="currentColor" stroke-width="1.1" opacity="0.4">
<path d="M484 204C500 206 516 206 529 207" />
<path d="M491 219C505 216 518 212 530 211" />
<path d="M543 206C556 204 570 202 584 201" />
<path d="M540 214C550 218 560 221 570 223" />
</g>
<g stroke="currentColor" stroke-width="1.3">
<circle cx="478" cy="202" r="6" />
<circle cx="486" cy="222" r="5.5" />
<circle cx="590" cy="200" r="5.5" />
<circle cx="575" cy="225" r="5" />
</g>
<circle cx="536" cy="208" r="7" fill="none" :stroke="gold" stroke-width="1.5" />
<!-- 四路索引汇入核心 -->
<g stroke="currentColor" stroke-width="1.1" opacity="0.3">
<path d="M612 45C672 45 700 104 716 106" />
<path d="M612 100C670 100 700 116 716 118" />
<path d="M612 155C670 155 700 128 716 126" />
<path d="M612 210C672 210 700 140 716 138" />
</g>
<!-- ---------- 核心:同一套知识库与 Agent ---------- -->
<circle cx="760" cy="122" r="53" fill="none" stroke="currentColor" stroke-width="1" opacity="0.16" />
<circle cx="760" cy="122" r="46" fill="none" :stroke="gold" stroke-width="1.5" />
<g stroke="currentColor" stroke-width="1.3" stroke-linecap="round" opacity="0.75">
<path d="M740 112h40M740 124h40M740 136h22" />
</g>
<circle cx="775" cy="136" r="3.4" :fill="gold" />
<!-- 核心扇形展开到九种客户端 -->
<g stroke="currentColor" stroke-width="1.1" opacity="0.3">
<path v-for="(sp, i) in spokes" :key="i" :d="sp.d" />
</g>
<!-- ---------- 右:九种客户端 ---------- -->
<!-- Web 控制台:窗口里是带出处标记的回答 -->
<g :transform="`translate(${spokes[0].cx} ${spokes[0].cy})`">
<rect x="-13" y="-10" width="26" height="20" rx="3.5" fill="none" stroke="currentColor" stroke-width="1.3" />
<path d="M-13-4h26" stroke="currentColor" stroke-width="1.1" opacity="0.45" />
<circle cx="-9" cy="-7" r="1.4" :fill="gold" />
<path d="M-9 1h13" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" opacity="0.4" />
<rect x="-9" y="4" width="7" height="4" rx="1.5" fill="none" :stroke="gold" stroke-width="1.2" />
</g>
<!-- 桌面客户端 -->
<g :transform="`translate(${spokes[1].cx} ${spokes[1].cy})`" fill="none">
<rect x="-13" y="-11" width="26" height="17" rx="3" stroke="currentColor" stroke-width="1.3" />
<path stroke="currentColor" stroke-width="1.3" stroke-linecap="round" d="M-5 11h10M0 6v5" />
<path :stroke="gold" stroke-width="1.2" stroke-linecap="round" d="M-8-5h11" />
</g>
<!-- Chrome 插件:网页右侧的问答边栏 -->
<g :transform="`translate(${spokes[2].cx} ${spokes[2].cy})`" fill="none">
<rect x="-13" y="-10" width="26" height="20" rx="3.5" stroke="currentColor" stroke-width="1.3" />
<path d="M-9-4h9M-9 1h6" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" opacity="0.4" />
<path :stroke="gold" stroke-width="1.3" d="M4-10v20" />
<path :stroke="gold" stroke-width="1.1" stroke-linecap="round" opacity="0.75" d="M7-3h4M7 2h4" />
</g>
<!-- 网页嵌入挂件:站点角上的悬浮问答 -->
<g :transform="`translate(${spokes[3].cx} ${spokes[3].cy})`" fill="none">
<rect x="-13" y="-11" width="26" height="22" rx="3.5" stroke="currentColor" stroke-width="1.3" />
<path d="M-9-6h10M-9-1h7" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" opacity="0.4" />
<path :stroke="gold" stroke-width="1.3" stroke-linejoin="round" d="M0 2h11v7H5l-3 2.6V9H0z" />
</g>
<!-- IM 机器人 -->
<g :transform="`translate(${spokes[4].cx} ${spokes[4].cy})`" fill="none">
<rect x="-11" y="-6" width="22" height="17" rx="5" stroke="currentColor" stroke-width="1.3" />
<path d="M0-10.5v4.5" stroke="currentColor" stroke-width="1.3" stroke-linecap="round" />
<circle cx="0" cy="-12" r="1.6" stroke="currentColor" stroke-width="1.2" />
<g :fill="gold">
<circle cx="-4" cy="2.5" r="1.7" />
<circle cx="4" cy="2.5" r="1.7" />
</g>
</g>
<!-- 微信小程序 -->
<g :transform="`translate(${spokes[5].cx} ${spokes[5].cy})`" fill="none">
<rect x="-7" y="-12" width="14" height="24" rx="3.5" stroke="currentColor" stroke-width="1.3" />
<rect x="-4" y="-6" width="8" height="8" rx="2" :stroke="gold" stroke-width="1.3" />
<path d="M-2.5 8.5h5" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" opacity="0.45" />
</g>
<!-- 命令行 -->
<g :transform="`translate(${spokes[6].cx} ${spokes[6].cy})`" fill="none">
<rect x="-13" y="-10" width="26" height="20" rx="3.5" stroke="currentColor" stroke-width="1.3" />
<path :stroke="gold" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" d="M-7-3.5L-3.5 0-7 3.5" />
<path d="M0 4h7" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" opacity="0.45" />
</g>
<!-- REST API 与 SDK -->
<g :transform="`translate(${spokes[7].cx} ${spokes[7].cy})`" fill="none">
<g stroke="currentColor" stroke-width="1.3" stroke-linecap="round">
<path d="M-4-10c-2.8 0-3.2 1.4-3.2 3.4v2.6c0 2-1 3-2.6 3 1.6 0 2.6 1 2.6 3v2.6c0 2 .4 3.4 3.2 3.4" />
<path d="M4-10c2.8 0 3.2 1.4 3.2 3.4v2.6c0 2 1 3 2.6 3-1.6 0-2.6 1-2.6 3v2.6c0 2-.4 3.4-3.2 3.4" />
</g>
<circle cx="0" cy="1" r="1.8" :fill="gold" />
</g>
<!-- MCP:双向 -->
<g :transform="`translate(${spokes[8].cx} ${spokes[8].cy})`" fill="none">
<g stroke="currentColor" stroke-width="1.3">
<rect x="-13" y="-7" width="9" height="14" rx="2.5" />
<rect x="4" y="-7" width="9" height="14" rx="2.5" />
</g>
<path :stroke="gold" stroke-width="1.3" stroke-linecap="round" stroke-linejoin="round" d="M-2.5-3h5.5M1-5l2 2-2 2" />
<path stroke="currentColor" stroke-width="1.2" stroke-linecap="round" stroke-linejoin="round" opacity="0.6" d="M2.5 3h-5.5M-1 1l-2 2 2 2" />
</g>
</svg>
<!-- ==================== 处理流程 四枚 ==================== -->
<!-- 读懂文档:版式还原 -->
<svg v-else-if="name === 'read'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M5 3h9l5 5v13H5z" />
<path v-bind="s" d="M14 3v5h5" />
<path v-bind="s" opacity="0.5" d="M8 12h8M8 15.5h8" />
<path :stroke="gold" stroke-width="1.4" stroke-linecap="round" d="M8 19h5" />
</svg>
<!-- 切好知识:父块套子块 -->
<svg v-else-if="name === 'chunk'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="3" y="3.5" width="18" height="17" rx="2" />
<path v-bind="s" opacity="0.5" d="M3 9h18M3 15h18" />
<rect x="5.5" y="10" width="9" height="4" rx="1" fill="none" :stroke="gold" stroke-width="1.4" />
</svg>
<!-- 找准证据:两路召回汇合成一份排好序的结果 -->
<svg v-else-if="name === 'retrieve'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M2.5 5.5c5.5 0 3.5 6.5 8 6.5" />
<path v-bind="s" d="M2.5 18.5c5.5 0 3.5-6.5 8-6.5" />
<path :stroke="gold" stroke-width="1.6" stroke-linecap="round" d="M13.5 8.5h8" />
<path v-bind="s" opacity="0.55" d="M13.5 12.5h6M13.5 16.5h4" />
</svg>
<!-- 可核对的回答:气泡 + 出处角标 -->
<svg v-else-if="name === 'answer'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M4 4h16v12H9l-5 4z" />
<path v-bind="s" opacity="0.5" d="M7.5 8.5h9M7.5 12h5" />
<circle cx="17" cy="12" r="2" fill="none" :stroke="gold" stroke-width="1.4" />
</svg>
<!-- ==================== 核心能力 九枚 ==================== -->
<!-- Wiki:互链的页面 -->
<svg v-else-if="name === 'wiki'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="2.5" y="3" width="8" height="10" rx="1.5" />
<rect v-bind="s" x="13.5" y="11" width="8" height="10" rx="1.5" />
<path :stroke="gold" stroke-width="1.4" stroke-linecap="round" fill="none" d="M10.5 8h5a2 2 0 012 2v1" />
</svg>
<!-- 分块编辑与版本:历史堆叠 -->
<svg v-else-if="name === 'version'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" opacity="0.45" x="3" y="3" width="14" height="8" rx="1.5" />
<rect v-bind="s" x="7" y="9.5" width="14" height="8" rx="1.5" />
<path :stroke="gold" stroke-width="1.4" stroke-linecap="round" d="M6 21h12" />
</svg>
<!-- 多渠道:中心向外分发 -->
<svg v-else-if="name === 'channels'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<circle cx="12" cy="12" r="3" fill="none" :stroke="gold" stroke-width="1.4" />
<path v-bind="s" opacity="0.7" d="M12 9V4.8M12 15v4.2M9 12H4.8M15 12h4.2" />
<g v-bind="s" opacity="0.55">
<circle cx="12" cy="3.4" r="1.4" />
<circle cx="12" cy="20.6" r="1.4" />
<circle cx="3.4" cy="12" r="1.4" />
<circle cx="20.6" cy="12" r="1.4" />
</g>
</svg>
<!-- MCP:双向 -->
<svg v-else-if="name === 'mcp'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="2.5" y="6" width="7" height="12" rx="1.5" />
<rect v-bind="s" x="14.5" y="6" width="7" height="12" rx="1.5" />
<path
:stroke="gold"
stroke-width="1.4"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
d="M10 9.5h4.5M13 8l1.6 1.5"
/>
<path v-bind="s" d="M14 14.5H9.5M11.1 13l-1.6 1.5" />
</svg>
<!-- 多空间隔离与审计:盾 -->
<svg v-else-if="name === 'govern'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M12 2.8l7.5 3v6.4c0 4-3.2 7.4-7.5 9-4.3-1.6-7.5-5-7.5-9V5.8z" />
<path
:stroke="gold"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
d="M8.8 12.2l2.3 2.3 4.1-4.4"
/>
</svg>
<!-- 可插拔:底板上的可替换模块 -->
<svg v-else-if="name === 'pluggable'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="2.5" y="4" width="19" height="16" rx="2" />
<path v-bind="s" opacity="0.5" d="M2.5 9.5h5M2.5 14.5h5" />
<rect x="10" y="8" width="8" height="8" rx="1.5" fill="none" :stroke="gold" stroke-width="1.4" />
</svg>
<!-- 知识图谱:实体与关系 -->
<svg v-else-if="name === 'graph'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" opacity="0.5" d="M7.2 5.9l9.2 -1.4M6.4 8.7l9.6 8.2M9.9 19.4l5.4 -0.9" />
<circle v-bind="s" cx="5" cy="6.5" r="2.3" />
<circle v-bind="s" cx="7.6" cy="19.6" r="2.3" />
<circle v-bind="s" cx="17.6" cy="18.2" r="2.3" />
<circle cx="18.6" cy="4.4" r="2.4" fill="none" :stroke="gold" stroke-width="1.5" />
</svg>
<!-- 数据源:按计划同步 -->
<svg v-else-if="name === 'sync'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M20 12a8 8 0 10-2.6 5.9" />
<path
:stroke="gold"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
d="M20.6 7.6V12h-4.4"
/>
<path v-bind="s" opacity="0.5" d="M12 7.8V12l2.8 1.8" />
</svg>
<!-- ==================== 部署形态 三枚 ==================== -->
<!-- Docker Compose:一叠一起起来的服务 -->
<svg v-else-if="name === 'compose'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect x="3" y="3.5" width="18" height="5" rx="1.4" fill="none" :stroke="gold" stroke-width="1.4" />
<g v-bind="s">
<rect x="3" y="10.5" width="18" height="5" rx="1.4" />
<rect x="3" y="17.5" width="18" height="5" rx="1.4" />
</g>
<g :fill="gold" opacity="0.85">
<circle cx="6.6" cy="13" r="0.9" />
<circle cx="6.6" cy="20" r="0.9" />
</g>
</svg>
<!-- Helm / Kubernetes:舵轮 -->
<svg v-else-if="name === 'helm'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M12 2.6l8.2 4v7.6L12 21.4 3.8 14.2V6.6z" />
<circle cx="12" cy="12" r="2.4" fill="none" :stroke="gold" stroke-width="1.4" />
<path v-bind="s" opacity="0.55" d="M12 6.4v3.2M9.9 13.4l-2.6 1.9M14.1 13.4l2.6 1.9" />
</svg>
<!-- Lite:单机一台 -->
<svg v-else-if="name === 'lite'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="4" y="4" width="16" height="11" rx="1.8" />
<path v-bind="s" opacity="0.5" d="M2.5 18.5h19" />
<rect x="9" y="7.5" width="6" height="4" rx="1" fill="none" :stroke="gold" stroke-width="1.4" />
</svg>
<!-- ==================== 文档地图 六枚 ==================== -->
<!-- 快速开始:起播 -->
<svg v-else-if="name === 'start'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<circle v-bind="s" cx="12" cy="12" r="9" />
<path
:stroke="gold"
stroke-width="1.5"
stroke-linejoin="round"
stroke-linecap="round"
fill="none"
d="M10 8.4l6 3.6-6 3.6z"
/>
</svg>
<!-- 架构:自上而下的层级 -->
<svg v-else-if="name === 'arch'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect x="8.5" y="2.5" width="7" height="5" rx="1.2" fill="none" :stroke="gold" stroke-width="1.4" />
<rect v-bind="s" x="2.5" y="16" width="7" height="5" rx="1.2" />
<rect v-bind="s" x="14.5" y="16" width="7" height="5" rx="1.2" />
<path v-bind="s" opacity="0.6" d="M12 7.5v4M6 16v-4.5h12V16" />
</svg>
<!-- 功能模块:栅格 -->
<svg v-else-if="name === 'modules'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<g v-bind="s">
<rect x="3" y="3" width="7" height="7" rx="1.4" />
<rect x="3" y="14" width="7" height="7" rx="1.4" />
<rect x="14" y="14" width="7" height="7" rx="1.4" />
</g>
<rect x="14" y="3" width="7" height="7" rx="1.4" fill="none" :stroke="gold" stroke-width="1.4" />
</svg>
<!-- 客户端:多端 -->
<svg v-else-if="name === 'clients'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M14.5 17H3.5a1 1 0 01-1-1V5a1 1 0 011-1h15a1 1 0 011 1v2.5" />
<path v-bind="s" opacity="0.5" d="M7.5 20h5M10 17v3" />
<rect x="15" y="10" width="6.5" height="10" rx="1.5" fill="none" :stroke="gold" stroke-width="1.4" />
</svg>
<!-- 开发指南:源码尖括号 -->
<svg v-else-if="name === 'dev'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M8 7.5L3.5 12 8 16.5M16 7.5l4.5 4.5-4.5 4.5" />
<path :stroke="gold" stroke-width="1.5" stroke-linecap="round" d="M13.6 5.5l-3.2 13" />
</svg>
<!-- ==================== 客户端入口 九枚 ==================== -->
<!-- Web 控制台:浏览器窗口 -->
<svg v-else-if="name === 'console'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="2.5" y="4" width="19" height="16" rx="2" />
<path v-bind="s" d="M2.5 8.5h19" />
<circle :fill="gold" cx="5.6" cy="6.2" r="0.9" />
<path v-bind="s" opacity="0.45" d="M6 12.5h8M6 16h5" />
</svg>
<!-- Chrome 插件:网页右侧的问答边栏 -->
<svg v-else-if="name === 'extension'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="2.5" y="4" width="19" height="16" rx="2" />
<path v-bind="s" opacity="0.45" d="M5.5 8.5h6M5.5 12h4" />
<path :stroke="gold" stroke-width="1.4" d="M14.5 4v16" />
<path :stroke="gold" stroke-width="1.4" stroke-linecap="round" opacity="0.75" d="M17 9h2M17 12.5h2" />
</svg>
<!-- 网页嵌入挂件:站点角上的悬浮问答 -->
<svg v-else-if="name === 'embed'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="2.5" y="3.5" width="19" height="17" rx="2" />
<path v-bind="s" opacity="0.45" d="M6 8h7M6 11.5h5" />
<path
:stroke="gold"
stroke-width="1.4"
stroke-linejoin="round"
fill="none"
d="M12.5 13.5h6.5v3.5h-4l-2.5 1.9z"
/>
</svg>
<!-- 桌面客户端:显示器 -->
<svg v-else-if="name === 'desktop'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="2.5" y="4" width="19" height="12.5" rx="2" />
<path v-bind="s" d="M9.5 20h5M12 16.5V20" />
<path :stroke="gold" stroke-width="1.4" stroke-linecap="round" d="M6.5 8.5h7" />
</svg>
<!-- IM 机器人 -->
<svg v-else-if="name === 'bot'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="3.5" y="7.5" width="17" height="12" rx="3" />
<path v-bind="s" d="M12 4.2v3.3" />
<circle v-bind="s" cx="12" cy="3.2" r="1.2" />
<g :fill="gold">
<circle cx="9" cy="13.2" r="1.3" />
<circle cx="15" cy="13.2" r="1.3" />
</g>
</svg>
<!-- 微信小程序:手机 -->
<svg v-else-if="name === 'mobile'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="6" y="2.5" width="12" height="19" rx="2.5" />
<path v-bind="s" opacity="0.5" d="M10.6 19h2.8" />
<rect x="9" y="7" width="6" height="6" rx="1.4" fill="none" :stroke="gold" stroke-width="1.4" />
</svg>
<!-- 命令行 -->
<svg v-else-if="name === 'cli'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<rect v-bind="s" x="2.5" y="4" width="19" height="16" rx="2" />
<path
:stroke="gold"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
d="M6.5 9.5L9.5 12l-3 2.5"
/>
<path v-bind="s" opacity="0.5" d="M12 15h5.5" />
</svg>
<!-- REST API 与 SDK:花括号 -->
<svg v-else-if="name === 'api'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M9 3.5c-2.2 0-2.6 1.2-2.6 3v2.3c0 1.8-.8 2.6-2.4 3.2 1.6.6 2.4 1.4 2.4 3.2v2.3c0 1.8.4 3 2.6 3" />
<path v-bind="s" d="M15 3.5c2.2 0 2.6 1.2 2.6 3v2.3c0 1.8.8 2.6 2.4 3.2-1.6.6-2.4 1.4-2.4 3.2v2.3c0 1.8-.4 3-2.6 3" />
<circle :fill="gold" cx="12" cy="12" r="1.5" />
</svg>
<!-- FAQ:问答对 -->
<svg v-else-if="name === 'faq'" class="illus" viewBox="0 0 24 24" aria-hidden="true">
<path v-bind="s" d="M3 3.5h13v9H8l-5 4z" />
<path v-bind="s" opacity="0.5" d="M6 7h7M6 10h4" />
<path
:stroke="gold"
stroke-width="1.4"
stroke-linecap="round"
stroke-linejoin="round"
fill="none"
d="M21 15.5h-8.5v4.5H18l3 2.2z"
/>
</svg>
</template>
<style scoped>
.illus {
display: block;
width: 100%;
height: 100%;
overflow: visible;
}
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,232 @@
<script setup lang="ts">
import { nextTick, onMounted, onUnmounted, ref } from 'vue'
const MIN = 0.3
const MAX = 8
const open = ref(false)
const scale = ref(1)
const tx = ref(0)
const ty = ref(0)
const stage = ref<HTMLElement>()
let dragging = false
let originX = 0
let originY = 0
function reset() {
scale.value = 1
tx.value = 0
ty.value = 0
}
function close() {
open.value = false
document.documentElement.style.removeProperty('overflow')
}
async function show(source: SVGElement) {
reset()
open.value = true
document.documentElement.style.overflow = 'hidden'
await nextTick()
const host = stage.value
if (!host) return
host.replaceChildren()
// mermaid 的 svg 内联样式以 #id 作用域,且插件会对同 id 元素重新渲染,
// 因此副本必须换一个 id 并同步改写内部样式表
const clone = source.cloneNode(true) as SVGElement
const oldId = source.id
if (oldId) {
const newId = `${oldId}-zoom`
clone.id = newId
clone.querySelectorAll('style').forEach((s) => {
s.textContent = (s.textContent ?? '').split(`#${oldId}`).join(`#${newId}`)
})
}
clone.removeAttribute('width')
clone.removeAttribute('height')
clone.style.maxWidth = 'none'
clone.style.width = '100%'
clone.style.height = '100%'
host.appendChild(clone)
}
function onDocClick(e: MouseEvent) {
const block = (e.target as HTMLElement | null)?.closest?.('.vp-doc .mermaid')
if (!block) return
const svg = block.querySelector('svg')
if (svg) show(svg as SVGElement)
}
function onKey(e: KeyboardEvent) {
if (!open.value) return
if (e.key === 'Escape') close()
if (e.key === '0') reset()
}
function zoomBy(factor: number) {
scale.value = Math.min(MAX, Math.max(MIN, scale.value * factor))
}
function onWheel(e: WheelEvent) {
e.preventDefault()
zoomBy(e.deltaY < 0 ? 1.12 : 1 / 1.12)
}
function onDown(e: MouseEvent) {
dragging = true
originX = e.clientX - tx.value
originY = e.clientY - ty.value
}
function onMove(e: MouseEvent) {
if (!dragging) return
tx.value = e.clientX - originX
ty.value = e.clientY - originY
}
function onUp() {
dragging = false
}
onMounted(() => {
document.addEventListener('click', onDocClick)
document.addEventListener('keydown', onKey)
window.addEventListener('mousemove', onMove)
window.addEventListener('mouseup', onUp)
})
onUnmounted(() => {
document.removeEventListener('click', onDocClick)
document.removeEventListener('keydown', onKey)
window.removeEventListener('mousemove', onMove)
window.removeEventListener('mouseup', onUp)
document.documentElement.style.removeProperty('overflow')
})
</script>
<template>
<Teleport to="body">
<Transition name="zoom-fade">
<div v-if="open" class="zoom" @wheel="onWheel" @mousedown.self="close">
<div
ref="stage"
class="zoom-stage"
:style="{ transform: `translate(${tx}px, ${ty}px) scale(${scale})` }"
@mousedown.stop="onDown"
/>
<div class="zoom-bar" @mousedown.stop>
<button title="缩小" @click="zoomBy(1 / 1.35)">−</button>
<span class="zoom-level">{{ Math.round(scale * 100) }}%</span>
<button title="放大" @click="zoomBy(1.35)">+</button>
<span class="zoom-sep" />
<button class="zoom-word" @click="reset">重置</button>
<button class="zoom-word" @click="close">关闭</button>
</div>
<p class="zoom-hint">滚轮缩放 · 拖拽平移 · Esc 关闭</p>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.zoom {
position: fixed;
inset: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
background: var(--wk-paper);
cursor: zoom-out;
overscroll-behavior: contain;
}
.zoom-stage {
width: min(1400px, 88vw);
height: 82vh;
cursor: grab;
transform-origin: center center;
}
.zoom-stage:active {
cursor: grabbing;
}
.zoom-bar {
position: fixed;
bottom: 28px;
left: 50%;
transform: translateX(-50%);
display: flex;
align-items: center;
gap: 4px;
padding: 6px 8px;
border: 1px solid var(--wk-rule);
border-radius: 3px;
background: var(--wk-paper);
box-shadow: 0 6px 22px rgba(16, 31, 56, 0.08);
cursor: default;
}
.zoom-bar button {
min-width: 30px;
height: 28px;
padding: 0 8px;
border-radius: 2px;
font-size: 15px;
line-height: 1;
color: var(--wk-ink-soft);
transition: background-color 0.18s ease, color 0.18s ease;
}
.zoom-bar button:hover {
background: var(--wk-paper-2);
color: var(--wk-ink);
}
.zoom-word {
font-size: 12.5px !important;
}
.zoom-level {
min-width: 46px;
text-align: center;
font-family: var(--wk-font-mono);
font-size: 11px;
color: var(--wk-ink-mute);
}
.zoom-sep {
width: 1px;
height: 16px;
margin: 0 4px;
background: var(--wk-rule);
}
.zoom-hint {
position: fixed;
top: 26px;
left: 50%;
transform: translateX(-50%);
margin: 0;
font-family: var(--wk-font-mono);
font-size: 10.5px;
letter-spacing: 0.14em;
color: var(--wk-ink-mute);
}
.zoom-fade-enter-active,
.zoom-fade-leave-active {
transition: opacity 0.18s ease;
}
.zoom-fade-enter-from,
.zoom-fade-leave-to {
opacity: 0;
}
</style>
@@ -0,0 +1,98 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { withBase } from 'vitepress'
const props = defineProps<{
/** 相对站点根的图片路径,约定放在 public/screenshots/ 下 */
src: string
/** 图注:说明这张图展示的是什么 */
caption: string
/** 补充说明:截图应覆盖哪些界面元素,供补图的人参考 */
hint?: string
}>()
// 图片可能尚未补充。默认先渲染占位框,只有确实加载成功才换成图片,
// 这样缺图时不会先闪一个碎图标再降级。
const loaded = ref(false)
const resolved = computed(() => withBase(props.src))
</script>
<template>
<figure class="wk-shot">
<img
v-show="loaded"
class="wk-shot-img"
:src="resolved"
:alt="caption"
@load="loaded = true"
/>
<div v-if="!loaded" class="wk-shot-placeholder">
<div class="wk-shot-placeholder-badge">截图待补充</div>
<div class="wk-shot-placeholder-caption">{{ caption }}</div>
<p v-if="hint" class="wk-shot-placeholder-hint">{{ hint }}</p>
<code class="wk-shot-placeholder-path">website-docs/public{{ src }}</code>
</div>
<figcaption class="wk-shot-caption">{{ caption }}</figcaption>
</figure>
</template>
<style scoped>
.wk-shot {
margin: 24px 0;
}
.wk-shot-img {
display: block;
width: 100%;
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
}
.wk-shot-placeholder {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 32px 24px;
text-align: center;
border: 1px dashed var(--vp-c-divider);
border-radius: 10px;
background: var(--vp-c-bg-soft);
}
.wk-shot-placeholder-badge {
padding: 2px 10px;
border-radius: 999px;
font-size: 12px;
line-height: 20px;
color: var(--vp-c-text-2);
background: var(--vp-c-default-soft);
}
.wk-shot-placeholder-caption {
font-size: 15px;
font-weight: 600;
color: var(--vp-c-text-1);
}
.wk-shot-placeholder-hint {
max-width: 46em;
margin: 0;
font-size: 13px;
line-height: 1.7;
color: var(--vp-c-text-2);
}
.wk-shot-placeholder-path {
font-size: 12px;
color: var(--vp-c-text-3);
}
.wk-shot-caption {
margin-top: 8px;
font-size: 13px;
line-height: 1.6;
text-align: center;
color: var(--vp-c-text-2);
}
</style>
+16
View File
@@ -0,0 +1,16 @@
import { h } from 'vue'
import type { Theme } from 'vitepress'
import DefaultTheme from 'vitepress/theme'
import Landing from './Landing.vue'
import MermaidZoom from './MermaidZoom.vue'
import Screenshot from './Screenshot.vue'
import './style.css'
export default {
extends: DefaultTheme,
Layout: () => h(DefaultTheme.Layout, null, { 'layout-bottom': () => h(MermaidZoom) }),
enhanceApp({ app }) {
app.component('Landing', Landing)
app.component('Screenshot', Screenshot)
},
} satisfies Theme
+761
View File
@@ -0,0 +1,761 @@
/* ==========================================================================
WeKnora 文档站主题
墨蓝(logo 主色)· 纸白 · 鎏金(帆浪点缀)
========================================================================== */
:root {
--wk-ink: #101f38;
--wk-ink-deep: #0a1526;
--wk-ink-soft: #40536f;
--wk-ink-mute: #6d7d94;
--wk-gold: #b8863b;
--wk-gold-light: #d9a94f;
--wk-paper: #fdfcfa;
--wk-paper-2: #f5f3ee;
--wk-paper-3: #edeae2;
--wk-rule: rgba(16, 31, 56, 0.12);
--wk-rule-soft: rgba(16, 31, 56, 0.07);
--wk-font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI",
"PingFang SC", "HarmonyOS Sans SC", "Source Han Sans SC", "Noto Sans CJK SC",
"Hiragino Sans GB", "Microsoft YaHei", sans-serif;
--wk-font-serif: "Iowan Old Style", "Palatino Linotype", Palatino, "Book Antiqua",
Georgia, "Songti SC", "Source Han Serif SC", "Noto Serif CJK SC", "STSong", serif;
--wk-font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
Consolas, "Liberation Mono", monospace;
/* —— 覆盖 VitePress 默认变量 —— */
--vp-font-family-base: var(--wk-font-sans);
--vp-font-family-mono: var(--wk-font-mono);
--vp-c-brand-1: var(--wk-ink);
--vp-c-brand-2: #24395a;
--vp-c-brand-3: var(--wk-gold);
--vp-c-brand-soft: rgba(184, 134, 59, 0.14);
--vp-c-bg: var(--wk-paper);
--vp-c-bg-alt: var(--wk-paper-2);
--vp-c-bg-soft: var(--wk-paper-2);
--vp-c-bg-elv: #ffffff;
--vp-c-text-1: var(--wk-ink);
--vp-c-text-2: var(--wk-ink-soft);
--vp-c-text-3: var(--wk-ink-mute);
--vp-c-divider: var(--wk-rule);
--vp-c-gutter: var(--wk-rule);
--vp-c-border: var(--wk-rule);
/* 铺满视口。默认主题用这个值给 .VPContent 算左右内边距
(padding-right: (100vw - max-width) / 2),任何小于视口的定值都会在两侧留出
大片空白;给一个大于视口的定值又会让左内边距被钳到 0、正文钻到侧栏底下。
取 100vw 时右侧内边距为 0、左侧正好等于侧栏宽度。 */
--vp-layout-max-width: 100vw;
--vp-nav-height: 68px;
--vp-sidebar-width: 288px;
--vp-code-block-bg: #fbf9f5;
--vp-home-hero-name-color: var(--wk-ink);
--vp-code-copy-copied-text-content: "已复制";
}
.dark {
--wk-ink: #e6ecf5;
--wk-ink-soft: #a9b6c9;
--wk-ink-mute: #7d8ca2;
--wk-gold: #d9a94f;
--wk-gold-light: #e8c176;
--wk-paper: #0b1220;
--wk-paper-2: #101a2b;
--wk-paper-3: #16233a;
--wk-rule: rgba(190, 208, 232, 0.14);
--wk-rule-soft: rgba(190, 208, 232, 0.08);
--vp-c-brand-1: #e6ecf5;
--vp-c-brand-2: #c3d0e2;
--vp-c-brand-3: var(--wk-gold);
--vp-c-bg-elv: #101a2b;
--vp-code-block-bg: #0d1626;
}
html {
scroll-behavior: smooth;
}
body {
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
font-feature-settings: "kern" 1, "liga" 1, "cv05" 1;
}
::selection {
background: rgba(184, 134, 59, 0.22);
}
/* --------------------------------------------------------------------------
顶栏
-------------------------------------------------------------------------- */
.VPNav {
backdrop-filter: saturate(140%) blur(14px);
}
.VPNavBar:not(.has-sidebar):not(.top) {
background-color: color-mix(in srgb, var(--wk-paper) 82%, transparent);
}
/* 导航栏与正文之间不要任何分隔线或渐变 */
.VPNavBar .divider,
.VPNavBar .curtain {
display: none;
}
/* 默认主题在有侧栏时把标题块撑到侧栏宽、搜索框推到正文列起点,
这里让它与首页(无侧栏)共用同一套居中容器,两种页面的 logo 与搜索位置才一致 */
@media (min-width: 960px) {
.VPNavBar.has-sidebar .wrapper {
padding: 0 32px;
}
.VPNavBar.has-sidebar .container {
max-width: calc(var(--vp-layout-max-width) - 64px);
}
.VPNavBar.has-sidebar .title {
position: static;
width: auto;
padding: 0;
}
.VPNavBar.has-sidebar .content {
padding-left: 0;
padding-right: 0;
}
}
.VPNavBarTitle .title {
font-family: var(--wk-font-sans);
font-weight: 600;
letter-spacing: 0.14em;
font-size: 15px;
text-transform: uppercase;
border-bottom: none !important;
}
.VPNavBarTitle .logo {
height: 22px;
margin-right: 10px;
}
.VPNavBarMenuLink {
position: relative;
font-size: 13.5px !important;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--wk-ink-soft) !important;
}
.VPNavBarMenuLink.active,
.VPNavBarMenuLink:hover {
color: var(--wk-ink) !important;
}
.VPNavBarMenuLink.active::after {
content: "";
position: absolute;
left: 12px;
right: 12px;
bottom: 16px;
height: 1px;
background: var(--wk-gold);
}
.VPNavBarSearch .DocSearch-Button,
.VPNavBarSearch button {
border-radius: 2px;
border: 1px solid var(--wk-rule);
background: transparent;
}
/* --------------------------------------------------------------------------
侧边栏
-------------------------------------------------------------------------- */
/* 默认公式在视口窄于 layout-max-width 时会把侧栏压窄,这里给它一个下限 */
@media (min-width: 1440px) {
.VPSidebar {
width: calc(
max(0px, (100vw - (var(--vp-layout-max-width) - 64px)) / 2) + var(--vp-sidebar-width) - 32px
) !important;
}
}
.VPSidebar {
border-right: 1px solid var(--wk-rule-soft);
}
/* 桌面侧栏透明,让正文背景贯通;窄屏抽屉必须不透明,否则会透出正文 */
@media (min-width: 960px) {
.VPSidebar {
/* 与默认主题一致:正好让出顶栏高度,首组标题紧贴顶栏下沿 */
padding-top: var(--vp-nav-height) !important;
background: transparent !important;
}
}
@media (max-width: 959px) {
.VPSidebar {
padding-top: 18px !important;
background: var(--wk-paper) !important;
border-right: none;
}
.dark .VPSidebar {
background: var(--wk-paper) !important;
}
/* 移动端不需要顶部 curtain */
.VPSidebar .curtain {
display: none;
}
}
/* 默认主题的 curtain 用负 margin 铺满侧栏宽度,会盖住滚动条顶部 */
@media (min-width: 960px) {
.VPSidebar .curtain {
/* 默认写死 -64px,本站 nav-height 为 68px,对齐以免 sticky 错位 */
top: calc(var(--vp-nav-height) * -1);
margin-right: 0;
width: calc(100% + 32px);
/* 叠加滚动条画在内容区右侧,需裁掉一条竖向通道 */
clip-path: inset(0 14px 0 0);
background-color: color-mix(in srgb, var(--wk-paper) 82%, transparent);
backdrop-filter: saturate(140%) blur(14px);
}
.dark .VPSidebar .curtain {
background-color: color-mix(in srgb, var(--wk-paper) 82%, transparent);
}
}
/* --------------------------------------------------------------------------
中等屏「本页目录」条(960–1279px)
默认通栏 + 底边会横穿侧栏,看起来像多余的线
-------------------------------------------------------------------------- */
@media (min-width: 960px) and (max-width: 1279px) {
.VPLocalNav.has-sidebar {
width: calc(100% - var(--vp-sidebar-width));
margin-left: var(--vp-sidebar-width);
/* 盖过默认主题 scoped 的 padding-left: sidebar-width,否则会双重偏移居中 */
padding-left: 0 !important;
background-color: color-mix(in srgb, var(--wk-paper) 82%, transparent);
backdrop-filter: saturate(140%) blur(14px);
border-bottom-color: var(--wk-rule-soft);
}
.VPLocalNav.has-sidebar .container {
justify-content: flex-start;
padding-left: 32px;
padding-right: 32px;
}
.VPLocalNav.has-sidebar .VPLocalNavOutlineDropdown {
padding-left: 0;
}
/* 默认 left 按通栏算了 sidebar+32;LocalNav 已右移,再加一次会落到中间 */
.VPLocalNav.has-sidebar .VPLocalNavOutlineDropdown .items {
left: 32px;
right: auto;
}
}
.VPSidebarItem.level-0 > .item > .text {
font-family: var(--wk-font-mono);
font-size: 11px !important;
font-weight: 500 !important;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--wk-ink-mute) !important;
}
.VPSidebarItem.level-0 {
padding-bottom: 22px;
}
.VPSidebarItem.level-0:not(:first-child) {
border-top: 1px solid var(--wk-rule-soft);
padding-top: 20px;
}
.VPSidebarItem.level-1 .link .text {
font-size: 13.5px;
line-height: 1.6;
color: var(--wk-ink-soft);
transition: color 0.2s ease;
}
.VPSidebarItem.level-1 .link:hover .text {
color: var(--wk-ink);
}
.VPSidebarItem.level-1.is-active .link .text {
color: var(--wk-ink) !important;
font-weight: 600;
}
.VPSidebarItem.level-1.is-active > .item {
position: relative;
}
.VPSidebarItem.level-1.is-active > .item::before {
content: "";
position: absolute;
left: -14px;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 3px;
border-radius: 50%;
background: var(--wk-gold);
}
/* --------------------------------------------------------------------------
正文排版
-------------------------------------------------------------------------- */
.VPDoc {
padding-top: 44px !important;
}
/* 默认主题用 scoped 选择器把正文锁死在 688px,覆盖它需要 !important。
这里让正文列吃满侧栏与右侧目录之间的空间,只在超宽屏留一个上限,
免得一行拉到两千像素。 */
@media (min-width: 1280px) {
.VPDoc .content-container,
.VPDoc.has-aside .content-container {
max-width: 1600px !important;
}
}
.vp-doc {
font-size: 15.5px;
line-height: 1.82;
color: var(--wk-ink-soft);
}
.vp-doc h1,
.vp-doc h2,
.vp-doc h3,
.vp-doc h4 {
color: var(--wk-ink);
letter-spacing: -0.01em;
}
.vp-doc h1 {
font-family: var(--wk-font-serif);
font-size: 42px;
line-height: 1.22;
font-weight: 500;
letter-spacing: -0.02em;
margin-bottom: 28px;
}
.vp-doc h2 {
font-family: var(--wk-font-serif);
font-size: 27px;
font-weight: 500;
line-height: 1.35;
margin-top: 66px;
padding-top: 26px;
border-top: 1px solid var(--wk-rule-soft);
}
.vp-doc h3 {
font-size: 18.5px;
font-weight: 650;
margin-top: 42px;
letter-spacing: 0;
}
.vp-doc h4 {
font-size: 15.5px;
font-weight: 650;
margin-top: 30px;
}
.vp-doc h2 .header-anchor,
.vp-doc h3 .header-anchor {
color: var(--wk-gold);
font-weight: 400;
}
.vp-doc p,
.vp-doc li {
color: var(--wk-ink-soft);
}
.vp-doc strong {
color: var(--wk-ink);
font-weight: 650;
}
.vp-doc a {
color: var(--wk-ink);
font-weight: 500;
text-decoration: none;
border-bottom: 1px solid color-mix(in srgb, var(--wk-gold) 55%, transparent);
padding-bottom: 1px;
transition: border-color 0.2s ease, background-color 0.2s ease;
}
.vp-doc a:hover {
border-bottom-color: var(--wk-gold);
background: rgba(184, 134, 59, 0.08);
}
.vp-doc ul,
.vp-doc ol {
padding-left: 1.3em;
}
.vp-doc li + li {
margin-top: 6px;
}
.vp-doc li::marker {
color: var(--wk-ink-mute);
}
.vp-doc blockquote {
border-left: 2px solid var(--wk-gold);
background: transparent;
padding: 2px 0 2px 20px;
margin: 26px 0;
}
.vp-doc blockquote > p {
font-size: 14.5px;
color: var(--wk-ink-mute);
margin: 0;
}
.vp-doc hr {
border-top: 1px solid var(--wk-rule-soft);
margin: 52px 0;
}
/* 代码 */
.vp-doc :not(pre) > code {
font-size: 0.86em;
font-family: var(--wk-font-mono);
background: var(--wk-paper-2);
border: 1px solid var(--wk-rule-soft);
border-radius: 3px;
padding: 2px 5px;
color: var(--wk-ink);
}
.vp-doc div[class*="language-"] {
border-radius: 4px;
border: 1px solid var(--wk-rule-soft);
background: var(--vp-code-block-bg);
margin: 26px 0;
}
.vp-doc div[class*="language-"] pre {
padding: 20px 0;
}
.vp-doc [class*="language-"] code {
font-size: 13px;
line-height: 1.72;
}
/* 复制按钮:默认隐形,悬停时才出现的细线方钮 */
.vp-doc [class*="language-"] > button.copy {
top: 9px;
right: 9px;
width: 28px;
height: 28px;
border: 1px solid var(--wk-rule-soft);
border-radius: 2px;
background-color: transparent;
background-size: 14px;
background-position: 50%;
opacity: 0;
transform: translateY(-2px);
transition: opacity 0.2s ease, transform 0.2s ease, border-color 0.2s ease,
background-color 0.2s ease;
}
.vp-doc [class*="language-"]:hover > button.copy,
.vp-doc [class*="language-"] > button.copy:focus-visible {
opacity: 1;
transform: none;
}
.vp-doc [class*="language-"] > button.copy:hover {
border-color: var(--wk-gold);
background-color: var(--wk-paper);
}
.vp-doc [class*="language-"] > button.copy.copied,
.vp-doc [class*="language-"] > button.copy:hover.copied {
border-color: var(--wk-gold);
background-color: var(--wk-paper);
}
.vp-doc [class*="language-"] > button.copy.copied::before,
.vp-doc [class*="language-"] > button.copy:hover.copied::before {
top: -1px;
height: 28px;
padding: 0 12px;
border: 1px solid var(--wk-rule-soft);
border-radius: 2px;
background-color: var(--wk-paper);
font-family: var(--wk-font-mono);
font-size: 10.5px;
letter-spacing: 0.1em;
color: var(--wk-ink-mute);
line-height: 26px;
}
.vp-doc [class*="language-"] > span.lang {
font-family: var(--wk-font-mono);
font-size: 10.5px;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var(--wk-ink-mute);
}
/* 表格 */
/* 滚动交给包裹层(见 config.mts 的 table_open 规则),表格本身保持
display:table + width:100%,所有表格宽度一致地撑满正文列 */
.vp-doc .wk-table {
overflow-x: auto;
margin: 28px 0;
max-width: 100%;
}
.vp-doc .wk-table > table {
display: table;
width: 100%;
/* 窄屏下宁可横向滚动,也不要把列挤成一字一行 */
min-width: 420px;
margin: 0;
font-size: 13.5px;
border-collapse: collapse;
}
.vp-doc .wk-table::-webkit-scrollbar {
height: 6px;
}
.vp-doc .wk-table::-webkit-scrollbar-thumb {
background: var(--wk-rule);
border-radius: 3px;
}
.vp-doc th {
font-family: var(--wk-font-mono);
font-size: 10.5px;
letter-spacing: 0.12em;
text-transform: uppercase;
font-weight: 500;
color: var(--wk-ink-mute);
background: transparent;
border: none;
border-bottom: 1px solid var(--wk-rule);
padding: 10px 14px 10px 0;
text-align: left;
white-space: nowrap;
}
.vp-doc td {
border: none;
border-bottom: 1px solid var(--wk-rule-soft);
padding: 12px 14px 12px 0;
vertical-align: top;
line-height: 1.7;
}
.vp-doc tr {
background: transparent !important;
border: none;
}
.vp-doc th:last-child,
.vp-doc td:last-child {
padding-right: 0;
}
/* 自定义容器 */
.vp-doc .custom-block {
border-radius: 3px;
border: 1px solid var(--wk-rule);
border-left-width: 2px;
background: var(--wk-paper-2);
font-size: 14px;
padding: 16px 20px;
}
.vp-doc .custom-block.tip {
border-left-color: var(--wk-gold);
}
.vp-doc .custom-block-title {
font-family: var(--wk-font-mono);
font-size: 10.5px;
letter-spacing: 0.14em;
text-transform: uppercase;
}
/* Mermaid 图 */
.vp-doc .mermaid {
position: relative;
margin: 34px 0;
padding: 28px 20px;
border: 1px solid var(--wk-rule-soft);
border-radius: 4px;
background: var(--wk-paper-2);
text-align: center;
overflow-x: auto;
cursor: zoom-in;
transition: border-color 0.2s ease;
}
.vp-doc .mermaid:hover {
border-color: var(--wk-rule);
}
.vp-doc .mermaid::after {
content: "点击放大";
position: absolute;
right: 12px;
bottom: 10px;
font-family: var(--wk-font-mono);
font-size: 10px;
letter-spacing: 0.14em;
color: var(--wk-ink-mute);
opacity: 0;
transition: opacity 0.2s ease;
}
.vp-doc .mermaid:hover::after {
opacity: 1;
}
/* 页脚区 */
.VPDocFooter {
margin-top: 72px;
padding-top: 26px;
border-top: 1px solid var(--wk-rule-soft);
}
.VPDocFooter .prev-link .desc,
.VPDocFooter .next-link .desc {
font-family: var(--wk-font-mono);
font-size: 10.5px;
letter-spacing: 0.14em;
text-transform: uppercase;
}
.VPDocFooter .pager-link {
border: 1px solid var(--wk-rule-soft);
border-radius: 3px;
background: transparent;
}
.VPDocFooter .pager-link:hover {
border-color: var(--wk-gold);
}
.VPDocFooter .pager-link .title {
font-weight: 550;
color: var(--wk-ink);
}
.VPDocAsideOutline .outline-title,
.VPDocOutlineItem .outline-title {
font-family: var(--wk-font-mono);
font-size: 10.5px !important;
letter-spacing: 0.14em;
text-transform: uppercase;
color: var(--wk-ink-mute);
}
.VPDocAside .outline-link {
font-size: 12.8px;
line-height: 1.65;
padding: 6px 0;
color: var(--wk-ink-mute);
}
.VPDocAside .outline-link.nested {
padding-inline-start: 16px;
}
.VPDocAside .VPDocAsideOutline .root {
padding-inline-start: 2px;
}
.VPDocAside .outline-link.active {
color: var(--wk-ink);
}
.VPDocAsideOutline .outline-marker {
background-color: var(--wk-gold) !important;
}
.VPFooter {
border-top: 1px solid var(--wk-rule-soft);
background: transparent !important;
padding: 34px 24px;
}
.VPFooter .message,
.VPFooter .copyright {
font-size: 12.5px;
color: var(--wk-ink-mute);
letter-spacing: 0.02em;
}
.VPLastUpdated {
font-family: var(--wk-font-mono);
font-size: 11px !important;
letter-spacing: 0.06em;
}
/* --------------------------------------------------------------------------
首页容器:去掉默认 page 布局的边距限制
-------------------------------------------------------------------------- */
.landing-page .VPPage {
padding: 0;
}
.landing-page .VPContent.is-home,
.landing-page .VPContent {
padding: 0 !important;
}
.landing-page .VPFooter {
display: none;
}
@media (max-width: 640px) {
.vp-doc h1 {
font-size: 32px;
}
.vp-doc h2 {
font-size: 23px;
margin-top: 52px;
}
}