docs(r2): DIY 工作台 6 项问题修正工作流
逐项根因定位(代码依据)+ 修复方案 + 契约影响 + 留意点: 1 重叠误报(CSS 钳制/原始像素与碰撞盒不一致,含旧数据兼容) 2 草稿持久化(防抖写缓存+服务端,status 不动) 3 SUBMITTED 触发点提前到进入工作台(决策#6 注记更新) 4 编辑页空白(问题2 修复即自动修复) 5 TouchSlider 启用 + CSS filter 预览替代 ctx.filter 6 checkout 地址同步滞后(R3 红线文件,记入 R3 待办) 问题5/6 的 R3 侧待办同步至 routes/R2/README.md Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,208 @@
|
|||||||
|
# DIY 工作台修正工作流(R2 收口后问题清单)
|
||||||
|
|
||||||
|
> 2026-09-12 真机验收后由需求方提出 6 个问题,本文档逐项记录:现象 → 根因(代码依据)→
|
||||||
|
> 修复方案 → 契约影响 → 留意点。
|
||||||
|
> 文件所有权:问题 1-5 全部在 R2 范围内(`pages/diy/*`);问题 6 的修复点在
|
||||||
|
> `pages/checkout/index.tsx`(**R3 红线文件**,需 R3 接手或经批准后最小侵入)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题 1:重叠判定误报——视觉上没重叠却判为重叠
|
||||||
|
|
||||||
|
**根因(已定位,实锤)**:碰撞盒与视觉尺寸不一致。三个因素叠加:
|
||||||
|
|
||||||
|
1. `addSticker` 用 `getImageInfo` 取**图片原始像素**作为 `width/height`(如 640×854),
|
||||||
|
而画布 `canvas-area` 只有 280~420px——贴纸元素天然比画布大;
|
||||||
|
2. CSS `.canvas-image` 有 `max-width: 100%; max-height: 100%`(`diy/index.scss:104`),
|
||||||
|
**渲染时把元素钳制到画布内**,且 Image `mode='aspectFit'` 还会在盒内留白——
|
||||||
|
但碰撞检测 `checkOverlap` 用的是 `width × scale`(原始像素),**完全没考虑钳制**;
|
||||||
|
3. 贴纸初始位置固定 (0,0),且坐标不钳制(数据库已出现 `x: -111.93` 的负值,
|
||||||
|
贴纸可以被拖出画布外)。
|
||||||
|
|
||||||
|
结果:两张视觉上分开的贴纸,碰撞盒仍然巨大且互相覆盖 → 误报。
|
||||||
|
|
||||||
|
**修复方案**:
|
||||||
|
|
||||||
|
1. `addSticker` 时**归一化贴纸尺寸**:按 mask 画布等比缩放,使 `max(w, h) = min(mask.w,
|
||||||
|
mask.h) × 0.6`,`scale=1` 起步——从此 `width/height` 语义 =「画布显示像素」,
|
||||||
|
渲染、碰撞、WCD 打包三者自洽;
|
||||||
|
2. `canvas-image` 保留 max 钳制作为兜底,但归一化后不再触发;
|
||||||
|
3. 拖动时把 `x/y` 钳制在 `[0, mask.w - 显示宽]` / `[0, mask.h - 显示高]` 区间内,
|
||||||
|
杜绝负坐标和拖出画布。
|
||||||
|
|
||||||
|
**契约影响**:`designData.stickers[].width/height` 的**语义**从「原始像素」变为
|
||||||
|
「画布显示像素」。这不是字段增删,但 WCD 打包按 `width × scale` 还原布局,语义必须
|
||||||
|
自洽——需在 `design-data-contract-v1.md` 加**实现注记**(非版本升级,与 2MB 注记同性质)。
|
||||||
|
|
||||||
|
**留意点**:
|
||||||
|
|
||||||
|
- 已落库的旧数据(如杯垫那条 640×854)是原始像素语义,新语义渲染会偏大——
|
||||||
|
需要一次性迁移(按同规则归一化)或在读取端兼容(判断 width > mask 宽则归一化);
|
||||||
|
推荐后者(读端兼容),避免写数据迁移脚本;
|
||||||
|
- WCD 打包器(词云平台侧)读到的是新语义数据,联调前要同步告知。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题 2:确认完成前设计数据不上库,中途退出即丢失
|
||||||
|
|
||||||
|
**现象确认**:属实。当前链路:进入 DIY 即 `createDesign`(DRAFT、无 designData),
|
||||||
|
`designData` 只在「确认完成」时一次性 PATCH。中途退出/切后台/杀进程 → 所有贴纸布局丢失,
|
||||||
|
只剩一条空 DRAFT 条目。
|
||||||
|
|
||||||
|
**是否正常**:是当前实现的设计取舍,但体验上不合理,应当改。
|
||||||
|
|
||||||
|
**修复方案(草稿持久化)**:
|
||||||
|
|
||||||
|
1. `stickers` 状态每次变化(add/delete/move/scale)**防抖 800ms** 后写入本地缓存
|
||||||
|
(`updateDesign(designId, { designData: draft })`,status 不动);
|
||||||
|
2. `useEffect` cleanup / `onHide` 时强制 flush 一次防抖;
|
||||||
|
3. 网络可用时异步 PATCH 服务端(同样不推状态);失败仅落缓存(现有降级语义);
|
||||||
|
4. 重新进入 DIY(source=designList)时从缓存 designData 恢复 `stickers`——
|
||||||
|
该恢复逻辑**已存在**(`diy/index.tsx` 读取 `design.designData.stickers`),
|
||||||
|
草稿持久化接上后自动生效。
|
||||||
|
|
||||||
|
**契约影响**:无。DRAFT 状态携带 designData 完全合法(白名单/1MB 校验与状态无关);
|
||||||
|
不触发任何状态迁移。
|
||||||
|
|
||||||
|
**留意点**:
|
||||||
|
|
||||||
|
- 拖动过程中 touchmove 每帧都触发,防抖窗口内的写入必须用最新值覆盖(闭包陷阱);
|
||||||
|
- 草稿里的贴纸 src 是 `wxfile://` 临时路径——**小程序临时文件可能被系统清理**,
|
||||||
|
草稿恢复时图片可能 404,渲染需有占位兜底(灰块/删除按钮),不要因加载失败崩页;
|
||||||
|
- 不要把「草稿自动保存」做成「自动确认」:status 必须保持 DRAFT,
|
||||||
|
`SUBMITTED` 只能由用户显式确认触发。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题 3:确认完成前清单条目一直是「未设计」
|
||||||
|
|
||||||
|
**根因**:这是阶段 0 决策#6 的字面实现——「DIY **保存设计** → SUBMITTED」。
|
||||||
|
原本地版行为是**进入 DIY 即 marking 设计中**(旧代码 createDesign 时直接写
|
||||||
|
`status: 'designing'`),R2 切服务端时按决策#6 改成了 DRAFT 起步,产生了语义回退感。
|
||||||
|
|
||||||
|
**修复方案(推荐)**:把 SUBMITTED 的触发点从「确认完成」提前到「**进入设计工作台**」:
|
||||||
|
|
||||||
|
- `diy/index.tsx` 的 `createDesignEntry` 创建后立即 PATCH `status: 'designing'`
|
||||||
|
(或 `createDesign` 成功后追加一次状态调用);从 designList 进入(继续设计)同理;
|
||||||
|
- 「待设计」筛选 tab(`toDesign` = undesigned ∪ designing)不受影响;
|
||||||
|
- 配合问题 2 的草稿持久化后,语义变为「动过工作台 = 设计中」。
|
||||||
|
|
||||||
|
**契约影响**:无版本升级。契约只规定状态机方向与映射,触发点是前端实现细节;
|
||||||
|
但 `r2-workflow.md` 阶段 0 决策#6 的文字需要更新注记,`routes/R2/README.md`
|
||||||
|
给 R3 的状态触发权说明同步更新(R3 依旧独占 PROCESSING/DONE)。
|
||||||
|
|
||||||
|
**留意点**:
|
||||||
|
|
||||||
|
- 「打开就进设计中」意味着用户误入也会推状态——可接受(原本地版即如此),
|
||||||
|
但验收文案要说清;
|
||||||
|
- 状态机单向,DRAFT→SUBMITTED 之后无法回「未设计」,与筛选 tab 的
|
||||||
|
「待设计」聚合展示核对一遍视觉效果。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题 4:确认完成前进入贴纸编辑页,Canvas 空白
|
||||||
|
|
||||||
|
**根因(与问题 2 同源)**:`stickerEdit` 从**本地缓存**读 `designData.stickers`
|
||||||
|
(`stickerEdit/index.tsx:146-148`)。而问题 2 的现状是确认完成前 designData 根本
|
||||||
|
不在缓存里 → `sticker` 为 undefined → Canvas 没有绘制目标 → 空白。
|
||||||
|
「应该任何时候进入都有渲染图」——对,这是缺陷不是设计。
|
||||||
|
|
||||||
|
**修复方案**:**做掉问题 2 即自动修复本问题**(草稿实时写缓存 → stickerEdit 永远
|
||||||
|
能读到最新 stickers)。无独立改动。
|
||||||
|
|
||||||
|
**契约影响**:无。
|
||||||
|
|
||||||
|
**留意点**:
|
||||||
|
|
||||||
|
- stickerEdit 找不到 designId/sticker 时目前静默返回(页面停 在空白 Canvas),
|
||||||
|
加一个空态提示 + 返回按钮,避免"看起来像死了";
|
||||||
|
- 修好后在真机回归一条链:DIY 加贴纸 → 不确认完成 → 直接点编辑 → 应看到该贴纸。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题 5:贴纸编辑页渲染失败——滑块拖不动、线稿无效果、保存无变化
|
||||||
|
|
||||||
|
**根因(三个独立缺陷叠加)**:
|
||||||
|
|
||||||
|
1. **滑块拖不动**:亮度/色相/对比度三个滑块用的是页内自定义 `onTouchMove` +
|
||||||
|
`Taro.createSelectorQuery().in(e.currentTarget)`(`stickerEdit/index.tsx:343`)。
|
||||||
|
Taro React 里 `.in()` 需要组件实例,`e.currentTarget` 是事件对象 → 查询失败/
|
||||||
|
rect 为 null → `ratio` 计算不出来 → 滑块不动。(文件顶部定义的 `TouchSlider`
|
||||||
|
组件反而是好的,但根本没被使用——死代码 + 错实现并存。)
|
||||||
|
2. **转线稿后滑块动了但画面不变**:`applyFrontendSketch` 只 `setState` 数值
|
||||||
|
(所以滑块位置跳变);画面重绘依赖 `ctx.filter`(Canvas 2D),**开发者工具/
|
||||||
|
部分基础库不支持 ctx.filter** → 重绘了但滤镜无效果。真调后端 `/api/sketch`
|
||||||
|
成功时才会换 src(当前后端 sketch 为占位 → 总是走降级)。
|
||||||
|
3. **保存回 DIY 无变化**:`handleSave` 用 `canvasToTempFilePath` 导出——Canvas 因
|
||||||
|
问题 4 空白/或 filter 无效时,导出的还是原图或直接失败 → 贴纸 src 没变或
|
||||||
|
toast 保存失败。
|
||||||
|
|
||||||
|
**修复方案**:
|
||||||
|
|
||||||
|
1. 三个滑块统一改用已定义的 `TouchSlider` 组件(基于 ref 的
|
||||||
|
`getBoundingClientRect`,在 H5/小程序两端都可用),删除页内三段坏的
|
||||||
|
onTouchMove 实现;
|
||||||
|
2. 预览层弃用 `ctx.filter`:改用 **CSS filter 直接套在 `<Image>` 上**
|
||||||
|
(`style={{ filter: 'brightness() hue-rotate() contrast()' }}`)——小程序 Image
|
||||||
|
支持 CSS filter,所见即所得且不依赖 Canvas 能力;
|
||||||
|
3. 「保存」重新设计导出链路:优先请求后端 `/api/sketch`(真实线稿);
|
||||||
|
纯滤镜场景导出保留 Canvas 方案但增加 `canvasReady` 前置校验,
|
||||||
|
Canvas 不可用时降级为「只保存 edits 参数、src 不变」——DIY 渲染端用
|
||||||
|
CSS filter 读 `edits` 实时呈现(效果等价,且这是 WCD 边界内允许的:
|
||||||
|
edits 本来就不进 document.json,只记 manifest.meta)。
|
||||||
|
|
||||||
|
**契约影响**:无版本升级。`edits` 字段契约已冻结且本期不进 WCD document.json;
|
||||||
|
「edits 在小程序端渲染时应用」与契约边界一致。
|
||||||
|
|
||||||
|
**留意点**:
|
||||||
|
|
||||||
|
- `edits` 应用点从「编辑页导出图」改为「DIY 渲染时套 CSS filter」后,
|
||||||
|
**DIY 画布和 checkout 预览都要消费 edits**,否则编辑效果只存在于编辑页;
|
||||||
|
checkout 是 R3 文件——本期只改 DIY 端,checkout 的滤镜预览记入 R3 待办;
|
||||||
|
- 删除 `TouchSlider` 死代码 or 启用它,二选一,不要留两套;
|
||||||
|
- 真机回归矩阵:调滑块 → 画面变;保存 → DIY 里效果保持;转线稿(后端就绪后)
|
||||||
|
→ src 被替换为持久 URL。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 问题 6:设计效果确认页(checkout)新建地址后不同步
|
||||||
|
|
||||||
|
**根因**:`checkout/index.tsx` 只在 **mount 时读一次**本地缓存地址
|
||||||
|
(`useEffect` + `getDefaultAddress/getAddressList`),从地址页 `navigateBack` 返回后
|
||||||
|
**没有任何 onShow 刷新**。而 R2 侧的地址页在新建成功后**已经正确回写了缓存**
|
||||||
|
(`setAddressList`)——数据就在缓存里,是 checkout 不重新读。
|
||||||
|
|
||||||
|
**是否 R2 工作内容**:**修复点在 checkout(R3 红线文件),不属于 R2**。
|
||||||
|
R2 侧职责(API 保存 + 缓存回写)已正确完成并经真机验证。
|
||||||
|
|
||||||
|
**修复方案(记入 R3 待办)**:
|
||||||
|
|
||||||
|
1. checkout 读地址改为「mount + `useDidShow`(onShow)双时机」,或
|
||||||
|
2. 地址页保存成功后 `Taro.eventCenter.emit('addressesChanged')`,checkout 监听刷新;
|
||||||
|
推荐前者(改动更小、无事件时序问题)。
|
||||||
|
|
||||||
|
**契约影响**:无。
|
||||||
|
|
||||||
|
**留意点**:
|
||||||
|
|
||||||
|
- 已同步写入 `routes/R2/README.md` 给 R3 的待办;
|
||||||
|
- 临时绕过方案(不改代码):从确认页退出重进即同步(现况即如此)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修复顺序与依赖
|
||||||
|
|
||||||
|
```
|
||||||
|
问题2(草稿持久化)──┬──> 问题4(编辑页空白,自动修复)
|
||||||
|
└──> 问题3(进入即 designing,独立但同文件顺手做)
|
||||||
|
问题1(碰撞归一化 + 坐标钳制)—— 独立,含旧数据读端兼容
|
||||||
|
问题5(TouchSlider + CSS filter 预览)—— 独立;edits 消费点需要 DIY 渲染端配合
|
||||||
|
问题6 —— 不修(R3 待办,已记录)
|
||||||
|
```
|
||||||
|
|
||||||
|
**建议批次**:① 问题 2+3+4(同一文件、一次回归)→ ② 问题 1(含兼容验证)→
|
||||||
|
③ 问题 5(改动面最大,单独一批)。
|
||||||
|
|
||||||
|
**统一验证清单**:加贴纸不重叠不误报 / 拖不出去 / 中途退出重进布局还在 /
|
||||||
|
进入工作台清单变「设计中」/ 编辑页任何时候有图 / 滑块可拖 / 编辑保存后 DIY 效果保持 /
|
||||||
|
`nest build` + `build:weapp` + 契约注记同步。
|
||||||
@@ -35,6 +35,11 @@
|
|||||||
(SetNull),否则用户可删掉订单引用的设计快照。
|
(SetNull),否则用户可删掉订单引用的设计快照。
|
||||||
4. orders / orderDetail / profile 页仍读本地 store,R3 接入时一并切 API
|
4. orders / orderDetail / profile 页仍读本地 store,R3 接入时一并切 API
|
||||||
(读的是 R2 回写的缓存,过渡期数据可用)。
|
(读的是 R2 回写的缓存,过渡期数据可用)。
|
||||||
|
5. **checkout 地址读取是一次性的**(仅 mount 读缓存,无 onShow 刷新)——用户从
|
||||||
|
checkout 进入新建地址后返回不会同步,要重进页面才刷新。R3 切 checkout 时改为
|
||||||
|
mount + onShow 双时机读取(详见 `docs/diy-fix-workflow.md` 问题 6)。
|
||||||
|
6. **checkout 预览不消费贴纸 `edits`**(亮度/色相/对比度):DIY 渲染端套 CSS filter
|
||||||
|
呈现编辑效果,checkout 预览接入时需同样消费(R3 待办,见 diy-fix-workflow 问题 5)。
|
||||||
5. 金额重算:按契约用服务端 Product 真实价格 + `designList.items[0]` 快照,
|
5. 金额重算:按契约用服务端 Product 真实价格 + `designList.items[0]` 快照,
|
||||||
忽略客户端金额;下单带 `requestId` 幂等。
|
忽略客户端金额;下单带 `requestId` 幂等。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user