Browse Source

chore: remove obsolete checklist and prompt files

main
Mohan 1 month ago
parent
commit
3230aa4991
  1. 199
      checklist-agent2-phase2.md
  2. 156
      checklist-agent2-phase3.md
  3. 322
      checklist-v0.2.md
  4. 507
      prompt-agent2-frontend-v0.3.md
  5. 228
      prompt-agent2-frontend-v0.4.md

199
checklist-agent2-phase2.md

@ -1,199 +0,0 @@
# Agent2 前端自查 Checklist - Phase 2 (v0.4)
> 项目: mach-cms-frontend
> 版本: Phase 2 v0.4
> 角色: 前端开发 Agent
> 要求: 逐项自查,全部通过后再提交验收
---
## 0. 环境前置
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 0.1 | 远程仓库可访问 | `git remote -v` 显示正确 origin | ✓ |
| 0.2 | 本地 main 分支最新 | `git pull --rebase origin main` 无冲突 | ✓ |
| 0.3 | Node.js 20+ | `node -v` 输出 v20.x | ✓ |
| 0.4 | 后端已启动 | `http://localhost:8080/actuator/health` 返回 UP | ✓ |
---
## 1. basePath 修正确认
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.1 | `client.ts` 中 basePath 为空字符串 | 所有 `new XxxApi(config, '', axiosInstance)` 第二个参数为 `''` | ✓ |
| 1.2 | 无 `/api/api` 请求 | 浏览器 DevTools Network 面板中,请求路径无 `/api/api` 前缀 | ✓ |
| 1.3 | 请求正常到达后端 | 所有 API 请求状态码 200/201,非 404 | ✓ |
---
## 2. Git 规范自查
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 2.1 | 提交历史规范 | `git log --oneline -20` 所有 message 符合 `type(scope): subject` | ✓ |
| 2.2 | 无 "update"/"fix bug" 等垃圾提交 | 全局搜索无此类 message | ✓ |
| 2.3 | 单 commit 行数 ≤ 200 | `git log --stat` 抽查最近 5 个 commit,无超 200 行 | ☐ | △ bfcbf4a=335、459e094=225 超200行(已push历史,本次验收后续 commit 均≤200) |
| 2.4 | 每次 commit 已 push | `git status` 显示 "Your branch is up to date with 'origin/main'" | ✓ |
| 2.5 | 无未提交代码 | `git status` 无未 stage 的修改 | ✓ |
---
## 3. Task 1: API 代码重新生成
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 3.1 | `npm run api:generate` 成功 | 无报错,生成目录更新 | ✓ |
| 3.2 | `src/generated/api/` 存在 | 目录存在且包含 `api.ts` + `models/` | ✓ |
| 3.3 | Comment 类型生成 | `models/` 包含 `comment-response.ts`, `comment-submit-request.ts` | ☐ | ✓* models/ 未单独拆目录(typescript-axios 默认单文件 api.ts),CommentResponse/CommentSubmitRequest 存在于 api.ts |
| 3.4 | User 类型生成 | `models/` 包含 `user-info.ts`, `update-role-request.ts` | ☐ | ✓* UserInfo/UpdateUserRoleRequest 存在于 api.ts(未拆 models/) |
| 3.5 | API 类生成 | `api.ts` 包含 `CommentsApi`, `CommentAdminApi`, `UserAdminApi` | ☐ | ✓* 类名 CommentApi/CommentAdminApi/UserApi(由契约 tag 决定),非清单 Suggest CommentsApi/UserAdminApi |
| 3.6 | `StoredFile` 扩展 | 包含 `thumbnailUrl` 字段 | ✓ |
| 3.7 | `client.ts` 已更新 | 新增 `commentsApi`, `commentAdminApi`, `userAdminApi` 实例,basePath 为 `''` | ☐ | ✓* 实例 commentApi/commentAdminApi/userApi(契约命名) |
---
## 4. Task 2: 注册页面
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 4.1 | 路由存在 | `/register` 可访问 | ✓ |
| 4.2 | 表单字段 | 用户名、密码、确认密码、邮箱 | ✓ |
| 4.3 | 前端校验 | 密码与确认密码不一致时阻止提交 | ✓ |
| 4.4 | 用户名长度校验 | 3-50 字符 | ✓ |
| 4.5 | 密码长度校验 | 6-100 字符 | ✓ |
| 4.6 | 调用注册 API | 点击注册调用 `authApi.register()` | ✓ |
| 4.7 | 注册后自动登录 | 成功后自动调用 `authStore.login()` | ✓ |
| 4.8 | 跳转后台 | 登录成功后进入 `/admin` | ✓ |
| 4.9 | 已登录拦截 | 已登录用户访问 `/register` 自动跳转 `/admin` | ✓ |
| 4.10 | 错误提示 | 注册失败(如用户名已存在)显示错误信息 | ✓ |
---
## 5. Task 3: 评论组件
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 5.1 | `CommentSection.vue` 存在 | `src/components/CommentSection.vue` 存在 | ✓ |
| 5.2 | 接收 slug prop | `<CommentSection :slug="slug" />` | ✓ |
| 5.3 | 加载评论列表 | 进入文章详情页自动加载评论 | ✓ |
| 5.4 | 只显示已审核 | 未审核评论不显示 | ✓ |
| 5.5 | 提交表单 | 昵称(必填)、邮箱(可选)、内容(必填) | ✓ |
| 5.6 | 提交后提示 | 显示"评论已提交,等待审核" | ✓ |
| 5.7 | 无需登录 | 未登录用户可提交评论 | ✓ |
| 5.8 | 样式 | 评论间有分隔线,时间格式化显示 | ✓ |
| 5.9 | 文章详情页引入 | `ArticleDetailView.vue` 底部包含 `<CommentSection />` | ✓ |
---
## 6. Task 4: Markdown 渲染 + XSS 防护
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 6.1 | 依赖安装 | `marked`, `dompurify`, `highlight.js` 已安装 | ✓ |
| 6.2 | `renderMarkdown` 工具 | `src/utils/markdown.ts` 存在 | ✓ |
| 6.3 | Markdown 转 HTML | 标题、段落、列表、链接正确渲染 | ✓ |
| 6.4 | 代码高亮 | 代码块有语法高亮(背景色、关键字着色) | ✓ |
| 6.5 | XSS 过滤 | `DOMPurify.sanitize()` 过滤 `<script>` 等危险标签 | ✓ |
| 6.6 | 样式引入 | `highlight.js` 主题样式已加载 | ✓ |
| 6.7 | 文章详情页使用 | `ArticleDetailView.vue``v-html="renderMarkdown(content)"` | ✓ |
---
## 7. Task 5: 前台标签云
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 7.1 | `TagCloud.vue` 存在 | `src/components/TagCloud.vue` 存在 | ✓ |
| 7.2 | 调用标签 API | `tagsApi.listTags()` 获取标签列表 | ✓ |
| 7.3 | 显示所有标签 | 页面上可见所有标签名称 | ✓ |
| 7.4 | 大小按文章数 | 文章数多的标签字体/尺寸更大(至少有两种大小) | ✓ |
| 7.5 | 首页引入 | `HomeView.vue` 包含 `<TagCloud />` | ✓ |
---
## 8. Task 6: 管理后台增强
### 8.1 评论审核页
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 8.1.1 | 路由存在 | `/admin/comments` 可访问 | ✓ |
| 8.1.2 | 加载待审核评论 | 调用 `commentAdminApi.listPendingComments()` | ✓* | 契约方法为 `commentAdminApi.apiAdminCommentsGet()`(列表仅含待审核) |
| 8.1.3 | 表格展示 | 作者、邮箱、内容、文章标题、提交时间 | ✓ |
| 8.1.4 | 通过操作 | 点击"通过"后状态变为 APPROVED,前台可见 | ✓ |
| 8.1.5 | 拒绝操作 | 点击"拒绝"后状态变为 REJECTED | ✓ |
| 8.1.6 | 删除操作 | 点击"删除"后评论消失 | ✓ |
| 8.1.7 | 权限控制 | 非 ADMIN 访问返回 403 或被路由拦截 | ✓ |
### 8.2 用户管理页
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 8.2.1 | 路由存在 | `/admin/users` 可访问 | ✓ |
| 8.2.2 | 加载用户列表 | 调用 `userAdminApi.listUsers()` | ✓* | 契约方法为 `userApi.apiAdminUsersGet()` |
| 8.2.3 | 表格展示 | 用户名、邮箱、角色、状态、注册时间 | ✓ |
| 8.2.4 | 角色修改 | 下拉选择 ADMIN/EDITOR/VISITOR,保存后生效 | ✓ |
| 8.2.5 | 启用/禁用 | 切换开关或按钮,状态实时更新 | ✓ |
| 8.2.6 | 分页 | 用户多时分页正常 | ✓ |
| 8.2.7 | 菜单权限 | 只有 ADMIN 角色才显示"用户管理"菜单项 | ✓ |
### 8.3 后台布局更新
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 8.3.1 | 菜单新增 | 侧边栏新增"评论审核"、"用户管理" | ✓ |
| 8.3.2 | 菜单条件渲染 | "用户管理"只在 ADMIN 角色下显示 | ✓ |
---
## 9. Task 7: 图片上传集成
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 9.1 | 封面图上传 | 文章编辑器有上传按钮,选择图片后返回 URL 填入表单 | ✓ |
| 9.2 | 粘贴上传 | 在内容编辑区粘贴图片,自动上传并插入 `![alt](url)` | ✓ |
| 9.3 | 上传调用 | 调用 `mediaApi.upload()` | ✓ |
| 9.4 | 缩略图显示 | 封面图上传后显示缩略图预览(如有 thumbnailUrl) | ✓ |
---
## 10. 构建与通用检查
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 10.1 | TypeScript 无错误 | `npm run build` 通过(或 `vue-tsc --noEmit` 无错误) | ✓ |
| 10.2 | 开发服务器正常 | `npm run dev` 启动无报错 | ✓ |
| 10.3 | 无手写 API 类型 | `src/generated/api/` 外无 `ArticleDetail`/`TokenPair` 等自定义接口 | ✓ |
| 10.4 | 统一 Axios 使用 | 全局搜索 `import axios from 'axios'`,除 `client.ts` 外无其他 | ✓ |
| 10.5 | 组合式 API | 所有 `.vue` 文件使用 `<script setup lang="ts">` | ✓ |
| 10.6 | 路由守卫 | 未登录访问 `/admin` 自动跳转 `/login` | ✓ |
| 10.7 | 登录状态保持 | 刷新浏览器后仍保持登录(Token 未过期) | ✓ |
| 10.8 | 401 自动刷新 | Token 过期后自动调用 `/auth/refresh`,无感知续期 | ✓ |
---
## 11. 端到端快速验证
手动执行以下流程,全部通过:
1. **注册** → 访问 `/register`,填写表单 → 注册成功 → 自动跳转 `/admin`
2. **创建文章** → 后台新建文章,填写标题/内容/标签 → 保存成功
3. **发布文章** → 点击"发布" → 前台首页可见该文章
4. **查看详情** → 点击文章卡片 → 详情页显示 Markdown 渲染内容 + 代码高亮
5. **提交评论** → 在详情页填写昵称和内容 → 提交 → 提示"等待审核"
6. **审核评论** → 后台"评论审核"页 → 看到待审核评论 → 点击"通过"
7. **前台查看评论** → 刷新文章详情页 → 评论显示
8. **标签云** → 首页标签云显示,大小按文章数变化
9. **用户管理** → 后台"用户管理"页 → 可查看用户列表,修改角色
10. **图片上传** → 文章编辑器粘贴图片 → 自动上传并插入 Markdown 语法
---
## 验收签字
- [x] 以上所有检查项全部通过(含差异标注项)
- [ ] 已提交给架构 Owner 验收
Agent2 签字: Agent2 日期: 2026-08-10

156
checklist-agent2-phase3.md

@ -1,156 +0,0 @@
# Agent2 前端自查 Checklist - Phase 3 (v0.5)
> 项目: mach-cms-frontend
> 阶段: Phase 3 — 部署与 SEO
> 要求: 逐项自查,全部通过后再提交验收
---
## 0. 环境前置
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 0.1 | 远程仓库可访问 | `git remote -v` 显示正确 origin | ✓ |
| 0.2 | 本地 main 最新 | `git pull --rebase origin main` 无冲突 | ✓ |
| 0.3 | Node.js 20+ | `node -v` 输出 v20.x | ✓ |
---
## 1. 已知差异确认
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.1 | 单文件输出接受 | `src/generated/api/api.ts` 存在(无 `models/` 目录),不手动拆分 | ✓ |
| 1.2 | 类名单数命名 | `CommentApi`/`UserApi` 等以实际生成为准,不强制复数 | ✓ |
| 1.3 | basePath 为 '' | `client.ts` 中所有 `new XxxApi(config, '', axiosInstance)` | ✓ |
---
## 2. Git 规范自查
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 2.1 | 提交历史规范 | `git log --oneline -20` 全部符合 `type(scope): subject` | ✓ |
| 2.2 | 无垃圾提交 | 无 "update"、"fix"、"ok" 等模糊 message | ✓ |
| 2.3 | 单 commit 行数 ≤ 200 | 抽查最近 5 个 commit | ✓ |
| 2.4 | 已 push | `git status` 显示 up to date | ✓ |
| 2.5 | 无未提交代码 | `git status` 无未 stage 修改 | ✓ |
---
## 3. Task 1: 构建优化
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 3.1 | `vite.config.ts` 生产配置 | `build` 配置块存在 | ✓ |
| 3.2 | `sourcemap` 关闭 | 生产环境 `sourcemap: false` | ✓ |
| 3.3 | 代码分割 | `manualChunks` 包含 `vendor`(vue/pinia/router/axios)和 `markdown` | ✓ |
| 3.4 | 输出目录 | `outDir: 'dist'` | ✓ |
| 3.5 | 构建成功 | `npm run build` 完成且无错误 | ✓ |
| 3.6 | 产物检查 | `dist/index.html``dist/assets/` 存在 | ✓ |
| 3.7 | 产物大小 | JS 文件有多个 chunk(vendor 分离) | ✓ |
---
## 4. Task 2: SEO Meta 工具
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 4.1 | `src/utils/seo.ts` 存在 | 文件存在 | ✓ |
| 4.2 | `setPageMeta` 函数 | 接收 `PageMeta` 参数 | ✓ |
| 4.3 | title 设置 | `document.title = "{title} | Mach-CMS"` | ✓ |
| 4.4 | og:title | 创建/更新 `<meta property="og:title">` | ✓ |
| 4.5 | og:description | 创建/更新 `<meta property="og:description">` | ✓ |
| 4.6 | og:image | 创建/更新 `<meta property="og:image">`(图片存在时) | ✓ |
| 4.7 | og:url | 创建/更新 `<meta property="og:url">` | ✓ |
| 4.8 | og:type | 默认 `article` | ✓ |
| 4.9 | name="description" | 同步设置 `<meta name="description">` | ✓ |
| 4.10 | meta 复用 | 多次调用不会重复创建 meta 标签 | ✓ |
---
## 5. Task 3: 路由级 Meta 管理
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 5.1 | 路由 meta 配置 | 所有路由有 `meta.title` | ✓ |
| 5.2 | `router.afterEach` | 全局后置钩子调用 `setPageMeta` | ✓ |
| 5.3 | 首页 title | 访问 `/` 显示 "首页 | Mach-CMS" | ✓ |
| 5.4 | 登录页 title | 访问 `/login` 显示 "登录 | Mach-CMS" | ✓ |
| 5.5 | 注册页 title | 访问 `/register` 显示 "注册 | Mach-CMS" | ✓ |
| 5.6 | 后台 title | 访问 `/admin` 显示 "管理后台 | Mach-CMS" | ✓* | `/admin` 重定向至 `/admin/articles`,实际显示 "文章管理 \| Mach-CMS"(子路由覆盖父 meta) |
| 5.7 | 文章列表 title | 访问 `/admin/articles` 显示 "文章管理 | Mach-CMS" | ✓ |
| 5.8 | 搜索页 title | 访问 `/search` 显示 "搜索 | Mach-CMS" | ✓ |
---
## 6. Task 4: 文章详情页动态 Meta
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 6.1 | 加载后设置 meta | `loadArticle()` 成功后调用 `setPageMeta` | ✓ |
| 6.2 | title 同步 | 文章标题作为 `document.title` | ✓ |
| 6.3 | description | summary 或 content 前 200 字符 | ✓ |
| 6.4 | image | coverImage 作为 `og:image` | ✓ |
| 6.5 | url | 当前文章 URL 作为 `og:url` | ✓ |
| 6.6 | 浏览器验证 | DevTools Elements 面板 `<head>` 中可见 og 标签 | ✓ | Playwright 断言 `og:title`/`og:url`/`og:type`/`og:description` 均存在 |
---
## 7. Task 5: 404 页面
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 7.1 | `NotFoundView.vue` 存在 | `src/views/NotFoundView.vue` 存在 | ✓ |
| 7.2 | 路由配置 | `path: '/:pathMatch(.*)*'` | ✓ |
| 7.3 | 显示内容 | "404" 标题 + "页面不存在" 提示 + 返回首页链接 | ✓ |
| 7.4 | meta 设置 | title 为 "页面不存在 | Mach-CMS" | ✓ |
| 7.5 | 访问测试 | 访问 `/nonexistent` 显示 404 页面,非空白 | ✓ |
---
## 8. Task 6: 加载状态与错误处理
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 8.1 | `useApi` composable | `src/composables/useApi.ts` 存在(或等效实现) | ✓ |
| 8.2 | loading 状态 | API 调用时显示 "加载中..." | ✓ |
| 8.3 | error 状态 | API 失败显示错误信息 | ✓ | 模拟 500 响应 → 页面显示错误文字 |
| 8.4 | 文章不存在 | slug 无效时显示 "文章不存在" | ✓ |
| 8.5 | 错误提示方式 | `alert()` 或页面文字提示(至少有一种) | ✓ |
| 8.6 | 文章详情页应用 | 有 loading / error / 不存在 / 正常 四种状态 | ✓ |
---
## 9. 端到端验证
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 9.1 | 开发服务器正常 | `npm run dev` 启动无报错 | ✓ |
| 9.2 | 首页访问 | `http://localhost:5173/` 正常 | ✓ |
| 9.3 | 文章详情 | 点击文章进入详情页,内容正确 | ✓ |
| 9.4 | 404 页面 | 访问不存在的路由显示 404 | ✓ |
| 9.5 | 登录/注册 | 功能正常,跳转正确 | ✓ |
| 9.6 | 后台管理 | 文章/标签/评论/用户管理可用 | ✓ | 四页均正常渲染 |
| 9.7 | 构建产物 | `dist/` 可直接复制到 Nginx 使用 | ✓ | 产物完整(相对路径),需 Nginx `try_files` SPA fallback |
---
## 10. 部署联调(与后端 Agent1 配合)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 10.1 | `docker compose up` 后前台可用 | `http://localhost/` 显示首页 | △ | 需 Agent1/部署环境(Nginx+Docker),本地无法独立验证 |
| 10.2 | Nginx 代理 API | `http://localhost/api/articles` 正常 | △ | 需部署环境验证 |
| 10.3 | 路由刷新 | 直接访问 `http://localhost/post/some-slug` 不 404 | △ | 需 Nginx `try_files` SPA fallback 验证 |
| 10.4 | 后台路由刷新 | 直接访问 `http://localhost/admin` 不 404 | △ | 需 Nginx `try_files` SPA fallback 验证 |
| 10.5 | OG 标签验证 | 用 [Facebook Sharing Debugger](https://developers.facebook.com/tools/debug/) 或 curl 检查 `og:title` | ✓* | 浏览器实测 `og:title`/`og:description` 存在 |
---
## 验收签字
- [x] 以上所有检查项全部通过(10.x 部署项依赖 Agent1 联调环境,标 △ 待部署验证)
- [ ] 已提交给架构 Owner 验收
Agent2 签字: Agent2 日期: 2026-08-10

322
checklist-v0.2.md

@ -1,322 +0,0 @@
# Mach-CMS 联调测试 Checklist
> 版本: 0.2.0
> 日期: 2026-08-09
> 用途: Agent1(后端)+ Agent2(前端)验收 + 联调
---
## 0. 环境准备(前置条件)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 0.1 | PostgreSQL 16 已启动 | `psql -U postgres -c "\l"` 能列出数据库 | ☐ |
| 0.2 | 数据库 `cms` 已创建 | `createdb -U postgres cms` 执行成功或已存在 | ☐ |
| 0.3 | Node.js 20+ 已安装 | `node -v` 输出 v20.x | ☐ |
| 0.4 | JDK 21 已安装 | `java -version` 输出 21 | ☐ |
| 0.5 | `openapi.yaml` 已放置于后端仓库根目录 | 文件存在且版本为 0.1.0+ | ☐ |
| 0.6 | `openapi.yaml` 已放置于前端仓库可引用路径 | `openapitools.json``inputSpec` 路径正确 | ☐ |
---
## 1. Agent1 后端独立验收
### 1.1 构建与启动
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.1.1 | `./gradlew build` 无编译错误 | 控制台 `BUILD SUCCESSFUL` | ☐ |
| 1.1.2 | `./gradlew test` 通过 | 所有测试通过(至少包含 Article 状态机测试) | ☐ |
| 1.1.3 | `./gradlew bootRun` 正常启动 | 控制台出现 `Started CmsApplication` 且无 ERROR | ☐ |
| 1.1.4 | Actuator 健康检查 | `GET http://localhost:8080/actuator/health` 返回 `{"status":"UP"}` | ☐ |
| 1.1.5 | Swagger UI 可访问 | `http://localhost:8080/swagger-ui.html` 正常渲染 | ☐ |
### 1.2 数据库迁移
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.2.1 | Flyway V1 迁移成功 | `flyway_schema_history` 表有 V1 记录 | ☐ |
| 1.2.2 | Flyway V2 迁移成功 | `flyway_schema_history` 表有 V2 记录;`articles/tags/article_tags` 表结构正确 | ☐ |
| 1.2.3 | Flyway V3 迁移成功 | `flyway_schema_history` 表有 V3 记录;`articles` 表有触发器 `article_search_vector_trigger` | ☐ |
| 1.2.4 | jOOQ 代码生成成功 | `build/generated-sources/jooq/top/jmto/mach_cms/jooq/` 存在 Kotlin 文件 | ☐ |
### 1.3 认证接口(独立测试)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.3.1 | 用户注册 | `POST /api/auth/register` 返回 `Response<TokenPair>`,code=0 | ☐ |
| 1.3.2 | 用户登录 | `POST /api/auth/login` 返回 `Response<TokenPair>`,包含 accessToken + refreshToken | ☐ |
| 1.3.3 | Token 刷新 | `POST /api/auth/refresh` 传入 refreshToken,返回新的 TokenPair | ☐ |
| 1.3.4 | 用户登出 | `POST /api/auth/logout` 返回 code=0;再次用旧 refreshToken 刷新应失败 | ☐ |
| 1.3.5 | 密码加密 | 直接查 PG `users` 表,`password` 字段为 BCrypt 哈希(以 `$2a$` 开头) | ☐ |
### 1.4 文章接口(独立测试)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.4.1 | 创建文章(Admin) | `POST /api/admin/articles` 带 Bearer Token,返回 `Response<ArticleDetail>`,slug 自动生成 | ☐ |
| 1.4.2 | 文章 slug 唯一性 | 同标题创建第二篇文章,slug 自动加后缀(如 `hello-world-1`) | ☐ |
| 1.4.3 | 查询文章详情(前台) | `GET /api/articles/{slug}` 无认证可访问,返回结构与 YAML 一致 | ☐ |
| 1.4.4 | 文章列表(前台) | `GET /api/articles?page=1&size=10` 返回 `Response<PageResult<ArticleListItem>>` | ☐ |
| 1.4.5 | 状态过滤 | `GET /api/articles?status=PUBLISHED` 只返回已发布文章 | ☐ |
| 1.4.6 | 发布文章 | `PATCH /api/admin/articles/{id}/publish` 后,status 变为 PUBLISHED,publishedAt 有值 | ☐ |
| 1.4.7 | 归档文章 | `PATCH /api/admin/articles/{id}/archive` 后,status 变为 ARCHIVED | ☐ |
| 1.4.8 | 状态机保护 | 对已发布文章再次调用 `publish`,返回业务错误(非 500) | ☐ |
| 1.4.9 | 更新文章 | `PUT /api/admin/articles/{id}` 更新后内容正确,updatedAt 变化 | ☐ |
| 1.4.10 | 删除文章 | `DELETE /api/admin/articles/{id}` 后,再次查询返回 404 或 null data | ☐ |
| 1.4.11 | 未认证访问 Admin | `POST /api/admin/articles` 不带 Token,返回 401 | ☐ |
| 1.4.12 | 无权限访问 Admin | 用 VISITOR 角色 Token 访问 Admin 接口,返回 403 | ☐ |
### 1.5 搜索接口(独立测试)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.5.1 | 全文搜索 | `GET /api/articles/search?q=spring` 返回相关文章列表 | ☐ |
| 1.5.2 | 搜索结果排序 | 标题含关键词的文章排在内容含关键词的前面(ts_rank 权重 A>B>C) | ☐ |
| 1.5.3 | 空关键词处理 | `q=` 或缺失,返回业务错误(code ≠ 0)或空列表 | ☐ |
| 1.5.4 | 搜索触发器生效 | 创建文章后立即搜索标题关键词,能搜到结果 | ☐ |
### 1.6 标签接口(独立测试)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.6.1 | 创建标签(Admin) | `POST /api/admin/tags` 返回 `Response<Tag>` | ☐ |
| 1.6.2 | 标签列表(前台) | `GET /api/tags` 返回所有标签,带 `articleCount` | ☐ |
| 1.6.3 | 文章关联标签 | 创建文章时传入 `tagIds`,详情页返回正确 `tagNames` | ☐ |
| 1.6.4 | 删除标签(Admin) | `DELETE /api/admin/tags/{id}` 成功,关联文章不再显示该标签 | ☐ |
### 1.7 媒体接口(独立测试)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.7.1 | 文件上传 | `POST /api/media/upload` 上传图片,返回 `Response<StoredFile>`,url 可访问 | ☐ |
| 1.7.2 | 文件本地存储 | `uploads/images/` 目录下存在上传的文件 | ☐ |
| 1.7.3 | 目录遍历防护 | 上传时篡改 `directory` 参数为 `../etc`,被拒绝或存储到安全路径 | ☐ |
### 1.8 响应格式一致性
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 1.8.1 | 所有业务接口返回 `Response<T>` | 抽查 5 个接口,结构均为 `{code, message, data}` | ☐ |
| 1.8.2 | 成功时 code=0 | 所有成功响应 code 字段为 0 | ☐ |
| 1.8.3 | 错误时 code≠0 | 业务错误(如 slug 重复)code 为对应 ErrorCode 值 | ☐ |
| 1.8.4 | 分页接口返回 `PageResult` | 列表接口包含 `list/total/page/size/totalPages` | ☐ |
| 1.8.5 | Swagger UI 与 YAML 一致 | Swagger 中显示的接口路径、参数、响应模型与 `openapi.yaml` 完全一致 | ☐ |
---
## 2. Agent2 前端独立验收
### 2.1 构建与启动
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 2.1.1 | `npm install` 成功 | 无依赖安装错误 | ✓ |
| 2.1.2 | `npm run api:generate` 成功 | `src/generated/api/` 目录存在且包含 api.ts + models/ | ✓ |
| 2.1.3 | `npm run dev` 正常启动 | 控制台无 ERROR,访问 `http://localhost:5173` 正常 | ✓ |
| 2.1.4 | TypeScript 严格模式无错误 | `npm run build` 通过(或 `vue-tsc --noEmit` 无类型错误) | ✓ |
### 2.2 代码规范检查
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 2.2.1 | 无手写 API 类型 | `src/generated/api/` 外无自定义的 `ArticleDetail`/`TokenPair` 等接口 | ✓ |
| 2.2.2 | 统一 Axios 客户端 | 全局搜索 `import axios from 'axios'`,除 `client.ts` 外无其他直接引用 | ✓ |
| 2.2.3 | `<script setup lang="ts">` | 所有 `.vue` 文件使用组合式 API + TS | ✓ |
| 2.2.4 | 无 `any` 类型滥用 | 全局搜索 `: any`,业务代码中不超过 3 处(配置/工具类除外) | ✓ |
### 2.3 前台页面(无需登录)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 2.3.1 | 首页加载文章列表 | 打开 `/`,能看到文章卡片列表,包含标题、摘要、时间、标签 | ✓ |
| 2.3.2 | 文章卡片点击跳转 | 点击卡片进入 `/post/:slug`,URL 正确 | ✓ |
| 2.3.3 | 文章详情页渲染 | 详情页显示标题、作者、发布时间、标签、内容 | ✓ |
| 2.3.4 | 标签云/列表 | 首页或独立页面展示标签列表,显示文章计数 | ✓ |
| 2.3.5 | 搜索功能 | 输入关键词后跳转到搜索页,展示结果列表 | ✓ |
| 2.3.6 | 分页组件 | 文章列表底部有分页,点击切换页码后内容更新 | ✓ |
### 2.4 认证流程
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 2.4.1 | 登录页面 | `/login` 能正常显示表单(用户名、密码) | ✓ |
| 2.4.2 | 登录成功 | 输入正确凭据后,localStorage 存入 `token``refreshToken`,跳转管理后台 | ✓ |
| 2.4.3 | 登录失败提示 | 输入错误密码,页面显示错误信息(alert 或文字提示) | ✓ |
| 2.4.4 | 路由守卫 | 未登录直接访问 `/admin`,自动跳转 `/login` | ✓ |
| 2.4.5 | 已登录防回退 | 已登录用户访问 `/login`,自动跳转 `/admin` | ✓ |
| 2.4.6 | 退出登录 | 点击退出后,localStorage 清除 Token,跳转首页 | ✓ |
| 2.4.7 | 刷新保持登录 | 登录后刷新浏览器(F5),仍保持登录状态(Token 未过期时) | ✓ |
### 2.5 管理后台(需登录)
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 2.5.1 | 后台布局 | `/admin` 显示侧边栏(文章管理、标签管理)+ 顶部栏 | ✓ |
| 2.5.2 | 文章列表 | `/admin/articles` 显示表格:标题、状态、发布时间、操作按钮 | ✓ |
| 2.5.3 | 状态标签颜色 | DRAFT=灰色/默认, PUBLISHED=绿色, ARCHIVED=橙色/红色 | ✓ |
| 2.5.4 | 创建文章 | 点击"新建"进入编辑器,填写后保存,列表页出现新文章 | ✓ |
| 2.5.5 | 编辑文章 | 点击"编辑"进入编辑器,表单预填充现有数据,保存后更新 | ✓ |
| 2.5.6 | 发布文章 | 列表页点击"发布",状态变为 PUBLISHED,前台可看到 | ✓ |
| 2.5.7 | 归档文章 | 列表页点击"归档",状态变为 ARCHIVED,前台不可见 | ✓ |
| 2.5.8 | 删除文章 | 点击"删除"后文章从列表消失,前台无法访问 | ✓ |
| 2.5.9 | 标签管理 | `/admin/tags` 显示标签列表,可新增、编辑、删除 | ✓ |
| 2.5.10 | 表单校验 | 创建文章时标题/内容为空,提交前前端拦截(或提交后显示后端错误) | ✓ |
### 2.6 文件上传
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 2.6.1 | 封面图上传 | 文章编辑器中上传图片,返回 URL 填入表单 | ✓ |
| 2.6.2 | 上传进度/反馈 | 上传成功后有明确反馈(URL 显示或提示) | ✓ |
---
## 3. 前后端联调验收(端到端)
### 3.1 完整业务流程
按以下顺序执行,**全部通过才算联调成功**:
| # | 场景 | 操作步骤 | 预期结果 | 结果 |
|---|------|---------|---------|------|
| 3.1.1 | 新用户注册 → 登录 → 创建文章 | 1. 前端注册页面填写用户名密码<br>2. 登录<br>3. 进入后台新建文章<br>4. 填写标题/内容/标签<br>5. 保存 | 文章出现在后台列表,状态为 DRAFT | ☐ |
| 3.1.2 | 发布文章 → 前台可见 | 1. 后台点击"发布"<br>2. 前台首页刷新 | 文章出现在首页卡片中 | ☐ |
| 3.1.3 | 前台阅读 → 搜索命中 | 1. 点击文章卡片进入详情<br>2. 复制文章标题关键词<br>3. 前台搜索框输入关键词 | 搜索结果包含该文章,且排名靠前 | ☐ |
| 3.1.4 | 多标签关联 | 1. 后台创建 2 个标签<br>2. 创建文章时选择这 2 个标签<br>3. 保存并发布 | 前台详情页显示 2 个标签;标签列表页计数正确 | ☐ |
| 3.1.5 | Token 过期自动刷新 | 1. 登录后等待 15 分钟(或手动改后端 Token 有效期为 5 秒测试)<br>2. 在后台执行任意操作 | 操作成功,无感知刷新;网络面板可见 `/auth/refresh` 请求 | ☐ |
| 3.1.6 | 并发编辑冲突(可选) | 1. 用户 A 登录编辑文章<br>2. 用户 B 登录编辑同一文章<br>3. 先后保存 | 后保存者覆盖前者(或后端返回版本冲突错误) | ☐ |
| 3.1.7 | 权限隔离 | 1. 注册一个普通用户(默认 VISITOR)<br>2. 用该用户 Token 尝试访问后台 | 前端路由守卫拦截或后端返回 403 | ☐ |
### 3.2 数据一致性
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 3.2.1 | 前端显示与后端数据一致 | 抽查 3 篇文章,前端详情页内容与后端 `GET /api/articles/{slug}` 返回一致 | ☐ |
| 3.2.2 | 分页数据一致 | 前台分页总数与后端 `total` 字段一致 | ☐ |
| 3.2.3 | 标签计数准确 | 标签列表页显示的文章数与后端 `articleCount` 一致 | ☐ |
| 3.2.4 | 搜索关键词高亮(可选) | 搜索结果中关键词有视觉高亮(或至少结果正确) | ☐ |
---
## 4. CDD 契约一致性检查(核心)
这是最关键的检查项,**任何一项不通过,整个联调失败**。
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 4.1 | YAML 与后端 Swagger 一致 | 对比 `openapi.yaml``http://localhost:8080/v3/api-docs`,路径/参数/模型无差异 | ☐ |
| 4.2 | YAML 与前端生成类型一致 | 前端 `src/generated/api/models/` 中的类型与 YAML `components/schemas` 一一对应 | ☐ |
| 4.3 | 后端响应与 YAML 一致 | 用 Swagger UI 或 curl 调用接口,响应 JSON 结构与 YAML 中定义的 `Response<T>` 一致 | ☐ |
| 4.4 | 前端请求与 YAML 一致 | 浏览器 DevTools Network 面板中,前端发出的请求路径/参数与 YAML 一致 | ☐ |
| 4.5 | 无契约外接口 | 后端不存在 YAML 中未定义的接口;前端不调用 YAML 中未定义的接口 | ☐ |
| 4.6 | 枚举值一致 | `ArticleStatus`/`UserRole` 前后端枚举字符串完全一致(大小写敏感) | ☐ |
---
## 5. 性能与安全基线
| # | 检查项 | 通过标准 | 结果 |
|---|--------|---------|------|
| 5.1 | 首页加载时间 | 文章列表页首屏渲染 < 2s本地环境10 篇文章 | |
| 5.2 | API 响应时间 | 单次 API 调用 < 200ms本地环境排除大数据量查询 | |
| 5.3 | 搜索响应时间 | `GET /api/articles/search` 千级数据 < 100ms | |
| 5.4 | XSS 防护 | 文章 content 中包含 `<script>alert(1)</script>`,前台渲染时不执行脚本 | ☐ |
| 5.5 | SQL 注入防护 | 搜索关键词输入 `' OR 1=1 --`,后端正常处理不报错 | ☐ |
| 5.6 | 目录遍历防护 | 上传时 `directory=../../etc` 被安全处理 | ☐ |
| 5.7 | JWT 安全 | Token 中不包含敏感信息(密码等);Secret 不是硬编码的弱密钥 | ☐ |
---
## 6. 问题记录模板
联调中发现的问题按以下格式记录:
### Issue #1 — 搜索接口复现为后端旧进程所致(已解决)
- **发现时间**: 2026-08-10 00:00
- **发现人**: Agent2(前端)
- **问题描述**: 初测时 `GET /api/articles/search?q=Spring` 返回 `{code:3001,"文章不存在: search"}`。后端重启(`./gradlew bootRun` 全新进程)后同一请求返回 code=0 正确搜索结果,确认该现象由旧后端进程(未包含 SearchController 的旧构建)造成,非契约/前端问题。
- **复现步骤**: 旧进程下 `curl "http://localhost:8080/api/articles/search?q=spring"`
- **预期结果**: 返回搜索结果
- **实际结果**: 重启后返回 `{code:0, data:{list:[...]}}`,2.3.5/1.5.1 通过
- **根因分析**: 后端旧进程加载了过期构建产物,未包含搜索路由
- **责任方**: Agent1(后端,运维)
- **修复方案**: 联调前确保后端为最新 `bootRun` 进程
- **验证结果**: ☐ 已解决(2026-08-10 复验通过)
### Issue #2 — 后端 `POST /auth/logout` 未吊销 Refresh Token
- **发现时间**: 2026-08-10 00:05
- **发现人**: Agent2(前端)
- **影响**: 前端 2.4.6 通过(本地清理 localStorage 正常);仅后端吊销语义缺失(1.3.4)
- **问题描述**: 前端 `auth.logout()``POST /api/auth/logout` 后,旧 refreshToken 仍可通过 `POST /api/auth/refresh` 换取新 TokenPair,令牌吊销语义失效(违反 checklist 1.3.4)。
- **复现步骤**: 1) 登录获取 refreshToken;2) 调 `/auth/logout`(code=0);3) 用旧 refreshToken 调 `/auth/refresh` → 仍返回 code=0 新 token(在最新 bootRun 进程上复验一致)
- **预期结果**: logout 后旧 refreshToken 刷新应失败(401/业务错误)
- **实际结果**: 刷新成功并返回新的 TokenPair
- **根因分析**: 后端登出逻辑未将 refreshToken 加入黑名单/吊销列表(JWT 无状态未记录失效)
- **责任方**: Agent1(后端)
- **修复方案**: 服务端维护 refreshToken 吊销集合(DB/redis),refresh 时校验
- **验证结果**: ☐ 待修复
### Issue #3 — 契约路径加 `/api` 前缀后前端 baseURL 适配(已处理)
- **发现时间**: 2026-08-10 00:20
- **发现人**: Agent2(前端,承接 Agent1 提示)
- **问题描述**: Agent1 将契约路径改为带 `/api` 前缀(`servers.url=http://localhost:8080`)。重新 `npm run api:generate` 后,生成的 localVarPath 变为 `/api/auth/...`、方法名变为 `apiXxx` 前缀、新增 `PublicApi`/`UserApi`。若前端 axios `baseURL` 仍为 `/api`,会拼成 `/api/api/...` → 404。
- **Agent1 提示**: 需把 client.ts 中 `new XxxApi(config, '/api', ...)``/api` 改为 `''`。经核实**不正确/不完整**:生成的 `common.ts` 拼接逻辑为 `axios.defaults.baseURL ? '' : (configuration?.basePath ?? basePath)`,axios 的 `baseURL` 优先于 XxxApi 的 basePath 参数;且 axios 会自行 combineURLs。若仅改 XxxApi 参数而保留 baseURL=`/api`,仍会双前缀。
- **实际修复**: 将 axios 实例 `baseURL``/api` 改为 `/`(各 XxxApi 的 `/api` basePath 参数无害保留),并批量重命名方法调用(`articlesGet`→`apiArticlesGet` 等)。实测 `/api/articles` 单前缀、经 vite 代理到 8080 正常,2.3~2.6 全部用例复验 PASS。
- **责任方**: 前端(Agent2)
- **验证结果**: ☑ 已处理(2026-08-10 复验 26/26 通过)
```markdown
### Issue #{编号}
- **发现时间**: YYYY-MM-DD HH:MM
- **发现人**: Agent1/Agent2/架构师
- **问题描述**:
- **复现步骤**:
- **预期结果**:
- **实际结果**:
- **根因分析**:
- **责任方**: Agent1(后端)/ Agent2(前端)/ openapi.yaml(契约)
- **修复方案**:
- **验证结果**: ☐ 待修复 / ☐ 已修复 / ☐ 已验证
```
---
## 验收签字
| 角色 | 姓名 | 日期 | 签字 |
|------|------|------|------|
| 架构 Owner | | | |
| Agent1(后端) | | | |
| Agent2(前端) | | | |
---
## 附录:快速测试命令
```bash
# 后端健康检查
curl http://localhost:8080/actuator/health
# 注册
curl -X POST http://localhost:8080/api/auth/register \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 登录
curl -X POST http://localhost:8080/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"123456"}'
# 创建文章(替换 $TOKEN)
curl -X POST http://localhost:8080/api/admin/articles \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"title":"Hello World","content":"This is a test article."}'
# 搜索
curl "http://localhost:8080/api/articles/search?q=hello&page=1&size=10"
# 前台列表(无认证)
curl "http://localhost:8080/api/articles?page=1&size=10"
```

507
prompt-agent2-frontend-v0.3.md

@ -1,507 +0,0 @@
# Agent2 Prompt: Mach-CMS Frontend Implementation
## 你的角色
你是前端开发 Agent,负责实现 Mach-CMS 的管理后台和前台页面。你**不是设计师**,不讨论 UI 美观性,只按契约和规范实现功能。
## 契约文件(唯一真理来源)
后端仓库根目录下的 `openapi.yaml` 是你唯一的 API 规范。你必须:
1. 用 OpenAPI Generator 从该 YAML 生成 TypeScript 类型和 API 客户端
2. **禁止手写任何 API 请求类型或接口定义**
3. 如果实现中发现 YAML 与实际返回不符,**暂停并上报**,禁止自行修改后不同步
## 技术栈(禁止变更)
- Vue 3.4+
- TypeScript 5.4+(严格模式)
- Vite 5.2+
- Vue Router 4.3+
- Pinia 2.1+
- Axios 1.7+
- OpenAPI Generator 7.x
## 项目初始化
```bash
npm create vue@latest mach-cms-frontend
# 选择:TypeScript + Vue Router + Pinia
cd mach-cms-frontend
npm install
npm install axios
npm install -D @openapitools/openapi-generator-cli
```
## 你的任务(按顺序执行,逐项验收)
### Task 1: OpenAPI 代码生成配置
创建 `openapitools.json`
```json
{
"$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.6.0",
"generators": {
"api": {
"generatorName": "typescript-axios",
"inputSpec": "../mach-cms-backend/openapi.yaml",
"output": "src/generated/api",
"additionalProperties": {
"supportsES6": "true",
"npmName": "mach-cms-api",
"snapshot": "false",
"withInterfaces": "false"
}
}
}
}
}
```
`package.json` 添加脚本:
```json
{
"scripts": {
"api:generate": "openapi-generator-cli generate"
}
}
```
执行:
```bash
npm run api:generate
```
确认生成目录结构:
```
src/generated/api/
├── api.ts # 所有 API 类(AuthApi, ArticlesApi, ArticleAdminApi, TagsApi, MediaApi...)
├── base.ts # Axios 实例和配置
├── configuration.ts
├── common.ts
├── index.ts
└── models/
├── index.ts
├── response.ts
├── page-result.ts
├── article-detail.ts
├── article-list-item.ts
├── article-create-request.ts
├── article-update-request.ts
├── token-pair.ts
├── login-request.ts
├── register-request.ts
├── tag.ts
├── stored-file.ts
└── ...
```
### Task 2: Axios 客户端封装
文件 `src/api/client.ts`
```typescript
import {
Configuration,
AuthApi,
ArticlesApi,
ArticleAdminApi,
TagsApi,
MediaApi,
SearchApi
} from '@/generated/api'
import axios from 'axios'
const axiosInstance = axios.create({
baseURL: '/api',
timeout: 10000
})
// 请求拦截:自动带 Access Token
axiosInstance.interceptors.request.use((config) => {
const token = localStorage.getItem('token')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
// 响应拦截:401 静默刷新 Token
axiosInstance.interceptors.response.use(
(res) => res,
async (err) => {
const original = err.config
if (err.response?.status === 401 && !original._retry) {
original._retry = true
const refresh = localStorage.getItem('refreshToken')
if (refresh) {
try {
const authApi = new AuthApi(new Configuration(), '/api', axiosInstance)
const resp = await authApi.refreshToken({ refreshToken: refresh })
const data = resp.data.data!
localStorage.setItem('token', data.accessToken)
localStorage.setItem('refreshToken', data.refreshToken)
original.headers.Authorization = `Bearer ${data.accessToken}`
return axiosInstance(original)
} catch {
localStorage.removeItem('token')
localStorage.removeItem('refreshToken')
window.location.href = '/login'
}
} else {
window.location.href = '/login'
}
}
return Promise.reject(err)
}
)
const config = new Configuration()
export const authApi = new AuthApi(config, '/api', axiosInstance)
export const articlesApi = new ArticlesApi(config, '/api', axiosInstance)
export const articleAdminApi = new ArticleAdminApi(config, '/api', axiosInstance)
export const tagsApi = new TagsApi(config, '/api', axiosInstance)
export const mediaApi = new MediaApi(config, '/api', axiosInstance)
export const searchApi = new SearchApi(config, '/api', axiosInstance)
```
**禁止**:直接 `import axios from 'axios'` 在任何组件或服务中使用,必须统一通过 `client.ts`
### Task 3: Pinia 认证 Store
文件 `src/stores/auth.ts`
```typescript
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { authApi } from '@/api/client'
import type { LoginRequest, RegisterRequest } from '@/generated/api'
export const useAuthStore = defineStore('auth', () => {
const token = ref<string | null>(localStorage.getItem('token'))
const refreshToken = ref<string | null>(localStorage.getItem('refreshToken'))
const username = ref<string | null>(null)
const isLoggedIn = computed(() => !!token.value)
async function register(request: RegisterRequest) {
const resp = await authApi.register(request)
const data = resp.data.data!
setTokens(data.accessToken, data.refreshToken)
}
async function login(request: LoginRequest) {
const resp = await authApi.login(request)
const data = resp.data.data!
setTokens(data.accessToken, data.refreshToken)
}
async function logout() {
try {
await authApi.logout()
} finally {
clearTokens()
}
}
function setTokens(access: string, refresh: string) {
token.value = access
refreshToken.value = refresh
localStorage.setItem('token', access)
localStorage.setItem('refreshToken', refresh)
}
function clearTokens() {
token.value = null
refreshToken.value = null
username.value = null
localStorage.removeItem('token')
localStorage.removeItem('refreshToken')
}
return {
token,
refreshToken,
username,
isLoggedIn,
register,
login,
logout,
setTokens,
clearTokens
}
})
```
### Task 4: Vue Router + 路由守卫
文件 `src/router/index.ts`
```typescript
import { createRouter, createWebHistory } from 'vue-router'
import { useAuthStore } from '@/stores/auth'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'Home',
component: () => import('@/views/HomeView.vue')
},
{
path: '/post/:slug',
name: 'ArticleDetail',
component: () => import('@/views/ArticleDetailView.vue')
},
{
path: '/search',
name: 'Search',
component: () => import('@/views/SearchView.vue')
},
{
path: '/login',
name: 'Login',
component: () => import('@/views/LoginView.vue'),
meta: { guestOnly: true }
},
{
path: '/admin',
component: () => import('@/views/admin/AdminLayout.vue'),
meta: { requiresAuth: true },
children: [
{
path: '',
redirect: '/admin/articles'
},
{
path: 'articles',
name: 'AdminArticleList',
component: () => import('@/views/admin/ArticleListView.vue')
},
{
path: 'articles/new',
name: 'AdminArticleCreate',
component: () => import('@/views/admin/ArticleEditorView.vue')
},
{
path: 'articles/edit/:id',
name: 'AdminArticleEdit',
component: () => import('@/views/admin/ArticleEditorView.vue')
},
{
path: 'tags',
name: 'AdminTagList',
component: () => import('@/views/admin/TagListView.vue')
}
]
}
]
})
router.beforeEach((to, from, next) => {
const auth = useAuthStore()
if (to.meta.requiresAuth && !auth.isLoggedIn) {
next('/login')
} else if (to.meta.guestOnly && auth.isLoggedIn) {
next('/admin')
} else {
next()
}
})
export default router
```
### Task 5: Vite 代理配置
文件 `vite.config.ts`
```typescript
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': resolve(__dirname, 'src')
}
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true
},
'/uploads': {
target: 'http://localhost:8080',
changeOrigin: true
}
}
}
})
```
### Task 6: 页面实现
#### 6.1 首页 `src/views/HomeView.vue`
- 调用 `articlesApi.listArticles()` 获取文章列表
- 展示文章卡片:标题、摘要、发布时间、作者、标签
- 点击卡片跳转 `/post/:slug`
- 支持分页
#### 6.2 文章详情 `src/views/ArticleDetailView.vue`
- 路由参数 `slug`
- 调用 `articlesApi.getArticleBySlug(slug)`
- 渲染 Markdown 内容(先用 `v-html` 配合 DOMPurify,后期可接 Markdown 渲染库)
- 展示:标题、作者、发布时间、标签、浏览量
#### 6.3 搜索页 `src/views/SearchView.vue`
- Query 参数 `?q=keyword`
- 调用 `searchApi.searchArticles(q, page, size)`
- 展示搜索结果列表
#### 6.4 登录页 `src/views/LoginView.vue`
- 表单:用户名、密码
- 调用 `authStore.login({ username, password })`
- 登录成功跳转 `/admin`
- 提供"去注册"链接(Phase 2 完善)
#### 6.5 管理后台布局 `src/views/admin/AdminLayout.vue`
- 侧边栏导航:文章管理、标签管理
- 顶部栏:显示当前用户、退出按钮
- 中间 `<router-view />`
#### 6.6 文章列表 `src/views/admin/ArticleListView.vue`
- 调用 `articleAdminApi`(注意:需要认证)
- 表格展示:标题、状态、发布时间、操作(编辑、删除、发布、归档)
- 分页
- 状态用不同颜色标签区分(DRAFT=灰色, PUBLISHED=绿色, ARCHIVED=橙色)
#### 6.7 文章编辑器 `src/views/admin/ArticleEditorView.vue`
- 复用组件:创建和编辑共用
- 表单:标题、摘要(textarea)、内容(textarea,后期接 Markdown 编辑器)、封面图 URL、标签选择
- 创建调用 `articleAdminApi.createArticle()`
- 编辑调用 `articleAdminApi.updateArticle(id, ...)`
- 保存成功后跳转列表页
#### 6.8 标签管理 `src/views/admin/TagListView.vue`
- 调用 `tagsApi.listTags()`
- 表格展示:名称、文章数、操作(编辑、删除)
- 新增标签表单
### Task 7: 组件规范
文件 `src/components/AppButton.vue`、`src/components/AppInput.vue` 等基础组件(可选,可用原生 HTML 先跑通)。
**必须有的组件**:
- `src/components/ArticleCard.vue`:文章卡片,接收 `ArticleListItem` 类型 props
- `src/components/Pagination.vue`:分页组件,接收 `page/size/totalPages`,emit `change`
## 代码规范(违反 = 拒收)
| # | 规则 | 处罚 |
|---|------|------|
| 1 | **禁止手写 API 类型**,必须从 `openapi.yaml` 生成 | 重写 |
| 2 | **禁止直接 `import axios`**,统一用 `src/api/client.ts` | 重写 |
| 3 | 组件必须用 `<script setup lang="ts">` | 重写 |
| 4 | API 响应必须用生成代码中的类型,禁止 `any` | 重写 |
| 5 | 路由跳转用 `useRouter()`,禁止 `window.location`(除 401 跳转) | 重写 |
| 6 | 异步操作必须 `try/catch`,错误用 `alert` 或控制台输出(后期接 Toast) | 重写 |
| 7 | 图片上传用 `FormData`,Content-Type 让浏览器自动设置 | 重写 |
| 8 | 集合字段默认 `[]`,禁止 `undefined` 作为列表值 | 重写 |
| 9 | 路由参数用 `const route = useRoute()` 获取,类型安全 | 重写 |
## 验收标准(全部通过才算完成)
- [ ] `npm run api:generate` 成功,生成 `src/generated/api/`
- [ ] `npm run dev` 正常启动,无 TypeScript 类型错误
- [ ] 首页能正确加载文章列表(后端需先启动)
- [ ] 点击文章卡片能进入详情页,内容正确渲染
- [ ] 搜索页输入关键词能返回结果
- [ ] 登录页能正常登录,Token 存入 localStorage,跳转管理后台
- [ ] 管理后台受路由守卫保护,未登录跳转登录页
- [ ] 文章列表页能展示文章,支持分页
- [ ] 文章编辑器能创建和编辑文章
- [ ] 标签管理页能展示和操作标签
- [ ] 关闭浏览器再打开,如果 Token 未过期,保持登录状态
## 输出格式
1. 按文件路径组织代码,每个文件用 markdown code block
2. 如果修改 `package.json``vite.config.ts`,明确标注变更
3. 最后附 `tree src/` 风格的文件清单
4. 如有类型错误,贴出完整错误日志 + 修复方案
## CDD 纪律
- 你**只修改前端代码**,禁止修改后端仓库任何文件
- 如果你发现 `openapi.yaml` 与实际后端返回不符,**停止开发,上报问题**,等 YAML 更新后再重新生成 API 代码
- 每次后端接口变更后,必须执行 `npm run api:generate` 重新生成类型
- 禁止为了"让代码跑通"而修改生成的 `src/generated/api/` 目录下的任何文件
---
## 附录:Git 开发规范(必须遵守)
> 远程仓库已配置完成,每次 commit 后立即 push。
### 分支策略
- 直接在 `main` 分支上开发,不创建 feature 分支
- 每次 commit 后立即 `git push origin main`
### Commit 格式(Conventional Commits)
```
type(scope): subject
```
**Type**: `feat` `fix` `refactor` `test` `docs` `chore` `style`
**Scope(前端)**: `page` `component` `store` `api` `router` `type` `asset` `config` `contract`
**示例**:
```bash
feat(page): implement article list with pagination
feat(component): add ArticleCard and Pagination
fix(api): handle 401 auto-refresh token correctly
chore(api-gen): regenerate types from openapi.yaml v0.2
style(component): fix indentation in AdminLayout
```
### 少量多次标准(核心)
- **单 commit 单意图**:一个 commit 只做一件事
- **文件数 ≤ 10**,**行数 ≤ 200**
- **每次 commit 前必须 `npm run build` 通过(无 TS 错误)**
- **commit 后 30 秒内必须 push**
- **提交信息用英文,祈使句,首字母小写,≤ 50 字符**
### 与 CDD 结合的提交顺序
1. 契约变更:`feat(contract): xxx`(由架构 Owner 发起)
2. 后端实现:`feat(api): implement xxx endpoint`(Agent1 执行)
3. 前端生成:`chore(api-gen): regenerate from openapi.yaml`
4. 前端对接:`feat(page): xxx`
### 禁止行为
- `git commit -m "update"` / `git commit -m "fix bug"`
- 一次性提交 20+ 个无关文件
- 提交有 TypeScript 类型错误的代码
- 本地 commit 后数小时不 push
- 绕过 openapi.yaml 直接手写 API 类型
### 标准流程
```bash
git pull --rebase origin main # 提交前同步
git add -p # 交互式添加(推荐)
git diff --cached # 自我审查
git commit -m "type(scope): subject"
git push origin main # 立即推送
```
### 提交信息自查清单(每次 commit 前)
- [ ] type 正确?
- [ ] scope 准确?
- [ ] subject 英文、祈使句、≤ 50 字符?
- [ ] 变更文件都与意图相关?
- [ ] `npm run build` 通过(无 TS 错误)?
- [ ] 已 push 到远程?

228
prompt-agent2-frontend-v0.4.md

@ -1,228 +0,0 @@
# Agent2 Prompt: Mach-CMS Frontend Phase 2 Implementation
## 你的角色
你是前端开发 Agent,负责实现 Mach-CMS Phase 2 功能。你不是设计师,只按契约和规范实现功能。
## 契约文件(唯一真理来源)
后端仓库根目录下的 `openapi.yaml` 是你唯一的 API 规范。
## 技术栈
- Vue 3.4+
- TypeScript 5.4+(严格模式)
- Vite 5.2+
- Vue Router 4.3+
- Pinia 2.1+
- Axios 1.7+
- OpenAPI Generator 7.6.0
- marked / markdown-it(Markdown 渲染)
- DOMPurify(XSS 防护)
- highlight.js 或 prismjs(代码高亮)
## 项目状态
Phase 1 已完成:
- OpenAPI 代码生成配置
- Axios 客户端封装(含 401 自动刷新)
- Pinia auth store
- Vue Router + 路由守卫
- 首页文章列表、文章详情、搜索页
- 登录页、管理后台布局、文章列表/编辑器、标签管理
## 重要修正:basePath
**`client.ts` 中所有 API 实例化时,basePath 必须传空字符串 `''`**:
```typescript
export const authApi = new AuthApi(config, '', axiosInstance)
export const articlesApi = new ArticlesApi(config, '', axiosInstance)
export const articleAdminApi = new ArticleAdminApi(config, '', axiosInstance)
export const tagsApi = new TagsApi(config, '', axiosInstance)
export const mediaApi = new MediaApi(config, '', axiosInstance)
export const searchApi = new SearchApi(config, '', axiosInstance)
// Phase 2 新增
export const commentsApi = new CommentsApi(config, '', axiosInstance)
export const commentAdminApi = new CommentAdminApi(config, '', axiosInstance)
export const userAdminApi = new UserAdminApi(config, '', axiosInstance)
```
**原因**:Vite 代理已配置 `/api``localhost:8080`,生成代码的 `BASE_PATH` 也是 `/api`,若不覆盖会导致 `/api/api/xxx`
**纪律**:每次 `npm run api:generate` 后,检查 `client.ts` 中 basePath 是否为 `''`
## 你的任务(按顺序执行,逐项验收)
### Task 1: 重新生成 API 代码
后端更新 `openapi.yaml` 至 0.2.0 后,执行:
```bash
npm run api:generate
```
确认生成:
- `CommentResponse`, `CommentSubmitRequest`
- `UserInfo`, `UpdateRoleRequest`
- `CommentAdminApi`, `UserAdminApi`
- `StoredFile` 增加 `thumbnailUrl`
### Task 2: 注册页面
文件:`src/views/RegisterView.vue`
- 路由 `/register`
- 表单:用户名(3-50 字符)、密码(6-100 字符)、确认密码、邮箱(可选)
- 前端校验:密码与确认密码一致
- 调用 `authApi.register({ username, password, email })`
- 注册成功后自动调用 `authStore.login()` 并跳转 `/admin`
- 已登录用户访问 `/register` 自动跳转 `/admin`
路由配置更新:`router/index.ts` 添加 `/register` 路由
### Task 3: 评论组件
文件:`src/components/CommentSection.vue`
- Props:`slug: string`
- 功能:
- 加载评论:`GET /api/articles/{slug}/comments`
- 展示评论列表(作者名、内容、时间)
- 评论提交表单:昵称(必填)、邮箱(可选)、内容(必填)
- 提交后提示"评论已提交,等待审核"
- 样式:简单即可,每条评论有分隔线
文件:`src/views/ArticleDetailView.vue`
- 底部引入 `<CommentSection :slug="slug" />`
### Task 4: Markdown 渲染 + XSS 防护
```bash
npm install marked dompurify highlight.js
npm install -D @types/dompurify @types/marked
```
文件:`src/utils/markdown.ts`
```typescript
import { marked } from 'marked'
import DOMPurify from 'dompurify'
import hljs from 'highlight.js'
import 'highlight.js/styles/github-dark.css'
marked.setOptions({
highlight: (code, lang) => {
if (lang && hljs.getLanguage(lang)) {
return hljs.highlight(code, { language: lang }).value
}
return hljs.highlightAuto(code).value
}
})
export function renderMarkdown(content: string): string {
const rawHtml = marked.parse(content) as string
return DOMPurify.sanitize(rawHtml)
}
```
文件:`src/views/ArticleDetailView.vue`
- 文章内容用 `v-html="renderMarkdown(article.content)"`
- 引入 `highlight.js` 样式
### Task 5: 前台标签云
文件:`src/components/TagCloud.vue`
- 调用 `tagsApi.listTags()`
- 展示所有标签,标签大小按 `articleCount` 比例(简单实现:用不同 font-size class)
- 点击标签跳转首页并过滤该标签文章(可选,先实现展示即可)
文件:`src/views/HomeView.vue`
- 侧边栏或底部引入 `<TagCloud />`
### Task 6: 管理后台增强
#### 6.1 评论审核页
文件:`src/views/admin/CommentReviewView.vue`
- 路由 `/admin/comments`
- 调用 `commentAdminApi.listPendingComments()`
- 表格:作者、邮箱、内容、文章标题、提交时间
- 操作:通过、拒绝、删除
- 通过后评论在前台显示
#### 6.2 用户管理页
文件:`src/views/admin/UserListView.vue`
- 路由 `/admin/users`
- 调用 `userAdminApi.listUsers()`
- 表格:用户名、邮箱、角色、状态、注册时间
- 操作:修改角色(下拉选择 ADMIN/EDITOR/VISITOR)、启用/禁用
- **只有 ADMIN 可见此菜单项**(路由守卫或菜单条件渲染)
#### 6.3 菜单更新
文件:`src/views/admin/AdminLayout.vue`
- 侧边栏新增:评论审核、用户管理
- 根据当前用户角色条件显示(先实现显示,权限由后端控制)
### Task 7: 图片上传集成到编辑器
文件:`src/views/admin/ArticleEditorView.vue`
- 封面图:点击上传按钮 → `mediaApi.upload()` → 返回 url 填入表单
- 内容区粘贴图片:监听 `paste` 事件,提取图片文件 → 上传 → 插入 Markdown 图片语法 `![alt](url)`
## 代码规范(违反 = 拒收)
| # | 规则 |
|---|------|
| 1 | **禁止手写 API 类型**,必须从 openapi.yaml 生成 |
| 2 | **禁止直接 import axios**,统一用 `src/api/client.ts` |
| 3 | 组件必须用 `<script setup lang="ts">` |
| 4 | API 响应必须用生成代码中的类型,禁止 `any` |
| 5 | 路由跳转用 `useRouter()`,禁止 `window.location`(除 401 跳转) |
| 6 | 异步操作必须 `try/catch`,错误用 alert 或控制台输出 |
| 7 | 图片上传用 `FormData`,Content-Type 让浏览器自动设置 |
| 8 | 集合默认 `[]`,禁止 `undefined` 作为列表值 |
| 9 | 路由参数用 `useRoute()` 获取 |
## Git 规范(必须遵守)
- 直接在 `main` 分支开发,commit 后立即 `git push origin main`
- Conventional Commits:`type(scope): subject`
- **单 commit 单意图**,文件 ≤ 10,行数 ≤ 200
- **每次 commit 前 `npm run build` 通过(无 TS 错误)**
- 提交信息用英文、祈使句、首字母小写、≤ 50 字符
**Scope(前端)**:`page` `component` `store` `api` `router` `type` `asset` `config` `contract`
**示例**:
```bash
feat(page): add register view with form validation
feat(component): add CommentSection for article detail
feat(component): add TagCloud with article count sizing
feat(page): add comment review page in admin
feat(page): add user management page with role editing
chore(api-gen): regenerate types from openapi.yaml v0.2.0
fix(api): set basePath to empty string to avoid /api/api
```
## 与 CDD 结合的提交顺序
1. `chore(api-gen): regenerate types from openapi.yaml v0.2.0`(契约更新后)
2. `feat(page): add register view`
3. `feat(component): add CommentSection with submit form`
4. `feat(component): add markdown renderer with syntax highlight`
5. `feat(component): add TagCloud component`
6. `feat(page): add admin comment review page`
7. `feat(page): add admin user management page`
8. `feat(component): integrate image upload in article editor`
## 验收标准
- [ ] `npm run api:generate` 成功,生成 Comment/User 相关类型
- [ ] `npm run build` 无 TS 错误
- [ ] `/register` 页面可用,注册后自动登录跳转
- [ ] 文章详情页显示评论区,可提交评论,提交后提示审核中
- [ ] Markdown 渲染正确,代码块有高亮,XSS 被过滤
- [ ] 标签云显示正常,大小按文章数变化
- [ ] 后台评论审核页可列出待审核评论,通过/拒绝/删除生效
- [ ] 后台用户管理页可查看用户列表,修改角色
- [ ] 文章编辑器可上传封面图,粘贴图片到内容区
- [ ] 所有 commit 符合 Conventional Commits 规范
## CDD 纪律
- 只修改前端代码,禁止修改后端仓库
- 发现 openapi.yaml 与实际返回不符,停止开发,上报问题
- 每次后端接口变更后,必须重新生成 API 代码
- 禁止修改 `src/generated/api/` 目录下的任何文件
Loading…
Cancel
Save