# 社群常用资料（热门资料）区块 — 设计与后端接口文档

- 日期：2026-05-29
- 作者：前端
- 状态：待评审
- 关联需求：1.6 熱門資料區

---

## 1. 背景与问题

首页（第一页）的「热门资料」区块当前与「最新更新」区块**使用同一个组件**（`LatestUpdateRow`，图标 + 文字行卡），两个区块视觉上几乎一模一样，用户无法区分。同时该区块：

- **没有封面**，不符合需求「展示封面」；
- 与官方推荐（大封面横滑）、最新更新（图标行卡）需要在首页形成**三种不同的版式**。

需求 1.6 明确：热门资料由后台数据自动排序，但**前端只展示资料、不展示具体数字**。

本文档定义两部分工作：

- **前端**：把该区块改造为「榜单」版式（封面 + 名次，无任何数字）。
- **后端**：提供/调整支撑该区块的接口（本文档的主要交付物，交付给后端团队实现）。

---

## 2. 范围

> **产品前提：第一版不做登录系统。** 凡是依赖用户身份的功能（收藏）一律顺延到 Phase 2。

### Phase 1（本次）

- 前端：首页区块改为榜单版式，卡片含**预览 + 下载**操作（两者都不依赖登录）。
- 后端：
  - `GET /api/posts?sort=popular` 热度排序；
  - 下载计数；
  - 管理员推荐权重字段。

### Phase 2（后续，本文档仅登记，不在本次实现）

- 收藏功能（依赖登录 / 用户身份系统——第一版无登录，故顺延；当前 Favorites 页仍为 Coming Soon）；
- 分享计数；
- 独立的「热门资料」Tab / 列表页。

---

## 3. 命名

- 区块标题：**热门资料**（zh-CN） / **Popular assets**（en）。
- 内部排序标识沿用 `sort=popular`，不改路由与参数。

> 备注：热度时间窗（累计 vs 本周）见 §7 开放问题。

---

## 4. 前端排版设计（前端负责）

### 4.1 组件

新增榜单组件（暂名 `PopularRankRow` / `PopularRankList`），替换首页 `popular` 区块当前的 `LatestUpdateRow` 网格（桌面）与 `MessageBubble` 列表（移动）。最多展示 **5 条**。

数据来源不变：继续调用 `/api/posts?sort=popular`，经现有 `postToResource` 适配为前端资源对象。

### 4.2 单行结构（从左到右）

| 元素 | 说明 |
|---|---|
| 名次徽章 | 第 1–3 名 🥇🥈🥉；第 4–5 名灰色等宽序号（`tabular-nums`） |
| 封面缩略图 | 取 `attachments[0]` 的 `thumbnailUrl` / `posterUrl`（复用现有 `coverFor` 逻辑）；前 3 名加金色描边；无封面时回退分类图标 |
| 标题 | 两行截断（`line-clamp-2`） |
| Meta 行 | `类型 · 分类 · 更新时间` |
| 操作 | **预览 + 下载**图标按钮（均不依赖登录；收藏依赖登录，见 Phase 2） |

### 4.3 交互

- 整行可点击，跳转 `/resource/:id`。
- **预览**按钮：打开资料预览（图片 / 视频 / 文档），不触发下载；复用现有预览浮层逻辑。
- **下载**按钮：复用现有 `downloadAttachment(postId, attachmentId)` 逻辑。
- 预览 / 下载按钮均独立响应（`stopPropagation`），点击不冒泡触发整行跳转。

### 4.4 响应式

- 移动端：单列榜单。
- 桌面端：居中定宽单列（保留「榜单」语义，避免退化成网格而与其他区块再次撞脸），行内加宽留白。

### 4.5 铁律与状态

- **零数字（指数量）**：下载量 / 收藏量 / 分享量 / 热度分等任何**数量型计数一律不显示**（即 1.6 所指「下载 500 次」类数字）。
- **名次序号属于「排名」非「数量」**：榜单展示完整名次 1–5（前 3 名 🥇🥈🥉，4–5 灰色等宽序号）。名次表达相对排序、不暴露任何后台计数，与「避免显示数字」的意图不冲突。
- 加载态：骨架屏。
- 不足 5 条：沿用现有 `ComingSoon` 占位。

---

## 5. 后端接口契约（后端负责 · 核心交付）

### 5.1 热度排序 `GET /api/posts?sort=popular`

**请求参数**（沿用现有约定，无新增必填项）：

```
GET /api/posts?sort=popular&lang=zh-CN&language=<sourceLang>&limit=5
```

**排序逻辑**：后端按热度分降序返回。

```
popularityScore =
    w_download * downloadCount
  + w_favorite * favoriteCount
  + w_share    * shareCount
  + adminWeight
```

- 建议初始权重（可配置）：`w_download = 1.0`、`w_favorite = 2.0`、`w_share = 3.0`；`adminWeight` 直接相加。
- Phase 1：`favoriteCount` / `shareCount` 暂为 0，不影响公式正确性，功能上线后自然生效。
- 同分回退顺序：`adminWeight` 降序 → `updatedAt` 降序。

**响应结构**：沿用现有 `PostListResponse`：

```jsonc
{
  "items": [ /* Post[] */ ],
  "nextCursor": "..."   // 可选
}
```

**❗硬性约束**：`items` 中**不得包含**任何计数 / 分值字段（`downloadCount`、`favoriteCount`、`shareCount`、`popularityScore` 等）。这些仅用于后端排序，前端不需要也不允许展示。

**Post 必含字段**（前端 `postToResource` 依赖，缺失会导致封面 / 类型 / 标题渲染异常）：

```jsonc
{
  "id": "string",
  "postType": "ppt | pdf | image | video | music | link | text | archive",
  "categoryId": 0,
  "categorySlug": "string",
  "language": "string",
  "attachments": [
    {
      "id": "string",
      "kind": "image | video | document",
      "url": "string",
      "mime": "string",
      "filename": "string",
      "thumbnailUrl": "string?",   // 封面优先取这里
      "posterUrl": "string?"       // 视频封面回退
    }
  ],
  "isRecommended": false,
  "publishedAt": "ISO8601",
  "updatedAt": "ISO8601",
  "localizations": { /* 多语言标题文本 */ },
  "tags": ["string"]
}
```

**封面 / 缩略图（重要）**：前端封面取值顺序为 `attachments[0].thumbnailUrl` → `posterUrl`。后端应在 body JSON 中为每条资料提供可用的封面 / 缩略图：

- 图片：`thumbnailUrl`（压缩图）；
- 视频：`posterUrl`（首帧 / 封面）；
- 文档（ppt/pdf 等）：尽量提供后端生成的预览缩略图 `thumbnailUrl`；若暂时无法生成，前端会按**资料类型**渲染兜底封面（类型色渐变 + 类型图标），但**仍建议后端长期补齐文档缩略图**以获得最佳效果。

> 前端兜底仅为优雅降级；最终视觉效果依赖后端在 body 中提供真实封面 / 缩略图。

### 5.2 下载计数

下载行为发生时累加 `downloadCount`（排序输入）。

- 采集点：现有下载接口（前端通过 `downloadAttachment(postId, attachmentId)` 触发）。
- 实现建议：在现有下载端点内 `++downloadCount`，或提供 `POST /api/posts/:postId/attachments/:attachmentId/download` 返回文件并计数。
- 防刷建议（后端定夺）：同一 IP / 设备在 N 分钟内对同一资源只计一次。

### 5.3 管理员推荐权重

- `Post` 增加字段 `adminWeight: number`（默认 `0`）。
- 后台资料编辑表单（`/api/admin/resources`）可设置该值，用于人工置顶热门。

### 5.4 Phase 2（登记，暂不实现）

- **收藏**：`POST` / `DELETE /api/posts/:id/favorite`，依赖登录 / 用户身份；累加 `favoriteCount`；提供收藏状态查询与收藏列表（支撑 Favorites 页）。
- **分享计数**：分享行为上报 `POST /api/posts/:id/share`，`++shareCount`。

---

## 6. 后端内部字段（不对外暴露）

`downloadCount`、`favoriteCount`、`shareCount`、`adminWeight`，以及派生的 `popularityScore`。前端响应中**不返回**。

---

## 7. 开放问题（需后端 / 产品确认）

1. **下载接口的确切路径与契约**：前端 `downloadAttachment` 当前对应的后端端点是什么？计数挂在哪里？
2. **热度时间窗**：是「累计热门」还是「本周 / 近 30 天热门」？若要「本周精选」语义需按时间窗统计计数。当前命名「社群常用资料」默认**累计**。
3. **数量与分页**：首页固定 5 条；Phase 2 独立热门页是否需要分页？
4. **防刷计数策略**的具体规则。
5. **预览实现**：图片 / 视频可直接用 `attachment.url` / `thumbnailUrl`；文档类（ppt/pdf）在线预览是否已有渲染服务，还是仅展示封面缩略图？（预览预计**不需要后端新接口**，待确认）

---

## 8. 验收标准

- `GET /api/posts?sort=popular` 按热度分降序返回，且响应中**无任何计数 / 分值字段**。
- 下载行为能累加 `downloadCount` 并反映到排序。
- 后台可设置 `adminWeight` 并影响排序。
- 前端首页区块呈现为榜单（含封面、名次、无数字），与「最新更新」「官方推荐」区块视觉区分明显。
