diff --git a/components/DongzhimenScene.vue b/components/DongzhimenScene.vue index bc5075e..4871de8 100644 --- a/components/DongzhimenScene.vue +++ b/components/DongzhimenScene.vue @@ -44,25 +44,48 @@ const showDuck = ref(true) // 调试时默认显示鸭子 const deskImage = ref('/static/dzm/img_desk1.png') const showGuideElements = ref(true) +// Canvas 触摸是否禁用 +const canvasDisabled = ref(false) + +// 调试模式 +const debugMode = ref(false) + // Canvas 上下文 const ctx = ref(null) // 鸭子图片路径 const duckImagePath = ref(null) -// 鸭子位置(相对于 Canvas) -const duckX = ref(275) -const duckY = ref(110) +// rpx 转换比例 +const rpxRatio = ref(0.5) // 默认 iPhone 6/7/8 的 375px / 750rpx = 0.5 -// 鸭子尺寸(与图片实际尺寸一致) -const duckWidth = 36 -const duckHeight = 70 +// 鸭子位置和尺寸(使用 rpx 单位,在 750rpx 设计稿下) +const duckRpx = ref({ + x: 550, // 275px * 2 = 550rpx + y: 220, // 110px * 2 = 220rpx + width: 72, // 36px * 2 = 72rpx + height: 140 // 70px * 2 = 140rpx +}) + +// 鸭子当前位置(px 单位,用于绘制) +const duckX = ref(0) +const duckY = ref(0) +const duckWidth = ref(0) +const duckHeight = ref(0) // 拖拽偏移量 const dragOffsetX = ref(0) const dragOffsetY = ref(0) -// Canvas 位置信息 +// Canvas 位置和尺寸(使用 rpx 单位) +const canvasRpx = ref({ + x: 0, + y: 1550, // 800px * 2 = 1600rpx + width: 750, // 375px * 2 = 750rpx (全屏宽度) + height: 600 // 300px * 2 = 600rpx +}) + +// Canvas 当前位置和尺寸(px 单位) const canvasRect = ref({ left: 0, top: 0, @@ -70,12 +93,20 @@ const canvasRect = ref({ height: 0 }) -// 目标区域 (餐桌区域) - 相对于 Canvas 的坐标 +// 目标区域 (餐桌区域) - 使用 rpx 单位 +const targetAreaRpx = ref({ + x: 0, + y: 160, // 80px * 2 = 160rpx + width: 400, // 200px * 2 = 400rpx + height: 300 // 150px * 2 = 300rpx +}) + +// 目标区域当前位置和尺寸(px 单位) const targetArea = ref({ x: 0, - y: 80, - width: 200, - height: 150 + y: 0, + width: 0, + height: 0 }) // 动画帧 ID @@ -115,20 +146,70 @@ const loadDuckImage = () => { }) } +// rpx 转 px +const rpxToPx = (rpx) => { + return rpx * rpxRatio.value +} + +// 更新鸭子位置(根据 rpx 计算 px) +const updateDuckPosition = () => { + duckX.value = rpxToPx(duckRpx.value.x) + duckY.value = rpxToPx(duckRpx.value.y) + duckWidth.value = rpxToPx(duckRpx.value.width) + duckHeight.value = rpxToPx(duckRpx.value.height) +} + +// 更新目标区域(根据 rpx 计算 px) +const updateTargetArea = () => { + targetArea.value = { + x: rpxToPx(targetAreaRpx.value.x), + y: rpxToPx(targetAreaRpx.value.y), + width: rpxToPx(targetAreaRpx.value.width), + height: rpxToPx(targetAreaRpx.value.height) + } +} + +// 更新 Canvas 位置和尺寸(根据 rpx 计算 px) +const updateCanvasRect = () => { + canvasRect.value = { + left: rpxToPx(canvasRpx.value.x), + top: rpxToPx(canvasRpx.value.y), + width: rpxToPx(canvasRpx.value.width), + height: rpxToPx(canvasRpx.value.height) + } +} + +// Canvas 在视口中的实际位置(用于触摸计算) +const canvasBoundingRect = ref({ + left: 0, + top: 0 +}) + // 获取 Canvas 元素位置 const getCanvasPosition = () => { + // 计算 rpx 比例:屏幕宽度 / 750 + const systemInfo = uni.getSystemInfoSync() + rpxRatio.value = systemInfo.windowWidth / 750 + console.log('屏幕宽度:', systemInfo.windowWidth, 'rpxRatio:', rpxRatio.value) + + // 更新 Canvas、鸭子和目标区域的 px 值 + updateCanvasRect() + updateDuckPosition() + updateTargetArea() + + console.log('Canvas 位置信息:', canvasRect.value) +} + +// 获取 Canvas 在视口中的实际位置(用于触摸坐标计算) +const getCanvasBoundingRect = () => { const query = uni.createSelectorQuery().in(instance) query.select('#dragDuckCanvas').boundingClientRect(res => { if (res) { - canvasRect.value = { + canvasBoundingRect.value = { left: res.left, - top: res.top, - width: res.width, - height: res.height + top: res.top } - console.log('Canvas 位置信息:', canvasRect.value) - } else { - console.error('无法获取 Canvas 位置信息') + console.log('Canvas 视口位置:', canvasBoundingRect.value) } }).exec() } @@ -163,64 +244,83 @@ const drawCanvas = () => { const drawTarget = () => { const target = targetArea.value - // 绘制目标区域背景 - ctx.value.setFillStyle('rgba(52, 152, 219, 0.3)') - ctx.value.fillRect(target.x, target.y, target.width, target.height) + if (debugMode.value) { + // 调试模式:显示目标区域边框和文字 + // 绘制目标区域背景 + ctx.value.setFillStyle('rgba(52, 152, 219, 0.3)') + ctx.value.fillRect(target.x, target.y, target.width, target.height) - // 绘制目标区域边框 - ctx.value.setStrokeStyle('#3498db') - ctx.value.setLineWidth(2) - ctx.value.setLineDash([5, 5]) - ctx.value.strokeRect(target.x, target.y, target.width, target.height) - ctx.value.setLineDash([]) + // 绘制目标区域边框 + ctx.value.setStrokeStyle('#3498db') + ctx.value.setLineWidth(2) + ctx.value.setLineDash([5, 5]) + ctx.value.strokeRect(target.x, target.y, target.width, target.height) + ctx.value.setLineDash([]) - // 绘制目标区域文字 - ctx.value.setFontSize(14) - ctx.value.setFillStyle('#3498db') - ctx.value.setTextAlign('center') - ctx.value.fillText( - '目标区域', - target.x + target.width / 2, - target.y + target.height / 2 - ) + // 绘制目标区域文字 + ctx.value.setFontSize(14) + ctx.value.setFillStyle('#3498db') + ctx.value.setTextAlign('center') + ctx.value.fillText( + '目标区域', + target.x + target.width / 2, + target.y + target.height / 2 + ) + } + // 非调试模式:完全透明,不绘制任何内容 } // 绘制鸭子 const drawDuck = () => { if (!showDuck.value) return + // 非调试模式下,只有拖拽时才显示鸭子 + if (!debugMode.value && !isDragging.value) { + return + } + + // 如果正在拖拽,设置半透明 + if (isDragging.value) { + ctx.value.globalAlpha = 0.6 + } else { + ctx.value.globalAlpha = 1.0 + } + // 如果图片已加载,绘制图片 if (duckImagePath.value) { - ctx.value.drawImage(duckImagePath.value, duckX.value, duckY.value, duckWidth, duckHeight) + ctx.value.drawImage(duckImagePath.value, duckX.value, duckY.value, duckWidth.value, duckHeight.value) } else { // 图片未加载时,绘制占位方块 ctx.value.setFillStyle('#ffcc00') - ctx.value.fillRect(duckX.value, duckY.value, duckWidth, duckHeight) + ctx.value.fillRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value) // 绘制边框 ctx.value.setStrokeStyle('#333') ctx.value.setLineWidth(2) - ctx.value.strokeRect(duckX.value, duckY.value, duckWidth, duckHeight) + ctx.value.strokeRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value) // 绘制文字 ctx.value.setFontSize(14) ctx.value.setFillStyle('#FFFFFF') ctx.value.setTextAlign('center') ctx.value.setTextBaseline('middle') - ctx.value.fillText('鸭', duckX.value + duckWidth / 2, duckY.value + duckHeight / 2) + ctx.value.fillText('鸭', duckX.value + duckWidth.value / 2, duckY.value + duckHeight.value / 2) } - // 如果正在拖拽,绘制拖拽效果 - if (isDragging.value) { + // 恢复透明度 + ctx.value.globalAlpha = 1.0 + + // 调试模式下显示拖拽效果红框 + if (debugMode.value && isDragging.value) { // 绘制拖拽阴影 ctx.value.setFillStyle('rgba(231, 76, 60, 0.3)') - ctx.value.fillRect(duckX.value, duckY.value, duckWidth, duckHeight) + ctx.value.fillRect(duckX.value, duckY.value, duckWidth.value, duckHeight.value) // 绘制拖拽边框 ctx.value.setStrokeStyle('#e74c3c') ctx.value.setLineWidth(2) ctx.value.setLineDash([5, 5]) - ctx.value.strokeRect(duckX.value - 3, duckY.value - 3, duckWidth + 6, duckHeight + 6) + ctx.value.strokeRect(duckX.value - 3, duckY.value - 3, duckWidth.value + 6, duckHeight.value + 6) ctx.value.setLineDash([]) } } @@ -228,9 +328,9 @@ const drawDuck = () => { // 检查触摸点是否在鸭子内 const checkTouchInDuck = (x, y) => { return x >= duckX.value && - x <= duckX.value + duckWidth && + x <= duckX.value + duckWidth.value && y >= duckY.value && - y <= duckY.value + duckHeight + y <= duckY.value + duckHeight.value } // 检查鸭子是否在目标区域内 @@ -238,8 +338,8 @@ const checkDuckInTarget = () => { const target = targetArea.value // 计算鸭子中心点 - const duckCenterX = duckX.value + duckWidth / 2 - const duckCenterY = duckY.value + duckHeight / 2 + const duckCenterX = duckX.value + duckWidth.value / 2 + const duckCenterY = duckY.value + duckHeight.value / 2 // 检查中心点是否在目标区域内 return duckCenterX >= target.x && @@ -250,6 +350,12 @@ const checkDuckInTarget = () => { // 触摸开始事件 const handleTouchStart = (e) => { + // 如果 Canvas 已禁用,不处理触摸事件 + if (canvasDisabled.value) { + console.log('Canvas 已禁用,忽略触摸事件') + return + } + console.log('触摸开始事件触发', e) // 获取触摸点 @@ -259,17 +365,20 @@ const handleTouchStart = (e) => { return } - // 确保 Canvas 位置信息是最新的 - getCanvasPosition() + // 获取 Canvas 在视口中的实际位置 + getCanvasBoundingRect() // 异步获取 Canvas 位置后执行检查 setTimeout(() => { - // 计算相对于 Canvas 的坐标 - const touchX = touch.clientX - canvasRect.value.left - const touchY = touch.clientY - canvasRect.value.top + // 计算相对于 Canvas 的坐标(使用视口位置) + const touchX = touch.clientX - canvasBoundingRect.value.left + const touchY = touch.clientY - canvasBoundingRect.value.top + console.log(`触摸点 client: (${touch.clientX}, ${touch.clientY})`) + console.log(`Canvas 视口位置: (${canvasBoundingRect.value.left}, ${canvasBoundingRect.value.top})`) console.log(`触摸点相对 Canvas: (${touchX}, ${touchY})`) console.log(`鸭子位置: (${duckX.value}, ${duckY.value})`) + console.log(`鸭子尺寸: (${duckWidth.value}, ${duckHeight.value})`) // 检查是否点击在鸭子上 if (checkTouchInDuck(touchX, touchY)) { @@ -296,9 +405,9 @@ const handleTouchMove = (e) => { const touch = e.touches[0] if (!touch) return - // 计算相对于 Canvas 的坐标 - const touchX = touch.clientX - canvasRect.value.left - const touchY = touch.clientY - canvasRect.value.top + // 计算相对于 Canvas 的坐标(使用视口位置) + const touchX = touch.clientX - canvasBoundingRect.value.left + const touchY = touch.clientY - canvasBoundingRect.value.top // 更新鸭子位置 duckX.value = touchX - dragOffsetX.value @@ -308,8 +417,8 @@ const handleTouchMove = (e) => { const canvasW = canvasRect.value.width || 400 const canvasH = canvasRect.value.height || 600 - duckX.value = Math.max(0, Math.min(canvasW - duckWidth, duckX.value)) - duckY.value = Math.max(0, Math.min(canvasH - duckHeight, duckY.value)) + duckX.value = Math.max(0, Math.min(canvasW - duckWidth.value, duckX.value)) + duckY.value = Math.max(0, Math.min(canvasH - duckHeight.value, duckY.value)) console.log(`鸭子新位置: (${duckX.value}, ${duckY.value})`) } @@ -330,10 +439,27 @@ const handleTouchEnd = () => { showGuideElements.value = false // 隐藏鸭子 showDuck.value = false + // 禁用 Canvas 触摸事件 + canvasDisabled.value = true + console.log('Canvas 触摸事件已禁用') + } else { + // 未放入目标区域,回归到原始位置 + console.log('未放入目标区域,回归原始位置') + resetDuckPosition() } } } +// 重置鸭子位置到初始值 +const resetDuckPosition = () => { + // 恢复原始 rpx 值 + duckRpx.value.x = 550 + duckRpx.value.y = 220 + // 更新 px 值 + updateDuckPosition() + console.log('鸭子已回归原始位置:', duckX.value, duckY.value) +} + // 开始拖拽(从触发区域开始) const startDrag = (e) => { console.log('开始拖拽', e) @@ -343,9 +469,10 @@ const startDrag = (e) => { initCanvas() } - // 设置鸭子初始位置(在触发区域附近) - duckX.value = 50 - duckY.value = 50 + // 设置鸭子初始位置(使用 rpx 值,会自动转换为 px) + duckRpx.value.x = 100 // 100rpx 对应约 50px (iPhone 6/7/8) + duckRpx.value.y = 100 + updateDuckPosition() showDuck.value = true // 触发触摸开始事件处理 @@ -413,6 +540,12 @@ onUnmounted(() => { canvas-id="dragDuckCanvas" id="dragDuckCanvas" class="drag-canvas" + :style="{ + left: canvasRect.left + 'px', + top: canvasRect.top + 'px', + width: canvasRect.width + 'px', + height: canvasRect.height + 'px' + }" @touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd" @@ -514,10 +647,6 @@ onUnmounted(() => { .drag-canvas { position: absolute; - left: 0; - top: 800px; - width: 375px; - height: 300px; z-index: 100; pointer-events: auto; }