1 changed files with 131 additions and 0 deletions
@ -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`**,与页面路由一致。 |
||||
Loading…
Reference in new issue