定位

pf-app 是图模工坊的终端用户入口,专为手机浏览器(H5)设计,覆盖”选模板 → 传图 → 编辑 → 提交合成”全流程。

目标用户:非专业设计人员(普通消费者、企业员工自助下单),核心场景在手机端。

  • 框架:Quasar 2.16 + Vue 3.5 + TypeScript 5.9
  • 渲染:Leafer 2.x(与 pf-editor 共用)
  • 状态:Pinia 3.x + pinia-plugin-persistedstate
  • 图片裁剪:Cropper.js 1.6
  • 端口:开发环境 8000

主流程

1
2
3
4
5
6
7
8
9
10
11
首页/活动入口
→ 选品类(名片 / 海报 / 相册 / 证件照 / 写真)
→ 选模板(或选相册 → 选尺寸 → 选 P 数)
→ 进入"工坊(workshop)"页
├─ 用户上传素材(拍照 / 相册 / 批量)
├─ 自适应裁剪 / 手动裁剪 / 旋转 / 翻转
├─ 文字编辑(每个 editable text 节点一格)
├─ 实时预览(与最终合成 1:1)
└─ 提交
→ 后台合成(异步)
→ 通知用户 → 预览 / 下载电子版

页面与组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apps/pf-app/src/
├── pages/
│ ├── IndexPage.vue # 首页
│ ├── AlbumTemplatesPage.vue # 相册模板列表
│ ├── TemplateListPage.vue # 通用模板列表
│ └── ErrorNotFound.vue
├── components/
│ ├── TemplateCard.vue # 模板卡片
│ ├── ImageCropper.vue # 通用裁剪器(Cropper.js)
│ ├── ImagePreview.vue # 预览
│ ├── WaterfallPreview.vue # 瀑布流预览(多图)
│ ├── Upload.ts # 上传逻辑(分片)
│ └── workshop/ # 工坊编辑场景
│ ├── WorkshopHeader.vue # 顶栏(步骤指示、保存)
│ ├── WorkshopFooter.vue # 底部工具条(添加图/文字/素材)
│ ├── ImageUploadSheet.vue # 上传选项面板(拍照/相册/批量)
│ └── TemplateReplaceDialog.vue
└── composables/
├── editor/ # 编辑器组合式函数
└── fontPreload.ts # 字体预加载(与 pf-editor 同源)

工坊页(Workshop)

工坊页是用户端的主战场,承载所有定制操作。它在内存里维护一份当前 sceneJson 实例——以模板的 sceneJson 为基底,把用户的填值(图片 URL、文字)覆盖到对应节点上,由 Leafer 实时重绘。

1
2
3
4
5
6
7
8
9
10
                ┌─────────── sceneJson (来自模板) ───────────┐
│ │
覆盖图层 ──────┼─→ userUploads[instanceKey][elementKey].url │
│ userTexts [instanceKey][elementKey].text│
│ │
└─────────────► Leafer 重绘 ────────────────┘


用户在屏幕上看到的画面
(与后端合成产物视觉一致)

提交时只把 userUploads / userTexts 加上模板 id 提交给后端——sceneJson 的”原貌”在后端通过 templateId 反查,避免大 JSON 传输;同时后端会按当前模板版本快照存一份到 order_template_submission_items.sceneJson

图片处理能力

1. 自适应裁剪(默认)

模板节点的 width / height / fit 已确定,用户传图后系统按比例自动裁剪:

  • 普通节点:等比缩放 + 居中裁切(fit: cover
  • 标记 faceSafe: true 的节点:先调用 face-service 取人脸框 → 再计算裁剪窗口让人脸尽量居中且不被裁掉

2. 手动裁剪

用户长按图片节点 → 唤起 ImageCropper.vue(Cropper.js)→ 触控拖动调框、旋转、翻转 → 实时预览。

3. 批量上传(相册场景)

ImageUploadSheet.vue 支持一次选数十张:

  • 取消单次上传图片数量限制(按设计文档)
  • 分片上传适配大文件(单图 ≤ 200MB)
  • 上传过程中显示进度、断点续传
  • 上传完毕后系统按相册模板顺序自动绑定,用户可拖拽重排

4. 原图模式

支持”不动原图”上传——适用对清晰度极敏感的写真/高清海报,保留 TIFF / 原始 PNG,绕过自动压缩。

文字编辑

文字节点遵循 sceneJson 中的 fontFamily / fontSize / fill / align,用户可在工坊页面的属性子面板修改。可改维度:

  • 文字内容
  • 字号(受限于节点允许范围)
  • 字体(从 font_variants 拉取)
  • 颜色、对齐、行间距

字体加载走 composables/fontPreload.ts——首次进入工坊时按模板用到的 fontFamily 集合预加载 woff2,避免渲染闪烁。

状态与持久化

Pinia store + pinia-plugin-persistedstate 把当前编辑中的草稿持久化到 localStorage:

  • 用户中途切到微信、回来还能继续
  • 网络断开时本地继续编辑,恢复后再提交
  • 草稿与订单/模板绑定,避免串草稿

提交与状态查询

1
2
3
4
5
6
7
8
9
10
pf-app
│ POST /pf-app/order-template-submissions
│ body: { orderId, items: [{ templateId, userUploads, userTexts }] }
│ → submissionId

│ GET /pf-app/order-template-submissions/:id
│ → { status: pending|processing|completed|failed, items: [...] }


pf-service (Elysia)

完整路由位于 apps/pf-service/src/routes/projects/pf-app/orderTemplateRoutes.ts

与 pf-editor 的复用

  • 同一个渲染层(Leafer + sceneJson)
  • 同一份字体预加载逻辑
  • 同一套 ImageCropper / ImagePreview 组件
  • 共享 packages/* 中的 hooks / 工具

差别在交互形态:pf-editor 暴露全部节点属性;pf-app 只允许触达模板里标记为 editable: true 的字段。

移动端优化要点

  • 视口适配:所有触控目标 ≥ 44×44 px
  • 触摸手势:双指缩放画布、长按出菜单
  • 图片预压缩:超 5 MB 的图在前端做一次有损压缩再上传(原图模式除外)
  • 网络容错:上传失败自动重试,断点续传
  • 字体兜底:远端字体未加载完成时回落到 Quasar 默认字体,不阻塞 UI

下一步