Files
crearte-monorepo/docs/specs/2026-09-29-author-page-design.md
T
XingfenD 2f3bade817 docs(spec): P4 author page design (pure frontend aggregation)
- docs/specs/2026-09-29-author-page-design.md: /users/:user pure
  aggregation page, minimal list, zero backend/migration changes
- docs/ROADMAP.md: P4 scope refined per design (frontend filtering
  instead of API author filter), register spec in doc index
2026-09-29 15:40:27 +08:00

82 lines
5.2 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.
# P4 作者主页(`/users/:user`)设计 / Author Page Design
- **状态**:设计已批准(2026-09-29),待 spec 审阅后转实现计划。
- **路线图**:`docs/ROADMAP.md` P4(第二波)。
- **范围定性**:纯作品聚合页,极简列表;**零后端改动、零数据迁移**。
## 1. 背景与意图
crearte 的作品链接已按用户命名空间组织(`/games/<user>/<slug>`),但站内没有任何按作者聚合的入口:读者读完一个作品想看同作者的其他作品,只能回目录页手动搜索。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>` + 作品数。**语义注意**:展示的是账号 `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)。