diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 680f70f..2d12cb5 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -24,7 +24,7 @@ | # | 子项目 | 内容 | 涉及仓库 | 依赖 | 状态 | |---|--------|------|----------|------|------| -| P4 | 作者主页 | `/users/:user` 聚合页(该作者全部作品),API 列表按作者过滤;顺带 `/games/:user/:slug` 面包屑与作者名互链 | 三仓(API 过滤 + 前端页面) | 无强依赖,可提前并行 | 待启动 | +| P4 | 作者主页 | `/users/:user` 聚合页(该作者全部已上架作品,前端按命名空间过滤,**零后端改动**),按最新上架排序;顺带 `/games/:user/:slug` 面包屑与作者名互链。spec:`docs/specs/2026-09-29-author-page-design.md` | crearte(前端) | 无强依赖,可提前并行 | 设计已批,待实现计划 | | P5 | 收藏 / 评分 | 新数据表 + 用户态 API + 目录/详情页 UI;产品线里最大的一块 | 三仓 | 建议 P2 先落地 | 待启动 | | P6 | hosted 作品投稿 | 范围模糊,需先定义 hosted 托管语义(只存 URL 还是真托管文件),再定投稿/审核流 | 三仓 | 需先决策 | 待澄清 | @@ -50,4 +50,4 @@ | 子项目 | spec | 实现计划 | |--------|------|----------| -| (待补) | | | +| P4 作者主页 | `docs/specs/2026-09-29-author-page-design.md` | (待写) | diff --git a/docs/specs/2026-09-29-author-page-design.md b/docs/specs/2026-09-29-author-page-design.md new file mode 100644 index 0000000..3e84290 --- /dev/null +++ b/docs/specs/2026-09-29-author-page-design.md @@ -0,0 +1,81 @@ +# P4 作者主页(`/users/:user`)设计 / Author Page Design + +- **状态**:设计已批准(2026-09-29),待 spec 审阅后转实现计划。 +- **路线图**:`docs/ROADMAP.md` P4(第二波)。 +- **范围定性**:纯作品聚合页,极简列表;**零后端改动、零数据迁移**。 + +## 1. 背景与意图 + +crearte 的作品链接已按用户命名空间组织(`/games//`),但站内没有任何按作者聚合的入口:读者读完一个作品想看同作者的其他作品,只能回目录页手动搜索。P4 补上这块:**给每位创作者一个可分享的主页 `/users/:user`,列出其全部已上架作品**。 + +成功标准:从作品详情页点作者名能到作者页,页面只显示该作者的作品,按最新上架排序;未知用户名不报错。 + +约束(与维护者确认): + +- 范围只到"纯作品聚合页":不做作者简介/头像等档案字段(不碰 `users` 表、不碰上传链路)。 +- 列表极简:无筛选侧栏、无查询参数(YAGNI,日后需要再加)。 + +## 2. 方案选型 + +| 方案 | 内容 | 取舍 | +|---|---|---| +| **A. 纯前端过滤(已选)** | `AuthorView` 复用 `repo.listGames()`,按 `resolveUserSlug(game).user` 过滤 | 零后端改动;与目录页同样拉全量列表(现状即如此,ETag/304);分页化另立子项目时再迁服务端过滤 | +| B. 后端 `GET /api/games?author=` | 服务端过滤 + ETag 按参数派生 | 为分页铺路,但当下 YAGNI;动 handler/service/repository 三层 | +| C. 新端点 `GET /api/users/:user` | 聚合用户信息 + 作品 | 引入用户名存在性探测面,超出纯聚合范围 | + +**对路线图的偏离**:`docs/ROADMAP.md` P4 原文写"API 列表按作者过滤"。设计阶段发现列表 DTO(`GameSummary`)已含 `user?`/`slug?` 与 `resolveUserSlug()`,前端过滤即可满足需求,因此 P4 **不修改 crearte-server**(顺带不占用 Postgres 迁移号段)。路线图措辞随本 spec 落地同步修订。 + +## 3. 设计详述 + +### 3.1 路由与页面 + +- 新路由 `/users/:user`(与 `/games/:user/:slug` 风格一致),注册于 `src/app/router/index.ts`。 +- 新视图 `src/app/views/AuthorView.vue`,复用既有组件:`GameCard`、`StatePanel`、`BaseButton` 等。 +- 页面头部:`@` + 作品数。**语义注意**:展示的是账号 `username`(命名空间所有者),不是作品元数据 `author_name`(自由文本、可空);卡片上的 `author_name` 照旧展示,两者不混用、不互相推导。 +- 列表按"最新上架"倒序,复用 `src/app/lib/filter.ts` 的既有排序逻辑(优先调用 `filterGames` 或其导出的比较器,**不复制**排序代码)。 +- 面包屑「目录 / @user」。 +- 入口:`GameView` 详情页作者名、`GameCard` 作者字样 → `/users/:user`。 + +### 3.2 数据流 + +`AuthorView` → `useAsync(repo.listGames())`(`src/app/data/` 的 `mergeRepo`:API + static 双源合并,API 宕机降级 static,与目录页同一链路)→ 按 `resolveUserSlug(game).user` 精确过滤(兼容旧式单段 id 作品:取 `id` 中 `/` 前段)→ `sort: 'new'` 排序 → 渲染。 + +### 3.3 边界与错误处理 + +- 未知用户名 / 该作者无已上架作品:空态「该作者暂无已上架作品」+ 返回目录按钮。**不做**用户存在性探测(需要新端点,YAGNI)。 +- 可见性语义与目录页完全一致:列表接口只返回已上架未下架作品,作者页自动继承。 +- 加载 / 失败态复用 `useAsync` + `StatePanel` 既有模式,不做新错误面。 + +## 4. 测试 + +- **Vitest**:AuthorView 过滤正确性(含 legacy 单段 id 形态与 `user` 字段缺失的回退)、空态渲染、`sort: 'new'` 排序、作者名链接跳转。 +- **Playwright `noauth`**:`/users/:user` 只显示该作者作品(基于 `src/fixtures/catalog` 现成作品数据)。 +- **后端**:无(零后端改动)。 + +## 5. 影响文件(预估) + +| 文件 | 改动 | +|---|---| +| `src/app/router/index.ts` | 注册 `/users/:user` | +| `src/app/views/AuthorView.vue` | 新建 | +| `src/app/views/GameView.vue` | 作者名加链接 | +| `src/app/components/GameCard.vue` | 作者字样加链接(如展示作者) | +| `src/app/lib/filter.ts` | 导出排序逻辑供 AuthorView 复用(如未导出) | +| 测试文件 | 与视图测试同目录、同命名约定(co-located)新建 | +| `docs/CHANGELOG.md`(crearte 仓) | 双语条目 | + +## 6. 验收标准 + +1. `/users/:user` 只显示该作者的已上架作品(API+static 合并源),按最新上架倒序。 +2. 旧式单段 id 作品不破坏过滤逻辑(`resolveUserSlug` 回退路径覆盖)。 +3. 未知用户名 / 无作品:空态渲染,无报错、无 404 路由错误。 +4. `GameView` 与 `GameCard` 的作者入口可跳转到作者页。 +5. 新增 Vitest 与 `noauth` Playwright 用例通过,且**存量测试全绿**(含 `vue-tsc` 类型检查)。 +6. `crearte-server` 零改动(本子项目不产生任何后端 diff)。 + +## 7. 明确不做(Out of scope) + +- 作者简介 / 头像 / 关注 / 统计等档案与社交面(对应路线图 P5 之后再议)。 +- 目录分页 / 服务端过滤(独立子项目)。 +- 用户存在性探测、`GET /api/users/:user` 类端点。 +- 静态兜底目录(P8)。