定位
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 | apps/pf-editor/src/ |
sceneJson 协议
整个编辑器围绕一份 JSON 工作。简化结构:
1 | { |
节点关键属性
| 属性 | 类型 | 作用 |
|---|---|---|
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 | pf-editor |
路由实现位于 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 的字段。渲染引擎与协议共用,是一份模板跨端可用的根本。