328 lines
17 KiB
Markdown
328 lines
17 KiB
Markdown
|
|
---
|
|||
|
|
title: "Telegram-style Resource Stream(资料分类查看全部 UI 重构)"
|
|||
|
|
type: brainstorm
|
|||
|
|
date: 2026-05-25
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# Telegram-style Resource Stream
|
|||
|
|
|
|||
|
|
## Problem Statement
|
|||
|
|
|
|||
|
|
当前"查看全部"打开的资料列表(`/browse` 和 `/category/:slug`)使用统一的卡片网格,无法表达"admin 上传的内容本质是不同类型的消息"(一张图、一段文字+链接、一个视频、一个 PDF、4+ 张图相册等)。手机端用户体验偏弱,缺少 Telegram 那种按类型差异化呈现的直观感受。
|
|||
|
|
|
|||
|
|
本次重构目标:把 `/browse` 与 `/category/:slug` 改成**单列、按时间倒序、按日期分组、按上传类型差异化渲染**的 Telegram-style 消息流,手机优先,保留 ARK 既有色系(深底 + 金色高亮)。
|
|||
|
|
|
|||
|
|
> 后端 endpoint 尚未实现。本次前端先用 mock data 完成视觉与交互,验收后再交接 API 契约给后端。
|
|||
|
|
|
|||
|
|
## Context
|
|||
|
|
|
|||
|
|
### 当前实现
|
|||
|
|
|
|||
|
|
- `src/pages/Home.tsx`:分类 section 头部"查看全部" → `/browse`;分类卡片 → `/category/<slug>`。
|
|||
|
|
- `src/pages/Browse.tsx`(221 行):含排序 tabs(最新/推荐/热门/发布)+ 类型 chips + 语言 chips + tag 过滤 + 分页(每页 24)。
|
|||
|
|
- `src/pages/CategoryPage.tsx`(156 行):含类型 chips + 语言 chips + 分页。
|
|||
|
|
- `src/pages/ResourceDetail.tsx`(229 行):`/r/:id` 资源详情独立路由。
|
|||
|
|
- `src/pages/FavoritesPage.tsx` + `postFavoriteDelta`:收藏功能。
|
|||
|
|
- `src/components/ResourceCard.tsx`:统一卡片,不区分资源类型。
|
|||
|
|
- `Resource` schema(`src/api.ts`)扁平:1 个 resource = 1 个文件(`coverImage` / `fileUrl`),无 attachments 数组。
|
|||
|
|
|
|||
|
|
### 设计参考(用户提供 7 张 Telegram 截图)
|
|||
|
|
|
|||
|
|
1. 图片当文档上传:缩略图 + ↓ + filename.ext + size + 右下时间,无头像、无 reaction。
|
|||
|
|
2. PDF / AI / PPT 等文档:蓝圆 ↓ + filename + size。
|
|||
|
|
3. 纯文本 + 链接:`https://...` 自动识别为可点链接。
|
|||
|
|
4. 视频:海报 + ▶️,第一次点 inline 播放预览,第二次点全屏;下方可有 admin 写的说明文字。
|
|||
|
|
5. 单张图片:直接显示,点全屏。
|
|||
|
|
6. 图片 + 文字:图片上方/下方文字,文字内链接 autolink。
|
|||
|
|
7. 4+ 张图相册:1-4 格 grid,第 4 张模糊 + `+N`;点开后全屏画廊。
|
|||
|
|
|
|||
|
|
## Chosen Approach
|
|||
|
|
|
|||
|
|
**方案 A:自建 `MessageStream` + 多态 `MessageBubble` 家族 + Mock-data layer**
|
|||
|
|
|
|||
|
|
- 新建一个 `MessageStream` 容器组件,被 `Browse.tsx` 和 `CategoryPage.tsx` 共用,差异通过 `scope` props 注入。
|
|||
|
|
- `MessageBubble` 内根据 `Post.attachments` 的形状分发到 6 个子组件(FileDoc / Text / Video / Image / ImageWithText / Album)。
|
|||
|
|
- 全屏交互(图片画廊 / 视频全屏)走 portal overlay。
|
|||
|
|
- 数据层用 `usePostStream` hook 抽象,默认走 `src/mocks/mockPosts.ts`(受 `VITE_USE_MOCK_POSTS` 控制),后端 ready 后切换为真 API。
|
|||
|
|
- 同时**收尾**收藏功能与详情页:删除 `/favorites`、`/r/:id`、heart 按钮、`postFavoriteDelta`。
|
|||
|
|
|
|||
|
|
## Why This Approach
|
|||
|
|
|
|||
|
|
### 拒绝方案 B(聊天 UI 库 `@chatscope/chat-ui-kit-react` 等)
|
|||
|
|
|
|||
|
|
- 默认主题与 ARK 深底+金色严重冲突,改主题成本 ≈ 自己写。
|
|||
|
|
- 不支持"4+ 图相册 +N 模糊"自定义。
|
|||
|
|
- 增加包体积与灰盒 bug 风险。
|
|||
|
|
|
|||
|
|
### 拒绝方案 C(重样 `ResourceCard`)
|
|||
|
|
|
|||
|
|
- 当前卡片统一渲染,无法满足"按类型差异化"(视频内嵌播放器 vs 多图相册 vs 文本+链接)。
|
|||
|
|
- 改动表面但不达 Telegram 风格。
|
|||
|
|
|
|||
|
|
### 拒绝其他子选项
|
|||
|
|
|
|||
|
|
- **保留排序 tabs**:Telegram 流天然按时间倒序,多余 tabs 破坏隐喻。Home 页仍保留"官方推荐 / 最新更新" section 作为入口。
|
|||
|
|
- **保留收藏功能在列表/详情页**:用户明确要求"不需要 reaction",且收藏与 Telegram 隐喻不符;整体下线最干净。
|
|||
|
|
- **保留 `ResourceDetail` 作 fallback**:所有交互(下载 / 全屏 / 链接)都能就地完成,独立详情页冗余;老 `/r/:id` 改 301 重定向到 `/category/<slug>#post-<id>` 锚点。
|
|||
|
|
- **`kind` 枚举铺开**:后端枚举膨胀难维护;前端按 `mime` / 文件后缀做细分图标更灵活。
|
|||
|
|
|
|||
|
|
## Design
|
|||
|
|
|
|||
|
|
### 1. 数据模型(前端使用 + 后端接口契约)
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
type Post = {
|
|||
|
|
id: string;
|
|||
|
|
categoryId: number;
|
|||
|
|
categorySlug: string;
|
|||
|
|
language: string; // zh-TW | zh-CN | en
|
|||
|
|
text?: string; // 可选;纯文本/图说,前端自动识别 https 链接
|
|||
|
|
attachments: Attachment[]; // 0~N;text-only post 时为 []
|
|||
|
|
isRecommended: boolean;
|
|||
|
|
publishedAt: string; // ISO,用于排序 + 日期分组
|
|||
|
|
updatedAt: string;
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
type Attachment = {
|
|||
|
|
id: string;
|
|||
|
|
kind: "image" | "video" | "document";
|
|||
|
|
url: string;
|
|||
|
|
mime: string; // image/jpeg | application/pdf | video/mp4 | ...
|
|||
|
|
filename: string; // "ARK项目一图读懂-01.jpg"
|
|||
|
|
sizeBytes: number;
|
|||
|
|
width?: number;
|
|||
|
|
height?: number;
|
|||
|
|
durationSec?: number; // video 专用
|
|||
|
|
posterUrl?: string; // video 海报
|
|||
|
|
thumbnailUrl?: string; // image 缩略
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
关键约定:
|
|||
|
|
- `kind: "document" + mime.startsWith("image/")` = 图片当文档上传(截图 1)。
|
|||
|
|
- `kind: "image"` = 图片当图片呈现(截图 5、6、7)。该开关在 admin 上传 UI 决定,传到后端落库。
|
|||
|
|
- 多图相册 = 一个 Post 带多个 `kind: "image"` 的 attachments。
|
|||
|
|
- 图片+文字 = Post 同时有 `text` 与 attachments。
|
|||
|
|
- 纯文本+链接 = Post 仅有 `text`,`attachments: []`。
|
|||
|
|
|
|||
|
|
### 2. 后端 API 契约(移交给后端)
|
|||
|
|
|
|||
|
|
| 方法 | 路径 | 用途 |
|
|||
|
|
|---|---|---|
|
|||
|
|
| GET | `/api/posts?category=<slug>&lang=&type=&language=&cursor=&limit=20` | 分类内消息流 |
|
|||
|
|
| GET | `/api/posts?lang=&type=&language=&cursor=&limit=20` | 全部消息流(`/browse`) |
|
|||
|
|
| GET | `/api/posts/recommended?lang=&limit=` | Home 推荐 section |
|
|||
|
|
| GET | `/api/posts/latest?lang=&limit=` | Home 最新 section |
|
|||
|
|
| GET | `/api/posts/:id` | 单条(用于老 `/r/:id` 301 重定向落地,前端拿到 `categorySlug` 后跳锚点) |
|
|||
|
|
| GET | `/api/categories` | 不变 |
|
|||
|
|
| POST/PUT/DELETE | `/api/admin/posts/...` | Admin CRUD,支持多附件 + 文本 + "图片是否以文档呈现"开关 |
|
|||
|
|
|
|||
|
|
废弃:
|
|||
|
|
- `/api/resources/:id/favorite`
|
|||
|
|
- 老 `/api/resources*` 系列保留过渡期,由后端写迁移脚本:每个老 Resource → 一个 Post。
|
|||
|
|
|
|||
|
|
返回结构:`{ items: Post[], nextCursor?: string }`,cursor 由后端不透明字符串提供。
|
|||
|
|
|
|||
|
|
`type` 参数语义:`all` / `image` / `video` / `pdf` / `ppt` / `text` / `link` / `archive`。一个 Post 命中条件 = `attachments[*].mime` 或 `text` 满足;具体由后端定义。
|
|||
|
|
|
|||
|
|
### 3. 组件结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
src/pages/
|
|||
|
|
CategoryPage.tsx ← 重写:<MessageStream scope={{ kind:'category', slug }} />
|
|||
|
|
Browse.tsx ← 重写:<MessageStream scope={{ kind:'all' }} />
|
|||
|
|
|
|||
|
|
src/components/messageStream/
|
|||
|
|
MessageStream.tsx 顶层:fetch + 无限滚动 + 日期分组 + sticky filter chips
|
|||
|
|
FilterChips.tsx 类型 + 语言 chips(横向滚动,sticky top)
|
|||
|
|
DaySeparator.tsx "2 月 27 日" 胶囊
|
|||
|
|
MessageBubble.tsx 单条 Post 容器:决定子组件 + 右下角时间戳
|
|||
|
|
bubbles/
|
|||
|
|
FileDocBubble.tsx 截图 1 + 2:文档(图片当文档 / pdf / ai / ppt / docx)
|
|||
|
|
TextBubble.tsx 截图 3:纯文本 + autolink
|
|||
|
|
VideoBubble.tsx 截图 4:海报 + ▶️,先 inline 后全屏
|
|||
|
|
ImageBubble.tsx 截图 5:单张图片
|
|||
|
|
ImageWithTextBubble.tsx 截图 6:图片 + 文本 + autolink
|
|||
|
|
AlbumBubble.tsx 截图 7:2-4 格 grid,4+ 时第 4 格模糊 + `+N`
|
|||
|
|
overlays/
|
|||
|
|
ImageLightbox.tsx 全屏画廊(左右滑、缩放、关闭、下载)
|
|||
|
|
VideoPlayer.tsx 全屏视频播放器
|
|||
|
|
hooks/
|
|||
|
|
usePostStream.ts cursor 分页 + IntersectionObserver;mock/real 切换
|
|||
|
|
useGroupedByDay.ts 按 publishedAt 本地日期分组
|
|||
|
|
utils/
|
|||
|
|
autolink.tsx 文本中 https?://... → <a target="_blank" rel="noopener">
|
|||
|
|
fileIcon.ts 按 mime/扩展名返回图标 + 颜色
|
|||
|
|
formatBytes.ts 3,549,239 → "3.5 MB"
|
|||
|
|
|
|||
|
|
src/mocks/
|
|||
|
|
mockPosts.ts 覆盖 7 种 bubble 类型的样本数据(图片用 picsum 占位或本地)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. Bubble 分发逻辑
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
function pickBubble(post: Post) {
|
|||
|
|
const a = post.attachments;
|
|||
|
|
if (a.length === 0) return TextBubble;
|
|||
|
|
if (a.length >= 2 && a.every(x => x.kind === "image")) return AlbumBubble;
|
|||
|
|
const only = a[0];
|
|||
|
|
if (only.kind === "video") return VideoBubble;
|
|||
|
|
if (only.kind === "image") {
|
|||
|
|
return post.text ? ImageWithTextBubble : ImageBubble;
|
|||
|
|
}
|
|||
|
|
return FileDocBubble; // document(含图片当文档:内部用 thumbnail 替代蓝圆图标)
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5. 移动端布局规范
|
|||
|
|
|
|||
|
|
- 容器宽度:手机 `max-w-full px-3`;md+ `max-w-[640px] mx-auto`。桌面端不做多列,保持单列聊天流(左右大留白)。
|
|||
|
|
- 气泡:`rounded-2xl bg-ark-panel`,左对齐,无头像,内边距 `p-3`(文本 `px-4 py-2.5`)。
|
|||
|
|
- 时间戳:右下角 `text-[11px] text-neutral-500`,绝对定位。
|
|||
|
|
- 文档下载按钮:圆形 36×36,金色 `bg-ark-gold` + 黑色 ↓。
|
|||
|
|
- Day separator:胶囊 `rounded-full bg-ark-panel/70 backdrop-blur px-3 py-1 text-xs text-neutral-400`,居中、sticky 在 FilterChips 下。
|
|||
|
|
- 多图 grid:宽度 100%,2×2,间距 2px;4+ 时第 4 格 `relative` 叠 `bg-black/45 backdrop-blur-sm` + `+N` 居中文字。
|
|||
|
|
- FilterChips 容器:`sticky top-0 z-10 bg-ark-bg/90 backdrop-blur` + 横向滚动 `overflow-x-auto whitespace-nowrap`。
|
|||
|
|
|
|||
|
|
### 6. 交互
|
|||
|
|
|
|||
|
|
| 交互 | 行为 |
|
|||
|
|
|---|---|
|
|||
|
|
| 点击文档下载按钮 | `window.open(attachment.url, "_blank")` 触发浏览器下载 |
|
|||
|
|
| 点击单张图片 | 打开 `ImageLightbox`(单图) |
|
|||
|
|
| 点击相册任一图 / `+N` | 打开 `ImageLightbox`,可左右切换 |
|
|||
|
|
| 点击视频海报 | 第一次:bubble 内 `<video controls autoPlay>` inline 播放 |
|
|||
|
|
| 点击播放中的视频 | 打开 `VideoPlayer` 全屏 overlay |
|
|||
|
|
| 文本中的链接 | `target="_blank" rel="noopener noreferrer"` 新标签打开 |
|
|||
|
|
| 滚动到底部 | IntersectionObserver 触发下一页 cursor 拉取 |
|
|||
|
|
| 筛选 chips 变化 | 重置 cursor,重新拉取;同步 URL `?type=&language=` |
|
|||
|
|
| 长按气泡 | 暂不做,列入 Open Questions |
|
|||
|
|
|
|||
|
|
### 7. Mock data 层
|
|||
|
|
|
|||
|
|
`src/mocks/mockPosts.ts` 导出 `MOCK_POSTS: Post[]`,至少包含:
|
|||
|
|
|
|||
|
|
- 2 条"图片当文档"(不同 mime:jpg、png)
|
|||
|
|
- 2 条文档(pdf、ai)
|
|||
|
|
- 2 条纯文本+链接(含中文 + 多链接 + emoji)
|
|||
|
|
- 1 条视频(带 posterUrl + duration)
|
|||
|
|
- 2 条单图(不同宽高比)
|
|||
|
|
- 1 条图+文字
|
|||
|
|
- 1 条 3 图相册
|
|||
|
|
- 1 条 7 图相册(验证 `+N` 行为)
|
|||
|
|
- 跨多天的 `publishedAt`,验证 DaySeparator
|
|||
|
|
|
|||
|
|
`usePostStream` 行为:
|
|||
|
|
|
|||
|
|
```ts
|
|||
|
|
const useMock = import.meta.env.VITE_USE_MOCK_POSTS !== "false";
|
|||
|
|
if (useMock) {
|
|||
|
|
// 1. 按 scope.slug / type / language 过滤 MOCK_POSTS
|
|||
|
|
// 2. 按 publishedAt 倒序
|
|||
|
|
// 3. 按 cursor(数字 offset 字符串)切 20 条
|
|||
|
|
// 4. setTimeout 200ms 模拟延迟
|
|||
|
|
// 5. 返回 nextCursor = offset+20 或 undefined
|
|||
|
|
} else {
|
|||
|
|
// fetch /api/posts?... 真接口
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
切真接口时只需在部署环境设 `VITE_USE_MOCK_POSTS=false`(或干脆删 mock 分支)。
|
|||
|
|
|
|||
|
|
### 8. 锚点 + 分享
|
|||
|
|
|
|||
|
|
- 每个 bubble 渲染为 `<article id="post-${post.id}">`。
|
|||
|
|
- 老路由 `/r/:id` 改为一个轻量重定向组件:fetch `/api/posts/:id` 拿到 `categorySlug` → `navigate(/category/${slug}#post-${id}, { replace: true })` → `scrollIntoView`。
|
|||
|
|
- Mock 模式下从 `MOCK_POSTS` 找。
|
|||
|
|
|
|||
|
|
### 9. 移除清单
|
|||
|
|
|
|||
|
|
文件:
|
|||
|
|
- `src/pages/ResourceDetail.tsx`
|
|||
|
|
- `src/pages/FavoritesPage.tsx`
|
|||
|
|
- `src/components/ResourceCard.tsx`
|
|||
|
|
- `src/components/ResourceListFooter.tsx`
|
|||
|
|
|
|||
|
|
代码:
|
|||
|
|
- `postFavoriteDelta` 及所有调用点
|
|||
|
|
- i18n keys:`favorites`, `addFavorite`, `removeFavorite` 等收藏相关
|
|||
|
|
- Home 中的 `/favorites` 入口
|
|||
|
|
|
|||
|
|
路由:
|
|||
|
|
- `/favorites`:删除
|
|||
|
|
- `/r/:id`:保留为轻量重定向
|
|||
|
|
|
|||
|
|
### 10. 测试 / 验证策略
|
|||
|
|
|
|||
|
|
- 视觉验证:本地 `npm run dev`,手机模拟器(Chrome DevTools iPhone 14 Pro 视口)逐一对照 7 张截图。
|
|||
|
|
- 单元测试:`pickBubble` 分发逻辑、`autolink` 正则、`formatBytes`、`useGroupedByDay`。
|
|||
|
|
- 类型检查:`npx tsc --noEmit`(项目 strict)。
|
|||
|
|
- 格式化:`npm run format`。
|
|||
|
|
- 删除后回归:确认 `/favorites` 与 `/r/:id` 老链接不报 404 而是合理跳转或 410。
|
|||
|
|
|
|||
|
|
### 11. 风险与缓解
|
|||
|
|
|
|||
|
|
- **真接口 schema 与 mock 不一致**:spec 是合同;后端实现时若需偏离,必须先回来改 spec。前端 hook 内 `Post` 类型从 `src/types/post.ts` 单一来源导入。
|
|||
|
|
- **`+N` 相册和单图 lightbox 的状态管理混乱**:用 React Context(`<ImageLightboxProvider>`)暴露 `openLightbox(images, startIndex)` 单一入口,所有 bubble 调它。
|
|||
|
|
- **视频 inline → 全屏切换的状态丢失**:全屏 overlay 接 `currentTime` 参数,避免重新加载。
|
|||
|
|
- **scroll restoration**:cursor 分页页内来回滑动时 IntersectionObserver 容易重复触发;用 `loadingRef` 守护。
|
|||
|
|
|
|||
|
|
## Implementation Checklist
|
|||
|
|
|
|||
|
|
> 全部项已被 `.unipi/docs/plans/2026-05-25-telegram-style-resource-stream-plan.md` 覆盖。
|
|||
|
|
|
|||
|
|
- [x] 定义 `src/types/post.ts`:`Post`、`Attachment`、`PostListResponse`
|
|||
|
|
- [x] 创建 `src/mocks/mockPosts.ts`:覆盖 7 种 bubble 类型 + 跨日期样本
|
|||
|
|
- [x] 创建 `src/components/messageStream/hooks/usePostStream.ts`(mock + real 双模式 + cursor 分页 + IntersectionObserver)
|
|||
|
|
- [x] 创建 `src/components/messageStream/hooks/useGroupedByDay.ts`
|
|||
|
|
- [x] 创建 `src/components/messageStream/utils/autolink.tsx`
|
|||
|
|
- [x] 创建 `src/components/messageStream/utils/fileIcon.ts`
|
|||
|
|
- [x] 创建 `src/components/messageStream/utils/formatBytes.ts`
|
|||
|
|
- [x] 创建 `FilterChips.tsx`(sticky + 横向滚动)
|
|||
|
|
- [x] 创建 `DaySeparator.tsx`
|
|||
|
|
- [x] 创建 `MessageBubble.tsx`(含 `pickBubble` 分发)
|
|||
|
|
- [x] 创建 `bubbles/FileDocBubble.tsx`(图片当文档 + pdf/ai/ppt)
|
|||
|
|
- [x] 创建 `bubbles/TextBubble.tsx`(autolink)
|
|||
|
|
- [x] 创建 `bubbles/VideoBubble.tsx`(inline 播放 + 全屏触发)
|
|||
|
|
- [x] 创建 `bubbles/ImageBubble.tsx`
|
|||
|
|
- [x] 创建 `bubbles/ImageWithTextBubble.tsx`
|
|||
|
|
- [x] 创建 `bubbles/AlbumBubble.tsx`(2-4 grid + `+N`)
|
|||
|
|
- [x] 创建 `overlays/ImageLightbox.tsx` + `ImageLightboxProvider` context
|
|||
|
|
- [x] 创建 `overlays/VideoPlayer.tsx`
|
|||
|
|
- [x] 创建 `MessageStream.tsx` 顶层组件
|
|||
|
|
- [x] 重写 `src/pages/CategoryPage.tsx` 为 `<MessageStream scope={{ kind:'category', slug }} />`
|
|||
|
|
- [x] 重写 `src/pages/Browse.tsx` 为 `<MessageStream scope={{ kind:'all' }} />`
|
|||
|
|
- [x] 删除 `src/pages/ResourceDetail.tsx`,将 `/r/:id` 改为重定向组件(mock 模式下从 `MOCK_POSTS` 查)
|
|||
|
|
- [x] 删除 `src/pages/FavoritesPage.tsx`、`src/components/ResourceCard.tsx`、`src/components/ResourceListFooter.tsx`
|
|||
|
|
- [x] 移除 `postFavoriteDelta` 及全部调用点
|
|||
|
|
- [x] 移除 `App.tsx` 中 `/favorites` 路由 + Home 入口
|
|||
|
|
- [x] 清理 i18n favorites 相关 keys
|
|||
|
|
- [x] 单元测试:`pickBubble`、`autolink`、`formatBytes`、`useGroupedByDay`
|
|||
|
|
- [x] 视觉对照 7 张参考截图(iPhone 14 Pro 视口)
|
|||
|
|
- [x] 运行 `npx tsc --noEmit && npm run format:check && npm test`
|
|||
|
|
- [x] 文档:在 README 注明 `VITE_USE_MOCK_POSTS` 用法
|
|||
|
|
- [x] 交付后端 API 契约文档(本 spec 的 §2 部分单独抽出 markdown 给后端)
|
|||
|
|
|
|||
|
|
## Open Questions
|
|||
|
|
|
|||
|
|
- **长按 / 右键菜单**:是否需要"复制链接"、"举报"、"分享"?v2 决定。
|
|||
|
|
- **`type` 筛选语义边界**:一个 Post 含多种 attachment 时(图+视频混合,目前 mock 不出现),`type=video` 命中规则由后端定,前端按返回展示即可。
|
|||
|
|
- **空状态文案**:消息流为空时显示什么?目前沿用 `t("noResults")`。
|
|||
|
|
- **错误重试**:网络失败时是否提供"重试"按钮?建议下方加一个 inline 重试条。
|
|||
|
|
- **视频自动暂停**:滚出视口时是否自动暂停?建议做,体验更顺。
|
|||
|
|
- **i18n 时间戳格式**:是否需要适配繁体/简体/英文不同的日期分组格式?沿用 `Intl.DateTimeFormat` 按 `lang` 切换。
|
|||
|
|
- **SEO**:删除 `/r/:id` 详情页后,搜索引擎抓取深度受影响吗?目前站点未做强 SEO,可忽略;如需保留可让 `/r/:id` 渲染服务端可解析的 `<noscript>` 摘要后再 JS 重定向。
|
|||
|
|
- **Admin 上传 UI 改造**:本次只覆盖前台浏览端;admin 端 Post 编辑器(多附件 + 文本 + 图片呈现方式开关)需要单独的 spec / 任务。
|
|||
|
|
|
|||
|
|
## Out of Scope
|
|||
|
|
|
|||
|
|
- Home 页面布局调整(分类卡片网格、推荐/最新 section 保持不变)
|
|||
|
|
- Admin 后台 UI 改造(单独 spec)
|
|||
|
|
- 真实 API 实现(后端工作)
|
|||
|
|
- 后端数据迁移脚本
|
|||
|
|
- 长按菜单、举报、分享等社交功能
|
|||
|
|
- 评论 / Reaction
|
|||
|
|
- 离线缓存 / Service Worker
|
|||
|
|
- 桌面端多列布局
|