# 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)。