定位

pf-app 是图模工坊的终端用户入口,专为手机浏览器(H5)设计,覆盖”选模板 → 传图 → 编辑 → 提交合成”全流程。它是全项目里唯一完整落地 Material Design 3 的应用。

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

  • 框架:Quasar ^2.23.3 + Vue ^3.5 + TypeScript
  • 渲染:Leafer ^2.2.9(与 pf-editor 共用)|状态:Pinia 4 + 持久化
  • 图片裁剪:Cropper.js v2(cropperjs ^2.1.1)
  • 端口:开发环境 8000(base /app/)
  • 依赖包:@sa/code-renderer、@sa/color
  • 鉴权:订单访问令牌(凭订单链接 ?orderNo=&token= 进入,无需登录账号)

Material Design 3 设计系统

pf-app 内建完整 MD3:

  • src/css/app.scss / quasar.variables.scss 定义 MD3 tonal palette(--md-primary-0…100、--md-neutral-*、--md-tertiary-*、--md-error-*,蓝色种子)
  • 专门的 M3 组件库 src/components/m3/:M3Button / M3Fab / M3LargeHeader / M3ListItem / M3QuickTile / M3SettingsCard / M3StatusChip
  • 纸张隐喻 + Elevation 层级 + 涟漪反馈贯穿工坊组件与卡片

(注:pf-manage 并非 Material,而是 Naive UI;pf-editor 用 Quasar 默认 Material 风格但无 MD3 palette。)

主流程

1
2
3
4
5
6
7
8
9
入口(订单链接 / 活动)
→ 选品类 / 选模板(或选相册 → 尺寸 → P 数)
→ 进入"工坊(workshop)"页
├─ 上传素材(拍照 / 相册 / 批量,乐观占位 + 并发 3)
├─ 自适应/手动裁剪、旋转、翻转
├─ 文字编辑(每个 editable text 一格)
├─ 实时预览(Leafer,与最终合成 1:1)
└─ 提交 → POST /api/app/order-templates/submission
→ 后台合成(异步)→ 通知 → 预览 / 下载

页面与组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apps/pf-app/src/
├── pages/index/ # 首页 / 列表 / editor.vue(工坊)
├── components/
│ ├── m3/ # MD3 组件库
│ ├── ImageCropper.vue # Cropper.js v2 裁剪器
│ ├── TemplateCard.vue / ImagePreview.vue / WaterfallPreview.vue
│ └── workshop/ # 工坊场景(Header/Footer/上传面板/替换弹窗)
├── composables/editor/
│ ├── useImageUpload.ts # 乐观上传 + 并发队列 + 直传重试/取消
│ ├── autoFillCore.ts / useAutoFill.ts # 人脸感知自动填充
│ └── ...
├── api/face.ts # 人脸检测调用
├── stores/ # Pinia(user-gallery-store 等,持久化到 localStorage)
└── utils/oss.ts # resolveOssAssetUrl(url, width)

工坊页(Workshop)

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

1
2
3
4
        ┌────────── sceneJson (来自模板) ──────────┐
覆盖 ────┼─→ userUploads[instanceKey][elementKey].url │
│ userTexts [instanceKey][elementKey].text │
└────────────► Leafer 重绘 ────────────────┘ → 屏幕(与合成产物一致)

提交时只把 userUploads / userTexts + 模板 id 提交给后端,sceneJson 原貌在后端按 templateId 反查并快照存 order_template_submission_items.sceneJson,避免大 JSON 传输。

图片处理能力

  • 乐观上传 + 并发队列(useImageUpload,UPLOAD_CONCURRENCY = 3):占位卡即时呈现、后台直传、单项失败重试/取消(AbortController + OssUploadAbortError),细节见 传图与裁剪管线
  • 动态缩略图:resolveOssAssetUrl(url, width) → imgproxy WebP,列表不直连原图
  • 手动裁剪:ImageCropper.vue(Cropper.js v2)全屏裁剪、旋转、翻转
  • 人脸感知自动填充:模块级 AsyncConcurrencyQueue(FACE_DETECTION_CONCURRENCY = 3)在上传后后台调 faceApi.detectByImageUrl({ minScore: 0.45, maxFaces: 10 }),把 faces 存进图库 meta,供 useAutoFill 做避脸放置

人脸检测调用

src/api/face.ts::faceApi.detectByImageUrl({ imageUrl, minScore?, maxFaces? }) → POST /api/app/face/detect(经 pf-service 代理到 Rust 人脸服务),返回 { image, faces:[{x,y,w,h,score}] 归一化, modelVersion, elapsedMs }。

文字编辑

文字节点遵循 sceneJson 的 fontFamily / fontSize / fill / align,用户可改内容、字号、字体(从 font_variants 拉取)、颜色、对齐。字体首次进入工坊按模板 fontFamily 集合预加载 woff2,避免渲染闪烁。

状态与持久化

Pinia + 持久化把当前草稿存 localStorage(切到微信回来能继续、断网本地续编),草稿与订单/模板绑定。注意:在途上传任务不持久化——避免把临时 blob URL 写进磁盘。

提交与状态查询

1
2
3
4
5
6
7
8
pf-app
│ POST /api/app/order-templates/submission
│ body: { orderNo, items: [{ templateId, userUploads, userTexts, sceneJson }] }
│ (首次且唯一,重复提交返回 409)
│ GET /api/app/order-templates/submission/by-order-no/:orderNo
│ GET /api/app/order-templates/resources/by-order-no/:orderNo
▼
pf-service(订单访问令牌鉴权,通用 CRUD 被 403 关闭)

移动端优化要点

  • 触控目标 ≥ 44×44 px,双指缩放画布、长按出菜单
  • 乐观上传 + 并发 3 + 失败重试,弱网友好
  • 缩略图 imgproxy 动态 WebP,Zero CLS
  • 远端字体未加载完回落默认字体,不阻塞 UI

与 pf-editor 的复用

同一渲染层(Leafer + sceneJson)、同套字体预加载、同套乐观上传结构约束、共享 @sa/* 包。差别:pf-editor 暴露全部节点属性用于”出版”,pf-app 只触达 editable: true 字段用于”消费”。

下一步

站内搜索

没有找到内容!