# 画布模板导入导出包(`.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`:存储层重构后文档落库设计。