需求决定(2026-09-12):负坐标/超出画布为合法摆放状态; 重叠误报根因是尺寸语义(已由归一化修复),坐标钳制一并移除。 文档同步标注该决策。 Co-Authored-By: Claude <noreply@anthropic.com>
13 KiB
DIY 工作台修正工作流(R2 收口后问题清单)
2026-09-12 真机验收后由需求方提出 6 个问题,本文档逐项记录:现象 → 根因(代码依据)→ 修复方案 → 契约影响 → 留意点。 文件所有权:问题 1-5 全部在 R2 范围内(
pages/diy/*);问题 6 的修复点在pages/checkout/index.tsx(R3 红线文件,需 R3 接手或经批准后最小侵入)。
问题 1:重叠判定误报——视觉上没重叠却判为重叠
根因(已定位,实锤):碰撞盒与视觉尺寸不一致。三个因素叠加:
addSticker用getImageInfo取图片原始像素作为width/height(如 640×854), 而画布canvas-area只有 280~420px——贴纸元素天然比画布大;- CSS
.canvas-image有max-width: 100%; max-height: 100%(diy/index.scss:104), 渲染时把元素钳制到画布内,且 Imagemode='aspectFit'还会在盒内留白—— 但碰撞检测checkOverlap用的是width × scale(原始像素),完全没考虑钳制; - 贴纸初始位置固定 (0,0),且坐标不钳制(数据库已出现
x: -111.93的负值, 贴纸可以被拖出画布外)。
结果:两张视觉上分开的贴纸,碰撞盒仍然巨大且互相覆盖 → 误报。
修复方案:
addSticker时归一化贴纸尺寸:按 mask 画布等比缩放,使max(w, h) = min(mask.w, mask.h) × 0.6,scale=1起步——从此width/height语义 =「画布显示像素」, 渲染、碰撞、WCD 打包三者自洽;canvas-image保留 max 钳制作为兜底,但归一化后不再触发;拖动时把(已按需求撤销,2026-09-12:贴纸允许拖出画布外自由摆放,负坐标为合法状态; 误报根因是尺寸语义而非坐标,归一化后已消除)。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 条目。
是否正常:是当前实现的设计取舍,但体验上不合理,应当改。
修复方案(草稿持久化):
stickers状态每次变化(add/delete/move/scale)防抖 800ms 后写入本地缓存 (updateDesign(designId, { designData: draft }),status 不动);useEffectcleanup /onHide时强制 flush 一次防抖;- 网络可用时异步 PATCH 服务端(同样不推状态);失败仅落缓存(现有降级语义);
- 重新进入 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创建后立即 PATCHstatus: '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:贴纸编辑页渲染失败——滑块拖不动、线稿无效果、保存无变化
根因(三个独立缺陷叠加):
- 滑块拖不动:亮度/色相/对比度三个滑块用的是页内自定义
onTouchMove+Taro.createSelectorQuery().in(e.currentTarget)(stickerEdit/index.tsx:343)。 Taro React 里.in()需要组件实例,e.currentTarget是事件对象 → 查询失败/ rect 为 null →ratio计算不出来 → 滑块不动。(文件顶部定义的TouchSlider组件反而是好的,但根本没被使用——死代码 + 错实现并存。) - 转线稿后滑块动了但画面不变:
applyFrontendSketch只setState数值 (所以滑块位置跳变);画面重绘依赖ctx.filter(Canvas 2D),开发者工具/ 部分基础库不支持 ctx.filter → 重绘了但滤镜无效果。真调后端/api/sketch成功时才会换 src(当前后端 sketch 为占位 → 总是走降级)。 - 保存回 DIY 无变化:
handleSave用canvasToTempFilePath导出——Canvas 因 问题 4 空白/或 filter 无效时,导出的还是原图或直接失败 → 贴纸 src 没变或 toast 保存失败。
修复方案:
- 三个滑块统一改用已定义的
TouchSlider组件(基于 ref 的getBoundingClientRect,在 H5/小程序两端都可用),删除页内三段坏的 onTouchMove 实现; - 预览层弃用
ctx.filter:改用 CSS filter 直接套在<Image>上 (style={{ filter: 'brightness() hue-rotate() contrast()' }})——小程序 Image 支持 CSS filter,所见即所得且不依赖 Canvas 能力; - 「保存」重新设计导出链路:优先请求后端
/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 待办):
- checkout 读地址改为「mount +
useDidShow(onShow)双时机」,或 - 地址页保存成功后
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/决策#4(R2 允许暂存本地图、
R4 派单前持久化回写)直接矛盾,且真机落库数据即为 wxfile://。已按冻结决策修正措辞
(非字段/语义变更,不升版本)。
修复顺序与依赖
问题2(草稿持久化)──┬──> 问题4(编辑页空白,自动修复)
└──> 问题3(进入即 designing,独立但同文件顺手做)
问题1(碰撞归一化 + 坐标钳制)—— 独立,含旧数据读端兼容
问题5(TouchSlider + CSS filter 预览)—— 独立;edits 消费点需要 DIY 渲染端配合
问题6 —— 不修(R3 待办,已记录)
建议批次:① 问题 2+3+4(同一文件、一次回归)✅ 已实施(2026-09-12,commit eea6c3f,
build:weapp 通过,tsc 无新增错误)→ ② 问题 1(含兼容验证)✅ 已实施(2026-09-12,
commit 3b224cf:归一化 + 坐标钳制 + 读端兼容 + transform-origin;
坐标钳制后经需求确认撤销,commit 见后续,贴纸恢复可拖出画布)→
③ 问题 5(改动面最大,单独一批)✅ 已实施(2026-09-12,commit 2e3676a:
TouchSlider 组件化 + Image/CSS filter 预览 + edits 参数化保存 + DIY 渲染端
消费 edits;checkout 预览消费 edits 仍记 R3 待办)。
统一验证清单:加贴纸不重叠不误报 / 拖不出去 / 中途退出重进布局还在 /
进入工作台清单变「设计中」/ 编辑页任何时候有图 / 滑块可拖 / 编辑保存后 DIY 效果保持 /
nest build + build:weapp + 契约注记同步。