定位

pf-editor 是图模工坊的设计端入口——管理员/设计师在浏览器里拖出版面、定义可编辑区域、保存为模板。它不是 PS 的替代品,而是面向”批量交付”的模板生产工具。

  • 框架:Quasar ^2.23.3 + Vue ^3.5 + TypeScript
  • 渲染引擎:Leafer ^2.2.9(开源高性能矢量渲染引擎)
  • 构建:Vite(quasar-cli-vite)|裁剪:cropperjs ^2.1.1|状态:Pinia 4
  • 端口:开发环境 8100(publicPath / base /editor/)
  • 依赖包:@sa/auth、@sa/code-renderer、@sa/color
  • 鉴权:JWT(与 pf-manage 共账号,走 /api/editor/*)

它和 PS / Figma 的区别

维度 PS / Figma pf-editor
输出 PSD / Fig 文件 sceneJson,可被代码消费
可编辑区域 靠图层命名约定 节点上显式 editable: true
字段化 用户在 PS 里改 用户在 H5 里填,编辑器只描述”哪里能改”
批量 手工另存 N 张 一份模板 + N 份用户填值 → N 张成品
人脸避让 设计师手动放区域 节点声明 faceSafe: true,合成时自动避让

源码结构

1
2
3
4
5
6
7
8
9
apps/pf-editor/src/
├── api/ # oss / template / background / font / compositionPreview / albumSize / auth
├── composables/editor/ # useEditorCanvas / useCanvasDragDrop / useOssImageUploadTasks
│ # useCanvasBackground / useLayerManager / useCanvasList / canvasDirtyTracker
├── layouts/EditorLayout/ # 面板:ImagePanel / MaterialPanel / TemplatePanel / TextPanel / ToolPanel
│ └── composables/ # useImageLibraryState / useTemplatePanelState / useMaterialPreview
├── components/property-editor/ # 属性面板 + assetUrl.ts + useFillLibrary / useFill / useImageElement
└── utils/ # oss.ts / concurrency.ts / imagePlaceholder.ts
# transientNodeMetadata.ts / transientNodeQuery.ts / printGuideMetadata.ts

sceneJson 协议

整个编辑器围绕一份 JSON 工作。简化结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
"version": "1",
"width": 800, "height": 600, // 与 templates.width/height 同步
"background": "#ffffff",
"children": [
{ "id": "bg-1", "tag": "Image", "url": "https://.../bg.jpg",
"x": 0, "y": 0, "width": 800, "height": 600, "locked": true },
{ "id": "user-photo", "tag": "Image",
"x": 100, "y": 80, "width": 600, "height": 400,
"editable": true, "placeholder": "请上传照片", "faceSafe": true, "fit": "cover" },
{ "id": "user-name", "tag": "Text",
"x": 100, "y": 520, "width": 600, "height": 40,
"editable": true, "text": "张三", "fontFamily": "SourceHanSans",
"fontSize": 32, "fill": "#222", "align": "center" }
]
}

节点关键属性

属性 类型 作用
id string 提交时作为 userUploads / userTexts 的 elementKey
tag string Leafer 节点:Rect / Image / Text / Group / Path
x/y/width/height number 几何,单位与 templates.width/height 一致
editable boolean 是否允许终端用户修改
locked boolean 编辑器侧锁定(防误改)
placeholder string 用户端未填值时的占位
faceSafe boolean 图片节点:合成时避脸裁剪
fit string 图片填充:cover / contain / fill

条码/二维码类节点由 @sa/code-renderer 提供自定义 Leafer 节点(CodeData),绑定 orderNo 动态渲染。

编辑能力

  • 基础节点:矩形、图片、文字、形状(Path)、组(Group)、条码/二维码
  • 图层管理:useLayerManager——节点树、层级、显隐、锁定
  • 画布操作:缩放/平移/对齐/吸附/网格,多画布 useCanvasList
  • 属性面板:右侧按选中节点 tag 切换子组件
  • 背景:useCanvasBackground 支持纯色/渐变/图片填充(cover/stretch)
  • 素材库联动:拉取 /api/.../background-*、/font-variants 共享素材
  • 瞬态节点:出血/裁切辅助线 + 图片加载占位统一由 isTransientNode 识别,序列化/导出时排除,不进 sceneJson
  • 持久化脏检查:canvasDirtyTracker / useTemplatePersistence

高性能数据流

编辑器按 AGENTS.md 的”海量数据 / 弱网 / 并发”准则做了三处关键优化:

1. 乐观上传 + 并发队列(useOssImageUploadTasks)

  • DEFAULT_UPLOAD_CONCURRENCY = 3,runWithConcurrencyLimit 固定 Worker 池
  • 选文件 0ms 出占位卡(URL.createObjectURL),后台直传,成功后 optimistic insert 到列表头 + id 去重
  • 非响应式 Map 存 File/AbortController/尺寸,disposeTask 单一清理,onScopeDispose 全量取消
  • 底层 uploadOssFile:预签名 → axios.put 直传(120s、1 次网络重试)

2. 模板面板无限滚动 + 瘦身 + 竞态令牌(useTemplatePanelState)

  • 分页 templateApi.getTemplates({ current, size }),pageSize 24,loadNextPage 追加,hasMore = cards.length < total
  • 竞态令牌:currentFetchToken 自增,token !== currentFetchToken 丢弃过期响应(快速检索防覆盖)
  • 贪心多列瀑布流,ResizeObserver 驱动列数,关键词/分类变更 300ms 防抖

3. 画布落图占位 + 抗跳帧(useCanvasDragDrop + imagePlaceholder)

  • 详见 传图与裁剪管线:占位缩略图零网络过渡、scheduleForceRender(3) 修 Leafer 首帧跳帧

图片面板 IMAGE_THUMB_WIDTH = 240 统一缩略图宽度,缩略图与画布占位命中同一浏览器缓存条目,并 warmImageUrl 预热。

与后端的交互

1
2
3
4
5
6
7
8
9
10
11
12
13
pf-editor
│ GET /api/editor/templates(/page) ← 列表(分页/瘦身,不含 sceneJson)
│ GET /api/editor/templates/:id ← 详情(含 sceneJson)
│ POST /api/editor/templates ← 新建
│ PUT /api/editor/templates/:id ← 保存
│ DELETE /api/editor/templates/:id ← 软删
│ 合成预览 compositionPreview(编辑期试合成)
│
│ GET /api/.../background-categories · /background-assets/page
│ GET /api/.../font-variants
│ GET /api/.../oss/upload-url (presign) → 直传 MinIO
▼
pf-service (Elysia)

路由实现位于 apps/pf-service/src/routes/pf-editor/ + 复用 shared/。缩略图统一走 resolveOssAssetUrl(url, width) 的 imgproxy 管道。

字体加载

编辑器与用户端共享字体预加载:首次进入按 font_variants 拉取并 FontFace.load() 注册,确保画布文字与最终合成一致。字体”权威源”是 MinIO 中的 woff2;合成服务通过相同 URL 注入,三端字形完全对齐。

与 pf-app 的代码复用

两个 Quasar 应用共用 Leafer 渲染层 + sceneJson 解析 与乐观上传结构约束(pf-editor 注释明确”保持同一套结构”)。区别在交互形态:pf-editor 暴露全部节点属性,pf-app 只允许触达 editable: true 的字段。渲染引擎与协议共用,是一份模板跨端可用的根本。

下一步

站内搜索

没有找到内容!