Files
wechat_wc/docs/diy-fix-workflow.md
T
lhmin0604andClaude 190e59af94 revert(diy): 撤销贴纸坐标钳制,恢复可拖出画布外自由摆放
需求决定(2026-09-12):负坐标/超出画布为合法摆放状态;
重叠误报根因是尺寸语义(已由归一化修复),坐标钳制一并移除。
文档同步标注该决策。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-12 20:00:17 +08:00

233 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 - 显示高]` 区间内~~
**已按需求撤销**,2026-09-12:贴纸允许拖出画布外自由摆放,负坐标为合法状态;
误报根因是尺寸语义而非坐标,归一化后已消除)。
**契约影响**`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. 重新进入 DIYsource=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 工作内容****修复点在 checkoutR3 红线文件),不属于 R2**。
R2 侧职责(API 保存 + 缓存回写)已正确完成并经真机验证。
**修复方案(记入 R3 待办)**:
1. checkout 读地址改为「mount + `useDidShow`onShow)双时机」,或
2. 地址页保存成功后 `Taro.eventCenter.emit('addressesChanged')`checkout 监听刷新;
推荐前者(改动更小、无事件时序问题)。
**契约影响**:无。
**留意点**
- 已同步写入 `routes/R2/README.md` 给 R3 的待办;
- 临时绕过方案(不改代码):从确认页退出重进即同步(现况即如此)。
---
## 契约核对总表(api-contract-v1.md + design-data-contract-v1.md
2026-09-12 对两份契约逐项核对后的结论:
| # | api-contract-v1.md | design-data-contract-v1.md |
|---|---|---|
| 1 | 无需改(stickers 为 `unknown[]`,语义由子契约承载);§5 已加实现补充注记指引 | **加实现注记**width/height/x/y 语义 = 画布显示像素,历史数据读端兼容(已完成) |
| 2 | PATCH 部分更新语义后端已实现,1MB 按次校验不受高频影响;无契约文字改动 | 无 |
| 3 | 状态映射表未写死 SUBMITTED 触发点,DRAFT→SUBMITTED 合法;无契约改动(触发点调整记录在 r2-workflow 决策#6 注记) | 无 |
| 4 | 无 | 无 |
| 5 | stickers 透传 edits、§8 sketch 不变;无契约改动 | edits 边界(不进 document.json)不变 |
| 6 | §4 地址接口不变,读时机为客户端行为 | 无 |
**核对中发现并已修正的矛盾**:api-contract §5 引用块原写「贴纸图 src 必须为 COS 持久 URL
(禁止 `wxfile://`/`tmp`)」,与 design-data-contract 约束#1/决策#4R2 允许暂存本地图、
R4 派单前持久化回写)直接矛盾,且真机落库数据即为 wxfile://。已按冻结决策修正措辞
(非字段/语义变更,不升版本)。
## 修复顺序与依赖
```
问题2(草稿持久化)──┬──> 问题4(编辑页空白,自动修复)
└──> 问题3(进入即 designing,独立但同文件顺手做)
问题1(碰撞归一化 + 坐标钳制)—— 独立,含旧数据读端兼容
问题5TouchSlider + CSS filter 预览)—— 独立;edits 消费点需要 DIY 渲染端配合
问题6 —— 不修(R3 待办,已记录)
```
**建议批次**:① 问题 2+3+4(同一文件、一次回归)✅ 已实施(2026-09-12commit eea6c3f
`build:weapp` 通过,tsc 无新增错误)→ ② 问题 1(含兼容验证)✅ 已实施(2026-09-12
commit 3b224cf:归一化 + 坐标钳制 + 读端兼容 + transform-origin
**坐标钳制后经需求确认撤销**,commit 见后续,贴纸恢复可拖出画布)→
③ 问题 5(改动面最大,单独一批)✅ 已实施(2026-09-12commit 2e3676a
TouchSlider 组件化 + Image/CSS filter 预览 + edits 参数化保存 + DIY 渲染端
消费 editscheckout 预览消费 edits 仍记 R3 待办)。
**统一验证清单**:加贴纸不重叠不误报 / 拖不出去 / 中途退出重进布局还在 /
进入工作台清单变「设计中」/ 编辑页任何时候有图 / 滑块可拖 / 编辑保存后 DIY 效果保持 /
`nest build` + `build:weapp` + 契约注记同步。