fix(diy): 贴纸尺寸归一化画布空间 + 坐标钳制 + 旧数据读端兼容

修正工作流批次②(问题1 重叠误报):
- addSticker 按 mask 短边 60% 归一化 width/height(契约实现注记:
  width/height 语义 = 画布显示像素),渲染/碰撞/WCD 三方自洽
- 拖动与缩放后坐标钳制在 [0, mask尺寸-显示尺寸],杜绝负坐标/拖出画布
- 读取端兼容:旧数据 width > mask 尺寸时按 mask 等比归一化(不做存量迁移)
- canvas-image/preview-image 补 transform-origin: top left,
  缩放原点与碰撞盒(左上角)语义一致

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-09-12 19:10:37 +08:00
co-authored by Claude
parent eea6c3feef
commit 3b224cf41b
4 changed files with 54 additions and 16 deletions
+3
View File
@@ -103,6 +103,8 @@
position: absolute;
max-width: 100%;
max-height: 100%;
/* 问题1 修正:碰撞盒按左上角计算,缩放原点统一为左上角,渲染与碰撞语义自洽 */
transform-origin: top left;
z-index: 5;
border: 2rpx solid transparent;
border-radius: 8rpx;
@@ -391,6 +393,7 @@
position: absolute;
max-width: 100%;
max-height: 100%;
transform-origin: top left;
z-index: 5;
}
+49 -14
View File
@@ -88,16 +88,30 @@ export default function DIYPage() {
setQuantity(design.count)
const product = getProductById(design.productId)
if (product) setCategory(product)
if (design.designData ? design.designData.stickers : undefined) {
setStickers(design.designData.stickers)
} else if (design.designData ? design.designData.imageSrc : undefined) {
const dd = design.designData
const rawStickers = dd ? dd.stickers : undefined
if (rawStickers) {
// 问题1 留意点:旧数据 width/height 为图片原始像素语义,读取端按 mask 等比归一化
//(契约实现注记,不做存量迁移);新语义数据(≤mask 短边 60%)原样通过
const mask = (dd && dd.category && dd.category.mask) || (product ? product.mask : undefined)
const normalized: StickerItem[] = mask
? rawStickers.map(s => {
if (s.width > mask.width || s.height > mask.height) {
const k = (Math.min(mask.width, mask.height) * 0.6) / Math.max(s.width, s.height)
return { ...s, width: Math.round(s.width * k), height: Math.round(s.height * k) }
}
return s
})
: rawStickers
setStickers(normalized)
} else if (dd ? dd.imageSrc : undefined) {
// 兼容旧版数据
const s: StickerItem = {
id: 'legacy_' + Date.now(),
src: design.designData.imageSrc,
x: design.designData.imagePos ? design.designData.imagePos.x || 0 : 0,
y: design.designData.imagePos ? design.designData.imagePos.y || 0 : 0,
scale: design.designData.imagePos ? design.designData.imagePos.scale || 1 : 1,
src: dd.imageSrc,
x: dd.imagePos ? dd.imagePos.x || 0 : 0,
y: dd.imagePos ? dd.imagePos.y || 0 : 0,
scale: dd.imagePos ? dd.imagePos.scale || 1 : 1,
width: 200,
height: 200,
isOverlapping: false
@@ -207,14 +221,17 @@ export default function DIYPage() {
Taro.getImageInfo({
src,
success: (info) => {
// 问题1 修正:归一化到画布坐标空间(width/height = 画布显示像素,契约实现注记)
const maskMin = Math.min(category.mask.width, category.mask.height)
const k = (maskMin * 0.6) / Math.max(info.width, info.height)
const newSticker: StickerItem = {
id: 'stk_' + Date.now(),
src,
x: 0,
y: 0,
scale: 1,
width: info.width,
height: info.height,
width: Math.round(info.width * k),
height: Math.round(info.height * k),
isOverlapping: false
}
const next = [...stickers, newSticker]
@@ -223,15 +240,17 @@ export default function DIYPage() {
setActiveStickerId(newSticker.id)
},
fail: () => {
// fallback 尺寸
// fallback 尺寸(同样按画布空间归一化)
const maskMin = Math.min(category.mask.width, category.mask.height)
const k = (maskMin * 0.6) / 200
const newSticker: StickerItem = {
id: 'stk_' + Date.now(),
src,
x: 0,
y: 0,
scale: 1,
width: 200,
height: 200,
width: Math.round(200 * k),
height: Math.round(200 * k),
isOverlapping: false
}
const next = [...stickers, newSticker]
@@ -265,7 +284,14 @@ export default function DIYPage() {
const touch = e.touches[0]
const next = stickers.map(s => {
if (s.id !== activeStickerId) return s
return { ...s, x: touch.clientX - startPos.x, y: touch.clientY - startPos.y }
// 问题1 修正:坐标钳制在画布内,杜绝负坐标与拖出画布(碰撞盒 = x/y + 显示宽高)
const maxX = Math.max(0, category.mask.width - s.width * s.scale)
const maxY = Math.max(0, category.mask.height - s.height * s.scale)
return {
...s,
x: Math.min(Math.max(touch.clientX - startPos.x, 0), maxX),
y: Math.min(Math.max(touch.clientY - startPos.y, 0), maxY)
}
})
const checked = checkOverlap(next)
setStickers(checked)
@@ -284,7 +310,16 @@ export default function DIYPage() {
const handleScale = (id: string, delta: number) => {
const next = stickers.map(s => {
if (s.id !== id) return s
return { ...s, scale: Math.max(0.3, Math.min(3, s.scale + delta)) }
const scale = Math.max(0.3, Math.min(3, s.scale + delta))
// 问题1 修正:缩放后把位置钳回画布内
const maxX = Math.max(0, category.mask.width - s.width * scale)
const maxY = Math.max(0, category.mask.height - s.height * scale)
return {
...s,
scale,
x: Math.min(Math.max(s.x, 0), maxX),
y: Math.min(Math.max(s.y, 0), maxY)
}
})
const checked = checkOverlap(next)
setStickers(checked)