1. 建设背景

SPC 系统包含项目管理、IQC、OQC、数据分析和权限配置等功能。随着功能增加,仅依赖现场培训、口头说明或零散截图,会产生以下问题:

  • 新用户缺少统一的入门路径;

  • 同一操作可能被多次解释,增加实施和售后支持成本;

  • 系统升级后,旧培训材料不容易同步;

  • 权限、导入格式等细节无法长期依赖人工记忆;

  • 用户遇到问题时,不能快速定位到对应章节。

因此,系统需要一套随产品一起交付的用户手册。用户可以从业务系统入口打开新的浏览器页签,查阅操作说明并搜索相关内容。

用户手册是产品说明站点,不是业务系统的一部分。它不修改业务数据,也不决定用户拥有哪些业务权限。

2. 建设目标

当前方案需要满足以下目标:

  1. 使用 Markdown 维护文字和图片,降低内容维护成本;

  2. 手册界面可以按产品视觉规范自由定制;

  3. 页面构建后可以作为普通静态文件发布;

  4. 用户无需登录即可访问手册;

  5. 搜索在浏览器中完成,不依赖 FastAPI 搜索接口;

  6. 手册和主前端可以一起构建、一起进入后端发布镜像;

  7. 手册文件异常不能阻塞 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 构建期

  1. Nuxt Content 读取 manual/content;

  2. Markdown 被解析为结构化内容;

  3. Nuxt 为文章路由生成静态 HTML;

  4. Nuxt Content 生成浏览器可加载的数据库快照;

  5. 手册产物复制到主前端的 dist/manual;

  6. 后端镜像从前端镜像复制完整的 /app/dist。

构建期需要 Node.js 和 better-sqlite3,但这些依赖不会成为生产运行服务。

4.2 运行期

  1. FastAPI 将静态文件目录挂载到 HTTP 路径;

  2. 用户访问 /manual/ 时获得预渲染页面和前端资源;

  3. 用户首次搜索时,浏览器加载 Content 数据库快照;

  4. SQLite WASM 在浏览器内建立全文索引并执行查询;

  5. 后续搜索继续在浏览器内完成。

运行期没有手册搜索 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/images/iqc-import.png)

使用 /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. 内容维护流程

新增文章时:

  1. 在 manual/content 新建 .md 文件;

  2. 填写标题、简介、分类和排序;

  3. 编写正文并添加必要图片;

  4. 使用 manual:dev 检查目录、正文和移动端布局;

  5. 检查标题和正文能否被搜索;

  6. 执行类型检查和静态构建;

  7. 通过正常镜像构建流程发布。

维护原则:

  • 一篇文章只解决一个明确主题;

  • 操作步骤使用有序列表;

  • 权限名称和页面入口保持与系统一致;

  • 图片仅用于解释复杂界面,不用截图替代所有文字;

  • 正文不包含密码、令牌或未经脱敏的业务数据;

  • 功能变更时同步调整对应文章。

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. 后续扩展原则

后续能力按真实需求增量建设:

  • 视频:增加本地视频组件和资源发布规范;

  • 附件:增加文件目录、下载组件和类型限制;

  • 搜索:根据中文命中案例选择轻量前端方案或独立服务;

  • 内容协作:维护频率明显提高后,再评估编辑工作流;

  • 版本管理:出现多个长期维护版本后,再设计手册版本切换。

无论增加哪项能力,都应继续保持以下原则:

  1. 手册内容与业务代码版本可追踪;

  2. 首选静态能力,避免无必要的运行服务;

  3. 公开手册不引入业务权限耦合;

  4. 构建产物整体发布,不人工拆分复制文件;

  5. 复杂度由实际需求驱动,而不是提前堆叠。

13. 效果预览