Files
tools-show/design/plans/2026-04-11-tool-manual-detail-page-plan.md
2026-04-11 20:46:55 +08:00

190 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 用户手册详情页实施计划
**目标**:将访客端原有详情弹窗替换为基于 `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 内容导致测试不稳定。