232 lines
6.5 KiB
Markdown
232 lines
6.5 KiB
Markdown
# 画布模板导入导出包(`.wcd`)方案(规划)
|
||
|
||
> 状态:第一版已实现(画布导出 `.wcd`、首页导入 `.wcd`)。
|
||
> 目的:实现画布/设计的完整导入导出,要求包内不仅包含画布尺寸、背景、图层、元素摆放信息,还要把元素用到的素材一起带出去,使得包可以在另一台机器或另一个实例中导入复用。
|
||
|
||
## 1. 包格式
|
||
|
||
建议采用 Zip 包,文件后缀为 `.wcd`。没有必要自定义二进制格式。
|
||
|
||
预期目录结构:
|
||
|
||
```
|
||
example.wcd
|
||
├── manifest.json // 包元数据与 schema version
|
||
├── document.json // CanvasDocument:画布结构
|
||
├── preview.png // 可选封面图
|
||
├── fonts/ // 可选字体文件
|
||
│ └── ...
|
||
└── assets/
|
||
├── asset-001.svg
|
||
├── asset-002.png
|
||
└── asset-003.svg
|
||
```
|
||
|
||
## 2. manifest.json
|
||
|
||
```json
|
||
{
|
||
"format": "wordcloud-canvas",
|
||
"version": 1,
|
||
"name": "海报模板",
|
||
"description": "示例模板",
|
||
"createdAt": "2026-08-06T00:00:00Z",
|
||
"canvas": {
|
||
"width": 1600,
|
||
"height": 1000,
|
||
"background": "#ffffff"
|
||
},
|
||
"assets": [
|
||
{
|
||
"id": "asset-001",
|
||
"originalAssetId": "asset_xxx",
|
||
"name": "词云 A",
|
||
"type": "svg",
|
||
"mimeType": "image/svg+xml",
|
||
"sha256": "abc...",
|
||
"size": 1024
|
||
}
|
||
],
|
||
"fonts": []
|
||
}
|
||
```
|
||
|
||
字段说明:
|
||
|
||
- `format`:固定标识,防止其他 Zip 被误导入。
|
||
- `version`:包格式版本,后续升级时便于兼容。
|
||
- `assets[].id`:包内临时 ID,只在这个包内有效。
|
||
- `originalAssetId`:导出时的来源素材 ID,仅记录,不要求导入后保留。
|
||
- `sha256`:可选,导入时用于去重。
|
||
|
||
## 3. document.json
|
||
|
||
`document.json` 就是当前前端的 `CanvasDocument` 模型:
|
||
|
||
- `width`:画布宽度。
|
||
- `height`:画布高度。
|
||
- `background`:画布背景色。
|
||
- `layers`:图层列表。
|
||
- `layerFolders`:图层文件夹列表。
|
||
- `elements`:元素列表。
|
||
|
||
对 Sticker 元素有一个关键规则:**导出时把 `assetId` 替换成包内临时 ID**。
|
||
|
||
示例:
|
||
|
||
```json
|
||
{
|
||
"width": 1600,
|
||
"height": 1000,
|
||
"background": "#ffffff",
|
||
"layers": [
|
||
{ "id": "layer-1", "name": "词云", "visible": true, "locked": false }
|
||
],
|
||
"layerFolders": [],
|
||
"elements": [
|
||
{
|
||
"id": "element-1",
|
||
"type": "sticker",
|
||
"assetId": "asset-001",
|
||
"x": 100,
|
||
"y": 80,
|
||
"width": 800,
|
||
"height": 500,
|
||
"rotation": 0,
|
||
"opacity": 1
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
## 4. 导出流程
|
||
|
||
建议由后端提供导出接口,例如:
|
||
|
||
```text
|
||
GET /api/designs/{id}/export
|
||
```
|
||
|
||
或当前模板库扩展为:
|
||
|
||
```text
|
||
GET /api/design-templates/{id}/export
|
||
```
|
||
|
||
导出步骤:
|
||
|
||
1. 从数据库读取画布文档 `design_documents`。
|
||
2. 序列化 `CanvasDocument`。
|
||
3. 遍历 Sticker 元素,收集所有真实素材 ID。
|
||
4. 读取每个素材文件字节。
|
||
5. 为每个素材生成包内 ID,例如 `asset-001`。
|
||
6. 用包内 ID 替换 `document.json` 中的 `assetId`。
|
||
7. 把素材写入 `assets/` 目录。
|
||
8. 可选:生成 `preview.png` 作为导入时的缩略图。
|
||
9. 可选:如果模板使用了后端字体,把字体文件写入 `fonts/`。
|
||
10. 生成 `manifest.json`。
|
||
11. 打包为 `.wcd` 并返回。
|
||
|
||
## 5. 导入流程
|
||
|
||
建议后端提供导入接口,例如:
|
||
|
||
```text
|
||
POST /api/designs/import
|
||
multipart/form-data: file=.wcd
|
||
```
|
||
|
||
导入步骤:
|
||
|
||
1. 把 `.wcd` 解压到临时目录。
|
||
2. 校验 `manifest.json`:
|
||
- 是否是 `wordcloud-canvas` 格式。
|
||
- `version` 是否兼容。
|
||
- `document.json` 是否结构合法。
|
||
3. 读取 `document.json` 并通过现有 `normalizeDocument` 逻辑归一化。
|
||
4. 逐个处理 `assets/` 下素材:
|
||
- 计算 SHA-256。
|
||
- 如果素材表中已有相同 SHA-256,复用已有素材 ID。
|
||
- 否则调用素材导入逻辑写入 `assets` 表 + 文件系统。
|
||
5. 把 `document.json` 中的包内 `assetId` 重新映射为真实素材 ID。
|
||
6. 保存为新的 `design_documents`。
|
||
7. 可选:把 `preview.png` 作为模板封面。
|
||
8. 返回新设计/模板 ID。
|
||
|
||
## 6. 与现有模板系统的关系
|
||
|
||
当前模板保存是把 `CanvasDocument` 和 `reference_asset_ids` 写到目录 JSON 里:
|
||
|
||
- 在线模板:保持后端素材引用,适合当前实例内复用。
|
||
- `.wcd`:把素材一起打包,适合跨机器/离线/换实例导入导出。
|
||
|
||
两者最终统一到:
|
||
|
||
- `design_documents`:存画布文档。
|
||
- `assets`:存素材元数据。
|
||
- `design_templates`:存模板元数据并引用素材。
|
||
|
||
`.wcd` 只是外部交换容器。
|
||
|
||
## 7. 边界与设计决策
|
||
|
||
### 先不做自定义二进制格式
|
||
|
||
Zip + JSON 足够,方便调试、校验和后续扩展。
|
||
|
||
### 不把素材 base64 塞进 document.json
|
||
|
||
素材单独放文件,避免 JSON 膨胀;`document.json` 只保存引用 ID。
|
||
|
||
### 素材缺失处理
|
||
|
||
导出时如果某个素材文件缺失,可以选择:
|
||
|
||
- 导出失败并提示哪个素材缺失。
|
||
- 或在 `manifest` 中标记为 `missing`,导入时提示并跳过。
|
||
|
||
建议第一版采用“导出失败并提示”,保证导入包完整。
|
||
|
||
### 字体处理
|
||
|
||
第一版建议只保留 `fontFamily` 字符串,不打包字体。
|
||
|
||
后续如果确实需要跨机器还原,再把字体文件放进 `fonts/`,导入时注册到字体库。
|
||
|
||
### 去重
|
||
|
||
`assets.sha256` 是导入去重的关键字段:
|
||
|
||
- 包内相同素材只存一次。
|
||
- 多次导入相同素材时直接复用数据库中的现有素材。
|
||
|
||
## 8. 分阶段实施
|
||
|
||
### 阶段一:最小可用包(已完成)
|
||
|
||
- 定义 `.wcd`,包含 `manifest.json`、`document.json`、`assets/`。
|
||
- 支持导出当前画布或模板。
|
||
- 支持解压导入,只处理贴纸素材和画布布局。
|
||
- 不处理字体,不生成 preview。
|
||
|
||
### 阶段二:导入体验完善
|
||
|
||
- 生成 `preview.png`。
|
||
- 导入时检查素材缺失。
|
||
- 支持同一设计重复导入去重。
|
||
|
||
### 阶段三:与 PostgreSQL 打通
|
||
|
||
- `POST /api/designs/import` 最终写入 `design_documents`。
|
||
- 素材导入自动写入 `assets` 表。
|
||
- 导出接口直接读取 `design_documents` 和 `assets`,不再依赖前端状态。
|
||
|
||
## 9. 相关文件参考
|
||
|
||
- `frontend/src/lib/svgExport.ts`:当前图层 ZIP 导出。
|
||
- `frontend/src/lib/templateLibrary.ts`:当前模板保存/读取。
|
||
- `frontend/src/lib/canvasDocument.ts`:`CanvasDocument` 模型与归一化。
|
||
- `frontend/src/types.ts`:`CanvasDocument`、`StickerAsset` 等类型。
|
||
- `backend/service/app.py`:当前 `/api/assets`、`/api/design-templates` 接口。
|
||
- `docs/DESIGN_DATA_STORAGE_PLAN.md`:存储层重构后文档落库设计。
|