# 用户手册详情页实施计划 **目标**:将访客端原有详情弹窗替换为基于 `slug` 的独立详情页,使 `description` 可以长期作为 Markdown 用户手册展示,并补齐直达访问、空内容、404 和长文阅读体验。 **架构**:本次为单一功能子系统改造,不创建纲领文件。后端先补齐按 `slug` 查询已发布工具的公开详情能力,再由前端新增公开详情页路由和页面组件,最后替换首页详情入口并完善长文阅读样式与测试。前端测试基建当前缺失,因此计划第一步先补齐 `Vitest + Vue Test Utils + jsdom`,确保后续页面改造遵循测试优先。 **技术栈**:Vue 3、vue-router、Vite、Vitest、Vue Test Utils、NestJS、Prisma、Jest、Supertest、marked、DOMPurify --- ## 文件结构 ### 新建文件 - `client/vitest.config.js` - 前端测试配置,提供 Vue SFC 和 `jsdom` 运行环境 注释需求:低优先级,配置项仅在非直观处补简短说明 复杂度:低 - `client/src/test/setup.js` - 前端测试初始化,挂载全局匹配器、滚动和路由相关基础 mock 注释需求:中优先级,需要说明全局 mock 的用途 复杂度:中 - `client/src/utils/markdown-outline.js` - 提取 Markdown 标题、生成稳定锚点和目录结构 注释需求:高优先级,需要为重复标题去重和锚点规范化逻辑添加说明 复杂度:中 - `client/src/utils/markdown-outline.spec.js` - 验证目录提取、标题去重和无标题场景 注释需求:低优先级 复杂度:中 - `client/src/pages/ToolDetailPage.vue` - 公开详情页,负责按 `slug` 拉取详情、渲染摘要和 Markdown 手册、处理空状态/404/返回入口 注释需求:高优先级,需要在状态流转和 launch 复用入口处补说明 复杂度:高 - `client/src/pages/ToolDetailPage.spec.js` - 详情页组件测试,覆盖加载态、404、空内容、目录和主操作按钮 注释需求:低优先级 复杂度:高 - `client/src/App.spec.js` - 首页交互测试,覆盖详情按钮跳转和旧弹窗移除 注释需求:低优先级 复杂度:中 - `server/src/modules/tools/tools.service.spec.ts` - 后端服务单测,覆盖按 `slug` 查询详情的业务规则 注释需求:低优先级 复杂度:中 - `server/test/tools-detail.e2e-spec.ts` - 公开详情接口 e2e,覆盖成功命中和 404 注释需求:中优先级,需要说明测试数据准备和清理 复杂度:中 ### 修改文件 - `client/package.json` - 增加前端测试脚本和测试依赖声明 注释需求:低优先级 复杂度:低 - `client/package-lock.json` - 锁定前端测试依赖版本 注释需求:无 复杂度:低 - `client/src/api.js` - 新增按 `slug` 获取详情的方法,复用现有错误和 launch 行为 注释需求:中优先级,需要说明新接口和旧按 `id` 接口的职责差异 复杂度:中 - `client/src/admin/router.js` - 增加公开详情页路由 `/tools/:slug` 注释需求:低优先级 复杂度:低 - `client/src/App.vue` - 移除详情弹窗状态和模板,将详情入口改为路由跳转 注释需求:中优先级,需要说明首页仅负责列表和跳转,不再持有详情状态 复杂度:高 - `client/src/style.scss` - 增加详情页布局、目录、空状态、404 和长文阅读样式 注释需求:低优先级 复杂度:高 - `client/src/admin/components/ToolFormDialog.vue` - 微调“工具简介”提示文案,明确其可承载 Markdown 手册内容 注释需求:低优先级 复杂度:低 - `server/src/modules/tools/tools.service.ts` - 抽出详情映射逻辑,新增按 `slug` 查询已发布工具详情的方法 注释需求:高优先级,需要说明 `id` 与 `slug` 两套公开查询入口的职责分层 复杂度:中 - `server/src/modules/tools/tools.controller.ts` - 新增按 `slug` 获取公开详情的接口 注释需求:低优先级 复杂度:低 ### 测试文件 - `client/src/utils/markdown-outline.spec.js` - 验证 Markdown 目录提取和锚点生成规则 - `client/src/pages/ToolDetailPage.spec.js` - 验证详情页数据状态和长文阅读体验 - `client/src/App.spec.js` - 验证首页详情入口从弹窗切为路由跳转 - `server/src/modules/tools/tools.service.spec.ts` - 验证按 `slug` 查询的业务规则 - `server/test/tools-detail.e2e-spec.ts` - 验证公开详情接口路由行为 --- ## 任务列表 ### 任务 1:建立前端测试基建和 Markdown 目录工具 **文件**: - 创建:`client/vitest.config.js` - 创建:`client/src/test/setup.js` - 创建:`client/src/utils/markdown-outline.js` - 测试:`client/src/utils/markdown-outline.spec.js` - 修改:`client/package.json` - 修改:`client/package-lock.json` - [ ] **步骤 1**:在 `client/package.json` 中增加 `test`、`test:run` 脚本和 `vitest`、`@vue/test-utils`、`jsdom` 等依赖声明;创建 `client/vitest.config.js`、`client/src/test/setup.js`;编写 `client/src/utils/markdown-outline.spec.js`,先描述标题提取、重复标题去重、无标题返回空目录的失败用例。 - [ ] **步骤 2**:运行 `npm --prefix client install`;随后运行 `npm --prefix client run test:run -- client/src/utils/markdown-outline.spec.js`,预期输出为测试失败,原因是 `markdown-outline.js` 尚未实现或断言不满足。 - [ ] **步骤 3**:创建 `client/src/utils/markdown-outline.js`,实现 `extractMarkdownOutline` 和稳定锚点生成逻辑,保证只提取正文标题并对重复标题追加序号。 - [ ] **步骤 4**:再次运行 `npm --prefix client run test:run -- client/src/utils/markdown-outline.spec.js`,预期输出 `1 passed`,前端测试基建可用。 ### 任务 2:实现后端按 `slug` 查询详情的服务层能力 **文件**: - 创建:`server/src/modules/tools/tools.service.spec.ts` - 修改:`server/src/modules/tools/tools.service.ts` - [ ] **步骤 1**:编写 `server/src/modules/tools/tools.service.spec.ts`,覆盖已发布工具可按 `slug` 查询、未发布工具不可见、缺失工具抛出 404、详情映射字段与现有按 `id` 详情保持一致的失败测试。 - [ ] **步骤 2**:运行 `npm --prefix server test -- --runTestsByPath src/modules/tools/tools.service.spec.ts`,预期输出为编译失败或测试失败,因为 `getToolDetailBySlug` 及共享映射尚未存在。 - [ ] **步骤 3**:在 `server/src/modules/tools/tools.service.ts` 中抽出详情映射方法,新增 `getToolDetailBySlug(slug: string)`,查询条件限定为 `status=published`、`isDeleted=false`,并复用现有详情返回结构。 - [ ] **步骤 4**:再次运行 `npm --prefix server test -- --runTestsByPath src/modules/tools/tools.service.spec.ts`,预期输出 `Test Suites: 1 passed`。 ### 任务 3:补齐公开详情 `slug` 接口和后端 e2e **文件**: - 创建:`server/test/tools-detail.e2e-spec.ts` - 修改:`server/src/modules/tools/tools.controller.ts` - [ ] **步骤 1**:编写 `server/test/tools-detail.e2e-spec.ts`,准备一条已发布工具测试数据,定义 `GET /tools/slug/:slug` 成功返回详情和未知 `slug` 返回 404 的失败测试。 - [ ] **步骤 2**:运行 `npm --prefix server run test:e2e -- --runTestsByPath test/tools-detail.e2e-spec.ts`,预期输出为 404 或断言失败,因为控制器路由尚未暴露该接口。 - [ ] **步骤 3**:在 `server/src/modules/tools/tools.controller.ts` 中新增 `GET /tools/slug/:slug` 路由,调用 `toolsService.getToolDetailBySlug`;按需要调整 e2e 中的测试数据清理,避免污染现有数据。 - [ ] **步骤 4**:再次运行 `npm --prefix server run test:e2e -- --runTestsByPath test/tools-detail.e2e-spec.ts`,预期输出 `1 passed` 或 `2 passed`,接口直达行为稳定。 ### 任务 4:新增公开详情页路由、API 接入和页面状态 **文件**: - 创建:`client/src/pages/ToolDetailPage.vue` - 测试:`client/src/pages/ToolDetailPage.spec.js` - 修改:`client/src/api.js` - 修改:`client/src/admin/router.js` - [ ] **步骤 1**:编写 `client/src/pages/ToolDetailPage.spec.js`,覆盖按路由 `slug` 拉取详情、加载态、404 提示、空内容提示、返回首页入口以及主操作按钮展示的失败测试。 - [ ] **步骤 2**:运行 `npm --prefix client run test:run -- client/src/pages/ToolDetailPage.spec.js`,预期输出为模块不存在或多个断言失败,因为详情页组件、路由和新 API 方法尚未实现。 - [ ] **步骤 3**:在 `client/src/api.js` 中新增 `fetchToolDetailBySlug(slug)`;在 `client/src/admin/router.js` 中增加公开路由 `/tools/:slug`;创建 `client/src/pages/ToolDetailPage.vue`,完成按 `slug` 加载数据、页面级加载/错误/404/空内容状态和返回入口,并复用现有 launch 行为。 - [ ] **步骤 4**:再次运行 `npm --prefix client run test:run -- client/src/pages/ToolDetailPage.spec.js`,预期输出 `1 passed`,详情页基础行为成立。 ### 任务 5:替换首页详情弹窗为路由跳转 **文件**: - 创建:`client/src/App.spec.js` - 修改:`client/src/App.vue` - [ ] **步骤 1**:编写 `client/src/App.spec.js`,覆盖“详情”按钮改为跳转到 `/tools/:slug`、旧详情弹窗 DOM 不再渲染、首页仍保留概览弹窗的失败测试。 - [ ] **步骤 2**:运行 `npm --prefix client run test:run -- client/src/App.spec.js`,预期输出为断言失败,因为首页仍然保留 `openDetailModal` 流程和旧弹窗模板。 - [ ] **步骤 3**:修改 `client/src/App.vue`,移除 `detailModalOpen/detailLoading/detailError/detail` 等状态及弹窗模板,将详情按钮改为 `router.push` 或 `RouterLink` 跳转,并保留列表、筛选和概览弹窗逻辑不变。 - [ ] **步骤 4**:再次运行 `npm --prefix client run test:run -- client/src/App.spec.js client/src/pages/ToolDetailPage.spec.js`,预期输出全部通过,首页到详情页的主链路打通。 ### 任务 6:完善长文阅读体验、后台提示文案和整体验证 **文件**: - 修改:`client/src/style.scss` - 修改:`client/src/pages/ToolDetailPage.vue` - 修改:`client/src/pages/ToolDetailPage.spec.js` - 修改:`client/src/admin/components/ToolFormDialog.vue` - [ ] **步骤 1**:扩展 `client/src/pages/ToolDetailPage.spec.js`,新增目录渲染、无标题时不显示目录、长文时保留清晰段落层级和返回入口的失败测试。 - [ ] **步骤 2**:运行 `npm --prefix client run test:run -- client/src/pages/ToolDetailPage.spec.js`,预期输出为目录相关断言失败,因为页面尚未接入目录工具和阅读态样式。 - [ ] **步骤 3**:修改 `client/src/pages/ToolDetailPage.vue` 和 `client/src/style.scss`,接入 `extractMarkdownOutline`、渲染目录导航、优化正文宽度/标题层级/锚点区块;同步修改 `client/src/admin/components/ToolFormDialog.vue` 的简介提示文案,明确其支持 Markdown 手册内容。 - [ ] **步骤 4**:依次运行 `npm --prefix client run test:run -- client/src/utils/markdown-outline.spec.js client/src/pages/ToolDetailPage.spec.js client/src/App.spec.js`、`npm --prefix client run build`、`npm --prefix server test -- --runTestsByPath src/modules/tools/tools.service.spec.ts`、`npm --prefix server run test:e2e -- --runTestsByPath test/tools-detail.e2e-spec.ts`,预期输出均为通过,且客户端构建成功无报错。 --- ## 执行顺序说明 1. 先补前端测试基建,否则后续页面任务无法按 TDD 执行。 2. 后端先做服务层,再做控制器和 e2e,可减少路由级调试成本。 3. 前端先完成详情页自身状态,再替换首页入口,避免“按钮先改了但页面未就绪”。 4. 长文阅读体验放在最后收口,避免样式和目录逻辑反复返工。 ## 风险提示 - 前端当前没有测试依赖,任务 1 会同时引入测试脚手架和锁文件变更。 - `App.vue` 体量较大,任务 5 需要谨慎删除旧弹窗状态,避免误伤概览弹窗和列表筛选逻辑。 - `slug` 接口与按 `id` 接口将同时存在,任务 2 和任务 3 需要确保两者返回结构一致,避免前端出现双标准。 - e2e 测试需要自行准备和清理工具数据,避免依赖现有 seed 内容导致测试不稳定。