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>
This commit is contained in:
wxs
2026-03-03 15:43:25 +08:00
co-authored by Claude Opus 4.6
parent ce736f197d
commit 6cc703ada2
136 changed files with 16805 additions and 520 deletions
+669
View File
@@ -0,0 +1,669 @@
# Muse Creative Hotspots — 开发计划
## 文档信息
| 项目 | 内容 |
|------|------|
| 版本 | v1.0 |
| 创建日期 | 2026-03-02 |
| 来源文档 | FeatureSummary.md, PRD.md |
## 1. 项目概述
### 1.1 项目目标
构建面向个人创意工作者的全平台热点内容聚合浏览器,MVP 阶段实现抖音 + TikTok + 小红书三个平台的热点内容聚合浏览,包含卡片信息流、筛选排序、内容详情、收藏系统、数据刷新和设置管理。
### 1.2 技术栈
| 层级 | 技术选型 | 版本 | 说明 |
|------|----------|------|------|
| 框架 | Next.js (App Router) | 14+ | 全栈能力,API Routes 做后端代理,Vercel 部署 |
| UI 库 | Tailwind CSS + shadcn/ui | Tailwind 3.x | 简约现代风格,组件丰富 |
| 状态管理 | Zustand | 4.x | 轻量状态管理,persist 中间件支持持久化 |
| 数据请求 | TanStack Query | 5.x | 缓存、自动刷新、loading/error 状态管理 |
| 本地存储 | localStorage | - | MVP 阶段收藏/设置持久化 |
| 语言 | TypeScript | 5.x | 类型安全 |
| 包管理器 | pnpm | 8+ | 快速、节省磁盘 |
| 部署 | localhost → Vercel | - | 先本地开发,后期线上部署 |
### 1.3 开发原则
- **渐进式开发**:先跑通数据链路,再完善 UI 和交互
- **适配器模式**:平台差异封装在适配器内,新增平台零侵入
- **安全优先**:API Key 仅存在于服务端,前端不暴露
- **类型驱动**:先定义 TypeScript 类型,再实现逻辑
- **组件化**:UI 组件遵循单一职责,可独立测试
---
## 2. 技术架构
### 2.1 系统架构图
```
┌─────────────────────────────────────────────────────────────────┐
│ 客户端(浏览器) │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Next.js App (React) │ │
│ │ ┌─────────┐ ┌──────────┐ ┌─────────┐ ┌───────────┐ │ │
│ │ │ 首页 │ │ 详情页 │ │ 收藏页 │ │ 设置页 │ │ │
<!-- MODIFIED: 原内容为 "[id]/",补充 platform 参数(M-001 -->
│ │ │ page.tsx│ │[plt]/[id]│ │favorites│ │ settings │ │ │
│ │ └────┬────┘ └────┬─────┘ └────┬────┘ └─────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────▼────────────▼─────────────▼──────────────▼─────┐ │ │
│ │ │ TanStack Query (缓存 + 自动刷新) │ │ │
│ │ └────────────────────────┬───────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────────▼───────────────────────────┐ │ │
│ │ │ Zustand Stores (settings / favorites) │ │ │
│ │ │ ↕ localStorage │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────┬───────────────────────────────┘ │
└──────────────────────────────┼──────────────────────────────────┘
│ fetch /api/tikhub/[platform]
┌─────────────────────────────────────────────────────────────────┐
│ Next.js API Routes (服务端) │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ /api/tikhub/[platform]/route.ts │ │
│ │ ┌──────────┐ ┌──────────────┐ ┌───────────────────┐ │ │
│ │ │ 请求验证 │→│ 频率限制 │→│ 平台适配器分发 │ │ │
│ │ │ API Key │ │ 10 req/s │ │ douyin/tiktok/xhs │ │ │
│ │ └──────────┘ └──────────────┘ └─────────┬─────────┘ │ │
│ └────────────────────────────────────────────┼─────────────┘ │
│ │ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ 平台适配器层 │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 抖音 │ │ TikTok │ │ 小红书 │ ... (扩展) │ │
│ │ │ Adapter │ │ Adapter │ │ Adapter │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ └─────────────┼─────────────┘ │ │
│ │ ▼ │ │
│ │ ContentItem[] 统一数据模型 │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────┼───────────────────────────────────┘
│ Bearer Token
┌─────────────────────┐
│ TikHub API │
│ api.tikhub.io │
│ 10 req/s 限制 │
│ $0.001/请求 │
└─────────────────────┘
```
### 2.2 模块依赖图
```
┌───────────────────────────────────────────────────────┐
│ 页面层 (Pages) │
│ ┌──────────┐ ┌──────────┐ ┌──────┐ ┌──────────┐ │
│ │ 首页 │ │ 详情页 │ │收藏页│ │ 设置页 │ │
│ └────┬─────┘ └────┬─────┘ └──┬───┘ └────┬─────┘ │
└───────┼─────────────┼───────────┼───────────┼─────────┘
│ │ │ │
▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────┐
│ 组件层 (Components) │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ CardGrid │ │ DetailPnl│ │ Toolbar │ ... │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
└───────┼─────────────┼──────────────┼──────────────────┘
│ │ │
▼ ▼ ▼
┌───────────────────────────────────────────────────────┐
│ 数据层 (Hooks + Stores) │
│ ┌──────────────────┐ ┌──────────────────────────┐ │
│ │ TanStack Query │ │ Zustand Stores │ │
│ │ useContentQuery │ │ useFavoritesStore │ │
│ │ useDetailQuery │ │ useSettingsStore │ │
│ └────────┬─────────┘ └────────────┬─────────────┘ │
└───────────┼─────────────────────────┼─────────────────┘
│ │
▼ ▼
┌───────────────────────┐ ┌────────────────────────────┐
│ API 代理层 │ │ 本地存储 │
│ /api/tikhub/[plat] │ │ localStorage │
│ │ │ └────────────────────────────┘
│ ▼ │
│ 平台适配器层 │
│ adapters/*.ts │
│ │ │
│ ▼ │
│ ContentItem 类型 │
└───────────────────────┘
```
### 2.3 数据流图
```
用户操作 前端 API Route TikHub
│ │ │ │
│ 1.打开首页/切换Tab │ │ │
├─────────────────────▶│ │ │
│ │ 2.useContentQuery() │ │
│ ├───────────────────────▶│ │
│ │ │ 3.读取 API Key │
│ │ │ 4.选择适配器 │
│ │ ├────────────────────▶│
│ │ │ 5.TikHub原始响应 │
│ │ │◀────────────────────┤
│ │ │ 6.适配器转换 │
│ │ │ → ContentItem[] │
│ │ 7.返回标准化数据 │ │
│ │◀───────────────────────┤ │
│ │ 8.TanStack Query缓存 │ │
│ │ 9.前端排序/筛选 │ │
│ 10.渲染卡片网格 │ │ │
│◀─────────────────────┤ │ │
│ │ │ │
│ 11.点击收藏 │ │ │
├─────────────────────▶│ │ │
│ │ 12.Zustand更新 │ │
│ │ 13.localStorage持久化 │ │
│ 14.收藏状态反馈 │ │ │
│◀─────────────────────┤ │ │
```
---
## 3. 开发阶段
### 3.1 阶段时间线
```
Phase 1 Phase 2 Phase 3
基础架构搭建 核心功能实现 辅助功能 & 联调
│ │ │
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 项目初始化│ │ 内容展示 │ │ 收藏系统 │
│ 类型定义 │ ──────▶ │ 筛选排序 │ ──────▶ │ 设置页面 │
│ API代理层 │ │ 详情页 │ │ 性能优化 │
│ 适配器 │ │ 刷新机制 │ │ 联调验收 │
└──────────┘ └──────────┘ └──────────┘
交付物: 交付物: 交付物:
• 项目骨架 • 首页卡片信息流 • 收藏功能
• 类型系统 • 平台Tab切换 • 收藏夹页面
• 3平台适配器 • 排序功能 • 设置页面
• API代理可用 • 详情页 • 图片懒加载
• 全局布局 • 自动/手动刷新 • 全链路验收
```
### 3.2 Phase 1: 基础架构搭建
**目标**: 搭建项目骨架,打通 API 代理 → 适配器 → 统一数据模型的完整链路,确保能从 TikHub 获取到标准化的 ContentItem 数据。
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|--------|------|------|------|--------|----------|
<!-- MODIFIED: 补充 next.config.ts 图片域名配置(S-002 -->
| T-001 | 项目初始化 | Next.js 14+ App Router + Tailwind + shadcn/ui + pnpm;配置 next.config.ts images.remotePatterns(各平台图片 CDN 域名白名单) | - | P0 | - |
| T-002 | TypeScript 类型定义 | ContentItem、Platform、PlatformAdapter 接口定义 | T-001 | P0 | F-015 |
| T-003 | API 代理层实现 | `/api/tikhub/[platform]/route.ts`Bearer Token 认证,频率限制 | T-001 | P0 | F-014 |
| T-004 | TikHub API 客户端 | 封装 HTTP 请求,错误处理,10 req/s 限流 | T-003 | P0 | F-014 |
| T-005 | 平台适配器 — 抖音 | 热搜榜 + 内容详情 API,字段映射为 ContentItem | T-002, T-004 | P0 | F-016 |
| T-006 | 平台适配器 — TikTok | 趋势内容 + 内容详情 API,字段映射为 ContentItem | T-002, T-004 | P0 | F-016 |
| T-007 | 平台适配器 — 小红书 | 推荐内容 + 笔记详情 API,字段映射为 ContentItem | T-002, T-004 | P0 | F-016 |
| T-008 | Zustand Store 基础 | settingsStoreAPI Key、刷新间隔)+ favoritesStore 骨架 | T-001 | P0 | F-009, F-010 |
| T-009 | 全局布局组件 | HeaderLogo + 平台 Tab + 设置入口)+ 主内容区域 | T-001 | P0 | - |
**阶段依赖图:**
```
T-001 (项目初始化)
├──▶ T-002 (类型定义) ──┐
├──▶ T-003 (API代理层) │
│ └──▶ T-004 (API客户端) ──┐
├──▶ T-008 (Zustand) │ │
└──▶ T-009 (全局布局) │ │
▼ ▼
T-005 (抖音适配器)
T-006 (TikTok适配器)
T-007 (小红书适配器)
```
**Phase 1 验收**: `GET /api/tikhub/douyin` 返回标准化 ContentItem[] JSON。
---
### 3.3 Phase 2: 核心功能实现
**目标**: 实现首页卡片信息流、平台 Tab 切换、排序、详情页、自动/手动刷新,完成核心浏览体验。
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|--------|------|------|------|--------|----------|
| T-010 | TanStack Query 集成 | 配置 QueryClient,封装 `useContentQuery(platform)` hook | T-005~T-007 | P0 | F-001 |
| T-011 | 内容卡片组件 | ContentCard 组件:封面图、标题、平台图标、数据指标、作者信息 | T-002, T-009 | P0 | F-002 |
| T-012 | 卡片网格布局 | 响应式网格布局(CSS Grid),支持不同屏幕尺寸 | T-011 | P0 | F-002 |
| T-013 | 平台 Tab 切换 | 顶部 Tab 栏,切换平台触发数据重新获取,支持"全部"聚合视图 | T-010, T-012 | P0 | F-003 |
| T-014 | 排序功能 | 工具栏排序控件,支持 play_count/like_count/comment_count/publish_time + asc/desc | T-010 | P0 | F-003 |
<!-- MODIFIED: 路由改为 /detail/[platform]/[id],语义更清晰(M-001 -->
| T-015 | 内容详情页 | `/detail/[platform]/[id]` 页面,完整信息展示 + "查看原文"跳转 + 收藏按钮 | T-010 | P0 | F-004 |
| T-016 | 自动定时刷新 | TanStack Query refetchInterval,读取设置中的刷新间隔,页面不可见时暂停 | T-010, T-008 | P0 | F-005 |
<!-- MODIFIED: 合并 T-018 到 T-017T-018 粒度过细(S-003 -->
| T-017 | 手动刷新 + 刷新时间 | 工具栏刷新按钮,invalidateQueriesloading 状态,防抖处理,重置自动刷新计时器;工具栏显示"上次刷新: HH:MM",刷新后更新 | T-010, T-016 | P0 | F-005, F-006 |
**阶段依赖图:**
```
T-010 (TanStack Query) ──┬──▶ T-013 (平台Tab)
├──▶ T-014 (排序)
├──▶ T-015 (详情页)
├──▶ T-016 (自动刷新) ──▶ T-017 (手动刷新+刷新时间)
T-011 (卡片组件) ──▶ T-012 (网格布局) ──▶ T-013
```
**Phase 2 验收**: 首页展示三个平台的热点内容卡片网格,可切换平台/排序,点击进入详情页,自动/手动刷新正常。
---
### 3.4 Phase 3: 辅助功能 & 联调
**目标**: 完成收藏系统、设置页面、错误处理、性能优化,通过全部 MVP 验收标准。
| 任务ID | 任务 | 描述 | 依赖 | 优先级 | 关联功能 |
|--------|------|------|------|--------|----------|
| T-019 | 收藏功能实现 | favoritesStore 完善,addFavorite/removeFavoritepersist 到 localStorage | T-008, T-011 | P0 | F-007, F-009 |
| T-020 | 卡片收藏按钮 | ContentCard + DetailPage 中添加收藏按钮,实心/空心状态切换 | T-019, T-015 | P0 | F-007 |
| T-021 | 收藏夹页面 | `/favorites` 页面,网格展示收藏内容,支持取消收藏和跳转详情 | T-019, T-012 | P0 | F-008 |
| T-022 | 设置页面 | `/settings` 页面,API Key 输入框 + 刷新间隔选择 | T-008 | P0 | F-010, F-011 |
| T-023 | 错误处理 & 空状态 | API 错误提示、Key 未配置引导、数据为空提示、封面图加载失败占位 | T-010~T-022 | P0 | - |
| T-024 | 图片懒加载 | Next.js Image 组件 + loading="lazy",首屏性能优化 | T-012 | P0 | - |
| T-025 | 全链路联调 & 验收 | 按 PRD 第8节 MVP 验收标准逐项测试 | T-023, T-024 | P0 | 全部 |
**阶段依赖图:**
```
T-019 (收藏Store) ──┬──▶ T-020 (收藏按钮)
└──▶ T-021 (收藏夹页面)
T-022 (设置页面)
T-023 (错误处理) ◀── T-019~T-022 全部完成
T-024 (图片懒加载)
T-025 (联调验收) ◀── T-023 + T-024
```
**Phase 3 验收**: 通过 PRD 第8节全部 MVP 验收标准。
---
## 4. 技术方案
### 4.1 统一数据模型(F-015
**功能**: 定义 ContentItem TypeScript 类型,作为全系统的数据契约。
**类型定义**:
```typescript
// src/types/content.ts
interface ContentItem {
id: string;
title: string;
cover_url?: string;
video_url?: string;
author_name: string;
author_avatar?: string;
play_count?: number;
like_count?: number;
comment_count?: number;
share_count?: number;
publish_time: string;
platform: Platform;
original_url: string;
tags?: string[];
}
type Platform =
| 'douyin' | 'tiktok' | 'xiaohongshu' // MVP
| 'youtube' | 'instagram' | 'twitter' // P1
| 'bilibili' | 'weibo' // P1
| string; // P2 扩展
interface PlatformConfig {
id: Platform;
name: string;
icon: string;
color: string;
enabled: boolean;
endpoints: {
trending: string;
detail: string;
};
}
interface PlatformAdapter {
fetchTrending(count: number): Promise<ContentItem[]>;
fetchDetail(id: string): Promise<ContentItem>;
}
```
### 4.2 API 代理层(F-014
**功能**: Next.js API Routes 代理 TikHub 请求,隐藏 API Key。
**接口设计**:
| 接口 | 方法 | 路径 | 说明 |
|------|------|------|------|
| 获取热榜 | GET | `/api/tikhub/[platform]?count=20` | 返回 ContentItem[] |
| 获取详情 | GET | `/api/tikhub/[platform]/detail?id=xxx` | 返回 ContentItem |
| 保存设置 | POST | `/api/settings` | 保存 API Key(服务端) |
| 调用统计 | GET | `/api/stats` | 返回当日调用次数 |
**架构设计**:
```
┌───────────────────────────────────────────────────┐
│ /api/tikhub/[platform]/route.ts │
├───────────────────────────────────────────────────┤
│ │
│ 1. 解析 platform 参数 │
│ │ │
│ ▼ │
│ 2. 读取 API Key (环境变量 / settings) │
│ │ │
│ ▼ │
│ 3. 频率限制检查 (10 req/s) │
│ │ │
│ ▼ │
│ 4. 选择 PlatformAdapter │
│ ┌─────┼─────┐ │
│ ▼ ▼ ▼ │
│ douyin tiktok xiaohongshu │
│ │ │ │ │
│ └─────┼─────┘ │
│ ▼ │
│ 5. 调用 TikHub API + 转换为 ContentItem[] │
│ │ │
│ ▼ │
│ 6. 返回 JSON Response │
│ │
└───────────────────────────────────────────────────┘
```
**实现要点**:
<!-- MODIFIED: 明确 API Key 运行时存储方案和读取优先级(M-002) -->
- API Key 读取优先级:① 运行时内存变量(设置页面覆盖值)→ ② `.env.local``TIKHUB_API_KEY` 环境变量(预配置)
- 设置页面保存 API Key 时,通过 `POST /api/settings` 将 Key 写入服务端内存变量(进程生命周期内有效),不写入 `.env.local`(运行时无法修改);服务重启后回退到 `.env.local` 配置
- MVP 阶段(localhost):推荐在 `.env.local` 中预配置 Key,设置页面仅作为运行时覆盖手段
- 使用简单的内存计数器实现 10 req/s 限流(滑动窗口)
- 错误码映射:TikHub 401 → 前端提示配置 Key;429 → 提示稍后重试;5xx → 通用错误
### 4.3 平台适配器(F-016
**功能**: 各平台 API 调用和数据格式转换。
**架构设计**:
```
┌────────────────────────────────────────────────┐
│ PlatformAdapter 接口 │
│ fetchTrending(count) → ContentItem[] │
│ fetchDetail(id) → ContentItem │
└──────────────────┬─────────────────────────────┘
│ implements
┌─────────┼─────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Douyin │ │ TikTok │ │ Xhs │
│ Adapter │ │ Adapter │ │ Adapter │
├──────────┤ ├──────────┤ ├──────────┤
│ 端点: │ │ 端点: │ │ 端点: │
│ fetch_hot │ │ trending │ │ fetch_ │
│ _search_ │ │ _post │ │ feed │
│ result │ │ │ │ │
│ │ │ │ │ │
│ 映射: │ │ 映射: │ │ 映射: │
│ 平台特定 │ │ 平台特定 │ │ 平台特定 │
│ → Content │ │ → Content│ │ → Content│
│ Item │ │ Item │ │ Item │
└──────────┘ └──────────┘ └──────────┘
```
**MVP 平台端点配置**:
| 平台 | 热榜端点 | 详情端点 |
|------|----------|----------|
| 抖音 | `/api/v1/douyin/web/fetch_hot_search_result` | `/api/v1/douyin/web/fetch_one_video` |
| TikTok | `/api/v1/tiktok/web/fetch_trending_post` | `/api/v1/tiktok/web/fetch_post_detail` |
| 小红书 | `/api/v1/xiaohongshu/app/v2/fetch_feed` | `/api/v1/xiaohongshu/app/v2/fetch_note_detail` |
**实现要点**:
- 每个适配器独立文件:`src/lib/adapters/douyin.ts``tiktok.ts``xiaohongshu.ts`
- 适配器注册表:`src/lib/adapters/index.ts` 导出 `getAdapter(platform): PlatformAdapter`
- 字段映射中缺失字段使用合理默认值(如 `author_name: "未知作者"`
### 4.4 内容展示层(F-002, F-003
**功能**: 卡片网格布局 + 平台 Tab 切换 + 排序。
**组件结构**:
```
┌────────────────────────────────────────────────┐
│ Header │
│ ┌──────────────────────────────────────────┐ │
│ │ Logo [全部][抖音][TikTok][小红书] ⚙️ │ │
│ └──────────────────────────────────────────┘ │
├────────────────────────────────────────────────┤
│ Toolbar │
│ ┌──────────────────────────────────────────┐ │
│ │ 排序: [▼最热|最新] 🔄刷新 上次:10:30│ │
│ └──────────────────────────────────────────┘ │
├────────────────────────────────────────────────┤
│ ContentGrid │
│ ┌──────────────────────────────────────────┐ │
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │ │
│ │ │Content │ │Content │ │Content │ ... │ │
│ │ │Card │ │Card │ │Card │ │ │
│ │ └────────┘ └────────┘ └────────┘ │ │
│ └──────────────────────────────────────────┘ │
└────────────────────────────────────────────────┘
```
**接口设计**:
| 组件 | Props | 说明 |
|------|-------|------|
| `PlatformTabs` | `platforms`, `active`, `onChange` | 平台切换 Tab 栏 |
| `SortToolbar` | `sortBy`, `sortOrder`, `onSort`, `onRefresh`, `lastRefresh` | 排序 + 刷新工具栏 |
| `ContentGrid` | `items: ContentItem[]`, `loading`, `error` | 卡片网格容器 |
| `ContentCard` | `item: ContentItem`, `onFavorite`, `isFavorited` | 单个内容卡片 |
**实现要点**:
- 网格布局使用 CSS Grid`grid-template-columns: repeat(auto-fill, minmax(280px, 1fr))`
- 排序在前端内存中完成(useMemo),不重新请求 API
- 平台 Tab 切换触发 TanStack Query 的 queryKey 变更,自动重新获取数据
<!-- MODIFIED: 补充 rate-limiter 保护说明,与风险管理一致(S-001) -->
- "全部"视图使用 `Promise.all` 并发请求所有已启用平台,通过 rate-limiter 确保不超 10 req/s
### 4.5 收藏系统(F-007, F-008, F-009
**功能**: Zustand + persist 实现收藏功能。
**接口设计**:
```typescript
// src/stores/favorites.ts
interface FavoritesStore {
items: ContentItem[];
addFavorite: (item: ContentItem) => void;
removeFavorite: (id: string, platform: Platform) => void;
isFavorited: (id: string, platform: Platform) => boolean;
}
```
**实现要点**:
- 使用 Zustand `persist` 中间件,存储到 localStorage key `muse-favorites`
- 收藏去重:以 `id + platform` 组合作为唯一键
- 收藏夹页面复用 `ContentGrid` + `ContentCard` 组件
### 4.6 设置管理(F-010, F-011
**功能**: API Key 配置 + 刷新间隔设置。
**接口设计**:
```typescript
// src/stores/settings.ts
interface SettingsStore {
apiKey: string;
refreshInterval: 5 | 10 | 15 | 30 | 60; // 分钟
enabledPlatforms: Record<Platform, boolean>;
displayCount: number;
setApiKey: (key: string) => void;
setRefreshInterval: (minutes: number) => void;
togglePlatform: (platform: Platform) => void;
setDisplayCount: (count: number) => void;
}
```
**实现要点**:
<!-- MODIFIED: 与 4.2 保持一致,明确 API Key 存储为服务端内存变量(M-002 -->
- API Key 通过 `/api/settings` POST 接口保存到服务端内存变量(非 localStorage),读取优先级见 4.2 节
- 刷新间隔变更后,立即更新 TanStack Query 的 refetchInterval
- 设置页 UI 使用 shadcn/ui 的 Input、Select、Switch 组件
---
## 5. 项目目录结构
```
src/
├── app/
│ ├── layout.tsx # 全局布局 (Header + Main)
│ ├── page.tsx # 首页 (ContentGrid + Toolbar)
<!-- MODIFIED: 路由补充 platform 参数(M-001 -->
│ ├── detail/[platform]/[id]/page.tsx # 详情页
│ ├── favorites/page.tsx # 收藏夹页面
│ ├── settings/page.tsx # 设置页面
│ └── api/
│ ├── tikhub/
│ │ └── [platform]/
│ │ ├── route.ts # 热榜内容代理
│ │ └── detail/route.ts # 内容详情代理
│ ├── settings/route.ts # 设置保存接口
│ └── stats/route.ts # API 调用统计
├── components/
│ ├── layout/
│ │ ├── Header.tsx # 顶部导航
│ │ ├── PlatformTabs.tsx # 平台 Tab 栏
│ │ └── SortToolbar.tsx # 排序 + 刷新工具栏
│ ├── card/
│ │ ├── ContentCard.tsx # 内容卡片
│ │ ├── ContentGrid.tsx # 卡片网格容器
│ │ └── CardSkeleton.tsx # 加载骨架屏
│ ├── detail/
│ │ └── DetailPanel.tsx # 详情信息面板
│ ├── common/
│ │ ├── EmptyState.tsx # 空状态组件
│ │ ├── ErrorState.tsx # 错误状态组件
│ │ └── FavoriteButton.tsx # 收藏按钮
│ └── ui/ # shadcn/ui 组件
├── lib/
│ ├── tikhub.ts # TikHub HTTP 客户端
│ ├── rate-limiter.ts # 请求频率限制
│ ├── adapters/
│ │ ├── index.ts # 适配器注册表
│ │ ├── douyin.ts # 抖音适配器
│ │ ├── tiktok.ts # TikTok 适配器
│ │ └── xiaohongshu.ts # 小红书适配器
│ ├── platforms.ts # 平台配置
│ └── utils.ts # 工具函数
├── hooks/
│ ├── useContentQuery.ts # 内容查询 hook
│ └── useDetailQuery.ts # 详情查询 hook
├── stores/
│ ├── favorites.ts # 收藏 store
│ └── settings.ts # 设置 store
└── types/
└── content.ts # 类型定义
```
---
## 6. 风险管理
| 风险 | 可能性 | 影响 | 应对措施 |
|------|--------|------|----------|
| TikHub API 端点变更 | 中 | 高 | 适配器模式隔离变更,仅需修改对应适配器文件 |
| TikHub API 响应格式变化 | 中 | 高 | 字段映射做容错处理,缺失字段使用默认值 |
<!-- MODIFIED: 统一并发策略描述,移除"串行请求"矛盾说法(S-001 -->
| API 频率限制触发 (10 req/s) | 高 | 中 | 实现 rate-limiter 请求排队机制,确保并发请求不超 10 req/s;MVP 仅 3 平台,并发风险低 |
| API 成本失控 | 低 | 中 | 默认 30 分钟刷新间隔;页面不可见暂停刷新;F-017 成本监控 |
| localStorage 容量限制 (5MB) | 低 | 低 | 收藏数据量预估较小(数百条 ContentItem ≈ 几百 KB |
| 跨域图片加载失败 | 高 | 中 | 使用 Next.js Image 组件配置 remotePatterns;加载失败显示占位图 |
| P1/P2 平台 API 端点不确定 | 高 | 低 | MVP 不涉及;P1 阶段前在 TikHub 控制台确认最新端点 |
---
## 7. 里程碑
```
M1 M2 M3 M4
│ │ │ │
▼ ▼ ▼ ▼
◆─────────────────◆─────────────────◆─────────────────◆
│ │ │ │
Phase 1 完成 Phase 2 完成 Phase 3 完成 MVP 发布
数据链路打通 核心浏览体验 全功能可用 验收通过
```
| 里程碑 | 目标 | 交付物 | 验收标准 |
|--------|------|--------|----------|
| M1 — 数据链路打通 | API 代理层 + 3 平台适配器可用 | T-001 ~ T-009 | `GET /api/tikhub/douyin` 返回标准化 JSON |
<!-- MODIFIED: T-018 合并到 T-017,范围调整(S-003 -->
| M2 — 核心浏览体验 | 首页可浏览、可切换、可排序 | T-010 ~ T-017 | 首页展示卡片网格,Tab 切换 + 排序 + 详情页 + 刷新正常 |
| M3 — 全功能可用 | 收藏 + 设置 + 错误处理 | T-019 ~ T-024 | 收藏功能可用,设置页可配置 Key 和刷新间隔 |
| M4 — MVP 发布 | 全链路验收通过 | T-025 | 通过 PRD 第8节全部 8 条 MVP 验收标准 |
---
## 8. 任务与功能映射
| 功能ID | 功能名 | 实现任务 |
|--------|--------|----------|
| F-001 | 内容获取 | T-010 |
| F-002 | 卡片信息流展示 | T-011, T-012 |
| F-003 | 内容筛选与排序 | T-013, T-014 |
| F-004 | 内容详情页 | T-015 |
<!-- MODIFIED: T-017 合并了 T-018 的刷新时间展示,补充映射(S-003) -->
| F-005 | 自动定时刷新 | T-016, T-017 |
| F-006 | 手动刷新 | T-017 |
| F-007 | 内容收藏 | T-019, T-020 |
| F-008 | 收藏夹管理 | T-021 |
| F-009 | 收藏数据持久化 | T-019 |
| F-010 | API Key 配置 | T-022 |
| F-011 | 刷新间隔设置 | T-022 |
| F-014 | API 请求代理 | T-003, T-004 |
| F-015 | 统一数据模型 | T-002 |
| F-016 | 平台适配器 | T-005, T-006, T-007 |
> F-012(平台管理)、F-013(展示数量设置)、F-017API 调用量统计)为 v1.1/v2.0 功能,不在 MVP 任务中。
---
## 9. 资源需求
| 角色 | 人数 | 职责 | 参与阶段 |
|------|------|------|----------|
| 全栈开发 | 1 | 前后端全部实现 | Phase 1-3 |
> 本项目为个人项目,由单人全栈完成。
+497
View File
@@ -0,0 +1,497 @@
# Muse Creative Hotspots — 功能摘要
## 文档信息
| 项目 | 内容 |
|------|------|
| 版本 | v1.0 |
| 创建日期 | 2026-03-02 |
| 来源文档 | PRD.md |
## 1. 功能总览
### 1.1 功能统计
| 类别 | 数量 |
|------|------|
| 功能模块 | 5 个 |
<!-- MODIFIED: 原内容为 "P0: 10, P1: 5, P2: 1, 总计: 16"。F-007~F-009 升 P0F-011 升 P0,新增 F-017(P1) -->
| P0 功能 | 14 个 |
| P1 功能 | 2 个 |
| P2 功能 | 1 个 |
| **功能总计** | **17 个** |
### 1.2 功能架构图
```
┌─────────────────────────────────────────────────────────────────────────────┐
│ Muse Creative Hotspots(秒思创意热点) │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │
│ │ A. 热点内容聚合 │ │ B. 数据刷新管理 │ │ C. 收藏/书签系统 │ │
│ │ ──────────────── │ │ ──────────────── │ │ ──────────────── │ │
│ │ • F-001 内容获取 │ │ • F-005 自动刷新 │ │ • F-007 内容收藏 │ │
│ │ • F-002 卡片展示 │ │ • F-006 手动刷新 │ │ • F-008 收藏管理 │ │
│ │ • F-003 筛选排序 │ │ │ │ • F-009 数据持久化 │ │
│ │ • F-004 详情页 │ │ │ │ │ │
│ └──────────────────┘ └──────────────────┘ └──────────────────┘ │
│ │
│ ┌──────────────────┐ ┌──────────────────┐ │
│ │ D. 设置管理 │ │ E. API 代理层 │ │
│ │ ──────────────── │ │ ──────────────── │ │
│ │ • F-010 Key配置 │ │ • F-014 请求代理 │ │
│ │ • F-011 刷新间隔 │ │ • F-015 数据模型 │ │
│ │ • F-012 平台管理 │ │ • F-016 平台适配 │ │
│ │ • F-013 数量设置 │ │ │ │
│ │ • F-017 调用统计 │ │ │ │
│ └──────────────────┘ └──────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
```
### 1.3 模块依赖关系
```
┌──────────────────┐
│ E. API 代理层 │ ◀─── 所有数据请求的底层基础
│ F-014/F-015/F-016│
└───────┬──────────┘
│ 被依赖
┌──────────────────┐ ┌──────────────────┐
│ A. 热点内容聚合 │ ◀──── │ B. 数据刷新管理 │
│ F-001~F-004 │ │ F-005/F-006 │
└───────┬──────────┘ └──────────────────┘
│ 被依赖 ▲
▼ │ 读取配置
┌──────────────────┐ ┌──────────────────┐
│ C. 收藏/书签系统 │ │ D. 设置管理 │
│ F-007~F-009 │ │ F-010~F-013 │
└──────────────────┘ └──────────────────┘
│ 提供 API Key
┌──────────────────┐
│ E. API 代理层 │
└──────────────────┘
```
**依赖说明:**
- 模块 E 是基础设施层,模块 A/B 依赖其提供数据
- 模块 B 触发模块 A 的数据重新获取
- 模块 C 依赖模块 A 提供可收藏的内容
- 模块 D 为模块 B(刷新间隔)和模块 E(API Key)提供配置
---
## 2. 功能清单
### 2.1 模块 A — 热点内容聚合浏览
**模块职责**: 从各平台获取热点内容并以卡片信息流形式展示,支持筛选、排序和详情查看。
#### 功能列表
| ID | 功能 | 描述 | 优先级 | 关联场景 |
|----|------|------|--------|----------|
| F-001 | 内容获取 | 获取各平台官方热榜/推荐内容 | P0 | US-001/002/003 |
| F-002 | 卡片信息流展示 | 瀑布流/网格卡片布局展示内容 | P0 | US-001/004 |
| F-003 | 内容筛选与排序 | 按平台切换、按数据指标排序 | P0 | US-002/003 |
| F-004 | 内容详情页 | 站内详情页展示完整内容信息 | P0 | US-001/003/004 |
#### 功能契约详情
**F-001: 内容获取**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 1. 页面首次加载;2. 自动定时刷新触发;3. 手动点击刷新按钮;4. 切换平台 Tab |
| **输入** | 平台标识(platform)、展示数量(count,默认 20-50)、API Key(从设置读取) |
| **处理逻辑** | 1. 通过 API 代理层调用 TikHub 对应平台端点;2. 适配器将原始数据转换为统一 ContentItem;3. 缓存结果供前端展示 |
| **输出** | ContentItem[] 数组,包含 title、cover_url、author_name、play_count、like_count 等标准字段 |
| **异常情况** | API Key 无效 → 提示配置;请求超时 → 显示重试按钮;频率超限(10 req/s)→ 排队等待;平台未启用 → 跳过 |
| **边界说明** | 不包含内容的全文/完整视频获取;不做内容缓存持久化(刷新后替换);不支持分页加载更多 |
**F-002: 卡片信息流展示**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 内容数据加载完成(F-001 返回结果) |
| **输入** | ContentItem[] 数组 |
| **处理逻辑** | 1. 按瀑布流/网格布局渲染卡片;2. 每张卡片展示:封面图/缩略图、标题(截断)、平台图标+名称、关键数据(播放量/点赞/评论/分享)、发布时间、作者头像+昵称;3. 支持 hover 预览更多信息 |
| **输出** | 可视化的卡片网格界面 |
| **异常情况** | 封面图加载失败 → 显示占位图;数据为空 → 显示空状态提示 |
| **边界说明** | 不包含视频内联播放;不包含无限滚动(展示固定 Top N);卡片内标题截断展示,完整内容在详情页 |
**F-003: 内容筛选与排序**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户点击平台 Tab 切换或选择排序方式 |
| **输入** | 筛选条件:platform(平台标识 / "all");排序条件:sortByplay_count / like_count / comment_count / publish_time+ sortOrderasc / desc |
| **处理逻辑** | 1. 按平台筛选:切换 Tab 时过滤或重新请求对应平台数据;2. 按指标排序:前端对当前列表重新排序;3. "全部"视图聚合所有已启用平台的内容 |
| **输出** | 重新排列后的 ContentItem[] → 更新卡片信息流 |
| **异常情况** | 某平台数据为空 → Tab 上标注"暂无数据";排序字段缺失 → 该条目排到末尾 |
| **边界说明** | 不包含关键词搜索功能;不包含多条件组合筛选;排序为前端内存排序,不重新请求 API |
**F-004: 内容详情页**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户点击内容卡片 |
| **输入** | ContentItem 的完整数据(或 contentId + platform 用于请求详情 API |
| **处理逻辑** | 1. 展示完整内容信息(标题、描述、标签);2. 展示完整数据指标面板(播放/点赞/评论/分享);3. 展示作者信息(头像+昵称);4. 提供"查看原文"按钮(跳转原平台页面);5. 提供收藏按钮 |
| **输出** | 站内详情页面 |
| **异常情况** | 详情数据加载失败 → 显示错误提示 + 重试按钮;原文链接失效 → 提示"原文可能已被删除" |
<!-- MODIFIED: 明确 video_url 的展示方式 -->
| **边界说明** | 不包含站内视频播放器(视频内容通过"查看原文"按钮跳转原平台播放,video_url 不直接嵌入播放);不包含评论区展示;不包含相关推荐 |
---
### 2.2 模块 B — 数据刷新管理
**模块职责**: 管理内容数据的自动定时刷新和手动触发刷新机制。
#### 功能列表
| ID | 功能 | 描述 | 优先级 | 关联场景 |
|----|------|------|--------|----------|
| F-005 | 自动定时刷新 | 按设定间隔自动获取最新内容 | P0 | US-001/002 |
| F-006 | 手动刷新 | 用户点击按钮立即刷新内容 | P0 | US-001/002 |
#### 功能契约详情
**F-005: 自动定时刷新**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 页面加载后自动启动定时器 |
| **输入** | 刷新间隔(从设置读取,默认 30 分钟) |
| **处理逻辑** | 1. 启动定时器(setInterval / TanStack Query refetchInterval);2. 到达间隔时间后自动调用 F-001 重新获取所有已启用平台的内容;3. 刷新时更新"上次刷新时间"显示 |
| **输出** | 更新后的内容列表 + 更新刷新时间戳 |
| **异常情况** | 刷新失败 → 保留上一次数据,显示刷新失败提示;页面后台(不可见)→ 暂停刷新节省 API 调用 |
| **边界说明** | 不包含增量更新(每次全量替换);不包含推送通知新内容;最小间隔限制 5 分钟(防止 API 滥用) |
**F-006: 手动刷新**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户点击工具栏刷新按钮 🔄 |
| **输入** | 当前选中平台(或"全部" |
| **处理逻辑** | 1. 触发 F-001 重新获取内容;2. 刷新按钮显示 loading 状态;3. 完成后重置自动刷新计时器;4. 更新"上次刷新时间" |
| **输出** | 更新后的内容列表 + loading 状态反馈 |
| **异常情况** | 连续快速点击 → 防抖处理(2 秒内忽略重复点击);刷新中再次点击 → 忽略 |
<!-- MODIFIED: 标注单平台刷新为设计决策而非 PRD 约束 -->
| **边界说明** | 【设计决策】不包含单平台独立刷新(刷新所有已启用平台,PRD 未明确此约束,后续可按需调整);手动刷新会重置自动刷新倒计时 |
---
### 2.3 模块 C — 收藏/书签系统
**模块职责**: 允许用户收藏感兴趣的内容,构建个人灵感库。
#### 功能列表
| ID | 功能 | 描述 | 优先级 | 关联场景 |
|----|------|------|--------|----------|
<!-- MODIFIED: 原内容为 "P1"PRD MVP 验收标准要求"收藏功能可用",升级为 P0 以匹配 MVP 纳入 -->
| F-007 | 内容收藏 | 将任意内容卡片添加到收藏夹 | P0 | US-001/004 |
| F-008 | 收藏夹管理 | 独立页面查看和管理收藏内容 | P0 | US-004 |
| F-009 | 收藏数据持久化 | 收藏数据本地持久化存储 | P0 | US-004 |
#### 功能契约详情
**F-007: 内容收藏**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户在卡片上或详情页中点击收藏按钮 |
| **输入** | ContentItem 完整数据 |
| **处理逻辑** | 1. 检查是否已收藏(防重复);2. 已收藏 → 取消收藏;未收藏 → 添加到收藏列表;3. 更新收藏按钮状态(实心/空心);4. 触发 F-009 持久化存储 |
| **输出** | 收藏状态变更 + 视觉反馈(按钮状态切换) |
| **异常情况** | 存储空间不足 → 提示清理旧收藏 |
| **边界说明** | 不包含收藏分类/文件夹功能;不包含收藏备注;不包含收藏分享 |
**F-008: 收藏夹管理**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户点击导航中的"收藏"入口 |
| **输入** | 本地存储的收藏列表 |
| **处理逻辑** | 1. 从本地存储读取收藏列表;2. 以卡片网格形式展示所有收藏内容;3. 支持取消收藏(删除);4. 点击卡片可跳转详情页 |
| **输出** | 收藏内容列表页面 |
| **异常情况** | 收藏为空 → 显示空状态引导 |
| **边界说明** | 不包含收藏搜索;不包含收藏排序;不包含收藏导出;MVP 阶段不支持云同步 |
**F-009: 收藏数据持久化**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 收藏列表发生变更(添加/删除) |
| **输入** | 完整的收藏列表数据 |
| **处理逻辑** | 1. 使用 Zustand persist 中间件;2. 将收藏数据序列化存储到 localStorage;3. 页面加载时自动恢复收藏状态 |
| **输出** | 持久化的收藏数据 |
| **异常情况** | localStorage 不可用 → 降级为内存存储(关闭页面丢失);数据损坏 → 重置收藏列表并提示 |
| **边界说明** | MVP 仅支持 localStorage,不包含 IndexedDB;不包含数据库后端存储;不包含跨设备同步 |
---
### 2.4 模块 D — 设置管理
**模块职责**: 提供应用配置界面,允许用户自定义 API 认证、刷新策略和平台偏好。
#### 功能列表
| ID | 功能 | 描述 | 优先级 | 关联场景 |
|----|------|------|--------|----------|
| F-010 | API Key 配置 | 配置 TikHub API Key | P0 | - |
<!-- MODIFIED: 原内容为 "P1",根据 PRD 第8节 MVP 验收标准"设置页可配置 API Key 和刷新间隔"升级为 P0 -->
| F-011 | 刷新间隔设置 | 自定义自动刷新频率 | P0 | US-001 |
| F-012 | 平台管理 | 启用/禁用各平台 | P1 | US-002 |
| F-013 | 展示数量设置 | 配置每平台默认展示数量 | P2 | US-003 |
<!-- NEW START -->
| F-017 | API 调用量统计 | 展示当日 API 调用次数及成本估算 | P1 | - |
<!-- NEW END -->
#### 功能契约详情
**F-010: API Key 配置**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户进入设置页面 |
| **输入** | 用户输入的 TikHub API Key 字符串 |
| **处理逻辑** | 1. 提供文本输入框供用户粘贴 API Key;2. 保存时验证 Key 格式(非空检查);3. 存储到本地(加密或 env);4. API 代理层读取此 Key 发起请求 |
| **输出** | API Key 保存成功/失败提示 |
<!-- MODIFIED: 原异常情况"Key 无效(API 返回 401"与边界"保存时不测试连通性"矛盾,明确 401 在请求内容时触发 -->
| **异常情况** | Key 为空 → 提示必填;Key 无效 → 首次请求内容时 API 返回 401,引导用户检查 Key 配置 |
| **边界说明** | 保存时仅做非空校验,不测试 API 连通性(无效 Key 在实际请求时暴露);不包含多 Key 轮换;Key 仅存储于服务端环境变量或加密本地存储 |
**F-011: 刷新间隔设置**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户在设置页面调整刷新间隔 |
| **输入** | 刷新间隔值(分钟),可选范围:5 / 10 / 15 / 30 / 60 |
| **处理逻辑** | 1. 提供下拉选择或滑块控件;2. 保存后立即更新 F-005 的定时器间隔;3. 持久化存储设置 |
| **输出** | 新的刷新间隔生效 |
| **异常情况** | 无特殊异常 |
| **边界说明** | 最小 5 分钟(API 成本控制);不支持自定义任意分钟数 |
**F-012: 平台管理**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户在设置页面切换平台开关 |
| **输入** | 平台标识 + 启用/禁用状态 |
| **处理逻辑** | 1. 展示所有支持的平台列表,每个平台配有开关;2. 切换开关后持久化设置;3. 禁用的平台不再出现在首页 Tab 栏中;4. 禁用平台的数据不再自动刷新 |
| **输出** | 平台启用状态更新 |
| **异常情况** | 至少保留一个平台启用 → 否则提示 |
| **边界说明** | 不包含平台顺序自定义;不包含自定义添加新平台 |
**F-013: 展示数量设置**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户在设置页面调整展示数量 |
| **输入** | 每平台展示数量(默认 20,可选 10 / 20 / 30 / 50 |
| **处理逻辑** | 1. 提供数量选择控件;2. 保存后下次刷新时按新数量请求;3. 影响 F-001 的请求参数 |
| **输出** | 新的展示数量配置生效 |
| **异常情况** | 无特殊异常 |
| **边界说明** | 不支持每个平台独立设置不同数量(全局统一);更改后需等待下次刷新才生效 |
<!-- NEW START -->
**F-017: API 调用量统计**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 用户进入设置页面或首页工具栏查看 |
| **输入** | API 代理层的请求计数器数据 |
| **处理逻辑** | 1. API 代理层(F-014)每次转发请求时累加计数器;2. 按日期维度统计调用次数;3. 根据 $0.001/请求 计算估算成本;4. 在设置页面或工具栏展示"今日调用: N 次(≈$X.XX" |
| **输出** | 当日 API 调用次数 + 成本估算显示 |
| **异常情况** | 计数器数据丢失(页面刷新)→ 重新从 0 计数并提示"本次会话统计" |
| **边界说明** | 仅统计当前会话/当日数据,不做历史统计;计数存储于内存或 localStorage;成本为估算值,不保证与实际账单一致 |
<!-- NEW END -->
---
### 2.5 模块 E — API 代理层
**模块职责**: 通过 Next.js API Routes 代理 TikHub 请求,隐藏 API Key 并统一数据格式。
#### 功能列表
| ID | 功能 | 描述 | 优先级 | 关联场景 |
|----|------|------|--------|----------|
| F-014 | API 请求代理 | 通过服务端代理 TikHub API 请求 | P0 | - |
| F-015 | 统一数据模型 | 所有平台数据映射为 ContentItem | P0 | - |
| F-016 | 平台适配器 | 各平台 API 调用与数据格式转换 | P0 | - |
#### 功能契约详情
**F-014: API 请求代理**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 前端发起内容获取请求 |
| **输入** | 平台标识、请求类型(热榜/详情)、请求参数 |
| **处理逻辑** | 1. Next.js API Route 接收前端请求;2. 从环境变量/设置读取 API Key3. 构建 TikHub API 请求(Bearer Token 认证);4. 转发请求并返回结果;5. 遵守 10 req/s 频率限制 |
| **输出** | TikHub API 原始响应数据 |
| **异常情况** | API Key 未配置 → 返回 401 + 提示配置;TikHub 返回错误 → 透传错误信息;请求超时 → 返回超时错误;频率超限 → 排队或返回 429 |
| **边界说明** | 不包含响应缓存(由 TanStack Query 在前端处理);不包含请求日志记录;代理层仅做转发,不做业务逻辑 |
**F-015: 统一数据模型**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | 适配器处理 API 响应时 |
| **输入** | 各平台原始 API 响应数据 |
| **处理逻辑** | 定义统一 ContentItem 类型,包含:title、cover_url、video_url、author_name、author_avatar、play_count、like_count、comment_count、share_count、publish_time、platform、original_url、tags |
| **输出** | 标准化的 ContentItem 对象 |
| **异常情况** | 必填字段缺失 → 使用默认值(如"未知作者");数据类型不匹配 → 类型转换或置空 |
| **边界说明** | ContentItem 为只读展示模型,不包含编辑能力;字段定义以 PRD 4.3 为准 |
**F-016: 平台适配器**
| 契约项 | 说明 |
|--------|------|
| **触发条件** | F-001 调用内容获取时 |
| **输入** | 平台标识 + API 原始响应 |
| **处理逻辑** | 1. 根据平台标识选择对应适配器;2. 适配器调用平台特定的 TikHub API 端点;3. 将原始响应字段映射为 ContentItem(参照 PRD 中各平台的字段映射规则);4. MVP 实现抖音/TikTok/小红书三个适配器 |
| **输出** | ContentItem[] 标准化数组 |
| **异常情况** | 平台无适配器 → 返回空数组 + 日志警告;API 端点变更 → 适配器更新 |
| **边界说明** | 每个适配器独立实现,互不影响;新增平台只需新增适配器,无需修改已有代码 |
---
## 3. 功能依赖矩阵
<!-- MODIFIED: 修正 F-002/F-003 依赖方向;新增 F-017 行列 -->
| 功能 | F-001 | F-002 | F-003 | F-004 | F-005 | F-006 | F-007 | F-008 | F-009 | F-010 | F-011 | F-012 | F-013 | F-014 | F-015 | F-016 | F-017 |
|------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|-------|
| **F-001** 内容获取 | - | | | | | | | | | | | ✓ | ✓ | ✓ | ✓ | ✓ | |
| **F-002** 卡片展示 | ✓ | - | ✓ | | | | | | | | | | | | | | |
| **F-003** 筛选排序 | ✓ | | - | | | | | | | | | | | | | | |
| **F-004** 详情页 | ✓ | | | - | | | | | | | | | | ✓ | ✓ | ✓ | |
| **F-005** 自动刷新 | ✓ | | | | - | | | | | | ✓ | | | | | | |
| **F-006** 手动刷新 | ✓ | | | | | - | | | | | | | | | | | |
| **F-007** 内容收藏 | ✓ | | | | | | - | | ✓ | | | | | | | | |
| **F-008** 收藏管理 | | | | | | | | - | ✓ | | | | | | | | |
| **F-009** 数据持久化 | | | | | | | | | - | | | | | | | | |
| **F-010** Key配置 | | | | | | | | | | - | | | | | | | |
| **F-011** 刷新间隔 | | | | | | | | | | | - | | | | | | |
| **F-012** 平台管理 | | | | | | | | | | | | - | | | | | |
| **F-013** 数量设置 | | | | | | | | | | | | | - | | | | |
| **F-014** 请求代理 | | | | | | | | | | ✓ | | | | - | | | |
| **F-015** 数据模型 | | | | | | | | | | | | | | | - | | |
| **F-016** 平台适配 | | | | | | | | | | | | | | ✓ | ✓ | - | |
| **F-017** 调用统计 | | | | | | | | | | | | | | ✓ | | | - |
说明:
- ✓ 表示**行功能**依赖**列功能**
- 空白表示无依赖
- `-` 表示自身
---
## 4. 功能流程图
### 4.1 核心流程:内容浏览主流程
```
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ F-010 │ ──▶ │ F-014 │ ──▶ │ F-016 │ ──▶ │ F-015 │ ──▶ │ F-001 │
│ Key配置 │ │ 请求代理 │ │ 平台适配 │ │ 数据模型 │ │ 内容获取 │
└──────────┘ └──────────┘ └──────────┘ └──────────┘ └─────┬────┘
┌───────────────────────────────────────────────────┘
┌──────────┐ ┌──────────┐ ┌──────────┐
│ F-002 │ ──▶ │ F-003 │ ──▶ │ F-004 │
│ 卡片展示 │ │ 筛选排序 │ │ 详情页 │
└──────────┘ └──────────┘ └──────────┘
```
### 4.2 数据刷新流程
```
┌──────────────────────────────────────────────────────────┐
│ 刷新触发 │
│ ┌──────────┐ ┌──────────┐ │
│ │ F-005 │ │ F-006 │ │
│ │ 自动刷新 │ │ 手动刷新 │ │
│ │ (定时器) │ │ (按钮) │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ └──────────┬───────────┘ │
│ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ F-001 │ ──▶ │ F-002 │ │
│ │ 内容获取 │ │ 更新展示 │ │
│ └────┬─────┘ └──────────┘ │
│ │ │
│ ▼ 失败 │
│ ┌────────────┐ │
│ │ 保留旧数据 │ │
│ │ 显示错误提示│ │
│ └────────────┘ │
└──────────────────────────────────────────────────────────┘
```
### 4.3 收藏流程
```
┌──────────┐ ┌──────────┐ ┌──────────┐
│ F-002 │ ──▶ │ F-007 │ ──▶ │ F-009 │
│ 卡片展示 │ │ 点击收藏 │ │ 持久化 │
└──────────┘ └──────────┘ └──────────┘
或 │
┌──────────┐ ▼
│ F-004 │ ──▶ (同上) ┌──────────┐
│ 详情页 │ │ F-008 │
└──────────┘ │ 收藏管理 │
└──────────┘
```
---
## 5. 版本规划
| 版本 | 包含功能 | 功能ID | 目标 |
|------|----------|--------|------|
<!-- MODIFIED: MVP 纳入 F-011v1.1 纳入 F-017v2.0 暗色模式无功能 ID 故移除描述 -->
| **MVP** | 3平台内容获取、卡片展示、筛选排序、详情页、自动/手动刷新、收藏系统、API Key配置、刷新间隔设置、API代理层 | F-001 ~ F-011, F-014 ~ F-016 | 跑通核心链路:抖音+TikTok+小红书热点聚合浏览 |
| **v1.1** | 平台管理、API调用量统计、新增5个P1平台适配器 | F-012, F-017 + P1平台适配 | 扩展至8个平台,完善运维功能 |
| **v2.0** | 展示数量设置、剩余9个P2平台适配器 | F-013 + P2平台适配 | 全平台覆盖17个平台 |
---
## 6. 接口契约预览
> 详细接口定义在 DevelopmentPlan 中,此处仅列出关键接口
| 功能 | 接口类型 | 简要说明 |
|------|----------|----------|
| F-001 | API (GET) | `GET /api/tikhub/[platform]` — 获取指定平台热榜内容 |
| F-004 | API (GET) | `GET /api/tikhub/[platform]/detail?id=xxx` — 获取内容详情 |
| F-005 | Event | TanStack Query `refetchInterval` 定时触发 |
| F-006 | Event | 用户点击 → `queryClient.invalidateQueries()` |
| F-007 | Store | Zustand `useFavoritesStore.addFavorite(item)` |
| F-009 | Storage | Zustand persist → localStorage |
| F-010 | API (POST) | `POST /api/settings` — 保存 API Key |
| F-014 | API (Proxy) | Next.js API Route → TikHub APIBearer Token |
| F-016 | Module | `PlatformAdapter.fetchTrending(platform)``ContentItem[]` |
<!-- NEW START -->
| F-017 | API (GET) | `GET /api/stats` — 获取当日 API 调用次数及成本估算 |
<!-- NEW END -->
---
## 附录:用户场景映射
| ID | 场景 | 描述 | 关联功能 |
|----|------|------|----------|
| US-001 | 创意灵感获取 | 每天浏览各平台热点趋势,发现创意表达和内容形式 | F-001~F-006 |
| US-002 | 竞品/行业监控 | 跟踪特定领域在各平台的热门内容表现 | F-001, F-003, F-005, F-006, F-012 |
| US-003 | 数据分析研究 | 对比同类内容在不同平台的数据差异 | F-001, F-003, F-004, F-013 |
| US-004 | 内容搬运/分发 | 发现优质内容后进行跨平台二次创作 | F-002, F-004, F-007, F-008 |
+350
View File
@@ -0,0 +1,350 @@
# Muse Creative Hotspots — 产品需求文档 (PRD)
## 1. 产品概述
### 1.1 产品名称
Muse Creative Hotspots(秒思创意热点)
### 1.2 产品定位
面向个人创意工作者的**全平台热点内容聚合浏览器**,一站式查看 17 个主流社交媒体平台的热门内容及数据表现。
### 1.3 目标用户
个人独立使用者(创意工作者/内容创作者/自媒体从业者)
### 1.4 核心价值
- **效率提升**:告别逐个打开 17 个平台 APP/网站的低效模式
- **全局视野**:跨平台热点趋势一目了然
- **数据洞察**:热门内容的关键数据指标集中展示、可排序对比
- **灵感沉淀**:收藏感兴趣的内容,构建个人灵感库
### 1.5 核心使用场景
| 场景 | 描述 |
|------|------|
| 创意灵感获取 | 每天浏览各平台热点趋势,发现创意表达和内容形式 |
| 竞品/行业监控 | 跟踪特定领域在各平台的热门内容表现 |
| 数据分析研究 | 对比同类内容在不同平台的数据差异 |
| 内容搬运/分发 | 发现优质内容后进行跨平台二次创作 |
---
## 2. 平台覆盖范围
### 2.1 支持平台清单(共 17 个)
| 分类 | 平台 | 内容类型 | 优先级 |
|------|------|----------|--------|
| 国内短视频 | 抖音 | 短视频 | **MVP** |
| 国际短视频 | TikTok | 短视频 | **MVP** |
| 国内图文 | 小红书 | 图文+短视频 | **MVP** |
| 国际视频 | YouTube | 中长视频 | P1 |
| 国际图文 | Instagram | 图文+Reels | P1 |
| 国际社交 | Twitter/X | 文字+图文 | P1 |
| 国内视频 | 哔哩哔哩 | 中长视频 | P1 |
| 国内社交 | 微博 | 文字+图文+视频 | P1 |
| 国际社交 | Threads | 文字+图文 | P2 |
| 国际社交 | Reddit | 文字+图文+视频 | P2 |
| 国际职场 | 领英 (LinkedIn) | 文字+图文 | P2 |
| 国内问答 | 知乎 | 文字+图文 | P2 |
| 国际图文 | Lemon8 | 图文 | P2 |
| 国内短视频 | 快手 | 短视频 | P2 |
| 国内社交 | 微信 (公众号+视频号) | 图文+短视频 | P2 |
| 国内趣味 | 皮皮虾 | 短视频+图文 | P2 |
| AI 视频 | Sora | AI 生成视频 | P2 |
### 2.2 开发节奏
- **MVP 阶段**:抖音 + TikTok + 小红书(3 个平台)
- **P1 阶段**YouTube + Instagram + Twitter/X + 哔哩哔哩 + 微博(5 个平台)
- **P2 阶段**:其余 9 个平台
---
## 3. 功能需求
### 3.1 核心功能:热点内容聚合浏览
#### 3.1.1 内容获取
- 获取各平台**官方热榜/推荐内容**(如抖音热榜、微博热搜等)
- 每个平台展示热榜 Top N 条内容(默认 20-50 条)
- 数据刷新策略:**自动定时刷新(默认 30 分钟)+ 手动刷新按钮**
#### 3.1.2 内容展示 — 卡片信息流
- 采用**瀑布流/网格卡片**布局(类似小红书/Pinterest
- 每张卡片包含:
- 封面图/视频缩略图
- 内容标题/描述(截断展示)
- 来源平台标识(图标+名称)
- 关键数据指标(播放量/浏览量、点赞数、评论数、分享数)
- 发布时间
- 作者头像+昵称
- 卡片支持 hover 预览更多信息
#### 3.1.3 内容筛选与排序
- **按平台切换**:顶部 Tab 栏或侧边导航切换不同平台,支持"全部"聚合视图
- **按数据指标排序**:按播放量/浏览量、点赞数、评论数、发布时间等排序
- 支持切换排序方向(升序/降序)
#### 3.1.4 内容详情页
- 点击卡片进入**站内详情页**,展示:
- 完整的内容信息(标题、描述、标签等)
- 完整的数据指标面板
- 作者信息
- "查看原文"按钮(跳转原平台页面)
- 收藏按钮
### 3.2 辅助功能
#### 3.2.1 收藏/书签
- 支持将任意内容卡片添加到收藏夹
- 收藏夹独立页面查看
- 收藏数据持久化存储(本地存储/后端数据库)
#### 3.2.2 设置页面
- TikHub API Key 配置
- 自动刷新间隔设置
- 每个平台的启用/禁用开关
- 每个平台默认展示数量设置
---
## 4. 数据需求
### 4.1 数据源
- **API 提供商**TikHub (https://www.tikhub.io)
- **认证方式**Bearer Token(已有 API Key
- **调用限制**10 请求/秒
- **计费方式**$0.001/请求
### 4.2 各平台核心 API 端点
#### MVP 平台
##### 抖音
| 功能 | 端点 |
|------|------|
| 热搜榜 | `/api/v1/douyin/web/fetch_hot_search_result` |
| 内容详情 | `/api/v1/douyin/web/fetch_one_video` |
| 用户信息 | `/api/v1/douyin/web/fetch_user_profile` |
##### TikTok
| 功能 | 端点 |
|------|------|
| 趋势内容 | `/api/v1/tiktok/web/fetch_trending_post` |
| 探索内容 | `/api/v1/tiktok/web/fetch_explore_post` |
| 内容详情 | `/api/v1/tiktok/web/fetch_post_detail` |
##### 小红书
| 功能 | 端点 |
|------|------|
| 推荐内容 | `/api/v1/xiaohongshu/app/v2/fetch_feed` (App V2 API) |
| 内容详情 | `/api/v1/xiaohongshu/app/v2/fetch_note_detail` |
| 用户信息 | `/api/v1/xiaohongshu/app/v2/fetch_user_info` |
#### P1 平台
> 以下端点来自 TikHub API 文档,实现前建议在 TikHub 控制台确认最新版本。
##### YouTube
| 功能 | 端点 |
|------|------|
| 趋势视频 | `/api/v1/youtube/web/fetch_trending_video` |
| 视频详情 | `/api/v1/youtube/web/fetch_video_detail` |
**字段映射**: `videoId``id`, `title``title`, `thumbnails.high.url``cover_url`, `contentDetails.videoId``video_url` (需拼接播放页 URL), `statistics.viewCount``play_count`, `statistics.likeCount``like_count`, `statistics.commentCount``comment_count`, `snippet.publishedAt``publish_time`
**内容类型**: 中长视频,封面比例 16:9
##### Instagram
| 功能 | 端点 |
|------|------|
| 探索内容 | `/api/v1/instagram/web/fetch_explore_feed` |
| 内容详情 | `/api/v1/instagram/web/fetch_post_detail` |
**字段映射**: `code``id`, `caption.text``title`, `image_versions2.candidates[0].url` / `thumbnail_url``cover_url`, `video_url``video_url` (Reels 有值,图文为 undefined), `user.username``author_name`, `like_count``like_count`, `comment_count``comment_count`, `taken_at``publish_time`
**内容类型**: 图文 (3:4) + Reels (9:16),通过是否有 `video_url` 区分
##### Twitter/X
| 功能 | 端点 |
|------|------|
| 热搜话题 | `/api/v1/twitter/web/fetch_trending_topics` |
| 推文详情 | `/api/v1/twitter/web/fetch_tweet_detail` |
**字段映射**: `id_str``id`, `full_text``title`, `entities.media[0].media_url_https``cover_url` (纯文字推文为 undefined), `user.name``author_name`, `user.profile_image_url_https``author_avatar`, `favorite_count``like_count`, `reply_count``comment_count`, `retweet_count``share_count`, `created_at``publish_time`
**内容类型**: 以文字卡片为主(无封面图时使用文字卡片样式)
##### 哔哩哔哩
| 功能 | 端点 |
|------|------|
| 热门视频 | `/api/v1/bilibili/web/fetch_popular_video_list` |
| 视频详情 | `/api/v1/bilibili/web/fetch_video_detail` |
**字段映射**: `bvid``id`, `title``title`, `pic``cover_url`, `owner.name``author_name`, `owner.face``author_avatar`, `stat.view``play_count`, `stat.like``like_count`, `stat.reply``comment_count`, `stat.share``share_count`, `pubdate` (Unix 时间戳) → `publish_time`
**内容类型**: 中长视频,封面比例 16:9
##### 微博
| 功能 | 端点 |
|------|------|
| 热搜榜 | `/api/v1/weibo/app/fetch_hot_search` |
| 微博详情 | `/api/v1/weibo/app/fetch_post_detail` |
**字段映射**: `id``id`, `text` (去 HTML 标签) → `title`, `pic_ids[0]` 拼接图片 URL → `cover_url` (无图时为 undefined), `user.screen_name``author_name`, `user.avatar_hd``author_avatar`, `attitudes_count``like_count`, `comments_count``comment_count`, `reposts_count``share_count`, `created_at``publish_time`
**内容类型**: 文字+图文,热搜词条无封面图时展示文字卡片
#### P2 平台
> 以下平台的 API 端点待根据 TikHub 最新文档确认,适配器实现时以实际接口为准。
| 平台 | 推测热榜端点 | 推测详情端点 |
|------|-------------|-------------|
| Threads | `/api/v1/threads/web/fetch_trending_post` | `/api/v1/threads/web/fetch_post_detail` |
| Reddit | `/api/v1/reddit/web/fetch_hot_post` | `/api/v1/reddit/web/fetch_post_detail` |
| LinkedIn | `/api/v1/linkedin/web/fetch_trending_post` | `/api/v1/linkedin/web/fetch_post_detail` |
| 知乎 | `/api/v1/zhihu/app/fetch_hot_question` | `/api/v1/zhihu/app/fetch_question_detail` |
| Lemon8 | `/api/v1/lemon8/web/fetch_trending_post` | `/api/v1/lemon8/web/fetch_post_detail` |
| 快手 | `/api/v1/kuaishou/app/fetch_hot_video` | `/api/v1/kuaishou/app/fetch_video_detail` |
| 微信 | `/api/v1/wechat/mp/fetch_hot_article` | `/api/v1/wechat/mp/fetch_article_detail` |
| 皮皮虾 | `/api/v1/pipix/app/fetch_hot_video` | `/api/v1/pipix/app/fetch_video_detail` |
| Sora | `/api/v1/sora/web/fetch_featured_video` | `/api/v1/sora/web/fetch_video_detail` |
### 4.3 每条内容需采集的数据字段
| 字段 | 说明 | 展示位置 |
|------|------|----------|
| title | 标题/描述 | 卡片+详情页 |
| cover_url | 封面图 URL | 卡片 |
| video_url | 视频播放地址 | 详情页 |
| author_name | 作者昵称 | 卡片+详情页 |
| author_avatar | 作者头像 | 卡片+详情页 |
| play_count | 播放量/浏览量 | 卡片+详情页 |
| like_count | 点赞数 | 卡片+详情页 |
| comment_count | 评论数 | 卡片+详情页 |
| share_count | 分享/转发数 | 详情页 |
| publish_time | 发布时间 | 卡片+详情页 |
| platform | 来源平台 | 卡片+详情页 |
| original_url | 原文链接 | 详情页 |
| tags | 标签/话题 | 详情页 |
---
## 5. 技术方案(推荐)
### 5.1 技术栈选择
| 层级 | 技术 | 理由 |
|------|------|------|
| 框架 | **Next.js 14+ (App Router)** | 全栈能力、API Routes 做后端代理、SSR 支持、Vercel 一键部署 |
| UI 库 | **Tailwind CSS + shadcn/ui** | 简约现代风格、组件丰富、高度可定制 |
| 状态管理 | **Zustand** | 轻量、简洁、适合中小型项目 |
| 数据请求 | **TanStack Query (React Query)** | 缓存管理、自动刷新、loading/error 状态 |
| 本地存储 | **localStorage / IndexedDB** | 收藏数据持久化(MVP 阶段无需数据库) |
| 包管理器 | **pnpm** | 速度快、磁盘占用小 |
### 5.2 项目架构
```
muse_creative_hotspots/
├── src/
│ ├── app/ # Next.js App Router
│ │ ├── layout.tsx # 全局布局
│ │ ├── page.tsx # 首页(热点内容流)
│ │ ├── detail/[id]/ # 内容详情页
│ │ ├── favorites/ # 收藏页
│ │ ├── settings/ # 设置页
│ │ └── api/ # API Routes(代理 TikHub
│ │ └── tikhub/
│ │ └── [platform]/route.ts
│ ├── components/ # UI 组件
│ │ ├── layout/ # 布局组件(Header, Sidebar, etc.
│ │ ├── card/ # 内容卡片组件
│ │ └── ui/ # shadcn/ui 基础组件
│ ├── lib/ # 工具库
│ │ ├── tikhub.ts # TikHub API 封装
│ │ ├── platforms.ts # 平台配置与适配器
│ │ └── utils.ts # 通用工具函数
│ ├── stores/ # Zustand 状态管理
│ │ ├── favorites.ts # 收藏状态
│ │ └── settings.ts # 设置状态
│ └── types/ # TypeScript 类型定义
│ └── content.ts # 统一内容数据结构
├── public/ # 静态资源
├── tailwind.config.ts
├── next.config.ts
├── package.json
└── PRD.md
```
### 5.3 关键设计决策
1. **API 代理层**:通过 Next.js API Routes 代理 TikHub 请求,避免前端暴露 API Key
2. **统一数据模型**:所有平台的内容映射为统一的 `ContentItem` 类型,平台差异在适配器层处理
3. **平台适配器模式**:每个平台实现一个适配器,负责 API 调用和数据格式转换,方便后续扩展新平台
---
## 6. 页面设计规格
### 6.1 设计风格
- **简约现代**(参考 Notion/Linear 风格)
- 大量留白,信息层次清晰
- 浅色主题为主,后期可扩展暗色模式
- 平台图标采用各平台官方 logo 配色,便于快速识别
### 6.2 页面结构
```
┌─────────────────────────────────────────────┐
│ Logo [全部][抖音][TikTok][小红书]... ⚙️ │ ← 顶部导航:平台 Tab 切换
├─────────────────────────────────────────────┤
│ 排序: [最热] [最新] 🔄 刷新 上次: 10:30 │ ← 工具栏:排序+刷新
├─────────────────────────────────────────────┤
│ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │封面│ │封面│ │封面│ │封面│ │封面│ │
│ │ │ │ │ │ │ │ │ │ │ │
│ │标题│ │标题│ │标题│ │标题│ │标题│ │ ← 卡片网格信息流
│ │数据│ │数据│ │数据│ │数据│ │数据│ │
│ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ │
│ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │
│ │ .. │ │ .. │ │ .. │ │ .. │ │ .. │ │
│ └────┘ └────┘ └────┘ └────┘ └────┘ │
│ │
└─────────────────────────────────────────────┘
```
---
## 7. 非功能需求
| 项目 | 要求 |
|------|------|
| 部署方式 | 先本地开发(localhost),后期部署到 Vercel 或云服务器 |
| 响应式 | 桌面端优先,基本适配平板 |
| 性能 | 首屏加载 < 3s,支持图片懒加载 |
| API 成本控制 | 默认 30 分钟刷新一次,支持手动调整;需展示当日 API 调用量 |
---
## 8. MVP 验收标准
- [ ] 成功接入抖音、TikTok、小红书三个平台的热榜/推荐内容
- [ ] 卡片流展示内容,包含封面图、标题、关键数据
- [ ] 可按平台 Tab 切换查看
- [ ] 可按播放量/点赞数排序
- [ ] 点击卡片进入站内详情页,展示完整数据+原文链接
- [ ] 收藏功能可用,收藏数据本地持久化
- [ ] 支持手动刷新 + 自动定时刷新
- [ ] 设置页可配置 API Key 和刷新间隔
---
## 验证方式
1. `pnpm dev` 启动本地开发服务器
2. 访问首页,确认三个平台的热点内容正常加载和展示
3. 测试平台切换、排序、收藏、详情页等功能
4. 检查 API 代理层是否正确隐藏了 API Key
5. 测试自动刷新和手动刷新功能
+995
View File
@@ -0,0 +1,995 @@
# 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) |
+148
View File
@@ -0,0 +1,148 @@
# Muse Creative Hotspots — 任务列表
## 文档信息
| 项目 | 内容 |
|------|------|
| 版本 | v2.0 |
| 创建日期 | 2026-03-03 |
| 架构 | 前后端分离 Monorepo@muse/shared + @muse/backend + @muse/frontend |
## 1. 架构概览
```
museCreativeHotspots/
├── pnpm-workspace.yaml
├── packages/
│ ├── shared/ # @muse/shared — 共享类型和平台配置
│ ├── backend/ # @muse/backend — Hono API 服务器 (port 3001)
│ └── frontend/ # @muse/frontend — Next.js 前端 (port 3000)
```
---
## 2. Phase 1 — 基础架构搭建(已完成 ✅)
**目标**: 搭建 Monorepo 骨架,共享类型系统,API 代理链路,平台适配器。
| ID | 任务 | 包 | 状态 |
|----|------|----|------|
| T-001 | Monorepo 初始化 (pnpm workspace + 三个包) | root | ✅ |
| T-002 | TypeScript 类型定义 (ContentItem, Platform, PlatformAdapter) | @muse/shared | ✅ |
| T-003 | 平台配置 (MVP_PLATFORMS, getPlatformConfig) | @muse/shared | ✅ |
| T-004 | TikHub API 客户端与限流 (tikhubFetch, waitForSlot) | @muse/backend | ✅ |
| T-005 | 平台适配器 — 抖音 | @muse/backend | ✅ |
| T-006 | 平台适配器 — TikTok | @muse/backend | ✅ |
| T-007 | 平台适配器 — 小红书 | @muse/backend | ✅ |
| T-008 | 适配器注册表 (getAdapter, getSupportedPlatforms) | @muse/backend | ✅ |
| T-009 | Hono API 路由 — 热榜 (GET /:platform) | @muse/backend | ✅ |
| T-010 | Hono API 路由 — 详情 (GET /:platform/detail) | @muse/backend | ✅ |
| T-011 | Hono API 路由 — 设置 (GET /, POST /) | @muse/backend | ✅ |
---
## 3. Phase 2 — 核心功能实现(已完成 ✅)
**目标**: 前端页面、组件、数据查询、状态管理。
| ID | 任务 | 包 | 状态 |
|----|------|----|------|
| T-012 | Zustand Store — settings (apiKey, refreshInterval, persist) | @muse/frontend | ✅ |
| T-013 | Zustand Store — favorites (addFavorite, removeFavorite, persist) | @muse/frontend | ✅ |
| T-014 | TanStack Query 集成 (useContentQuery, useDetailQuery) | @muse/frontend | ✅ |
| T-015 | API_BASE_URL 前缀 (所有 fetch 指向 backend 3001) | @muse/frontend | ✅ |
| T-016 | 内容卡片组件 (ContentCard + ContentGrid + CardSkeleton) | @muse/frontend | ✅ |
| T-017 | 平台 Tab 切换 (PlatformTabs) | @muse/frontend | ✅ |
| T-018 | 排序功能 (SortToolbar) | @muse/frontend | ✅ |
| T-019 | 内容详情页 (DetailPanel + DetailSkeleton) | @muse/frontend | ✅ |
| T-020 | 收藏按钮组件 (FavoriteButton) | @muse/frontend | ✅ |
| T-021 | 首页页面组装 (page.tsx) | @muse/frontend | ✅ |
| T-022 | 设置页面 (settings/page.tsx) | @muse/frontend | ✅ |
| T-023 | 收藏夹页面 (favorites/page.tsx) | @muse/frontend | ✅ |
| T-024 | 全局布局 (layout.tsx + Header + QueryProvider) | @muse/frontend | ✅ |
---
## 4. Phase 3 — 测试与验证(已完成 ✅)
**目标**: 各包独立测试,80% 覆盖率阈值。
| ID | 任务 | 包 | 状态 |
|----|------|----|------|
| T-025 | 共享包测试 (platforms.test.ts) | @muse/shared | ✅ |
| T-026 | 后端单元测试 — 限流器、API 客户端 | @muse/backend | ✅ |
| T-027 | 后端单元测试 — 适配器 (douyin, tiktok, xiaohongshu) | @muse/backend | ✅ |
| T-028 | 后端集成测试 — Hono 路由 (app.request 风格) | @muse/backend | ✅ |
| T-029 | 前端单元测试 — stores (favorites, settings) | @muse/frontend | ✅ |
| T-030 | 前端单元测试 — format.ts | @muse/frontend | ✅ |
---
## 5. Phase 4 — 新增平台适配器(已完成 ✅)
**目标**: 新增 YouTube、Instagram、Twitter/X、哔哩哔哩、微博 5 个平台的适配器、配置和测试。
| ID | 任务 | 包 | 状态 |
|----|------|----|------|
| T-031 | 平台适配器 — YouTube | @muse/backend | ✅ |
| T-032 | 平台适配器 — Instagram | @muse/backend | ✅ |
| T-033 | 平台适配器 — Twitter/X | @muse/backend | ✅ |
| T-034 | 平台适配器 — 哔哩哔哩 | @muse/backend | ✅ |
| T-035 | 平台适配器 — 微博 | @muse/backend | ✅ |
| T-036 | 适配器注册表更新 (8 个平台) | @muse/backend | ✅ |
| T-037 | 共享包平台配置更新 (MVP_PLATFORMS 8 项) | @muse/shared | ✅ |
| T-038 | YouTube 适配器单元测试 | @muse/backend | ✅ |
| T-039 | Instagram 适配器单元测试 | @muse/backend | ✅ |
| T-040 | Twitter/X 适配器单元测试 | @muse/backend | ✅ |
| T-041 | 哔哩哔哩适配器单元测试 | @muse/backend | ✅ |
| T-042 | 微博适配器单元测试 | @muse/backend | ✅ |
| T-043 | E2E Mock 测试更新 (fixtures + home.spec) | e2e/ | ✅ |
| T-044 | E2E 真实测试更新 (新平台切换) | e2e-real/ | ✅ |
---
## 6. 启动方式
```bash
# 安装依赖
pnpm install
# 同时启动前后端
pnpm dev
# 仅启动后端 (port 3001)
pnpm --filter @muse/backend dev
# 仅启动前端 (port 3000)
pnpm --filter @muse/frontend dev
# 运行全部测试
pnpm test
# 运行单包测试
pnpm --filter @muse/backend test
pnpm --filter @muse/frontend test
pnpm --filter @muse/shared test
```
---
## 7. 关键设计决策
1. **shared 包不构建**: 直接导出 `.ts` 源文件,消费方各自编译
2. **app.ts 与 index.ts 分离**: app.ts 只导出 Hono 实例,测试可直接 import
3. **NEXT_PUBLIC_ 前缀**: 前端 API URL 使用此前缀确保客户端可读
4. **CORS 中间件**: 后端配置 CORS 允许前端跨域调用
5. **API 路由前缀**: 保持 `/api/tikhub/...``/api/settings` 路径不变
---
## 8. 未来计划 (v1.1+)
| 功能 | 描述 |
|------|------|
| F-012 平台管理 | 前端可启用/禁用平台 |
| F-013 展示数量设置 | 用户自定义每页展示数量 |
| F-017 API 调用量统计 | 后端统计 API 调用次数 |
| Docker 部署 | 容器化部署方案 |
| E2E 测试 | Playwright 端到端测试 |
+901
View File
@@ -0,0 +1,901 @@
# TikHub API 参考文档
> 版本:V5.2.92025-03-08
> Swagger UIhttps://api.tikhub.io
> ReDochttps://api.tikhub.io/docs
> Apifox 文档:https://docs.tikhub.io
---
## 基础信息
| 项目 | 说明 |
|------|------|
| Base URL(国际)| `https://api.tikhub.io` |
| Base URL(中国大陆)| `https://api.tikhub.dev`(路径和参数完全相同,仅域名不同)|
| 认证方式 | `Authorization: Bearer {API_TOKEN}` |
| 计费 | $0.001 / 请求(按量付费,支持批量折扣)|
| 速率限制 | 令牌桶 10 req/s(本项目在 `src/lib/tikhub.ts` 中已实现)|
| 超时 | 15s(本项目已配置)|
所有端点均为 `GET` 请求(除特别标注),返回 JSON。
---
## 错误码
| HTTP 状态码 | 含义 |
|------------|------|
| 400 | 参数错误 |
| 401 | API Key 无效或未提供 |
| 402 | 余额不足 |
| 403 | 无权限访问该端点 |
| 404 | 内容不存在 |
| 429 | 请求过于频繁 |
| 500 | 服务器内部错误 |
---
## 抖音(Douyin
### 抖音网页版 API — `/api/v1/douyin/web/`
#### ★ `GET /api/v1/douyin/web/fetch_hot_search_result`
> **本项目使用** · 抖音热搜榜
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| 无 | — | — | 无需参数 |
响应结构:
```json
{
"data": {
"data": {
"trending_list": [
{
"sentence_id": "string", // 热搜词 ID(用作 ContentItem.id
"word": "string", // 热搜词文本
"word_cover": {
"url_list": ["string"] // 封面图列表
},
"hot_value": 10000, // 热度值(→ like_count
"view_count": 500000, // 阅读量(→ play_count
"discuss_video_count": 200, // 讨论视频数(→ comment_count
"event_time": 1709000000 // Unix 时间戳(秒)
}
],
"word_list": [/* */]
}
}
}
```
> **注意**`trending_list` 为置顶热搜,`word_list` 为普通热词。本项目合并两者并按 `sentence_id` 去重。
---
#### ★ `GET /api/v1/douyin/web/fetch_one_video`
> **本项目使用** · 抖音视频详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `aweme_id` | string | 是 | 视频 ID |
响应结构:
```json
{
"data": {
"aweme_detail": {
"aweme_id": "string",
"desc": "string", // 视频标题/描述
"video": {
"cover": { "url_list": ["string"] },
"play_addr": { "url_list": ["string"] }
},
"author": {
"nickname": "string",
"avatar_thumb": { "url_list": ["string"] }
},
"statistics": {
"play_count": 100000,
"digg_count": 5000, // 点赞数(→ like_count
"comment_count": 200,
"share_count": 300
},
"create_time": 1709000000,
"text_extra": [{ "hashtag_name": "string" }]
}
}
}
```
---
#### 其他常用端点
| 端点 | 说明 | 关键参数 |
|------|------|----------|
| `GET /api/v1/douyin/web/fetch_user_profile` | 获取用户信息 | `unique_id`(用户名)或 `sec_user_id` |
| `GET /api/v1/douyin/web/fetch_user_post` | 获取用户作品 | `sec_user_id`, `cursor`(分页), `count` |
| `GET /api/v1/douyin/web/fetch_general_search_result` | 综合搜索 | `keyword`, `count`, `offset` |
| `GET /api/v1/douyin/web/fetch_video_search_result` | 视频搜索 | `keyword`, `count`, `offset` |
| `GET /api/v1/douyin/web/fetch_user_search_result_v2` | 用户搜索 | `keyword` |
| `GET /api/v1/douyin/web/fetch_post_comment` | 获取评论 | `aweme_id`, `cursor`, `count` |
| `GET /api/v1/douyin/app/v3/fetch_hot_search_list` | App 热搜榜(App V3 推荐)| 无 |
| `GET /api/v1/douyin/billboard/*` | 各类榜单(热门/音乐/挑战等)| 参见 Swagger |
---
## TikTok
### TikTok 网页版 API — `/api/v1/tiktok/web/`
#### ★ `GET /api/v1/tiktok/web/fetch_explore_post`
> **本项目使用** · TikTok 探索/热门视频列表
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `count` | integer | 否 | 返回数量,默认 20 |
响应结构:
```json
{
"data": {
"itemList": [
{
"id": "string", // 视频 ID
"desc": "string", // 视频描述/标题
"video": {
"cover": "string", // 封面图 URL(直接字符串)
"playAddr": "string" // 播放地址
},
"author": {
"nickname": "string",
"avatarThumb": "string", // 头像 URL
"uniqueId": "string" // 用户名(用于构建主页链接)
},
"stats": {
"playCount": 500000,
"diggCount": 25000, // 点赞数(→ like_count
"commentCount": 1000,
"shareCount": 500
},
"createTime": 1709000000, // Unix 时间戳(秒)
"challenges": [{ "title": "string" }] // 话题标签(→ tags
}
]
}
}
```
> **注意**`video.cover` 是直接字符串,不是数组(与抖音不同)。
---
#### ★ `GET /api/v1/tiktok/web/fetch_post_detail`
> **本项目使用** · TikTok 视频详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `itemId` | string | 是 | 视频 ID |
响应结构:
```json
{
"data": {
"itemInfo": {
"itemStruct": { /* itemList */ }
}
}
}
```
---
#### 其他常用端点
| 端点 | 说明 | 关键参数 |
|------|------|----------|
| `GET /api/v1/tiktok/web/fetch_user_profile` | 用户信息 | `uniqueId``secUid` |
| `GET /api/v1/tiktok/web/fetch_user_post` | 用户作品 | `secUid`, `cursor`, `count`, `post_item_list_request_type`(0=默认/1=热门/2=最旧) |
| `GET /api/v1/tiktok/web/fetch_user_like` | 用户喜欢(需公开)| `secUid`, `cursor`, `count` |
| `GET /api/v1/tiktok/web/fetch_post_comment` | 视频评论 | `aweme_id`, `cursor`, `count` |
| `GET /api/v1/tiktok/web/fetch_search_video` | 视频搜索 | `keyword`, `count`, `offset`, `search_id` |
| `GET /api/v1/tiktok/web/fetch_search_user` | 用户搜索 | `keyword`, `cursor`, `search_id` |
| `GET /api/v1/tiktok/web/fetch_tag_detail` | 话题详情 | `tag_name` |
| `GET /api/v1/tiktok/web/fetch_user_fans` | 粉丝列表 | `secUid`, `count`(默认30) |
| `GET /api/v1/tiktok/web/fetch_user_follow` | 关注列表 | `secUid`, `count`(默认30) |
| `POST /api/v1/tiktok/web/fetch_home_feed` | 首页推荐 | `count`, `cookie` |
---
## 小红书(Xiaohongshu
### 小红书 Web V2 API — `/api/v1/xiaohongshu/web_v2/`
#### ★ `GET /api/v1/xiaohongshu/web_v2/fetch_hot_list`
> **本项目使用** · 获取热榜关键词列表(第一步)
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| 无 | — | — | 无需参数 |
响应结构:
```json
{
"data": {
"data": {
"items": [
{ "title": "string" } // 热词标题,取第一个作为搜索关键词
]
}
}
}
```
---
### 小红书 App V2 API — `/api/v1/xiaohongshu/app_v2/`(推荐)
#### ★ `GET /api/v1/xiaohongshu/app_v2/search_notes`
> **本项目使用** · 按关键词搜索笔记(第二步)
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `keyword` | string | 是 | 搜索关键词 |
| `page` | integer | 否 | 页码,默认 1 |
响应结构:
```json
{
"data": {
"data": {
"items": [
{
"model_type": "note", // 只处理 model_type==='note' 的条目
"note": {
"id": "string",
"title": "string",
"desc": "string",
"type": "normal | video",
"timestamp": 1709000000,
"images_list": [
{
"url": "string",
"url_size_large": "string" // 优先使用大图
}
],
"user": {
"nickname": "string",
"images": "string", // 头像 URL
"user_id": "string"
},
"liked_count": 5000, // 点赞数(数字类型)
"comments_count": 200, // 评论数(注意:有 's'
"shared_count": 100,
"collected_count": 300,
"tag_info": [{ "name": "string" }]
}
}
]
}
}
}
```
> **注意**
> - `model_type` 不为 `"note"` 的条目(如广告)需过滤掉
> - 搜索结果中视频笔记的 `video_url` 不可用,仅详情接口可获取视频链接
---
#### ★ `GET /api/v1/xiaohongshu/app_v2/get_mixed_note_detail`
> **本项目使用** · 获取笔记详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `note_id` | string | 是 | 笔记 ID |
响应结构:
```json
{
"data": {
"id": "string",
"title": "string",
"name": "string", // 备用标题字段
"desc": "string",
"type": "normal | video",
"timestamp": 1709000000,
"likes": 5000, // 点赞数(注意:详情与搜索字段名不同)
"comments_count": 200,
"shared_count": 100,
"collected_count": 300,
"images_list": [
{ "url": "string", "url_size_large": "string" }
],
"user": {
"nickname": "string",
"images": "string"
},
"tag_info": [{ "name": "string" }],
"video_info_v2": {
"url": "string" // 仅视频笔记有此字段
}
}
}
```
---
#### 其他常用端点
| 端点 | 说明 | 关键参数 |
|------|------|----------|
| `GET /api/v1/xiaohongshu/app_v2/fetch_note_detail` | 笔记详情(旧版)| `note_id` |
| `GET /api/v1/xiaohongshu/web_v2/fetch_user_info` | 用户信息 | `user_id``username` |
| `GET /api/v1/xiaohongshu/web_v2/fetch_user_notes` | 用户笔记列表 | `user_id`, `cursor` |
| `GET /api/v1/xiaohongshu/app_v2/fetch_feed` | 推荐信息流 | `count` |
---
## 哔哩哔哩(Bilibili
### Bilibili 网页版 API — `/api/v1/bilibili/web/`
#### ★ `GET /api/v1/bilibili/web/fetch_popular_video_list`
> **本项目使用** · 综合热门视频列表
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `pn` | integer | 否 | 页码,默认 1 |
| `ps` | integer | 否 | 每页数量,默认 20 |
响应结构:
```json
{
"data": {
"list": [
{
"bvid": "string", // 视频 BV 号(主要 ID
"aid": 123456, // av 号(备用)
"title": "string",
"pic": "string", // 封面图 URL(直接字符串)
"desc": "string",
"owner": {
"mid": 123456,
"name": "string",
"face": "string" // UP 主头像
},
"stat": {
"view": 100000, // 播放量(→ play_count
"like": 5000, // 点赞数
"reply": 200, // 评论数(→ comment_count
"share": 300,
"danmaku": 500, // 弹幕数(未映射)
"favorite": 1000, // 收藏数(未映射)
"coin": 800 // 投币数(未映射)
},
"pubdate": 1709000000, // Unix 时间戳(秒)
"duration": 300, // 时长(秒)
"tname": "string", // 分区名(→ tags[0]
"tags": [{ "tag_id": 1, "tag_name": "string" }]
}
]
}
}
```
---
#### ★ `GET /api/v1/bilibili/web/fetch_video_detail`
> **本项目使用** · 视频详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `bvid` | string | 是 | 视频 BV 号(如 `BV1xx411c7mD`|
响应结构:
```json
{
"data": { /* list */ }
}
```
---
#### 其他常用端点
| 端点 | 说明 | 关键参数 |
|------|------|----------|
| `GET /api/v1/bilibili/web/fetch_hot_search` | 热搜词 | 无 |
| `GET /api/v1/bilibili/web/fetch_user_info` | UP 主信息 | `mid`(用户 ID|
| `GET /api/v1/bilibili/web/fetch_user_videos` | UP 主视频列表 | `mid`, `pn`, `ps` |
| `GET /api/v1/bilibili/web/fetch_video_comment` | 视频评论 | `oid`aid, `pn`, `ps` |
| `GET /api/v1/bilibili/web/fetch_ranking` | 全站排行榜(每日更新)| `tid`(分区ID), `day`(1/3/7) |
---
## 微博(Weibo
### 微博 App API — `/api/v1/weibo/app/`
#### ★ `GET /api/v1/weibo/app/fetch_hot_search`
> **本项目使用** · 微博热搜榜
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| 无 | — | — | 无需参数 |
响应结构(两种格式,取其一):
**格式 1:实际微博状态**
```json
{
"data": {
"statuses": [
{
"id": "string | number",
"mid": "string",
"text": "string", // HTML 格式,需 stripHtml 处理
"raw_text": "string",
"user": {
"screen_name": "string",
"profile_image_url": "string",
"avatar_hd": "string",
"avatar_large": "string"
},
"pic_ids": ["string"], // 图片 ID 列表
"pic_infos": {
"PIC_ID": {
"url": "string",
"large": { "url": "string" }
}
},
"attitudes_count": 5000, // 点赞数(→ like_count
"comments_count": 200,
"reposts_count": 300, // 转发数(→ share_count
"reads_count": 100000, // 阅读数(→ play_count
"created_at": "Mon Jan 01 00:00:00 +0800 2024"
}
]
}
}
```
**格式 2:热搜话题**
```json
{
"data": {
"band_list": [
{
"word": "string", // 热搜词
"num": 10000, // 热度(→ play_count
"label_name": "string", // 标签(热、沸)
"mid": "string",
"rank": 1
}
],
"realtime": [/* */]
}
}
```
> **注意**
> - `text` 字段含 HTML 标签(`<a>`, `<span>` 等),需 strip 处理
> - 图片 URL 构建:`https://ww1.sinaimg.cn/large/{pic_id}.jpg`
> - 时间格式:`"Mon Jan 01 00:00:00 +0800 2024"`,可直接用 `new Date()` 解析
---
#### ★ `GET /api/v1/weibo/app/fetch_post_detail`
> **本项目使用** · 微博详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `mid` | string | 是 | 微博 ID |
响应结构:
```json
{
"data": { /* statuses */ }
}
```
---
#### 其他常用端点
| 端点 | 说明 | 关键参数 |
|------|------|----------|
| `GET /api/v1/weibo/web/fetch_hot_search` | 网页版热搜榜 | 无 |
| `GET /api/v1/weibo/web_v2/fetch_hot_search` | 网页版 V2 热搜榜 | 无 |
| `GET /api/v1/weibo/web/fetch_user_info` | 用户信息 | `uid``screen_name` |
| `GET /api/v1/weibo/web/fetch_user_statuses` | 用户微博列表 | `uid`, `page`, `count` |
---
## YouTube
### YouTube 网页版 API — `/api/v1/youtube/web/`
#### ★ `GET /api/v1/youtube/web/fetch_trending_video`
> **本项目使用** · YouTube 热门视频列表
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `count` | integer | 否 | 返回数量,默认 20 |
响应结构:
```json
{
"data": {
"items": [
{
"id": "string | { videoId: string }", // 视频 ID(可能是字符串或对象)
"snippet": {
"title": "string",
"description": "string",
"channelTitle": "string",
"publishedAt": "2024-01-15T10:00:00Z", // ISO 8601
"thumbnails": {
"maxres": { "url": "string" },
"high": { "url": "string" },
"medium": { "url": "string" },
"default": { "url": "string" }
},
"tags": ["string"]
},
"statistics": {
"viewCount": "100000", // 注意:YouTube 统计字段是字符串类型
"likeCount": "5000", // 需 parseInt() 转换
"commentCount": "200"
}
}
]
}
}
```
> **注意**
> - `statistics.viewCount` 等字段为**字符串类型**,需用 `parseInt()` 转换
> - `id` 字段可能是字符串或 `{ videoId: string }` 对象,需兼容处理
> - 缩略图优先级:`maxres > high > medium > default`
---
#### ★ `GET /api/v1/youtube/web/fetch_video_detail`
> **本项目使用** · YouTube 视频详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `video_id` | string | 是 | 视频 ID(如 `dQw4w9WgXcQ`|
响应结构:
```json
{
"data": {
"items": [{ /* trending */ }]
}
}
```
---
#### 其他常用端点
| 端点 | 说明 | 关键参数 |
|------|------|----------|
| `GET /api/v1/youtube/web/fetch_channel_info` | 频道信息 | `channel_id``username` |
| `GET /api/v1/youtube/web/fetch_channel_videos` | 频道视频列表 | `channel_id`, `count`, `page_token` |
| `GET /api/v1/youtube/web/fetch_search_results` | 视频搜索 | `keyword`, `count`, `page_token` |
| `GET /api/v1/youtube/web/fetch_playlist_videos` | 播放列表视频 | `playlist_id`, `count`, `page_token` |
---
## Instagram
### Instagram 网页版 API — `/api/v1/instagram/web/`
#### ★ `GET /api/v1/instagram/web/fetch_explore_feed`
> **本项目使用** · Instagram 探索页内容
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `count` | integer | 否 | 返回数量,默认 20 |
响应结构(两种格式,需同时处理):
**格式 1:扁平 items 数组**
```json
{
"data": {
"items": [
{
"media": { /* InstagramMediaItem */ }
}
]
}
}
```
**格式 2:分区 sectional_items**
```json
{
"data": {
"sectional_items": [
{
"layout_content": {
"medias": [
{ "media": { /* InstagramMediaItem */ } }
]
}
}
]
}
}
```
**InstagramMediaItem 结构**
```json
{
"pk": "string",
"id": "string",
"code": "string", // shortcode(用于构建 URL 和作为 ID
"media_type": 1, // 1=图片, 2=视频, 8=轮播
"caption": { "text": "string" } | "string" | null,
"image_versions2": {
"candidates": [
{ "url": "string", "width": 1080, "height": 1080 }
]
},
"thumbnail_url": "string", // 视频封面(备用)
"video_url": "string", // 仅 media_type=2 时有值
"user": {
"username": "string",
"full_name": "string",
"profile_pic_url": "string"
},
"like_count": 5000,
"comment_count": 200,
"taken_at": 1709000000 // Unix 时间戳(秒)
}
```
> **注意**
> - 以 `code`shortcode)作为内容 IDURL 格式:`https://www.instagram.com/p/{code}/`
> - `caption` 字段格式不固定,可能是对象、字符串或 null,需兼容处理
---
#### ★ `GET /api/v1/instagram/web/fetch_post_detail`
> **本项目使用** · Instagram 帖子详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `shortcode` | string | 是 | 帖子 shortcode(即内容 ID|
响应结构:
```json
{
"data": {
"items": [{ /* InstagramMediaItem */ }]
}
}
```
---
#### 其他常用端点
| 端点 | 说明 | 关键参数 |
|------|------|----------|
| `GET /api/v1/instagram/web/fetch_user_info` | 用户信息 | `username` |
| `GET /api/v1/instagram/web/fetch_user_posts` | 用户帖子 | `username`, `cursor`, `count` |
| `GET /api/v1/instagram/web/fetch_search_result` | 搜索用户/标签 | `keyword` |
| `GET /api/v1/instagram/web/fetch_hashtag_posts` | 标签内容 | `hashtag`, `cursor` |
---
## Twitter / X
### Twitter 网页版 API — `/api/v1/twitter/web/`
#### ★ `GET /api/v1/twitter/web/fetch_trending_topics`
> **本项目使用** · Twitter 热门话题和趋势推文
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `count` | integer | 否 | 返回数量,默认 20 |
响应结构(三种格式,需全部处理):
**格式 1:推文数组**
```json
{
"data": {
"tweets": [
{
"id_str": "string",
"full_text": "string", // 优先使用
"text": "string", // 备用
"user": {
"name": "string",
"screen_name": "string", // 用户名(@后面的部分)
"profile_image_url_https": "string"
},
"favorite_count": 5000, // 点赞数(→ like_count
"reply_count": 200, // 回复数(→ comment_count
"retweet_count": 300, // 转推数(→ share_count
"created_at": "Mon Jan 01 00:00:00 +0000 2024",
"entities": {
"media": [
{ "media_url_https": "string", "type": "photo | video" }
],
"hashtags": [{ "text": "string" }]
},
"extended_entities": {
"media": [/* 使 */]
}
}
]
}
}
```
**格式 2GraphQL 时间线(Timeline Instructions**
```json
{
"data": {
"timeline": {
"instructions": [
{
"entries": [
{
"content": {
"itemContent": {
"tweet_results": {
"result": {
"legacy": { /* TwitterTweet */ },
"core": {
"user_results": {
"result": {
"legacy": { /* TwitterUser */ }
}
}
}
}
}
}
}
}
]
}
]
}
}
}
```
**格式 3:热门话题列表**
```json
{
"data": {
"trends": [
{
"name": "string", // 话题名称
"tweet_volume": 10000, // 推文量(→ play_count
"query": "string" // URL 编码的搜索查询
}
]
}
}
```
> **注意**
> - 时间格式:`"Mon Jan 01 00:00:00 +0000 2024"`,可直接用 `new Date()` 解析
> - 封面图来自 `extended_entities.media`(优先)或 `entities.media`,找 `type === 'photo'` 的条目
> - 原帖 URL 格式:`https://x.com/{screen_name}/status/{id_str}`
---
#### ★ `GET /api/v1/twitter/web/fetch_tweet_detail`
> **本项目使用** · 推文详情
| 参数 | 类型 | 必填 | 说明 |
|------|------|------|------|
| `tweet_id` | string | 是 | 推文 ID |
响应结构(两种格式):
```json
// 格式 1GraphQL
{
"data": {
"tweetResult": {
"result": {
"legacy": { /* TwitterTweet */ },
"core": { "user_results": { "result": { "legacy": { /* TwitterUser */ } } } }
}
}
}
}
// 格式 2(直接对象)
{
"data": {
"tweet": { /* TwitterTweet */ }
}
}
```
---
#### 其他常用端点
| 端点 | 说明 | 关键参数 |
|------|------|----------|
| `GET /api/v1/twitter/web/fetch_user_info` | 用户信息 | `screen_name` |
| `GET /api/v1/twitter/web/fetch_user_tweets` | 用户推文 | `screen_name`, `cursor`, `count` |
| `GET /api/v1/twitter/web/fetch_search_timeline` | 搜索推文 | `keyword`, `cursor`, `count` |
| `GET /api/v1/twitter/web/fetch_tweet_replies` | 推文回复 | `tweet_id`, `cursor` |
---
## 通用说明
### 本项目中使用的端点汇总
| 平台 | fetchHotList 端点 | fetchDetail 端点 |
|------|------------------|-----------------|
| 抖音 | `douyin/web/fetch_hot_search_result` | `douyin/web/fetch_one_video` → 回退 `fetch_hot_search_result` |
| TikTok | `tiktok/web/fetch_explore_post` | `tiktok/web/fetch_post_detail` |
| 小红书 | `xiaohongshu/web_v2/fetch_hot_list` + `xiaohongshu/app_v2/search_notes` | `xiaohongshu/app_v2/get_mixed_note_detail` |
| YouTube | `youtube/web/fetch_trending_video` | `youtube/web/fetch_video_detail` |
| Instagram | `instagram/web/fetch_explore_feed` | `instagram/web/fetch_post_detail` |
| Twitter | `twitter/web/fetch_trending_topics` | `twitter/web/fetch_tweet_detail` |
| 哔哩哔哩 | `bilibili/web/fetch_popular_video_list` | `bilibili/web/fetch_video_detail` |
| 微博 | `weibo/app/fetch_hot_search` | `weibo/app/fetch_post_detail` |
### 时间戳处理
| 平台 | 时间格式 | 处理方式 |
|------|---------|----------|
| 抖音、TikTok、小红书、B站、微博、Instagram | Unix 时间戳(秒)| `new Date(timestamp * 1000).toISOString()` |
| YouTube | ISO 8601 字符串 | 直接使用 |
| Twitter | `"Mon Jan 01 00:00:00 +0000 2024"` | `new Date(dateStr).toISOString()` |
| 微博 | `"Mon Jan 01 00:00:00 +0800 2024"` | `new Date(dateStr).toISOString()` |
### 图片 URL 类型
| 平台 | 图片字段类型 |
|------|------------|
| 抖音 | `url_list: string[]`(取 `[0]`|
| TikTok | `cover: string`(直接字符串)|
| 小红书 | `images_list[0].url_size_large \|\| url` |
| B站 | `pic: string`(直接字符串)|
| YouTube | `thumbnails.maxres.url`(对象嵌套)|
| Instagram | `image_versions2.candidates[0].url` |
| Twitter | `entities/extended_entities.media[0].media_url_https` |
| 微博 | `pic_infos[id].large.url``https://ww1.sinaimg.cn/large/{id}.jpg` |
---
*文档基于项目实际使用端点 + TikHub OpenAPI 规范整理。完整端点列表(700+)请访问 https://api.tikhub.io*