wxs 6cc703ada2 feat: monorepo 重构 + 新增 5 个平台适配器
项目从单体结构重构为 pnpm monorepo (shared/backend/frontend),
新增 YouTube、Instagram、Twitter/X、哔哩哔哩、微博 5 个平台适配器,
包含完整的单元测试和 E2E 测试覆盖。

- 完成 T-031~T-044: 5 个适配器实现、注册、配置和测试
- 重构前后端分离: Hono 后端 + Next.js 前端
- 151 个单元测试 + 21 个 Mock E2E + 25 个真实 E2E
- 适配器基于真实 TikHub API 响应结构实现

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 15:43:25 +08:00

996 lines
62 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.

# Muse Creative Hotspots — UI 设计文档
## 文档信息
| 项目 | 内容 |
|------|------|
| 版本 | v1.0 |
| 创建日期 | 2026-03-02 |
| 来源文档 | DevelopmentPlan.md, FeatureSummary.md, PRD.md |
## 1. 设计概述
### 1.1 设计原则
| 原则 | 说明 |
|------|------|
| 简约现代 | 参考 Notion/Linear 风格,大量留白,信息层次清晰 |
| 内容优先 | 卡片封面图和数据指标是核心UI 元素不喧宾夺主 |
| 平台可识别 | 平台图标采用官方配色,用户可快速辨别内容来源 |
| 状态完备 | 每个页面覆盖默认、加载中、空状态、错误四种状态 |
| 响应式 | 桌面端优先CSS Grid 自适应屏幕宽度 |
### 1.2 页面总览
| 页面ID | 页面名称 | 路由 | 描述 | 对应功能 | 优先级 |
|--------|----------|------|------|----------|--------|
| P-001 | 首页 | `/` | 热点内容卡片信息流,含平台 Tab、排序、刷新 | F-001, F-002, F-003, F-005, F-006 | P0 |
| P-002 | 详情页 | `/detail/[platform]/[id]` | 单条内容完整信息展示 | F-004, F-007 | P0 |
| P-003 | 收藏夹 | `/favorites` | 已收藏内容的网格展示与管理 | F-007, F-008, F-009 | P0 |
| P-004 | 设置页 | `/settings` | API Key 配置、刷新间隔设置 | F-010, F-011 | P0 |
### 1.3 页面导航图
```
┌──────────────────┐
│ P-001 │
│ 首页 │
│ (默认入口页面) │
└───────┬──────────┘
┌──────────────────┼──────────────────┐
│ │ │
▼ ▼ ▼
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ P-002 │ │ P-003 │ │ P-004 │
│ 详情页 │ │ 收藏夹 │ │ 设置页 │
│ 点击卡片进入 │ │ Header导航进入 │ │ Header ⚙ 进入 │
└────────┬───────┘ └────────┬───────┘ └────────────────┘
│ │
│ ┌─────────────┘
│ │ 点击收藏卡片
▼ ▼
┌────────────────┐
│ P-002 │
│ 详情页(复用) │
└────────────────┘
```
**导航说明**:
- Header 常驻所有页面提供全局导航Logo 回首页、收藏夹入口、设置入口)
- 首页 → 详情页:点击任意内容卡片
- 首页 → 收藏夹:点击 Header 收藏入口
- 首页 → 设置页:点击 Header ⚙ 图标
- 收藏夹 → 详情页:点击收藏的内容卡片
- 详情页 → 返回上一页:浏览器 Back / 返回按钮
---
## 2. 页面设计
### 2.1 P-001: 首页
**页面信息**
| 属性 | 值 |
|------|-----|
| 页面ID | P-001 |
| 路由 | `/` |
| 对应功能 | F-001, F-002, F-003, F-005, F-006 |
| 入口 | 应用默认页面Header Logo 点击 |
| 出口 | P-002点击卡片、P-003Header 收藏、P-004Header 设置) |
**页面布局 — ASCII 原型图**
```
┌──────────────────────────────────────────────────────────────────────────┐
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Header │ │
│ │ │ │
│ │ Muse [全部] [抖音] [TikTok] [小红书] [♡ 收藏] [⚙] │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────┤
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Toolbar │ │
│ │ │ │
│ │ 排序: [▼ 播放量] [↓ 降序] [🔄 刷新] 上次: 10:30 │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ │ 封面图 │ │ │ │ 封面图 │ │ │ │ 封面图 │ │ │
│ │ │ │ │ │ │ │ │ │ │ │ │ │
│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │
│ │ │ │ │ │ │ │
│ │ 📱 抖音 │ │ 🎵 TikTok │ │ 📕 小红书 │ │
│ │ 标题文字截断展 │ │ Title text tr.. │ │ 标题文字截断展 │ │
│ │ 示最多两行... │ │ uncated to two.. │ │ 示最多两行... │ │
│ │ │ │ │ │ │ │
│ │ 👤 作者昵称 │ │ 👤 Author Name │ │ 👤 作者昵称 │ │
│ │ ▶1.2M ❤5.3K │ │ ▶800K ❤3.1K │ │ ❤2.1K 💬156 │ │
│ │ 💬 203 [♡] │ │ 💬 150 [♡] │ │ [♡] │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ 封面图 │ │ │ │ 封面图 │ │ │ │ 封面图 │ │ │
│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │
│ │ 📱 抖音 │ │ 📕 小红书 │ │ 🎵 TikTok │ │
│ │ 标题文字... │ │ 标题文字... │ │ Title text... │ │
│ │ 👤 作者 ▶ ❤ 💬 │ │ 👤 作者 ❤ 💬 │ │ 👤 Author ▶ ❤ │ │
│ │ [♡] │ │ [♡] │ │ [♡] │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ... 更多卡片 ... │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
**组件清单**
| 组件ID | 组件名称 | 类型 | 说明 | 交互 |
|--------|----------|------|------|------|
| C-001 | Header | 导航栏 | 顶部固定,含 Logo、平台 Tab、收藏入口、设置入口 | Logo 回首页Tab 切换平台 |
| C-002 | PlatformTabs | Tab 栏 | Header 内嵌,展示"全部"+各平台 Tab | 点击切换平台,触发数据重新获取 |
| C-003 | SortToolbar | 工具栏 | 排序选择器 + 排序方向 + 刷新按钮 + 上次刷新时间 | 选择排序字段/方向;点击刷新 |
| C-004 | ContentCard | 卡片 | 单条内容:封面图、平台标识、标题、作者、数据指标、收藏按钮 | 点击进入详情页;点击 ♡ 收藏 |
| C-005 | ContentGrid | 网格容器 | CSS Grid 响应式网格 `repeat(auto-fill, minmax(280px, 1fr))` | - |
| C-006 | CardSkeleton | 骨架屏 | 数据加载时的占位动画 | - |
| C-008 | FavoriteButton | 收藏按钮 | 卡片右下角,♡ 空心/♥ 实心切换 | 点击切换收藏状态 |
**交互说明**
| 触发 | 动作 | 结果 |
|------|------|------|
| 点击平台 Tab | 切换 queryKey | 重新获取对应平台数据,"全部"聚合所有平台 |
| 选择排序字段/方向 | 前端内存排序useMemo | 卡片网格重新排列,不请求 API |
| 点击刷新按钮 | invalidateQueries + 重置自动刷新计时器 | 按钮显示 loading 旋转,完成后更新"上次刷新"时间 |
| 点击卡片(非收藏按钮区域) | 路由跳转 | 进入 `/detail/[platform]/[id]` 详情页 |
| 点击卡片收藏按钮 ♡ | Zustand addFavorite/removeFavorite | ♡ ↔ ♥ 状态切换,数据持久化到 localStorage |
| 自动定时刷新 | refetchInterval 触发 | 静默刷新数据,更新"上次刷新"时间 |
| 页面不可见(切换 Tab | 暂停自动刷新 | 节省 API 调用 |
**页面状态**
| 状态 | 说明 | 展示 |
|------|------|------|
| 默认 | 数据加载完成 | 卡片网格正常展示 |
| 加载中 | 首次加载或切换平台 | 骨架屏CardSkeleton x N |
| 刷新中 | 手动/自动刷新 | 刷新按钮旋转,保留当前卡片 |
| 空状态 | 平台无数据返回 | 空状态组件 + "暂无热点内容" |
| 错误 — API Key 未配置 | 未设置 API Key | 引导提示 + "去配置" 按钮跳转设置页 |
| 错误 — 请求失败 | 网络/服务端错误 | 错误提示 + "重试" 按钮 |
| 错误 — 频率超限 | 429 响应 | 提示"请求过于频繁,请稍后重试" |
**加载态原型**
```
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │
│ │░░░░░░░░░░░░░│ │ │ │░░░░░░░░░░░░░│ │ │ │░░░░░░░░░░░░░│ │
│ │░░░░░░░░░░░░░│ │ │ │░░░░░░░░░░░░░│ │ │ │░░░░░░░░░░░░░│ │
│ │░░░░░░░░░░░░░│ │ │ │░░░░░░░░░░░░░│ │ │ │░░░░░░░░░░░░░│ │
│ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │
│ ░░░░░░ │ │ ░░░░░░ │ │ ░░░░░░ │
│ ░░░░░░░░░░░░░░ │ │ ░░░░░░░░░░░░░░ │ │ ░░░░░░░░░░░░░░ │
│ ░░░░░░░░░░ │ │ ░░░░░░░░░░ │ │ ░░░░░░░░░░ │
│ ░░░░ ░░░░ ░░░░ │ │ ░░░░ ░░░░ ░░░░ │ │ ░░░░ ░░░░ ░░░░ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
```
**空状态原型**
```
┌──────────────────────────────────────────────────────────┐
│ │
│ ┌──────────┐ │
│ │ 📭 │ │
│ └──────────┘ │
│ │
│ 暂无热点内容 │
│ 当前平台暂时没有热门内容 │
│ │
│ [🔄 刷新试试] │
│ │
└──────────────────────────────────────────────────────────┘
```
**API Key 未配置引导原型**
```
┌──────────────────────────────────────────────────────────┐
│ │
│ ┌──────────┐ │
│ │ 🔑 │ │
│ └──────────┘ │
│ │
│ 请先配置 API Key │
│ 需要配置 TikHub API Key 才能获取内容 │
│ │
│ [⚙ 前往设置] │
│ │
└──────────────────────────────────────────────────────────┘
```
---
### 2.2 P-002: 详情页
**页面信息**
| 属性 | 值 |
|------|-----|
| 页面ID | P-002 |
| 路由 | `/detail/[platform]/[id]` |
| 对应功能 | F-004, F-007 |
| 入口 | P-001点击卡片、P-003点击收藏卡片 |
| 出口 | 返回上一页;外部跳转(查看原文) |
**页面布局 — ASCII 原型图**
```
┌──────────────────────────────────────────────────────────────────────────┐
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Header同首页 C-001 │ │
│ └────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ [← 返回] [♡ 收藏] │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────────────────────────────────────┐ ┌──────────────┐ │ │
│ │ │ │ │ │ │ │
│ │ │ │ │ 平台信息 │ │ │
│ │ │ │ │ │ │ │
│ │ │ 封面图 │ │ 📱 抖音 │ │ │
│ │ │ (大图展示) │ │ │ │ │
│ │ │ │ │ 发布时间 │ │ │
│ │ │ │ │ 2026-03-01 │ │ │
│ │ │ │ │ 14:30 │ │ │
│ │ │ │ │ │ │ │
│ │ └──────────────────────────────────────────┘ └──────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ 内容标题(完整展示,不截断) │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ 作者信息 │ │ │
│ │ │ ┌────┐ │ │ │
│ │ │ │头像│ 作者昵称 │ │ │
│ │ │ └────┘ │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ 数据指标面板 │ │ │
│ │ │ │ │ │
│ │ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │ │
│ │ │ │ ▶ 播放量 │ │ ❤ 点赞 │ │ 💬 评论 │ │ ↗ 分享 │ │ │ │
│ │ │ │ 1,234,567│ │ 53,210 │ │ 2,031 │ │ 8,456 │ │ │ │
│ │ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │ │
│ │ │ │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ 标签 │ │ │
│ │ │ [#热门话题] [#创意] [#内容创作] │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐ │ │
│ │ │ [🔗 查看原文] │ │ │
│ │ └──────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
**组件清单**
| 组件ID | 组件名称 | 类型 | 说明 | 交互 |
|--------|----------|------|------|------|
| C-001 | Header | 导航栏 | 复用全局 Header | 同首页 |
| C-007 | DetailPanel | 信息面板 | 封面大图 + 标题 + 作者 + 数据指标 + 标签 | - |
| C-008 | FavoriteButton | 收藏按钮 | 右上角,大号 ♡/♥ | 点击切换收藏 |
**交互说明**
| 触发 | 动作 | 结果 |
|------|------|------|
| 点击 [← 返回] | 路由 back | 返回上一页面(首页或收藏夹) |
| 点击 [♡ 收藏] | Zustand toggle | 收藏状态切换 |
| 点击 [🔗 查看原文] | window.open | 新窗口打开原平台页面 |
| 封面图加载失败 | 显示占位图 | 灰色占位 + 平台图标 |
**页面状态**
| 状态 | 说明 | 展示 |
|------|------|------|
| 默认 | 详情数据加载完成 | 完整信息面板 |
| 加载中 | 请求详情 API | 骨架屏(大图区域 + 文字行) |
| 错误 | 详情加载失败 | 错误提示 + "重试" + "返回首页" 按钮 |
<!-- NEW START -->
**错误态原型**
```
┌────────────────────────────────────────────────────────────┐
│ [← 返回] │
│ │
│ │
│ ┌──────────┐ │
│ │ ⚠️ │ │
│ └──────────┘ │
│ │
│ 内容加载失败 │
│ 请检查网络连接或稍后重试 │
│ │
│ [🔄 重试] [🏠 返回首页] │
│ │
│ │
└────────────────────────────────────────────────────────────┘
```
<!-- NEW END -->
**加载态原型**
```
┌────────────────────────────────────────────────────────────┐
│ [← 返回] │
│ │
│ ┌──────────────────────────────────┐ ┌──────────────┐ │
│ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │░░░░░░░░░░░░░░│ │
│ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │░░░░░░░░░░░░░░│ │
│ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ │░░░░░░░░░░░░░░│ │
│ │░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ └──────────────┘ │
│ └──────────────────────────────────┘ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ ░░░░ ░░░░░░░░░░░ │
│ ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░ ░░░░░░░░░░ │
│ ░░░░░░░░░ ░░░░░░ ░░░░░░░ │
└────────────────────────────────────────────────────────────┘
```
---
### 2.3 P-003: 收藏夹页面
**页面信息**
| 属性 | 值 |
|------|-----|
| 页面ID | P-003 |
| 路由 | `/favorites` |
| 对应功能 | F-007, F-008, F-009 |
| 入口 | Header 收藏入口 |
| 出口 | P-002点击卡片Header 导航至其他页面 |
**页面布局 — ASCII 原型图**
```
┌──────────────────────────────────────────────────────────────────────────┐
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Header同首页 C-001 │ │
│ └────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 我的收藏 共 12 条 │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ 封面图 │ │ │ │ 封面图 │ │ │ │ 封面图 │ │ │
│ │ └─────────────┘ │ │ └─────────────┘ │ │ └─────────────┘ │ │
│ │ 📱 抖音 │ │ 🎵 TikTok │ │ 📕 小红书 │ │
│ │ 标题文字... │ │ Title text... │ │ 标题文字... │ │
│ │ 👤 作者 │ │ 👤 Author │ │ 👤 作者 │ │
│ │ ▶1.2M ❤5.3K │ │ ▶800K ❤3.1K │ │ ❤2.1K 💬156 │ │
│ │ [♥] │ │ [♥] │ │ [♥] │ │
│ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ ┌─────────────┐ │ │ ┌─────────────┐ │ │
│ │ │ 封面图 │ │ │ │ 封面图 │ │ │
│ │ └─────────────┘ │ │ └─────────────┘ │ │
│ │ ... │ │ ... │ │
│ │ [♥] │ │ [♥] │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
**组件清单**
| 组件ID | 组件名称 | 类型 | 说明 | 交互 |
|--------|----------|------|------|------|
| C-001 | Header | 导航栏 | 复用全局 Header | 同首页 |
| C-005 | ContentGrid | 网格容器 | 复用首页网格布局 | - |
| C-004 | ContentCard | 卡片 | 复用首页卡片,收藏按钮为实心 ♥ | 点击进入详情;点击 ♥ 取消收藏 |
**交互说明**
| 触发 | 动作 | 结果 |
|------|------|------|
| 点击卡片 | 路由跳转 | 进入 `/detail/[platform]/[id]` |
| 点击 ♥ 取消收藏 | Zustand removeFavorite | 卡片从列表移除 |
**页面状态**
| 状态 | 说明 | 展示 |
|------|------|------|
| 默认 | 有收藏内容 | 卡片网格展示 |
| 空状态 | 无收藏内容 | 空状态引导 |
**空状态原型**
```
┌──────────────────────────────────────────────────────────┐
│ │
│ ┌──────────┐ │
│ │ ♡ │ │
│ └──────────┘ │
│ │
│ 还没有收藏内容 │
│ 浏览热点内容时,点击 ♡ 收藏感兴趣的内容 │
│ │
│ [去浏览热点] │
│ │
└──────────────────────────────────────────────────────────┘
```
---
### 2.4 P-004: 设置页面
**页面信息**
| 属性 | 值 |
|------|-----|
| 页面ID | P-004 |
| 路由 | `/settings` |
| 对应功能 | F-010, F-011 |
| 入口 | Header ⚙ 图标 |
| 出口 | Header 导航至其他页面 |
**页面布局 — ASCII 原型图**
```
┌──────────────────────────────────────────────────────────────────────────┐
│ ┌────────────────────────────────────────────────────────────────────┐ │
│ │ Header同首页 C-001 │ │
│ └────────────────────────────────────────────────────────────────────┘ │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 设置 │ │
│ │ │ │
│ │ ─────────────────────────────────────────────── │ │
│ │ │ │
│ │ API 配置 │ │
│ │ │ │
│ │ TikHub API Key │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ sk-xxxxxxxxxxxxxxxxxxxx │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ API Key 保存在服务端,不会暴露到浏览器。 │ │
│ │ 优先使用 .env.local 中的预配置值。 │ │
│ │ │ │
│ │ [保存 API Key] │ │
│ │ │ │
│ │ ─────────────────────────────────────────────── │ │
│ │ │ │
│ │ 刷新设置 │ │
│ │ │ │
│ │ 自动刷新间隔 │ │
│ │ ┌──────────────────────────────────────────┐ │ │
│ │ │ 30 分钟 ▼ │ │ │
│ │ └──────────────────────────────────────────┘ │ │
│ │ 可选: 5 / 10 / 15 / 30 / 60 分钟 │ │
│ │ 最低 5 分钟,防止 API 成本过高。 │ │
│ │ │ │
│ │ ─────────────────────────────────────────────── │ │
│ │ │ │
│ │ 关于 │ │
│ │ │ │
│ │ Muse Creative Hotspots v1.0 │ │
│ │ 数据来源: TikHub API (api.tikhub.io) │ │
│ │ │ │
│ └──────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────────────────┘
```
**组件清单**
| 组件ID | 组件名称 | 类型 | 说明 | 交互 |
|--------|----------|------|------|------|
| C-001 | Header | 导航栏 | 复用全局 Header | 同首页 |
| C-011 | ApiKeyInput | 输入框 | password 类型输入框 + 保存按钮 | 输入 Key → 点击保存 |
| C-012 | IntervalSelect | 下拉选择 | 刷新间隔选择器,选项: 5/10/15/30/60 分钟 | 选择后立即生效 |
**交互说明**
| 触发 | 动作 | 结果 |
|------|------|------|
| 输入 API Key + 点击保存 | POST /api/settings | 保存到服务端内存变量;成功/失败 Toast 提示 |
| 选择刷新间隔 | Zustand setRefreshInterval | 立即更新 TanStack Query refetchInterval |
| API Key 为空点击保存 | 前端校验 | 输入框红色边框 + "请输入 API Key" 提示 |
**页面状态**
| 状态 | 说明 | 展示 |
|------|------|------|
| 默认 | 正常展示设置表单 | 当前 Key掩码+ 当前间隔 |
| 保存中 | API Key 保存请求中 | 保存按钮 loading |
| 保存成功 | 保存完成 | Toast "API Key 已保存" |
| 保存失败 | 保存请求失败 | Toast "保存失败,请重试" |
---
## 3. 用户流程
### 3.1 内容浏览主流程
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 打开应用 │ ──▶ │ 浏览首页 │ ──▶ │ 筛选/排序 │ ──▶ │ 查看详情 │
│ │ │ 卡片信息流│ │ 切换平台 │ │ 完整信息 │
│ P-001 │ │ P-001 │ │ P-001 │ │ P-002 │
└──────────┘ └────┬─────┘ └──────────┘ └────┬─────┘
│ │
│ API Key 未配置 │
▼ ▼
┌──────────┐ ┌──────────┐
│ 配置Key │ │ 查看原文 │
│ P-004 │ │ (外部跳转)│
└──────────┘ └──────────┘
```
**流程步骤**
| 步骤 | 页面 | 用户操作 | 系统响应 |
|------|------|----------|----------|
| 1 | P-001 | 打开应用 | 自动获取默认平台(全部)的热点内容 |
| 2 | P-001 | 浏览卡片信息流 | 展示卡片网格(封面图+标题+数据) |
| 3 | P-001 | 点击平台 Tab 切换 | 重新获取对应平台数据,更新卡片网格 |
| 4 | P-001 | 选择排序方式 | 前端内存排序,卡片重新排列 |
| 5 | P-001 | 点击感兴趣的卡片 | 路由跳转到详情页 |
| 6 | P-002 | 查看完整信息 | 展示大图+标题+数据面板+标签 |
| 7 | P-002 | 点击"查看原文" | 新窗口打开原平台页面 |
### 3.2 收藏管理流程
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 浏览内容 │ ──▶ │ 点击收藏 │ ──▶ │ 打开收藏夹│ ──▶ │ 查看详情 │
│ P-001 │ │ ♡ → ♥ │ │ P-003 │ │ P-002 │
└──────────┘ └──────────┘ └──────────┘ └────┬─────┘
│ │
│ 也可在详情页收藏 │
▼ │
┌──────────┐ │
│ 详情页收藏│ │
│ P-002 │ ◀────────────────────────────┘
└──────────┘
```
**流程步骤**
| 步骤 | 页面 | 用户操作 | 系统响应 |
|------|------|----------|----------|
| 1 | P-001/P-002 | 点击卡片收藏按钮 ♡ | ♡ → ♥ 切换,存入 localStorage |
| 2 | P-003 | 点击 Header 收藏入口 | 展示所有已收藏内容的卡片网格 |
| 3 | P-003 | 点击卡片 | 跳转详情页 |
| 4 | P-003 | 点击 ♥ 取消收藏 | 卡片从收藏列表移除 |
### 3.3 设置配置流程
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ 点击 ⚙ │ ──▶ │ 输入Key │ ──▶ │ 保存Key │ ──▶ │ 设置间隔 │
│ P-001 │ │ P-004 │ │ P-004 │ │ P-004 │
└──────────┘ └──────────┘ └────┬─────┘ └────┬─────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│ 保存成功 │ │ 立即生效 │
│ Toast提示 │ │ 更新定时器│
└──────────┘ └──────────┘
```
**流程步骤**
| 步骤 | 页面 | 用户操作 | 系统响应 |
|------|------|----------|----------|
| 1 | P-001 | 点击 Header ⚙ 图标 | 跳转设置页 |
| 2 | P-004 | 输入 TikHub API Key | 输入框显示内容password 掩码) |
| 3 | P-004 | 点击"保存 API Key" | POST /api/settings成功后 Toast 提示 |
| 4 | P-004 | 选择刷新间隔(如 15 分钟) | Zustand 更新TanStack Query refetchInterval 立即变更 |
| 5 | P-001 | 返回首页 | 使用新 Key 获取数据,按新间隔自动刷新 |
### 3.4 数据刷新流程
```
┌─────────────────────────────────────┐
│ 刷新触发 │
│ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 自动刷新 │ │ 手动刷新 │ │
│ │ 定时器 │ │ 点击 🔄 │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ └────────┬─────────┘ │
│ ▼ │
│ ┌──────────┐ │
│ │ 获取数据 │ │
│ │ F-001 │ │
│ └────┬─────┘ │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ ┌────────┐ ┌────────┐ │
│ │ 成功 │ │ 失败 │ │
│ └───┬────┘ └───┬────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ 更新卡片 │ │ 保留旧数据│ │
│ │ 更新时间 │ │ 错误提示 │ │
│ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────┘
```
---
## 4. 组件规范
### 4.1 全局组件
**C-001: Header 导航栏**
```
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ Muse [全部] [抖音] [TikTok] [小红书] [♡ 收藏] [⚙] │
│ ─────── │
│ (当前Tab下划线高亮) │
│ │
└──────────────────────────────────────────────────────────────────────┘
说明:
- 左侧: Logo 文字 "Muse",点击回首页
- 中部: 平台 Tab 栏(仅首页展示),当前选中 Tab 有下划线高亮
- 右侧: 收藏入口 + 设置图标
- 固定在页面顶部 (sticky top)
- 背景: 白色,底部 1px 边框线
```
<!-- NEW START -->
**C-001 变体: 非首页 Header详情页、收藏夹、设置页使用**
```
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ Muse [♡ 收藏] [⚙] │
│ │
└──────────────────────────────────────────────────────────────────────┘
说明:
- 平台 Tab 栏在非首页隐藏Logo 与右侧图标之间保持留白
- 其余元素位置不变Logo 左对齐,图标右对齐)
- 适用于: P-002 详情页、P-003 收藏夹、P-004 设置页
```
<!-- NEW END -->
**C-002: PlatformTabs 平台 Tab 栏**
```
默认态: [全部] [抖音] [TikTok] [小红书]
─────
(选中态: 下划线 + 文字加粗)
各 Tab 含平台图标:
全部: 🌐 全部
抖音: 📱 抖音 (品牌色: #000000)
TikTok: 🎵 TikTok (品牌色: #00F2EA)
小红书: 📕 小红书 (品牌色: #FF2442)
Tab 切换时,下划线平滑滑动过渡
```
**C-003: SortToolbar 工具栏**
```
┌──────────────────────────────────────────────────────────────────────┐
│ │
│ 排序: [▼ 播放量] [↓ 降序] [🔄 刷新] 上次: 10:30│
│ ────────── ──────── ──────── │
│ 下拉选择 切换按钮 图标按钮 │
│ │
└──────────────────────────────────────────────────────────────────────┘
排序字段选项:
- 播放量 (play_count)
- 点赞数 (like_count)
- 评论数 (comment_count)
- 发布时间 (publish_time)
排序方向:
- ↓ 降序 (desc) — 默认
- ↑ 升序 (asc)
刷新按钮状态:
- 默认: 🔄 图标
- 刷新中: 旋转动画
- 防抖: 2 秒内重复点击忽略
```
### 4.2 业务组件
**C-004: ContentCard 内容卡片**
```
┌─────────────────────┐
│ ┌─────────────────┐ │
│ │ │ │ ← 封面图区域 (aspect-ratio: 4/3 或 3/4)
│ │ Cover Image │ │ 加载失败 → 灰色占位 + 平台图标
│ │ │ │ 使用 Next.js Image + loading="lazy"
│ └─────────────────┘ │
│ │
│ 📱 抖音 │ ← 平台图标 + 平台名 (品牌色小标签)
│ │
│ 标题文字截断展示最 │ ← 标题 (最多 2 行, line-clamp-2)
│ 多两行省略号... │
│ │
│ ┌──┐ │
│ │头│ 作者昵称 │ ← 作者头像 (24px 圆形) + 昵称
│ └──┘ │
│ │
│ ▶ 1.2M ❤ 5.3K │ ← 数据指标 (数字缩写: K/M)
│ 💬 203 [♡] │ ← 评论数 + 收藏按钮
│ │
└─────────────────────┘
宽度: minmax(280px, 1fr)
圆角: 8px (rounded-lg)
阴影: hover 时提升阴影
边框: 1px solid border色
过渡: hover 时 translateY(-2px)
收藏按钮 [♡]:
未收藏: ♡ 空心,灰色
已收藏: ♥ 实心,红色
点击区域: 足够大 (44x44px),防止误触卡片跳转
指标展示规则: <!-- NEW -->
指标值为空undefined时隐藏该指标项而非展示 0。
与 DevelopmentPlan ContentItem 类型中 play_count?: number可选字段一致。
Hover 行为: <!-- NEW -->
MVP 阶段: hover 仅做视觉反馈(阴影提升 + translateY(-2px))。
v1.1 迭代: 考虑增加 hover tooltip/popover 展示更多信息(如完整标题、分享数)。
```
**C-006: CardSkeleton 骨架屏**
```
┌─────────────────────┐
│ ┌─────────────────┐ │
│ │░░░░░░░░░░░░░░░░░│ │ ← 封面占位 (pulse 动画)
│ │░░░░░░░░░░░░░░░░░│ │
│ │░░░░░░░░░░░░░░░░░│ │
│ └─────────────────┘ │
│ ░░░░░░░ │ ← 平台标签占位
│ ░░░░░░░░░░░░░░░░░░ │ ← 标题行占位
│ ░░░░░░░░░░░░░ │
│ ░░ ░░░░░░░░░ │ ← 作者占位
│ ░░░░ ░░░░ ░░░░ │ ← 数据指标占位
└─────────────────────┘
动画: Tailwind animate-pulse
颜色: bg-slate-200
```
**C-008: FavoriteButton 收藏按钮**
```
未收藏态: ♡ (text-slate-400, hover: text-red-400)
已收藏态: ♥ (text-red-500)
过渡动画: 点击时 scale 弹跳效果 (scale-110 → scale-100)
点击区域: 44px x 44px (无障碍最小触摸区域)
阻止冒泡: e.stopPropagation() 防止触发卡片跳转
```
**C-009: EmptyState 空状态**
```
┌──────────────────────────────────────┐
│ │
│ ┌──────────┐ │
│ │ {icon} │ │
│ └──────────┘ │
│ │
│ {主要文案} │ ← text-slate-800, 16px, medium
│ {辅助文案} │ ← text-slate-500, 14px, regular
│ │
│ [{action button}] │ ← 可选操作按钮
│ │
└──────────────────────────────────────┘
Props:
icon: ReactNode (图标)
title: string (主要文案)
description: string (辅助文案)
action?: { label: string, onClick: () => void }
```
<!-- NEW START -->
**C-013: Toast 通知**
```
成功态 (右上角弹出):
┌──────────────────────────────┐
│ ✅ API Key 已保存 │
└──────────────────────────────┘
失败态 (右上角弹出):
┌──────────────────────────────┐
│ ❌ 保存失败,请重试 │
└──────────────────────────────┘
规范:
- 使用 shadcn/ui Toast 组件
- 位置: 页面右上角 (top-right)
- 自动消失时间: 3 秒
- 成功态: 绿色左边框 (border-l-4 border-green-500)
- 失败态: 红色左边框 (border-l-4 border-red-500)
- 支持手动关闭 (点击 × 按钮)
- 背景: white阴影: shadow-lg
- 进入动画: slide-in-from-right
- 退出动画: fade-out
```
<!-- NEW END -->
**C-010: ErrorState 错误状态**
```
┌──────────────────────────────────────┐
│ │
│ ┌──────────┐ │
│ │ ⚠️ │ │
│ └──────────┘ │
│ │
│ {错误描述} │ ← text-red-600
│ │
│ [🔄 重试] │ ← 主按钮
│ │
└──────────────────────────────────────┘
```
---
## 5. 设计规范
### 5.1 色彩规范
| 用途 | 色值 | Tailwind Class | 示例 |
|------|------|----------------|------|
| 主色 | #2563EB | `blue-600` | 主按钮、链接、选中态 |
| 主色悬停 | #1D4ED8 | `blue-700` | 按钮 hover |
| 成功 | #16A34A | `green-600` | 保存成功提示 |
| 警告 | #D97706 | `amber-600` | 频率限制提示 |
| 错误 | #DC2626 | `red-600` | 错误提示、必填校验 |
| 收藏红 | #EF4444 | `red-500` | ♥ 已收藏状态 |
| 文字主色 | #1E293B | `slate-800` | 标题、正文 |
| 文字次色 | #64748B | `slate-500` | 描述、辅助信息 |
| 文字弱色 | #94A3B8 | `slate-400` | 占位文字、禁用态 |
| 背景色 | #FFFFFF | `white` | 页面背景 |
| 表面色 | #F8FAFC | `slate-50` | 卡片背景、工具栏 |
| 边框色 | #E2E8F0 | `slate-200` | 卡片边框、分割线 |
**平台品牌色**
| 平台 | 色值 | 用途 |
|------|------|------|
| 抖音 | #000000 | Tab 标签、平台图标背景 |
| TikTok | #00F2EA | Tab 标签、平台图标背景 |
| 小红书 | #FF2442 | Tab 标签、平台图标背景 |
### 5.2 字体规范
| 用途 | 字号 | 字重 | Tailwind Class |
|------|------|------|----------------|
| 页面标题 | 24px | Bold (700) | `text-2xl font-bold` |
| 区域标题 | 20px | Semibold (600) | `text-xl font-semibold` |
| 卡片标题 | 14px | Medium (500) | `text-sm font-medium` |
| 正文 | 14px | Regular (400) | `text-sm` |
| 数据指标 | 12px | Medium (500) | `text-xs font-medium` |
| 辅助文字 | 12px | Regular (400) | `text-xs` |
- 字体族: 系统默认字体栈 (Inter, system-ui, sans-serif)
### 5.3 间距规范
| 间距 | 值 | Tailwind | 用途 |
|------|-----|----------|------|
| xs | 4px | `1` | 图标与文字间距 |
| sm | 8px | `2` | 卡片内元素间距 |
| md | 12px | `3` | 卡片内部 padding |
| lg | 16px | `4` | 组件间距、网格 gap |
| xl | 24px | `6` | 区域间距 |
| 2xl | 32px | `8` | 页面 padding |
### 5.4 圆角规范
| 元素 | 圆角 | Tailwind |
|------|------|----------|
| 卡片 | 8px | `rounded-lg` |
| 按钮 | 6px | `rounded-md` |
| 输入框 | 6px | `rounded-md` |
| 头像 | 50% | `rounded-full` |
| 平台标签 | 4px | `rounded` |
### 5.5 阴影规范
| 场景 | 阴影 | Tailwind |
|------|------|----------|
| 卡片默认 | 0 1px 2px rgba(0,0,0,0.05) | `shadow-sm` |
| 卡片悬停 | 0 4px 6px rgba(0,0,0,0.1) | `shadow-md` |
| Header | 0 1px 3px rgba(0,0,0,0.1) | `shadow-sm` |
| 弹窗/Toast | 0 10px 15px rgba(0,0,0,0.1) | `shadow-lg` |
### 5.6 响应式断点
| 断点 | 宽度 | Tailwind | 布局说明 |
|------|------|----------|----------|
| Mobile | < 640px | `sm:` | 单栏卡片全宽 |
| Tablet | 640px - 1024px | `md:` / `lg:` | 2 列卡片网格 |
| Desktop | 1024px - 1280px | `xl:` | 3 列卡片网格 |
| Wide | > 1280px | `2xl:` | 4-5 列卡片网格 |
**网格响应式规则**:
```
CSS Grid: grid-template-columns: repeat(auto-fill, minmax(280px, 1fr))
< 640px: 1 列 (卡片宽度 100%)
640-960: 2 列
960-1240: 3 列
1240-1520: 4 列
> 1520: 5 列
```
---
## 6. 页面与功能映射
| 功能ID | 功能名称 | 所在页面 | 主要组件 |
|--------|----------|----------|----------|
| F-001 | 内容获取 | P-001 | TanStack Query (数据层) |
| F-002 | 卡片信息流展示 | P-001 | C-005 ContentGrid + C-004 ContentCard |
| F-003 | 内容筛选与排序 | P-001 | C-002 PlatformTabs + C-003 SortToolbar |
| F-004 | 内容详情页 | P-002 | C-007 DetailPanel |
| F-005 | 自动定时刷新 | P-001 | C-003 SortToolbar (时间显示) |
| F-006 | 手动刷新 | P-001 | C-003 SortToolbar (刷新按钮) |
| F-007 | 内容收藏 | P-001, P-002 | C-008 FavoriteButton |
| F-008 | 收藏夹管理 | P-003 | C-005 ContentGrid + C-004 ContentCard |
| F-009 | 收藏数据持久化 | P-003 | Zustand persist (数据层) |
| F-010 | API Key 配置 | P-004 | C-011 ApiKeyInput |
| F-011 | 刷新间隔设置 | P-004 | C-012 IntervalSelect |
| F-014 | API 请求代理 | - | 后端 API Route (无 UI) |
| F-015 | 统一数据模型 | - | TypeScript 类型 (无 UI) |
| F-016 | 平台适配器 | - | 后端适配器 (无 UI) |