1. 建设背景
SPC 系统包含项目管理、IQC、OQC、数据分析和权限配置等功能。随着功能增加,仅依赖现场培训、口头说明或零散截图,会产生以下问题:
新用户缺少统一的入门路径;
同一操作可能被多次解释,增加实施和售后支持成本;
系统升级后,旧培训材料不容易同步;
权限、导入格式等细节无法长期依赖人工记忆;
用户遇到问题时,不能快速定位到对应章节。
因此,系统需要一套随产品一起交付的用户手册。用户可以从业务系统入口打开新的浏览器页签,查阅操作说明并搜索相关内容。
用户手册是产品说明站点,不是业务系统的一部分。它不修改业务数据,也不决定用户拥有哪些业务权限。
2. 建设目标
当前方案需要满足以下目标:
使用 Markdown 维护文字和图片,降低内容维护成本;
手册界面可以按产品视觉规范自由定制;
页面构建后可以作为普通静态文件发布;
用户无需登录即可访问手册;
搜索在浏览器中完成,不依赖 FastAPI 搜索接口;
手册和主前端可以一起构建、一起进入后端发布镜像;
手册文件异常不能阻塞 SPC 核心业务启动。
2.1 当前范围
Markdown 文章;
本地图片;
左侧文章目录;
右侧页内目录;
图片点击放大;
标题和正文全文搜索;
独立开发、静态构建和统一发布。
2.2 当前不做
在线编辑器或后台内容管理;
视频播放器;
附件下载专区;
登录、角色或权限控制;
搜索同义词、语义理解或智能问答;
单独运行的生产 Node/Nitro 服务。
这些能力可以根据实际使用情况分期补充,不在首期架构中提前预留复杂服务。
3. 技术选型
用户手册采用 Nuxt 4、Nuxt Content 3 和 Vue 3。
3.1 为什么使用 Nuxt Content
Nuxt Content 负责把 Markdown 转换为可查询、可渲染的内容数据,并提供以下能力:
Front Matter 元数据;
Markdown 和 MDC 解析;
内容集合和类型约束;
页面、目录及相邻文章查询;
静态预渲染;
浏览器侧 SQLite WASM 查询;
useSearchCollection全文搜索。
页面结构仍由项目自己实现,因此页头、目录、正文宽度、颜色、搜索弹窗和移动端样式都可以定制,不受固定文档主题限制。
3.2 与传统 VitePress 路线的取舍
VitePress 适合快速建设标准技术文档站,主题、目录和常见交互开箱即用。当前项目更关注产品化界面以及与现有 Vue 技术栈的统一,因此选择 Nuxt Content:
页面组件和交互完全由项目控制;
可以使用 Vue 组件扩展 Markdown;
路由、数据查询和搜索都使用同一套 Nuxt Content 数据;
后续增加产品化功能时,不需要绕过主题层进行深度改造。
代价是页头、目录、搜索弹窗等界面需要项目自行维护。当前功能规模较小,这部分成本可控。
4. 总体架构
flowchart LR
A[Markdown 与图片] --> B[Nuxt Content 解析]
B --> C[Nuxt 静态生成]
C --> D[预渲染 HTML]
C --> E[Content 数据库快照]
C --> F[JS CSS 图片]
D --> G[dist/manual]
E --> G
F --> G
G --> H[后端静态目录]
H --> I[浏览器访问 /manual/]
I --> J[SQLite WASM 本地搜索]
方案分为构建期和运行期。
4.1 构建期
Nuxt Content 读取
manual/content;Markdown 被解析为结构化内容;
Nuxt 为文章路由生成静态 HTML;
Nuxt Content 生成浏览器可加载的数据库快照;
手册产物复制到主前端的
dist/manual;后端镜像从前端镜像复制完整的
/app/dist。
构建期需要 Node.js 和 better-sqlite3,但这些依赖不会成为生产运行服务。
4.2 运行期
FastAPI 将静态文件目录挂载到 HTTP 路径;
用户访问
/manual/时获得预渲染页面和前端资源;用户首次搜索时,浏览器加载 Content 数据库快照;
SQLite WASM 在浏览器内建立全文索引并执行查询;
后续搜索继续在浏览器内完成。
运行期没有手册搜索 API,也不需要额外维护 Node 进程。
5. 项目结构
project-tianlu-web/
├── manual/
│ ├── app/
│ │ ├── components/docs/ # 页头、搜索、目录和正文组件
│ │ ├── pages/ # 手册文章路由
│ │ ├── styles/ # 手册全局样式
│ │ └── types/ # 页面使用的类型
│ ├── content/ # Markdown 正文
│ ├── public/ # 图片等静态资源
│ ├── content.config.ts # 内容集合定义
│ ├── nuxt.config.ts # 基础路径和预渲染配置
│ └── package.json # 手册开发与构建命令
├── scripts/package-manual.mjs # 合并手册产物
└── dist/
├── index.html # 主前端
└── manual/ # 用户手册
后端镜像最终得到:
project-tianlu/src/static/
├── index.html
├── js/
├── css/
└── manual/
├── index.html
├── _nuxt/
├── __nuxt_content/
└── ...
手册页面、Content 数据库快照和浏览器搜索所需的 WASM 文件都属于同一份静态产物,不需要单独复制搜索索引。
6. 内容如何生成页面
6.1 Markdown 与 Front Matter
每篇文章存放在 manual/content,顶部通过 Front Matter 描述标题、简介、分类和排序:
---
title: IQC 数据导入
description: IQC 文件导入操作说明。
category: 数据管理
order: 20
navigation:
title: IQC 数据导入
---
# IQC 数据导入
正文内容。
正文作者只需要维护 Markdown,不需要手写页面组件或路由配置。
6.2 内容集合
content.config.ts 定义手册集合以及文章字段。Nuxt Content 在开发和构建阶段校验、解析这些文件,并把它们写入内容数据库。
集合同时供以下功能使用:
根据当前路径查询文章;
生成左侧导航;
计算上一篇和下一篇;
生成右侧页内目录;
建立全文搜索数据。
6.3 路由与预渲染
nuxt.config.ts 扫描 manual/content 下的 Markdown 文件,将文章路径加入预渲染列表。动态页面 [...slug].vue 负责查询当前路径并组合页面组件,ContentRenderer 负责渲染正文。
生产环境访问文章时,服务器直接返回预渲染文件。浏览器不需要请求 FastAPI 获取正文。
6.4 图片
图片存放在 manual/public,正文使用标准 Markdown 图片语法引用:

使用 /manual/ 前缀可以保证手册作为子路径发布时仍能正确访问图片。当前页面组件支持点击正文图片查看大图。
7. 搜索实现
7.1 搜索流程
搜索弹窗直接使用 Nuxt Content 的 useSearchCollection('manual'):
用户输入关键词
→ 首次使用时加载 Content 数据库快照
→ 浏览器初始化 SQLite WASM
→ 建立 FTS5 全文索引
→ 执行前缀查询和 BM25 排序
→ 返回章节、路径、正文和摘要
→ 页面展示结果并跳转到对应锚点
ManualSearch.vue 使用 immediate: false,避免用户没有使用搜索时主动初始化索引。输入关键词后最多展示 30 条结果,并将内置高亮摘要转换为现有的纯文本样式。
7.2 为什么不需要 FastAPI 搜索
手册内容全部公开,而且体量较小。将搜索放在浏览器有以下好处:
不增加 API 路由、请求模型和后端服务代码;
不需要后端进程加载搜索索引;
手册可以脱离业务 API 单独预览;
静态页面和搜索数据始终来自同一次构建;
不受许可证中间件、登录状态和角色权限影响。
FastAPI 仍然负责提供静态文件,但不参与关键词检索。
7.3 当前限制
内置搜索使用 SQLite FTS5 前缀匹配。英文和以空格分隔的内容效果较好;连续中文可能只能命中词首,中间词存在漏检可能。
首期先使用内置能力减少维护成本。后续只有在真实内容和用户反馈证明效果不足时,再评估:
调整中文内容中的标题和关键词表达;
使用 Fuse.js 或 MiniSearch;
增加中文分词或 n-gram 索引;
内容规模显著增大后引入独立搜索服务。
8. 前后端职责边界
8.1 手册前端负责
Markdown 内容解析;
页面、目录和图片展示;
静态路由生成;
浏览器侧全文搜索;
生成可发布的
dist/manual。
8.2 FastAPI 负责
挂载
/manual静态目录;手册产物缺失时返回明确的不可用提示;
与主前端一起提供统一访问地址。
8.3 FastAPI 不负责
查询或渲染手册正文;
手册关键词搜索;
手册内容权限判断;
启动 Node/Nitro 服务。
手册无需登录即可访问。业务系统中的查看、创建、上传、修改等权限仍按原有权限体系判断;阅读操作说明不代表账号已经获得对应业务权限。
9. 开发、构建与发布
9.1 安装依赖
主前端和手册有各自的依赖文件:
npm install
npm --prefix manual install
9.2 单独开发手册
在 project-tianlu-web 目录执行:
npm run manual:dev
开发地址:
http://localhost:3000/manual/
开发服务器只用于本地开发,不是生产部署组件。
9.3 检查和构建
npm --prefix manual run type-check
npm run manual:build
npm run build-all
type-check:检查 Nuxt、Vue 和 TypeScript 类型;manual:build:静态生成手册;build-all:构建主前端、构建手册并合并到同一个dist;package-manual.mjs:先清理dist/manual,再复制完整手册产物。
9.4 Docker 发布
前端 Dockerfile 执行 npm run build-all,最终镜像输出 /app/dist。后端 Dockerfile 使用该前端镜像作为构建阶段,并执行:
COPY --from=frontend_static /app/dist ./src/static
因此,主前端和手册使用同一份前端构建产物进入后端镜像,部署时不需要额外发布手册服务。
10. 内容维护流程
新增文章时:
在
manual/content新建.md文件;填写标题、简介、分类和排序;
编写正文并添加必要图片;
使用
manual:dev检查目录、正文和移动端布局;检查标题和正文能否被搜索;
执行类型检查和静态构建;
通过正常镜像构建流程发布。
维护原则:
一篇文章只解决一个明确主题;
操作步骤使用有序列表;
权限名称和页面入口保持与系统一致;
图片仅用于解释复杂界面,不用截图替代所有文字;
正文不包含密码、令牌或未经脱敏的业务数据;
功能变更时同步调整对应文章。
11. 异常处理与排查
11.1 手册入口返回 503
检查后端静态目录是否存在 manual/index.html,以及前端镜像是否包含最新的 /app/dist/manual。
11.2 页面可以打开,但搜索失败
检查:
浏览器是否支持 WebAssembly;
/manual/__nuxt_content/manual/sql_dump.txt是否可以访问;_nuxt下的 SQLite WASM 和 Worker 文件是否完整;发布过程是否完整复制了
dist/manual;浏览器控制台是否存在数据库初始化错误。
11.3 新文章没有出现在目录中
检查 Markdown 是否位于 manual/content,Front Matter 是否满足集合定义,并重新启动开发服务或重新构建。
11.4 搜索不到某个中文词
先尝试更完整的词组或文章标题。连续中文的中间词可能受 FTS5 分词方式影响;记录实际案例,后续统一评估搜索优化,避免为少量假设场景提前增加依赖。
11.5 构建提示 better_sqlite3.node 缺失
Nuxt Content 构建阶段需要原生 SQLite 依赖。重新安装手册依赖;在 Alpine 镜像中需要 Python、make 和 C++ 编译工具,然后重建 better-sqlite3。
12. 后续扩展原则
后续能力按真实需求增量建设:
视频:增加本地视频组件和资源发布规范;
附件:增加文件目录、下载组件和类型限制;
搜索:根据中文命中案例选择轻量前端方案或独立服务;
内容协作:维护频率明显提高后,再评估编辑工作流;
版本管理:出现多个长期维护版本后,再设计手册版本切换。
无论增加哪项能力,都应继续保持以下原则:
手册内容与业务代码版本可追踪;
首选静态能力,避免无必要的运行服务;
公开手册不引入业务权限耦合;
构建产物整体发布,不人工拆分复制文件;
复杂度由实际需求驱动,而不是提前堆叠。
13. 效果预览
