diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue index 57df5bd..35fd011 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue @@ -427,40 +427,114 @@ const onDragEnd = (eventData) => { // 处理事件总线的area.resize.move事件 const onAreaResizeMove = (eventData) => { console.log(`[Area:${props.id}] 收到AREA_RESIZE_MOVE事件:`, eventData) // 添加调试日志 - const { areaId, size, position, direction } = eventData + const { areaId, size, position, direction, timestamp } = eventData if (areaId !== props.id) { console.log(`[Area:${props.id}] areaId不匹配,期望: ${props.id}, 实际: ${areaId}`) // 添加调试日志 return } + // 防御性检查,确保事件数据完整 + if (!size || !position) { + console.error(`[Area:${props.id}] 无效的事件数据,缺少size或position`) + return + } + console.log(`[Area:${props.id}] 更新前originalPosition:`, originalPosition.value) // 添加调试日志 // 应用最小尺寸限制,与CSS保持一致 const minWidth = 200 const minHeight = 30 - let newWidth = size.width - let newHeight = size.height - let newLeft = position.left - let newTop = position.top + // 对输入值进行验证和过滤,确保数值有效 + const inputWidth = Number(size.width) + const inputHeight = Number(size.height) + const inputLeft = Number(position.left) + const inputTop = Number(position.top) - // 宽度限制 - if (direction.includes('right') || direction.includes('left')) { - newWidth = Math.max(minWidth, newWidth) - originalPosition.value.width = newWidth - if (direction.includes('left')) { - originalPosition.value.left = newLeft - } + if (isNaN(inputWidth) || isNaN(inputHeight) || isNaN(inputLeft) || isNaN(inputTop)) { + console.error(`[Area:${props.id}] 收到无效的数值,跳过更新`) + return } - // 高度限制 - if (direction.includes('bottom') || direction.includes('top')) { - newHeight = Math.max(minHeight, newHeight) - originalPosition.value.height = newHeight - if (direction.includes('top')) { - originalPosition.value.top = newTop - } + // 直接使用Panel计算好的尺寸和位置,不再重新计算 + // Panel已经处理了对角线方向的特殊逻辑,Area只需要验证和应用即可 + let newWidth = Math.max(minWidth, inputWidth) + let newHeight = Math.max(minHeight, inputHeight) + let newLeft = inputLeft + let newTop = inputTop + + // 计算边界位置,用于调试 + const oldBottom = originalPosition.value.top + originalPosition.value.height + const newBottom = newTop + newHeight + const oldRight = originalPosition.value.left + originalPosition.value.width + const newRight = newLeft + newWidth + + // 对对角线方向进行特殊处理和日志记录 + switch (direction) { + case 'top-right': + console.log(`[Area:${props.id}] top-right方向处理: 原下边框: ${oldBottom}, 新下边框: ${newBottom}, 差值: ${newBottom - oldBottom}`) + // 验证top-right方向下边框位置是否保持不变 + if (Math.abs(newBottom - oldBottom) > 1) { + console.warn(`[Area:${props.id}] top-right方向下边框位置变化较大,可能存在计算误差`) + } + break + + case 'top-left': + console.log(`[Area:${props.id}] top-left方向处理: 原下边框: ${oldBottom}, 新下边框: ${newBottom}, 差值: ${newBottom - oldBottom}`) + console.log(`[Area:${props.id}] top-left方向处理: 原右边框: ${oldRight}, 新右边框: ${newRight}, 差值: ${newRight - oldRight}`) + break + + case 'bottom-left': + console.log(`[Area:${props.id}] bottom-left方向处理: 原上边框: ${originalPosition.value.top}, 新上边框: ${newTop}, 差值: ${newTop - originalPosition.value.top}`) + console.log(`[Area:${props.id}] bottom-left方向处理: 原右边框: ${oldRight}, 新右边框: ${newRight}, 差值: ${newRight - oldRight}`) + break + + case 'top': + console.log(`[Area:${props.id}] top方向处理: 原下边框: ${oldBottom}, 新下边框: ${newBottom}, 差值: ${newBottom - oldBottom}`) + break + + case 'left': + console.log(`[Area:${props.id}] left方向处理: 原右边框: ${oldRight}, 新右边框: ${newRight}, 差值: ${newRight - oldRight}`) + break + } + + // 使用已有的边界位置计算结果,无需重新声明 + // 确保新的位置和尺寸在合理范围内 + // 注意:不直接限制newTop和newLeft,而是通过调整尺寸来保持在可视区域内 + // 这样可以避免拖拽时的突然跳动 + + // 获取父容器尺寸,用于边界检查 + let parentWidth = window.innerWidth + let parentHeight = window.innerHeight + + if (parentContainer.value && parentContainer.value !== window) { + const parentRect = parentContainer.value.getBoundingClientRect() + parentWidth = parentRect.width + parentHeight = parentRect.height + } + + // 确保整个Area在父容器可视范围内 + // 如果右侧超出,调整宽度 + if (newRight > parentWidth) { + newWidth = parentWidth - newLeft + } + + // 如果底部超出,调整高度 + if (newBottom > parentHeight) { + newHeight = parentHeight - newTop + } + + // 确保不小于最小尺寸 + newWidth = Math.max(minWidth, newWidth) + newHeight = Math.max(minHeight, newHeight) + + // 原子性更新originalPosition,避免中间状态被访问 + originalPosition.value = { + width: newWidth, + height: newHeight, + left: newLeft, + top: newTop } console.log(`[Area:${props.id}] 更新后originalPosition:`, originalPosition.value) // 添加调试日志 @@ -471,7 +545,8 @@ const onAreaResizeMove = (eventData) => { left: originalPosition.value.left, top: originalPosition.value.top, width: originalPosition.value.width, - height: originalPosition.value.height + height: originalPosition.value.height, + timestamp: timestamp || Date.now() }, { source: { component: 'Area', areaId: props.id } }) diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue index 8eab222..2819a1b 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue @@ -430,6 +430,10 @@ const onResizeStart = (e, direction) => { return; } + // 重置之前的resize状态,避免状态污染 + isResizing = false; + currentResizeDirection = null; + isResizing = true; currentResizeDirection = direction; currentAreaId = getCurrentAreaId(); @@ -439,20 +443,72 @@ const onResizeStart = (e, direction) => { const startPosition = { x: e.clientX, y: e.clientY }; const areaHandler = getAreaHandler(); + if (!areaHandler) { + console.error(`[Panel:${props.id}] 无法获取AreaHandler`); + return; + } + + // 获取最新的Area状态,添加防御性检查 const areaState = areaHandler.getAreaState(currentAreaId); + if (!areaState) { + console.error(`[Panel:${props.id}] 无法获取Area状态,areaId: ${currentAreaId}`); + return; + } + + // 使用当前DOM元素的实际尺寸作为基准,确保获取最新状态 + let actualWidth = areaState.width || 0; + let actualHeight = areaState.height || 0; + let actualLeft = areaState.left || 0; + let actualTop = areaState.top || 0; + + // 优先使用Area元素的实际尺寸和位置,而不是Panel元素 + const areaElement = document.querySelector(`[data-area-id="${currentAreaId}"]`); + if (areaElement) { + const rect = areaElement.getBoundingClientRect(); + // 只有当获取到的尺寸有效时才使用DOM尺寸 + if (rect.width > 0 && rect.height > 0) { + // 注意:rect.left和rect.top是相对于视口的位置,需要转换为相对于父容器的位置 + const parentElement = areaElement.parentElement; + if (parentElement) { + const parentRect = parentElement.getBoundingClientRect(); + actualLeft = rect.left - parentRect.left; + actualTop = rect.top - parentRect.top; + } + actualWidth = rect.width; + actualHeight = rect.height; + console.log(`[Panel:${props.id}] 使用Area DOM实际尺寸和位置作为参考: {width: ${actualWidth}, height: ${actualHeight}, left: ${actualLeft}, top: ${actualTop}}`); + } + } + + // 如果Area元素获取失败,尝试使用Panel元素作为备选 + else { + const currentElement = document.querySelector(`[data-panel-id="${props.id}"]`); + if (currentElement) { + const rect = currentElement.getBoundingClientRect(); + if (rect.width > 0 && rect.height > 0) { + actualWidth = rect.width; + actualHeight = rect.height; + console.log(`[Panel:${props.id}] 使用Panel DOM实际尺寸作为参考: {width: ${actualWidth}, height: ${actualHeight}}`); + } + } + } + const areaStartState = { - width: areaState.width || 0, - height: areaState.height || 0, - left: areaState.left || 0, - top: areaState.top || 0 + width: Math.max(200, actualWidth), + height: Math.max(30, actualHeight), + left: actualLeft, + top: actualTop }; - // 发送resize开始事件 + console.log(`[Panel:${props.id}] 确定的areaStartState:`, areaStartState); + + // 发送resize开始事件,包含初始状态便于调试 emitEvent(EVENT_TYPES.PANEL_RESIZE_START, { panelId: props.id, areaId: currentAreaId, direction, position: startPosition, + initialState: { width: actualWidth, height: actualHeight, left: actualLeft, top: actualTop }, // 添加初始状态 timestamp: Date.now() }, { source: { component: 'Panel', panelId: props.id } @@ -465,40 +521,107 @@ const onResizeStart = (e, direction) => { if (isResizing && currentAreaId) { const currentPosition = { x: e.clientX, y: e.clientY }; - const totalDelta = { - width: currentPosition.x - startPosition.x, - height: currentPosition.y - startPosition.y - }; + // 直接计算拖动距离,使用更简洁的变量名 + const deltaX = currentPosition.x - startPosition.x; + const deltaY = currentPosition.y - startPosition.y; // 定义最小尺寸常量 const minWidth = 200; const minHeight = 30; - // 根据方向正确计算新尺寸 - let newWidth = areaStartState.width; - let newHeight = areaStartState.height; + // 使用初始状态作为基准,确保对角线方向拖拽时保持相对位置不变 + const initialWidth = areaStartState.width; + const initialHeight = areaStartState.height; + const initialLeft = areaStartState.left; + const initialTop = areaStartState.top; - // 宽度计算 - if (currentResizeDirection.includes('left')) { - // 左方向:宽度增加 = 原始宽度 - deltaX(因为deltaX为负) - newWidth = areaStartState.width - totalDelta.width; - } else if (currentResizeDirection.includes('right')) { - // 右方向:宽度增加 = 原始宽度 + deltaX - newWidth = areaStartState.width + totalDelta.width; + // 计算初始边界位置 + const initialRight = initialLeft + initialWidth; + const initialBottom = initialTop + initialHeight; + + let newWidth = initialWidth; + let newHeight = initialHeight; + let newLeft = initialLeft; + let newTop = initialTop; + + // 处理不同方向的拖拽逻辑 + switch (currentResizeDirection) { + case 'top-right': + // 向右上方拖拽:保持下边框位置不变 + newTop = Math.max(0, initialTop + deltaY); + newHeight = initialBottom - newTop; + newWidth = initialWidth + deltaX; + break; + + case 'top-left': + // 向左上方拖拽:保持下边框和右边框位置不变 + newLeft = Math.max(0, initialLeft + deltaX); + newWidth = initialRight - newLeft; + newTop = Math.max(0, initialTop + deltaY); + newHeight = initialBottom - newTop; + break; + + case 'bottom-right': + // 向右下方拖拽:保持上边框位置不变 + newWidth = initialWidth + deltaX; + newHeight = initialHeight + deltaY; + break; + + case 'bottom-left': + // 向左下方拖拽:保持上边框和右边框位置不变 + newLeft = Math.max(0, initialLeft + deltaX); + newWidth = initialRight - newLeft; + newHeight = initialHeight + deltaY; + break; + + case 'top': + // 向上拖拽:保持下边框位置不变 + // 注意:向上拖拽时deltaY为负,所以newTop会减小,newHeight会增加 + newTop = Math.max(0, initialTop + deltaY); + newHeight = initialBottom - newTop; + break; + + case 'right': + // 向右拖拽:保持高度不变 + newWidth = initialWidth + deltaX; + break; + + case 'bottom': + // 向下拖拽:保持高度增加 + newHeight = initialHeight + deltaY; + break; + + case 'left': + // 向左拖拽:保持右边框位置不变 + newLeft = Math.max(0, initialLeft + deltaX); + newWidth = initialRight - newLeft; + break; + + default: + // 其他方向,使用默认逻辑 + if (currentResizeDirection.includes('left')) { + newWidth = initialWidth - deltaX; + newLeft = initialLeft + deltaX; + } else if (currentResizeDirection.includes('right')) { + newWidth = initialWidth + deltaX; + } + + if (currentResizeDirection.includes('top')) { + // 向上拖拽时,deltaY为负,所以newHeight应该增加 + newTop = Math.max(0, initialTop + deltaY); + newHeight = initialBottom - newTop; + } else if (currentResizeDirection.includes('bottom')) { + newHeight = initialHeight + deltaY; + } + break; } - // 高度计算 - if (currentResizeDirection.includes('top')) { - // 上方向:高度增加 = 原始高度 - deltaY(因为deltaY为负) - newHeight = areaStartState.height - totalDelta.height; - } else if (currentResizeDirection.includes('bottom')) { - // 下方向:高度增加 = 原始高度 + deltaY - newHeight = areaStartState.height + totalDelta.height; - } + // 确保尺寸在合理范围内 + newWidth = Math.max(minWidth, Math.min(newWidth, window.innerWidth - 50)); + newHeight = Math.max(minHeight, Math.min(newHeight, window.innerHeight - 100)); // 检查尺寸是否小于最小值,如果小于则不发送事件 if (newWidth < minWidth || newHeight < minHeight) { - // 尺寸小于最小值,不发送事件,停止拖拽 return; } @@ -508,18 +631,19 @@ const onResizeStart = (e, direction) => { }; const newPosition = { - left: areaStartState.left, - top: areaStartState.top + left: newLeft, + top: newTop }; - if (currentResizeDirection.includes('left')) { - newPosition.left = areaStartState.left + totalDelta.width; - } - if (currentResizeDirection.includes('top')) { - newPosition.top = areaStartState.top + totalDelta.height; - } + // 计算实际的下边框位置,用于调试 + const actualBottom = newTop + newHeight; + console.log(`[Panel:${props.id}] resize移动,方向: ${currentResizeDirection},初始边界: {top: ${initialTop}, bottom: ${initialBottom}, left: ${initialLeft}, right: ${initialRight}},拖动距离: {deltaX: ${deltaX}, deltaY: ${deltaY}},新位置: {left: ${newLeft}, top: ${newTop}},新尺寸: ${JSON.stringify(newSize)},实际下边框: ${actualBottom},高度差: ${actualBottom - initialBottom}`); - console.log(`[Panel:${props.id}] resize移动,方向: ${currentResizeDirection},总量: ${JSON.stringify(newSize)},位置: ${JSON.stringify(newPosition)}`); + // 发送事件前,确保所有值都是合理的数字 + if (isNaN(newWidth) || isNaN(newHeight) || isNaN(newPosition.left) || isNaN(newPosition.top)) { + console.error(`[Panel:${props.id}] 计算出无效的resize值,跳过事件发送`); + return; + } emitEvent(EVENT_TYPES.PANEL_RESIZE_MOVE, { panelId: props.id, @@ -527,6 +651,7 @@ const onResizeStart = (e, direction) => { direction: currentResizeDirection, size: newSize, position: newPosition, + delta: { x: deltaX, y: deltaY }, // 添加拖动距离,便于调试 timestamp: Date.now() }, { source: { component: 'Panel', panelId: props.id } @@ -560,8 +685,9 @@ const onResizeStart = (e, direction) => { document.removeEventListener('mouseup', onResizeEnd); document.removeEventListener('mouseleave', onResizeEnd); - // 重置resize状态 + // 完全重置resize状态,避免状态污染 currentResizeDirection = null; + currentAreaId = null; } };