Browse Source

docs: add media library and /me/media hub design spec

Made-with: Cursor
tags/邮箱功能前置
npmrun 3 weeks ago
parent
commit
0fa7b79232
  1. 104
      docs/superpowers/specs/2026-04-19-media-library-design.md

104
docs/superpowers/specs/2026-04-19-media-library-design.md

@ -0,0 +1,104 @@
# 媒体库与「媒体」入口设计
## 背景与目标
控制台已有 **`/me/media/orphans`**(图片孤儿审查与删除)与 **`POST /api/file/upload`**(图片入库到 `media_assets`),但缺少面向作者的 **资源库**:集中上传、浏览自己名下的媒体,并 **复制公开 URL / Markdown 图片语法**,以便在文章等编辑器中手动粘贴使用。
本设计首版强调 **与编辑器解耦**:不修改 Markdown 编辑器行为;后续可在编辑器内增加「从媒体库选用」等深度集成。
## 需求结论(已确认)
- **核心价值**:以 **上传 + 选用(复制)** 为主,列表浏览为辅。
- **首版范围**:**媒体库页面 + 复制公开 URL + 复制 `![](url)`**,不在文章/资料编辑器内做集成。
- **复制形态**:同时提供 **「复制图片 URL」** 与 **「复制 Markdown 图片」** 两种操作(两个按钮或等价 UI)。
- **URL 策略**:在浏览器端使用 **`window.location.origin + '/public/assets/<storageKey>'`** 生成 **绝对 URL**,两种复制均使用该地址,避免相对路径在外部编辑器中失效。
- **导航**:**单一「媒体」入口**,下挂 **资源库****孤儿清理** 两个子能力。
- **首版不做**:媒体库内对仍被引用的资源的一键删除(避免与 `media_refs` / 正文同步规则冲突);孤儿删除与宽限逻辑 **继续仅在「孤儿清理」子页** 完成。
## 信息架构与路由
采用 **Nuxt 父布局 + 子页面**(推荐方案,已采纳):
| 路径 | 说明 |
|------|------|
| `/me/media` | 默认落地:**资源库**(`media/index.vue`) |
| `/me/media/orphans` | **图片孤儿审查**(沿用现有逻辑与 API,页面作为子路由) |
新增父级壳页面(例如 `app/pages/me/media.vue`):
- 顶区标题:**媒体**
- 子导航:**资源库** ↔ **孤儿清理**(与当前子路由联动高亮)
- 内容区:`<NuxtPage />`
文件布局约定:
- `app/pages/me/media.vue` — 壳(导航 + 插槽)
- `app/pages/me/media/index.vue` — 资源库
- `app/pages/me/media/orphans.vue` — 孤儿页(由现有页面迁入嵌套结构;业务与接口尽量不变)
## 媒体库页(功能)
### 列表
- 数据源:当前登录用户的 **`media_assets`**(`userId` 严格等于会话用户)。
- 排序:默认 **`createdAt` 降序**。
- 分页:与孤儿页类似,提供 **10 / 20 / 50** 每页可选。
- 展示:**缩略图网格**;每张卡片展示 **文件大小**、**上传时间**。
- **引用数**:对每条资产聚合 **`media_refs` 行数** 作为 `refCount` 展示,便于区分「已出现在文章/封面/资料等」与「仅上传未写入内容」。不展示可编辑的「引用详情」全文(首版保持简单)。
### 上传
- 调用现有 **`POST /api/file/upload`**(multipart,与资料页一致:格式、大小上限、多文件上限等沿用服务端实现)。
- 上传成功后 **刷新列表**,并用 toast 提示成功;失败展示服务端错误信息。
### 复制
- **复制图片 URL**:将绝对 URL 写入剪贴板。
- **复制 Markdown 图片**:写入 `![](<绝对URL>)`
- 复制成功 / 失败:**toast** 反馈。若剪贴板 API 不可用,首版以 toast 说明即可;**可选增强**:弹窗展示可手动复制的文本(实现阶段再决定是否纳入首版计划)。
### 明确不包含(首版)
- 媒体库内 **删除** 仍被引用或非孤儿宽限内的资源。
- 文章/资料编辑器内嵌媒体选择器、自动插入光标等。
## 服务端 API
### `GET /api/me/media/assets`
- **鉴权**:必须登录;仅返回 **当前用户**`media_assets`
- **Query**:`page`(从 1 起)、`pageSize`(允许值与前端分页选项对齐,服务端需校验上限)。
- **响应**(示意):`{ items: [...], total: number }`
每项至少包含:`id`、`storageKey`(或等价字段)、`sizeBytes`、`createdAt`(ISO 或统一项目惯例)、`mime`、`refCount`;**公开路径**可与前端约定为固定前缀 `/public/assets/` + `storageKey`,或由服务端返回 `publicPath` 以避免重复逻辑。
- **安全**:禁止按参数指定其他 `userId`;不得泄露其他用户资源。
列表查询实现可放在 `server/service/media` 或并列模块,保持 handler 轻薄。
## 全局导航与控制台首页
### `AppShell` 控制台子导航
- 在现有「资料 / 文章 / 时光机 / RSS」基础上增加 **「媒体」→ `/me/media`**。
- 更新相关注释(原「桌面端下拉内仅四个子链接」等)以反映 **五个** 子项。
### `/me` 控制台首页
- 将原 **「文章媒体清理」** 卡片 **合并** 为单一 **「媒体」** 入口:文案说明内含 **资源库****孤儿清理**,主按钮指向 **`/me/media`**。
- 不在首页重复放置两个独立卡片指向同一能力域(除非产品明确要求「孤儿」快捷直达,本 spec 不采用)。
## 错误处理与空状态
- 列表加载失败:toast + 可重试(按钮或自动提示)。
- 空列表:说明可上传图片,并提供上传入口。
- 上传失败:toast,错误信息来自服务端。
## 测试建议
- **`GET /api/me/media/assets`**:未登录返回 **401**;分页边界;**用户隔离**(仅本人数据);`pageSize` 非法值处理。
- 若有可单测的纯函数(例如 Markdown 拼接),可补充 **单元测试**;UI 以手测为主。
## 后续扩展(非本 spec 交付范围)
- 文章 Markdown 编辑器内:**打开媒体库**、插入链接或 Markdown。
- 资料页头像/头图:**从媒体库选用**(仍可与裁剪流程组合)。
- 媒体库内 **删除**:需与 `syncPostMediaRefs` / `syncProfileMediaRefs` 及孤儿规则统一产品设计后再做。
Loading…
Cancel
Save