From 520811e883fc3fba4456a26224ed95aa056175dc Mon Sep 17 00:00:00 2001 From: zhanghao <774378400@qq.com> Date: Wed, 5 Aug 2026 15:35:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(map):=20=E4=BF=AE=E5=A4=8D=E5=B9=B3?= =?UTF-8?q?=E6=9D=BF=E8=A7=A6=E6=8E=A7=E4=B8=8E=E4=BF=A1=E6=81=AF=E7=AA=97?= =?UTF-8?q?=E5=AE=9A=E4=BD=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 统一鼠标、触摸和触控笔的画布坐标转换。修复 autofit 及浏览器缩放下的信息窗偏移,并支持平板点击机器人显示详情、点击空白隐藏。 --- src/views/chassis/MapCanvas.vue | 73 ++++++++++++++++++++++++--------- 1 file changed, 54 insertions(+), 19 deletions(-) diff --git a/src/views/chassis/MapCanvas.vue b/src/views/chassis/MapCanvas.vue index c80245e..5fefa61 100644 --- a/src/views/chassis/MapCanvas.vue +++ b/src/views/chassis/MapCanvas.vue @@ -106,11 +106,14 @@ const init = () => { */ function resizeCanvasToContainer() { if (!mapCanvas || !containerRef.value) return; - const containerRect = containerRef.value.getBoundingClientRect(); // 获取容器实际尺寸 - if (!containerRect.width || !containerRect.height) return; + // clientWidth/clientHeight 是布局坐标,不受 autofit.js 的 CSS transform 影响。 + // getBoundingClientRect() 返回的是缩放后的屏幕尺寸,不能用于 Canvas 逻辑尺寸。 + const containerWidth = containerRef.value.clientWidth; + const containerHeight = containerRef.value.clientHeight; + if (!containerWidth || !containerHeight) return; applicationState.devicePixelRatio = window.devicePixelRatio || 1; // 获取设备像素比 - applicationState.canvasWidthPx = containerRect.width; // 更新逻辑宽度 - applicationState.canvasHeightPx = containerRect.height; // 逻辑高度 = 容器高度 - 状态栏高度 + applicationState.canvasWidthPx = containerWidth; // 更新逻辑宽度 + applicationState.canvasHeightPx = containerHeight; // 逻辑高度 = 容器高度 - 状态栏高度 mapCanvas.width = applicationState.canvasWidthPx * applicationState.devicePixelRatio; // Canvas 物理像素宽 mapCanvas.height = applicationState.canvasHeightPx * applicationState.devicePixelRatio; // Canvas 物理像素高 mapCanvas.style.width = applicationState.canvasWidthPx + 'px'; // CSS 显示宽度 @@ -292,12 +295,25 @@ const bindEvent = () => { let activePointerId = null let lastPointerX = 0 let lastPointerY = 0 + let pointerStartX = 0 + let pointerStartY = 0 + let hasDragged = false + + const getCanvasPointerPosition = (pointerEvent) => { + const canvasRect = mapCanvas.getBoundingClientRect() + const scaleX = applicationState.canvasWidthPx / canvasRect.width + const scaleY = applicationState.canvasHeightPx / canvasRect.height + return { + x: (pointerEvent.clientX - canvasRect.left) * scaleX, + y: (pointerEvent.clientY - canvasRect.top) * scaleY + } + } // Pointer Events 同时支持鼠标、触控笔和 Pad 触摸。 mapCanvas.addEventListener('pointermove', (pointerEvent) => { - const canvasRect = mapCanvas.getBoundingClientRect(); // Canvas 在页面中的位置 - applicationState.mouseState.screenX = pointerEvent.clientX - canvasRect.left; - applicationState.mouseState.screenY = pointerEvent.clientY - canvasRect.top; + const pointerPosition = getCanvasPointerPosition(pointerEvent) + applicationState.mouseState.screenX = pointerPosition.x; + applicationState.mouseState.screenY = pointerPosition.y; // 反算世界坐标 const worldPos = screenToWorld(applicationState.mouseState.screenX, applicationState.mouseState.screenY, applicationState); applicationState.mouseState.worldX = worldPos.x; @@ -305,12 +321,15 @@ const bindEvent = () => { if (applicationState.mouseState.isDragging && pointerEvent.pointerId === activePointerId) { pointerEvent.preventDefault() - const movementX = pointerEvent.clientX - lastPointerX - const movementY = pointerEvent.clientY - lastPointerY + const movementX = pointerPosition.x - lastPointerX + const movementY = pointerPosition.y - lastPointerY + if (Math.hypot(pointerPosition.x - pointerStartX, pointerPosition.y - pointerStartY) > 6) { + hasDragged = true + } applicationState.camera.centerX -= movementX / applicationState.camera.pixelsPerMeter; applicationState.camera.centerY += movementY / applicationState.camera.pixelsPerMeter; - lastPointerX = pointerEvent.clientX - lastPointerY = pointerEvent.clientY + lastPointerX = pointerPosition.x + lastPointerY = pointerPosition.y } if (pointerEvent.pointerType === 'mouse') { @@ -319,9 +338,7 @@ const bindEvent = () => { }); mapCanvas.addEventListener('click', (mouseEvent) => { - const canvasRect = mapCanvas.getBoundingClientRect(); // Canvas 在页面中的位置 - const screenX = mouseEvent.clientX - canvasRect.left; // 鼠标在 Canvas 上的 X - const screenY = mouseEvent.clientY - canvasRect.top; // 鼠标在 Canvas 上的 Y + const { x: screenX, y: screenY } = getCanvasPointerPosition(mouseEvent) const worldPos = screenToWorld(screenX, screenY, applicationState); }) @@ -331,15 +348,33 @@ const bindEvent = () => { return } pointerEvent.preventDefault() + const pointerPosition = getCanvasPointerPosition(pointerEvent) activePointerId = pointerEvent.pointerId - lastPointerX = pointerEvent.clientX - lastPointerY = pointerEvent.clientY + lastPointerX = pointerPosition.x + lastPointerY = pointerPosition.y + pointerStartX = pointerPosition.x + pointerStartY = pointerPosition.y + hasDragged = false applicationState.mouseState.isDragging = true; mapCanvas.setPointerCapture(pointerEvent.pointerId) }); - const endPointerDrag = (pointerEvent) => { + const endPointerDrag = (pointerEvent, cancelled = false) => { if (pointerEvent.pointerId !== activePointerId) return + + // Pad/触控笔轻点时执行命中检测;轻点空白区域会隐藏信息窗。 + if (!cancelled && !hasDragged && pointerEvent.pointerType !== 'mouse') { + const pointerPosition = getCanvasPointerPosition(pointerEvent) + applicationState.mouseState.screenX = pointerPosition.x + applicationState.mouseState.screenY = pointerPosition.y + const worldPos = screenToWorld( + applicationState.mouseState.screenX, + applicationState.mouseState.screenY, + applicationState + ) + updateTooltipDisplay(hitTestRobots(worldPos.x, worldPos.y, applicationState)) + } + applicationState.mouseState.isDragging = false; if (mapCanvas.hasPointerCapture(pointerEvent.pointerId)) { mapCanvas.releasePointerCapture(pointerEvent.pointerId) @@ -347,8 +382,8 @@ const bindEvent = () => { activePointerId = null } - mapCanvas.addEventListener('pointerup', endPointerDrag); - mapCanvas.addEventListener('pointercancel', endPointerDrag); + mapCanvas.addEventListener('pointerup', (pointerEvent) => endPointerDrag(pointerEvent)); + mapCanvas.addEventListener('pointercancel', (pointerEvent) => endPointerDrag(pointerEvent, true)); // ── 滚轮:缩放(向鼠标位置缩放)── mapCanvas.addEventListener('wheel', (wheelEvent) => {