You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
7.0 KiB
7.0 KiB
设计:公开主页预览 + 完整列表子页(文章 / 时光机 / 阅读)
日期: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 从数组改为对象(字段名保持与现有前端语义一致,减少无谓重命名):
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 不冲突:列表与单篇分文件注册即可。
成功响应(三端统一形状):
{
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 「阅读」列表气质。
- 「查看全部」:
UButtonoutline 或 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,与页面路由一致。