From dcc7e62240619462540a6c6941df669b07a88818 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Sat, 18 Apr 2026 21:28:43 +0800 Subject: [PATCH] docs: add public profile preview and list pages design spec Made-with: Cursor --- ...04-18-public-profile-preview-and-list-design.md | 131 +++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 docs/superpowers/specs/2026-04-18-public-profile-preview-and-list-design.md diff --git a/docs/superpowers/specs/2026-04-18-public-profile-preview-and-list-design.md b/docs/superpowers/specs/2026-04-18-public-profile-preview-and-list-design.md new file mode 100644 index 0000000..79660db --- /dev/null +++ b/docs/superpowers/specs/2026-04-18-public-profile-preview-and-list-design.md @@ -0,0 +1,131 @@ +# 设计:公开主页预览 + 完整列表子页(文章 / 时光机 / 阅读) + +**日期**:2026-04-18 +**状态**:已定稿(与产品对话一致) + +## 1. 背景与目标 + +公开主页 `/@:publicSlug` 在 **「展示」** 与 **「阅读」** 两种布局下,**文章、时光机、阅读(RSS 公开项)** 目前或拉全量、或在「阅读」模式主栏内分页,首页信息密度与首包体积都不理想。 + +**目标**: + +- 首页每类仅展示 **最新 5 条** 预览,并显示 **总数**。 +- 当 **`total > 5`** 时提供 **「查看全部」** 入口,进入 **独立子页面**;列表 **每页 10 条**,分页体现在 **URL**(`?page=`),便于分享与刷新。 +- **瘦身** 聚合接口:首页只拉预览 + 总数;完整列表仅由 **分页 API** 提供。 +- 在现有 Nuxt UI 与绿色主色体系内 **统一预览与列表的视觉层级**(卡片、时间轴、阅读行),不做换肤级重构。 + +## 2. 产品规则(已确认) + +| 维度 | 规则 | +|------|------| +| 布局范围 | **「展示」与「阅读」两种模式都做**,三块行为一致:预览 5 条 + 条件「查看全部」。 | +| 预览条数 | 三类 **统一** 各 **5** 条(`PUBLIC_PREVIEW_LIMIT = 5`)。 | +| 列表每页条数 | **10** 条(`PUBLIC_LIST_PAGE_SIZE = 10`),**仅服务端生效**;请求中的 `pageSize` **忽略或拒绝**,防止任意大页。 | +| 「查看全部」 | 仅当 **`total > 5`** 时显示;**`total === 0`** 不展示该区块;**`1 ≤ total ≤ 5`** 只展示预览,不显示「查看全部」。 | +| 子页路由 | `/@:publicSlug/posts`、`/@:publicSlug/timeline`、`/@:publicSlug/reading`(与界面「阅读」一致,路径不用 `rss`)。 | +| 聚合接口 | **`GET /api/public/profile/:publicSlug`** 改为每类 `{ items, total }`,**破坏性变更**;当前仅 `app/pages/@[publicSlug]/index.vue` 消费该聚合响应,改版面时一并更新类型。 | +| 分页接口 | 三类独立 **`GET`**:`.../posts`、`.../timeline`、`.../reading`,Query **`page`**(默认 1,非法或 `<1` **规范为 1**)。 | +| 空页 | 页码超出末页时 **`items: []`**,**不**对空页返回 **404**(`total` 与规范化后的 `page` 仍返回)。 | +| 可见性 | 仅 **`visibility === public`** 的内容;unlisted / share 链路不在本次范围。 | + +## 3. 非目标 + +- 管理后台(`/me/*`)列表与分页改造。 +- RSS 同步策略、抓取频率、私有订阅源管理。 +- 全文搜索、标签筛选、按日期归档视图。 +- 为列表页单独做 SSR HTML 的复杂 SEO(仅轻量 `title`/meta 即可)。 + +## 4. API 契约 + +### 4.1 `GET /api/public/profile/:publicSlug`(变更) + +响应中 **`posts` / `timeline` / `rssItems`** 从数组改为对象(字段名保持与现有前端语义一致,减少无谓重命名): + +```ts +posts: { + items: Array<{ title, excerpt, slug, publishedAt, coverUrl }> // 长度 ≤ 5 + total: number +} +timeline: { + items: Array<{ /* 与现 listPublicTimelineBySlug 公开字段一致 */ }> + total: number +} +rssItems: { + items: Array<{ /* 与现 listPublicRssItemsBySlug 公开字段一致 */ }> + total: number +} +``` + +`user`、`bio`、`links` 保持现状。`total` 必须通过 **数据库计数**(或与分页等价的稳定查询)得到,**禁止**拉全表后在内存计算。 + +### 4.2 新增分页接口 + +| 方法 | 路径 | Query | +|------|------|--------| +| GET | `/api/public/profile/:publicSlug/posts` | `page`(可选,默认 1) | +| GET | `/api/public/profile/:publicSlug/timeline` | 同上 | +| GET | `/api/public/profile/:publicSlug/reading` | 同上 | + +与已有 **`GET .../posts/:postSlug`** 不冲突:列表与单篇分文件注册即可。 + +**成功响应**(三端统一形状): + +```ts +{ + items: [...] // 当前页,最多 10 条 + total: number + page: number // 规范化后的页码 + pageSize: 10 // 固定常量,与 PUBLIC_LIST_PAGE_SIZE 一致 +} +``` + +**404**:`publicSlug` 对应用户不存在或非 `active`,与现 profile 行为一致。 + +### 4.3 服务层职责 + +在 `#server/service/posts`、`timeline`、`rss` 中新增或重构为: + +- 每类:**预览**(`items` 上限 5 + `total`)、**分页**(`page` → `items` + `total` + 规范化 `page` + `pageSize`)。 +- 现有无 limit 的 `listPublic*` 应改为复用上述逻辑或内部 helper,避免重复 SQL;RSS 现有 **`limit(200)`** 的公开列表实现应由 **正式分页 + count** 替代,不再依赖硬编码 200 作为「全量」。 + +## 5. 前端:路由与数据流 + +### 5.1 新增页面 + +| 文件 | 路由 | +|------|------| +| `app/pages/@[publicSlug]/posts/index.vue` | `/@:publicSlug/posts` | +| `app/pages/@[publicSlug]/timeline/index.vue` | `/@:publicSlug/timeline` | +| `app/pages/@[publicSlug]/reading/index.vue` | `/@:publicSlug/reading` | + +均使用 **`layout: 'public'`**。列表数据来自对应分页 **`GET`**,`page` 与 **`route.query.page`** 同步;翻页优先使用 **`router.replace`** 更新 query,减少历史栈噪音。 + +### 5.2 主页 `@[publicSlug]/index.vue` + +- 仅请求 **`GET .../profile/:slug`**,使用返回的 **`items` / `total`**。 +- **删除**「阅读」模式下主栏内对全量数组的 **`slice` + `UPagination`**;主栏三块与「展示」模式一致:**预览 + 条件「查看全部」**。 +- 侧栏数字改为 **`total`**。 + +### 5.3 分页 UI + +使用 **`UPagination`**:`total` 为接口 `total`,`items-per-page` 固定 **10**。首屏从 URL 读取 `page`;非法字符串是否在客户端先规范为 **1** 由实现计划写明,并与服务端规范化一致。 + +## 6. 体验与美化 + +- **文章预览**:展示 **日期**(沿用 `formatPublishedDateOnly`、`occurredOnToIsoAttr` 等工具,与旧 detailed 列表信息层级一致)。 +- **时光机预览**:保留 **圆点 + 竖线** 时间轴;与现 showcase 块级卡片协调。 +- **阅读预览**:从纯链接改为 **块级行**(标题 + hostname 次要文案),hover **边框/背景** 微变化,对齐旧 detailed 「阅读」列表气质。 +- **「查看全部」**:`UButton` **outline** 或 **ghost + 右箭头**;位置在区块标题行或区块底部统一,**两种布局模式同一套位置**。 +- **列表页**:分隔线/密度对齐现 detailed 列表;**`UEmpty`**、**`UAlert`**(404)、加载态与现站一致。 + +## 7. 测试与验收 + +- **手工**:主页预览条数、`total` 与侧栏、按钮显隐(`total > 5`);子页翻页、URL、刷新、超大 `page` 空列表;单篇文章 `/@/posts/:slug` 仍可访问。 +- **自动化(可选)**:若项目已有 service 层测试模式,可为 **计数 + limit/offset** 增加用例;API 路由可做轻量集成测试(视仓库惯例)。 + +## 8. 自检记录(定稿前) + +- **占位符**:无 TBD。 +- **一致性**:预览 5、列表 10、按钮仅 `total > 5`、空页不 404,与对话一致。 +- **范围**:仅公开 profile 与三列表页;不含后台。 +- **歧义**:`rssItems` 键名保留在 profile 响应中;公开分页路径使用 **`/reading`**,与页面路由一致。