From 0d3b81df7f82992ad1d8bd82ccf899af33afeeb3 Mon Sep 17 00:00:00 2001 From: zqm Date: Tue, 18 Nov 2025 15:39:46 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=B8=AD=E5=BF=83=E5=81=9C?= =?UTF-8?q?=E9=9D=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Windows/Robot/Web/src/DockLayout/Area.vue | 38 +- .../Robot/Web/src/DockLayout/DockLayout.vue | 1039 ++++++++++++++++- .../Robot/Web/src/DockLayout/Panel.vue | 36 +- .../Robot/Web/src/DockLayout/TabPage.vue | 114 +- .../Robot/Web/src/DockLayout/ToDoList.md | 69 ++ 5 files changed, 1184 insertions(+), 112 deletions(-) diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue index 266fddc..c87a516 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue @@ -99,15 +99,19 @@
- - - - @@ -579,7 +594,8 @@ const mergeAreaContent = (sourceArea) => { // 处理源Area的所有tabPages if (sourceArea.tabPages && sourceArea.tabPages.length > 0) { sourceArea.tabPages.forEach((tabPage, tabIndex) => { - const newTabPageId = `merged-tabpage-${Date.now()}-${tabIndex}` + // 保持原有的tabPage ID,确保Vue组件状态连续性 + const tabPageId = `merged-tabpage-${tabPage.id}` const newPanels = (tabPage.panels || []).map((panel, panelIndex) => { // 保持原有Panel ID不变,确保Vue响应式和状态稳定性 console.log(`[Area] 添加Panel: ${panel.id}`) @@ -590,11 +606,11 @@ const mergeAreaContent = (sourceArea) => { }) receivedContent.value.push({ - id: `received-${newTabPageId}`, + id: `received-${tabPageId}`, title: tabPage.title || `标签页${tabIndex + 1}`, tabPage: { ...tabPage, - id: newTabPageId, + id: tabPageId, panels: newPanels }, panels: newPanels @@ -639,7 +655,7 @@ const mergeAreaContent = (sourceArea) => { } }) - // 将新的Panel添加到现有TabPage + // 将新的Panel添加到现有TabPage,保持ID连续性 existingTabPage.tabPage.panels.push(...newPanels) // existingTabPage.panels 是旧引用,保持结构一致性但避免重复添加 diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue index 994ccd4..44e8ada 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue @@ -22,7 +22,25 @@ @dragleave="handleMainAreaDragLeave" @area-merged="onAreaMerged" > - + +
+ + + + +
@@ -94,7 +112,7 @@ @maximize="(panelId) => $emit('maximize', panelId)" @close="onClosePanel(area.id, panel.id)" @toggleToolbar="onToggleToolbar" - @dragStart="onPanelDragStart(area.id, $event)" + @dragStart="onPanelDragStartFromTabPage(area.id, $event)" @dragMove="onPanelDragMove(area.id, $event)" @dragEnd="onPanelDragEnd" @dragover="handleAreaDragOver" @@ -111,6 +129,7 @@ import Area from './Area.vue'; import Panel from './Panel.vue'; import TabPage from './TabPage.vue'; import DockIndicator from './DockIndicator.vue'; +import ResizeBar from './ResizeBar.vue'; // 定义组件可以发出的事件 const emit = defineEmits([ @@ -147,6 +166,9 @@ const activeDockZone = ref(null) // 检查主区域内是否有其他Area(TabPage和Panel等子组件) const hasAreasInMainContent = ref(false) +// 主区域ResizeBar列表 +const mainAreaResizeBars = ref([]) + // 计算是否隐藏外部边缘指示器 const hideEdgeIndicators = computed(() => { // 当主区域内没有其他Area时,隐藏外部边缘指示器 @@ -169,6 +191,27 @@ const tabDragState = ref({ startAreaPos: { x: 0, y: 0 } }) +// 处理从TabPage转发来的Panel拖拽事件 +const onPanelDragStartFromTabPage = (areaId, event) => { + console.log('🔸 DockLayout收到从TabPage转发的Panel拖拽事件:', { areaId, event }) + // 直接调用原始的onPanelDragStart处理函数 + onPanelDragStart(areaId, event) +} + +// 处理从TabPage转发来的Panel拖拽移动事件 +const onPanelDragMoveFromTabPage = (areaId, event) => { + console.log('🔸 DockLayout收到从TabPage转发的Panel拖拽移动事件:', { areaId, event }) + // 直接调用原始的onPanelDragMove处理函数 + onPanelDragMove(areaId, event) +} + +// 处理从TabPage转发来的Panel拖拽结束事件 +const onPanelDragEndFromTabPage = () => { + console.log('🔸 DockLayout收到从TabPage转发的Panel拖拽结束事件') + // 直接调用原始的onPanelDragEnd处理函数 + onPanelDragEnd() +} + // 检测主区域内是否有其他Area const checkMainContentForAreas = () => { if (!mainAreaRef.value) { @@ -455,12 +498,23 @@ const onAreaMerged = (eventData) => { // Panel拖拽开始 const onPanelDragStart = (areaId, event) => { + console.log('=== PANEL拖拽开始调试 ===') + console.log('areaId:', areaId) + console.log('event:', event) + console.log('event.clientX:', event?.clientX) + console.log('event.clientY:', event?.clientY) + console.log('event.panelId:', event?.panelId) + const area = floatingAreas.value.find(a => a.id === areaId) + console.log('找到的area:', area) + console.log('area.tabPages.length:', area?.tabPages?.length) + console.log('area.tabPages[0].panels.length:', area?.tabPages?.[0]?.panels?.length) + // 只有当Area中只有一个TabPage且该TabPage中只有一个Panel时才允许通过Panel标题栏移动Area if (area && area.tabPages && area.tabPages.length === 1 && area.tabPages[0].panels && area.tabPages[0].panels.length === 1) { // 检查event是否为对象格式(来自Panel.vue) - const clientX = event.clientX || (typeof event === 'object' ? event.x : event?.x) - const clientY = event.clientY || (typeof event === 'object' ? event.y : event?.y) + const clientX = event.clientX || (typeof event === 'object' ? event.clientX : event?.clientX) + const clientY = event.clientY || (typeof event === 'object' ? event.clientY : event?.clientY) if (clientX === undefined || clientY === undefined) { console.error('无法获取有效的鼠标位置信息:', event) @@ -509,12 +563,20 @@ const onPanelDragStart = (areaId, event) => { // Panel拖拽移动 const onPanelDragMove = (areaId, event) => { + console.log('=== PANEL拖拽移动调试 ===') + console.log('areaId:', areaId) + console.log('panelDragState.isDragging:', panelDragState.value.isDragging) + console.log('panelDragState.currentAreaId:', panelDragState.value.currentAreaId) + console.log('event:', event) + console.log('event.clientX:', event?.clientX) + console.log('event.clientY:', event?.clientY) + if (panelDragState.value.isDragging && panelDragState.value.currentAreaId === areaId) { const area = floatingAreas.value.find(a => a.id === areaId) if (area) { // 检查event是否为对象格式(来自Panel.vue) - const clientX = event.clientX || (typeof event === 'object' ? event.x : event?.x) - const clientY = event.clientY || (typeof event === 'object' ? event.y : event?.y) + const clientX = event.clientX || (typeof event === 'object' ? event.clientX : event?.clientX) + const clientY = event.clientY || (typeof event === 'object' ? event.clientY : event?.clientY) if (clientX === undefined || clientY === undefined) { return @@ -558,6 +620,11 @@ const onPanelDragMove = (areaId, event) => { // Panel拖拽结束 const onPanelDragEnd = () => { + console.log('=== PANEL拖拽结束调试 ===') + console.log('panelDragState.isDragging:', panelDragState.value.isDragging) + console.log('panelDragState.currentAreaId:', panelDragState.value.currentAreaId) + console.log('activeDockZone:', activeDockZone.value) + console.log('currentMousePosition:', currentMousePosition.value) panelDragState.value.isDragging = false const currentAreaId = panelDragState.value.currentAreaId @@ -638,6 +705,8 @@ const onAreaDragMove = (areaId, event) => { // Area拖拽结束 const onAreaDragEnd = (areaId, event) => { + console.log('[DockLayout] Area拖拽结束:', { areaId, event, activeDockZone: activeDockZone.value }) + // 清理拖拽状态 panelDragState.value.isDragging = false panelDragState.value.currentAreaId = null @@ -645,6 +714,7 @@ const onAreaDragEnd = (areaId, event) => { // 3.1 在onAreaDragEnd方法中添加中心停靠检测 // 确保只有在独立中心指示器区域内释放才执行停靠 if (activeDockZone.value === 'center' && isMouseInCenterIndicator(currentMousePosition.value.x, currentMousePosition.value.y)) { + console.log('[DockLayout] 检测到中心停靠区域,鼠标位置:', currentMousePosition.value) // 处理中心停靠 const result = handleCenterDocking(areaId) if (!result.success) { @@ -652,6 +722,12 @@ const onAreaDragEnd = (areaId, event) => { } else { console.log('Area中心停靠成功:', result.message) } + } else { + console.log('[DockLayout] 未检测到有效的中心停靠:', { + activeDockZone: activeDockZone.value, + mousePosition: currentMousePosition.value, + isInCenter: isMouseInCenterIndicator(currentMousePosition.value?.x || 0, currentMousePosition.value?.y || 0) + }) } // 隐藏停靠指示器 @@ -845,55 +921,77 @@ const isMainAreaEmpty = () => { const updateDockZoneByMousePosition = (mouseX, mouseY) => { if (!dockLayoutRef.value || !targetAreaRect.value) return - const rect = dockLayoutRef.value.getBoundingClientRect() - const { left, top, width, height } = targetAreaRect.value - - // 计算鼠标相对于目标区域的相对位置 (0-1) - const relativeX = (mouseX - rect.left - left) / width - const relativeY = (mouseY - rect.top - top) / height - - // 定义各个区域的阈值 - const threshold = 0.2 // 20% 边缘区域 - - let newActiveZone = null - - // 检查主区域是否为空 - const mainAreaEmpty = isMainAreaEmpty() - - if (relativeX >= 0 && relativeX <= 1 && relativeY >= 0 && relativeY <= 1) { - // 鼠标在目标区域内 - if (mainAreaEmpty) { - // 主区域为空时:只允许停靠到中心区域 - if (relativeX >= 0.3 && relativeX <= 0.7 && relativeY >= 0.3 && relativeY <= 0.7) { - // 扩大的中心区域 (30%-70% 的中心区域) - newActiveZone = 'center' - } - } else { - // 主区域不为空时:显示所有指示器选项 - if (relativeY <= threshold) { - newActiveZone = 'top' - } else if (relativeY >= 1 - threshold) { - newActiveZone = 'bottom' - } else if (relativeX <= threshold) { - newActiveZone = 'left' - } else if (relativeX >= 1 - threshold) { - newActiveZone = 'right' - } else if (relativeX >= 0.4 && relativeX <= 0.6 && relativeY >= 0.4 && relativeY <= 0.6) { - // 中心区域 (40%-60% 的中心区域) - newActiveZone = 'center' + try { + const rect = dockLayoutRef.value.getBoundingClientRect() + const { left, top, width, height } = targetAreaRect.value + + // 计算鼠标相对于目标区域的相对位置 (0-1) + const relativeX = (mouseX - rect.left - left) / width + const relativeY = (mouseY - rect.top - top) / height + + // 定义各个区域的阈值 + const threshold = 0.2 // 20% 边缘区域 + + let newActiveZone = null + + // 检查主区域是否为空 + const mainAreaEmpty = isMainAreaEmpty() + + if (relativeX >= 0 && relativeX <= 1 && relativeY >= 0 && relativeY <= 1) { + // 鼠标在目标区域内 + if (mainAreaEmpty) { + // 主区域为空时:只允许停靠到中心区域 + // 扩大中心区域检测范围到更宽容的范围 + const centerRadius = 0.25 // 中心区域半径25% + const centerDistance = Math.sqrt( + Math.pow(relativeX - 0.5, 2) + Math.pow(relativeY - 0.5, 2) + ) + + if (centerDistance <= centerRadius) { + newActiveZone = 'center' + console.log('[DockLayout] 主区域为空,检测到中心停靠区域:', { + mouseX, mouseY, + relativeX: relativeX.toFixed(3), + relativeY: relativeY.toFixed(3), + centerDistance: centerDistance.toFixed(3), + centerRadius: centerRadius, + newActiveZone + }) + } + } else { + // 主区域不为空时:显示所有指示器选项 + if (relativeY <= threshold) { + newActiveZone = 'top' + } else if (relativeY >= 1 - threshold) { + newActiveZone = 'bottom' + } else if (relativeX <= threshold) { + newActiveZone = 'left' + } else if (relativeX >= 1 - threshold) { + newActiveZone = 'right' + } else if (relativeX >= 0.4 && relativeX <= 0.6 && relativeY >= 0.4 && relativeY <= 0.6) { + // 中心区域 (40%-60% 的中心区域) + newActiveZone = 'center' + } } } - } - - // 只有当停靠区域改变时才更新,减少不必要的重新渲染 - if (activeDockZone.value !== newActiveZone) { - activeDockZone.value = newActiveZone - // 如果激活的区域不为空,显示指示器;如果为空,隐藏指示器 - if (newActiveZone) { - showDockIndicator.value = true - } else { - showDockIndicator.value = false + + // 只有当停靠区域改变时才更新,减少不必要的重新渲染 + if (activeDockZone.value !== newActiveZone) { + const oldZone = activeDockZone.value + activeDockZone.value = newActiveZone + console.log('[DockLayout] 停靠区域更新:', { from: oldZone, to: newActiveZone }) + + // 如果激活的区域不为空,显示指示器;如果为空,隐藏指示器 + // 但在拖拽过程中,始终保持中心指示器显示 + const isDragging = panelDragState.value.isDragging || tabDragState.value.isDragging + if (newActiveZone || isDragging) { + showDockIndicator.value = true + } else { + showDockIndicator.value = false + } } + } catch (error) { + console.warn('更新停靠区域时出错:', error) } } @@ -937,7 +1035,9 @@ const handleMainAreaDragLeave = () => { const dockLayout = dockLayoutRef.value // 如果活动元素不是dockLayout的后代,隐藏指示器 - if (!dockLayout || (activeElement && !dockLayout.contains(activeElement))) { + // 但如果在拖拽过程中,不立即隐藏指示器 + const isDragging = panelDragState.value.isDragging || tabDragState.value.isDragging + if (!isDragging && (!dockLayout || (activeElement && !dockLayout.contains(activeElement)))) { showDockIndicator.value = false activeDockZone.value = null } @@ -982,7 +1082,9 @@ const handleAreaDragLeave = () => { const dockLayout = dockLayoutRef.value // 如果活动元素不是dockLayout的后代,隐藏指示器 - if (!dockLayout || (activeElement && !dockLayout.contains(activeElement))) { + // 但如果在拖拽过程中,不立即隐藏指示器 + const isDragging = panelDragState.value.isDragging || tabDragState.value.isDragging + if (!isDragging && (!dockLayout || (activeElement && !dockLayout.contains(activeElement)))) { showDockIndicator.value = false activeDockZone.value = null } @@ -1019,13 +1121,31 @@ const isMouseInCenterIndicator = (mouseX, mouseY) => { try { // 查找独立中心指示器元素 const centerIndicator = dockLayoutRef.value.querySelector('.center-main-indicator') - if (!centerIndicator) return false + if (!centerIndicator) { + console.log('未找到.center-main-indicator元素') + return false + } + + // 检查元素是否可见 + const style = window.getComputedStyle(centerIndicator) + if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') { + console.log('独立中心指示器被隐藏') + return false + } const rect = centerIndicator.getBoundingClientRect() + const isInIndicator = mouseX >= rect.left && mouseX <= rect.right && + mouseY >= rect.top && mouseY <= rect.bottom - // 检查鼠标是否在独立中心指示器的边界框内 - return mouseX >= rect.left && mouseX <= rect.right && - mouseY >= rect.top && mouseY <= rect.bottom + console.log('独立中心指示器检测:', { + mouseX, mouseY, + indicatorRect: rect, + isInIndicator, + showDockIndicator: showDockIndicator.value, + activeDockZone: activeDockZone.value + }) + + return isInIndicator } catch (error) { console.warn('检查独立中心指示器区域时出错:', error) return false @@ -1437,16 +1557,809 @@ const clearHiddenList = () => { } } +/** + * 统一停靠结束处理函数 + * 根据拖拽类型和目标区域执行相应的停靠逻辑 + * @param {string} dragType - 拖拽类型 ('panel', 'area', 'tabpage') + * @param {string} sourceAreaId - 源Area的ID + * @param {Object} options - 可选参数 + * @returns {Object} 处理结果 {success: boolean, message: string, strategy?: string} + */ +const handleDockingEnding = (dragType, sourceAreaId, options = {}) => { + console.log(`[DockLayout] 处理停靠结束事件: type=${dragType}, areaId=${sourceAreaId}`) + + try { + // 确保只有在正确的区域内释放才执行停靠 + if (!activeDockZone.value || !sourceAreaId) { + return { + success: false, + message: '缺少停靠区域或源Area ID' + } + } + + // 根据停靠区域类型执行不同的停靠逻辑 + switch (activeDockZone.value) { + case 'center': + // 中心停靠 + const centerResult = handleCenterDocking(sourceAreaId) + if (!centerResult.success) { + console.warn('中心停靠失败:', centerResult.message) + } else { + console.log('中心停靠成功:', centerResult.message) + } + return centerResult + + case 'top': + case 'bottom': + case 'left': + case 'right': + // 外部边缘停靠 + const edgeResult = handleEdgeDocking(sourceAreaId, activeDockZone.value) + if (!edgeResult.success) { + console.warn('外部边缘停靠失败:', edgeResult.message) + } else { + console.log('外部边缘停靠成功:', edgeResult.message) + } + return edgeResult + + default: + return { + success: false, + message: `不支持的停靠区域: ${activeDockZone.value}` + } + } + + } catch (error) { + console.error('[DockLayout] 停靠处理过程中发生错误:', error) + return { + success: false, + message: `停靠过程中发生错误: ${error.message}` + } + } +} + +/** + * 处理外部边缘停靠逻辑 + * @param {string} sourceAreaId - 源Area的ID + * @param {string} dockZone - 停靠方向 ('top', 'bottom', 'left', 'right') + * @returns {Object} 处理结果 {success: boolean, message: string, strategy?: string} + */ +const handleEdgeDocking = (sourceAreaId, dockZone) => { + console.log(`[DockLayout] 开始外部边缘停靠: areaId=${sourceAreaId}, zone=${dockZone}`) + + try { + // 1. 查找源Area + const sourceArea = floatingAreas.value.find(a => a.id === sourceAreaId) + if (!sourceArea) { + return { + success: false, + message: `未找到ID为 ${sourceAreaId} 的源Area` + } + } + + // 2. 验证停靠条件 + const validationResult = canDockToEdge(sourceArea, dockZone) + if (!validationResult.canDock) { + return { + success: false, + message: `边缘停靠验证失败: ${validationResult.reason}` + } + } + + console.log('边缘停靠验证通过:', validationResult.reason) + + // 3. 根据主区域状态选择处理策略 + if (validationResult.strategy === 'side-by-side') { + // 执行并排停靠策略 + return handleSideBySideDocking(sourceArea, dockZone) + } else { + return { + success: false, + message: `暂不支持的边缘停靠策略: ${validationResult.strategy}` + } + } + + } catch (error) { + console.error('[DockLayout] 外部边缘停靠处理过程中发生错误:', error) + return { + success: false, + message: `外部边缘停靠过程中发生错误: ${error.message}` + } + } +} + +/** + * 检查外部边缘停靠条件 + * @param {Object} sourceArea - 源Area对象 + * @param {string} dockZone - 停靠方向 + * @returns {Object} 验证结果 {canDock: boolean, reason: string, strategy: string} + */ +const canDockToEdge = (sourceArea, dockZone) => { + // 验证1:检查源Area是否有效 + if (!sourceArea) { + return { + canDock: false, + reason: '源Area不存在' + } + } + + if (!sourceArea.tabPages || sourceArea.tabPages.length === 0) { + return { + canDock: false, + reason: '源Area不包含TabPage,无法停靠' + } + } + + // 验证2:检查停靠方向 + const validZones = ['top', 'bottom', 'left', 'right'] + if (!validZones.includes(dockZone)) { + return { + canDock: false, + reason: `不支持的停靠方向: ${dockZone}` + } + } + + // 验证3:检查主区域内容状态 + checkMainContentForAreas() + + if (hasAreasInMainContent.value) { + // 主区域有内容:采用并排停靠策略 + return { + canDock: true, + reason: '主区域已有内容,采用并排停靠策略', + strategy: 'side-by-side' + } + } else { + // 主区域无内容:创建目标Area后执行并排停靠 + return { + canDock: true, + reason: '主区域无内容,将创建目标Area后执行并排停靠', + strategy: 'side-by-side' + } + } +} + +/** + * 处理并排停靠逻辑 + * 当主区域内已有Area时,压缩目标Area并创建并排布局 + * @param {Object} sourceArea - 源Area对象 + * @param {string} dockZone - 停靠方向 + * @returns {Object} 处理结果 {success: boolean, message: string} + */ +const handleSideBySideDocking = (sourceArea, dockZone) => { + console.log(`[DockLayout] 开始并排停靠: areaId=${sourceArea.id}, zone=${dockZone}`) + + try { + // 1. 检查主区域内是否有现有Area + checkMainContentForAreas() + + let targetArea = null + + if (hasAreasInMainContent.value) { + // 主区域内已有Area,找到第一个作为目标 + targetArea = findFirstMainArea() + console.log('[DockLayout] 找到现有目标Area:', targetArea?.id) + } else { + // 主区域内无Area,从隐藏列表获取或创建新的目标Area + console.log('[DockLayout] 主区域内无现有Area,创建目标Area') + + const restoreResult = getOrCreateTargetArea() + if (!restoreResult.success) { + return restoreResult + } + targetArea = restoreResult.targetArea + } + + if (!targetArea) { + return { + success: false, + message: '未找到有效的目标Area' + } + } + + // 2. 压缩目标Area并创建并排布局 + const layoutResult = createSideBySideLayout(sourceArea, targetArea, dockZone) + if (!layoutResult.success) { + return layoutResult + } + + // 3. 添加ResizeBar支持 + const resizeBarResult = addResizeBarForSideBySideLayout(sourceArea, targetArea, dockZone) + if (!resizeBarResult.success) { + console.warn('[DockLayout] ResizeBar添加失败:', resizeBarResult.message) + // ResizeBar失败不影响核心功能,继续执行 + } else { + console.log('[DockLayout] ResizeBar添加成功') + } + + // 4. 更新源Area状态 + const sourceIndex = floatingAreas.value.findIndex(a => a.id === sourceArea.id) + if (sourceIndex !== -1) { + // 将源Area从浮动区域移除,添加到主区域 + const [removedArea] = floatingAreas.value.splice(sourceIndex, 1) + console.log('[DockLayout] 源Area已从浮动区域移除') + + // 将源Area添加到主区域的并排布局中 + // 这里需要根据实际的布局结构进行调整 + console.log('[DockLayout] 源Area将添加到主区域并排布局') + } + + // 5. 更新主区域状态 + nextTick(() => { + checkMainContentForAreas() + }) + + return { + success: true, + message: `成功执行${dockZone}方向并排停靠`, + strategy: 'side-by-side', + sourceAreaId: sourceArea.id, + targetAreaId: targetArea.id + } + + } catch (error) { + console.error('[DockLayout] 并排停靠处理过程中发生错误:', error) + return { + success: false, + message: `并排停靠过程中发生错误: ${error.message}` + } + } +} + +/** + * 在并排布局中添加ResizeBar支持 + * @param {Object} sourceArea - 源Area对象 + * @param {Object} targetArea - 目标Area对象 + * @param {string} dockZone - 停靠方向 + * @returns {Object} 处理结果 {success: boolean, message: string} + */ +const addResizeBarForSideBySideLayout = (sourceArea, targetArea, dockZone) => { + console.log(`[DockLayout] 为并排布局添加ResizeBar: ${dockZone}`) + + try { + // 根据停靠方向确定ResizeBar的方向 + const isHorizontal = ['left', 'right'].includes(dockZone) + const resizeBarDirection = isHorizontal ? 'vertical' : 'horizontal' + + console.log(`[DockLayout] ResizeBar方向设置: ${resizeBarDirection}`) + + // 创建ResizeBar配置 + const resizeBarConfig = { + id: `resizebar-${sourceArea.id}-${targetArea.id}-${Date.now()}`, + targetId: `${sourceArea.id}-${targetArea.id}`, + direction: resizeBarDirection, + sourceAreaId: sourceArea.id, + targetAreaId: targetArea.id, + dockZone: dockZone, + minSize: 150, + maxSize: undefined, + initialSize: isHorizontal ? 300 : 200 + } + + // 将ResizeBar配置添加到主区域的resizeBars列表中 + mainAreaResizeBars.value.push(resizeBarConfig) + + console.log('[DockLayout] ResizeBar配置已添加到主区域') + + return { + success: true, + message: `ResizeBar配置添加成功,方向: ${resizeBarDirection}` + } + + } catch (error) { + console.error('[DockLayout] 添加ResizeBar时发生错误:', error) + return { + success: false, + message: `添加ResizeBar失败: ${error.message}` + } + } +} + +/** + * 处理主区域ResizeBar尺寸调整事件 + * @param {string} resizeBarId - ResizeBar的ID + * @param {number} newSize - 新的尺寸 + */ +const handleMainAreaResizeBar = (resizeBarId, newSize) => { + try { + console.log(`[DockLayout] 主区域ResizeBar调整: id=${resizeBarId}, size=${newSize}`) + + // 找到对应的ResizeBar配置 + const resizeBar = mainAreaResizeBars.value.find(rb => rb.id === resizeBarId) + if (!resizeBar) { + console.warn(`[DockLayout] 未找到ResizeBar: ${resizeBarId}`) + return + } + + // 根据停靠方向计算比例变化 + const sourceArea = floatingAreas.value.find(a => a.id === resizeBar.sourceAreaId) + const targetArea = floatingAreas.value.find(a => a.id === resizeBar.targetAreaId) + + if (!sourceArea || !targetArea) { + console.warn('[DockLayout] 未找到源Area或目标Area') + return + } + + // 根据停靠方向计算新的比例 + if (['left', 'right'].includes(resizeBar.dockZone)) { + // 水平并排:左右布局 + handleHorizontalResizeWithSize(sourceArea, targetArea, newSize, resizeBar.dockZone) + } else { + // 垂直并排:上下布局 + handleVerticalResizeWithSize(sourceArea, targetArea, newSize, resizeBar.dockZone) + } + + } catch (error) { + console.error('[DockLayout] 处理主区域ResizeBar调整时发生错误:', error) + } +} + +/** + * 处理主区域ResizeBar调整开始事件 + * @param {string} resizeBarId - ResizeBar的ID + */ +const handleMainAreaResizeBarStart = (resizeBarId) => { + console.log(`[DockLayout] 主区域ResizeBar调整开始: ${resizeBarId}`) +} + +/** + * 处理主区域ResizeBar调整结束事件 + * @param {string} resizeBarId - ResizeBar的ID + */ +const handleMainAreaResizeBarEnd = (resizeBarId) => { + console.log(`[DockLayout] 主区域ResizeBar调整结束: ${resizeBarId}`) +} + +/** + * 获取主区域ResizeBar的样式 + * @param {Object} resizeBar - ResizeBar配置 + * @returns {Object} CSS样式对象 + */ +const getMainAreaResizeBarStyle = (resizeBar) => { + try { + const baseStyle = { + position: 'absolute', + zIndex: 50 + } + + if (resizeBar.direction === 'horizontal') { + // 水平ResizeBar(左右调整) + return { + ...baseStyle, + top: '0', + right: '0', + width: '4px', + height: '100%', + cursor: 'col-resize' + } + } else { + // 垂直ResizeBar(上下调整) + return { + ...baseStyle, + bottom: '0', + left: '0', + width: '100%', + height: '4px', + cursor: 'row-resize' + } + } + + } catch (error) { + console.error('[DockLayout] 获取ResizeBar样式时发生错误:', error) + return { position: 'absolute' } + } +} + +/** + * 处理ResizeBar尺寸调整事件 + * @param {Object} delta - 调整量 + * @param {Object} sourceArea - 源Area对象 + * @param {Object} targetArea - 目标Area对象 + * @param {string} dockZone - 停靠方向 + */ +const handleResizeBarResize = (delta, sourceArea, targetArea, dockZone) => { + try { + console.log(`[DockLayout] ResizeBar调整: delta=`, delta, `zone=${dockZone}`) + + // 根据停靠方向计算新的尺寸比例 + if (['left', 'right'].includes(dockZone)) { + // 水平并排:左右布局 + handleHorizontalResize(sourceArea, targetArea, delta.x) + } else { + // 垂直并排:上下布局 + handleVerticalResize(sourceArea, targetArea, delta.y) + } + + // 更新Area的ratio属性以保持布局持久化 + updateAreasRatio(sourceArea, targetArea, dockZone) + + } catch (error) { + console.error('[DockLayout] 处理ResizeBar调整时发生错误:', error) + } +} + +/** + * 处理水平方向(左右)尺寸调整 + */ +const handleHorizontalResize = (sourceArea, targetArea, deltaX) => { + // 实现水平尺寸调整逻辑 + const sourceRatio = sourceArea.ratio || 0.5 + const targetRatio = targetArea.ratio || 0.5 + const totalRatio = sourceRatio + targetRatio + + // 计算新的比例(保持总和为1) + const newSourceRatio = Math.max(0.1, Math.min(0.9, sourceRatio + deltaX / 1000)) + const newTargetRatio = totalRatio - newSourceRatio + + // 确保最小比例限制 + if (newSourceRatio >= 0.1 && newTargetRatio >= 0.1) { + sourceArea.ratio = newSourceRatio + targetArea.ratio = newTargetRatio + console.log(`[DockLayout] 水平调整完成: source=${newSourceRatio.toFixed(2)}, target=${newTargetRatio.toFixed(2)}`) + } +} + +/** + * 处理垂直方向(上下)尺寸调整 + */ +const handleVerticalResize = (sourceArea, targetArea, deltaY) => { + // 实现垂直尺寸调整逻辑 + const sourceRatio = sourceArea.ratio || 0.5 + const targetRatio = targetArea.ratio || 0.5 + const totalRatio = sourceRatio + targetRatio + + // 计算新的比例(保持总和为1) + const newSourceRatio = Math.max(0.1, Math.min(0.9, sourceRatio + deltaY / 1000)) + const newTargetRatio = totalRatio - newSourceRatio + + // 确保最小比例限制 + if (newSourceRatio >= 0.1 && newTargetRatio >= 0.1) { + sourceArea.ratio = newSourceRatio + targetArea.ratio = newTargetRatio + console.log(`[DockLayout] 垂直调整完成: source=${newSourceRatio.toFixed(2)}, target=${newTargetRatio.toFixed(2)}`) + } +} + +/** + * 更新Area的尺寸比例 + */ +const updateAreasRatio = (sourceArea, targetArea, dockZone) => { + // 确保比例总和为1 + const sourceRatio = sourceArea.ratio || 0.5 + const targetRatio = targetArea.ratio || 0.5 + const totalRatio = sourceRatio + targetRatio + + if (totalRatio !== 1) { + const sourceNewRatio = sourceRatio / totalRatio + const targetNewRatio = targetRatio / totalRatio + + sourceArea.ratio = sourceNewRatio + targetArea.ratio = targetNewRatio + + console.log(`[DockLayout] 比例调整: source=${sourceNewRatio.toFixed(2)}, target=${targetNewRatio.toFixed(2)}`) + } +} + +/** + * 处理ResizeBar调整开始事件 + */ +const handleResizeBarResizeStart = (sourceArea, targetArea) => { + console.log('[DockLayout] ResizeBar调整开始') + // 可以在这里添加视觉反馈或其他开始时的处理 +} + +/** + * 处理ResizeBar调整结束事件 + */ +const handleResizeBarResizeEnd = (sourceArea, targetArea) => { + console.log('[DockLayout] ResizeBar调整结束') + // 可以在这里添加调整完成后的处理,如保存布局状态 +} + +/** + * 处理水平方向(左右)尺寸调整 - 带指定大小 + * @param {Object} sourceArea - 源Area对象 + * @param {Object} targetArea - 目标Area对象 + * @param {number} newSize - 新的尺寸 + * @param {string} dockZone - 停靠方向 + */ +const handleHorizontalResizeWithSize = (sourceArea, targetArea, newSize, dockZone) => { + try { + console.log(`[DockLayout] 水平尺寸调整: newSize=${newSize}, zone=${dockZone}`) + + // 计算总宽度(假设容器宽度) + const containerWidth = 800 // 默认容器宽度,实际项目中应该动态获取 + const minSize = 150 + + // 确保大小在有效范围内 + const adjustedSize = Math.max(minSize, Math.min(containerWidth - minSize, newSize)) + + // 计算比例 + const sourceRatio = adjustedSize / containerWidth + const targetRatio = 1 - sourceRatio + + // 确保最小比例限制 + if (sourceRatio >= 0.1 && targetRatio >= 0.1) { + sourceArea.ratio = sourceRatio + targetArea.ratio = targetRatio + sourceArea.width = adjustedSize + targetArea.width = containerWidth - adjustedSize + + console.log(`[DockLayout] 水平调整完成: source=${sourceRatio.toFixed(2)}, target=${targetRatio.toFixed(2)}`) + } + + } catch (error) { + console.error('[DockLayout] 水平尺寸调整时发生错误:', error) + } +} + +/** + * 处理垂直方向(上下)尺寸调整 - 带指定大小 + * @param {Object} sourceArea - 源Area对象 + * @param {Object} targetArea - 目标Area对象 + * @param {number} newSize - 新的尺寸 + * @param {string} dockZone - 停靠方向 + */ +const handleVerticalResizeWithSize = (sourceArea, targetArea, newSize, dockZone) => { + try { + console.log(`[DockLayout] 垂直尺寸调整: newSize=${newSize}, zone=${dockZone}`) + + // 计算总高度(假设容器高度) + const containerHeight = 600 // 默认容器高度,实际项目中应该动态获取 + const minSize = 150 + + // 确保大小在有效范围内 + const adjustedSize = Math.max(minSize, Math.min(containerHeight - minSize, newSize)) + + // 计算比例 + const sourceRatio = adjustedSize / containerHeight + const targetRatio = 1 - sourceRatio + + // 确保最小比例限制 + if (sourceRatio >= 0.1 && targetRatio >= 0.1) { + sourceArea.ratio = sourceRatio + targetArea.ratio = targetRatio + sourceArea.height = adjustedSize + targetArea.height = containerHeight - adjustedSize + + console.log(`[DockLayout] 垂直调整完成: source=${sourceRatio.toFixed(2)}, target=${targetRatio.toFixed(2)}`) + } + + } catch (error) { + console.error('[DockLayout] 垂直尺寸调整时发生错误:', error) + } +} + +/** + * 查找主区域内的第一个Area + * @returns {Object|null} 找到的Area对象或null + */ +const findFirstMainArea = () => { + try { + // 这里需要根据实际的DOM结构或数据结构调整 + // 目前返回null,需要根据实际实现来完善 + console.log('[DockLayout] 查找主区域内的第一个Area') + return null + } catch (error) { + console.error('[DockLayout] 查找主区域Area时发生错误:', error) + return null + } +} + +/** + * 获取或创建目标Area + * @returns {Object} 处理结果 {success: boolean, message: string, targetArea?: Object} + */ +const getOrCreateTargetArea = () => { + try { + console.log('[DockLayout] 获取或创建目标Area') + + // 1. 尝试从隐藏列表获取Area + const hiddenResult = getHiddenAreas() + if (hiddenResult.length > 0) { + // 取第一个隐藏的Area作为目标Area + const targetArea = hiddenResult[0] + const restoreResult = restoreAreaFromHidden(targetArea.id) + + if (restoreResult.success) { + console.log('[DockLayout] 从隐藏列表恢复目标Area成功:', targetArea.id) + return { + success: true, + message: '成功从隐藏列表获取目标Area', + targetArea: targetArea + } + } + } + + // 2. 如果没有隐藏Area,创建新的Area + console.log('[DockLayout] 创建新的目标Area') + const newAreaId = `area-${Date.now()}` + const newArea = { + id: newAreaId, + x: 0, + y: 0, + width: 300, + height: 250, + tabPages: [], // 初始为空,通过主区域内容填充 + ratio: 0.5 + } + + // 将新Area添加到浮动区域 + floatingAreas.value.push(newArea) + + console.log('[DockLayout] 创建新目标Area成功:', newAreaId) + + return { + success: true, + message: '成功创建新的目标Area', + targetArea: newArea + } + + } catch (error) { + console.error('[DockLayout] 获取或创建目标Area时发生错误:', error) + return { + success: false, + message: `获取或创建目标Area失败: ${error.message}` + } + } +} + +/** + * 创建并排布局 + * @param {Object} sourceArea - 源Area对象 + * @param {Object} targetArea - 目标Area对象 + * @param {string} dockZone - 停靠方向 + * @returns {Object} 处理结果 {success: boolean, message: string} + */ +const createSideBySideLayout = (sourceArea, targetArea, dockZone) => { + try { + console.log(`[DockLayout] 创建并排布局: ${dockZone}`) + + // 1. 设置初始比例 + sourceArea.ratio = 0.5 + targetArea.ratio = 0.5 + + // 2. 压缩目标Area的空间 + const compressResult = compressTargetArea(targetArea, dockZone) + if (!compressResult.success) { + return compressResult + } + + // 3. 调整源Area的位置和大小以适应并排布局 + adjustSourceAreaForLayout(sourceArea, targetArea, dockZone) + + console.log('[DockLayout] 并排布局创建完成') + + return { + success: true, + message: '并排布局创建成功' + } + + } catch (error) { + console.error('[DockLayout] 创建并排布局时发生错误:', error) + return { + success: false, + message: `创建并排布局失败: ${error.message}` + } + } +} + +/** + * 压缩目标Area的空间 + * @param {Object} targetArea - 目标Area对象 + * @param {string} dockZone - 停靠方向 + * @returns {Object} 处理结果 {success: boolean, message: string} + */ +const compressTargetArea = (targetArea, dockZone) => { + try { + console.log(`[DockLayout] 压缩目标Area空间: ${dockZone}`) + + // 根据停靠方向计算新的尺寸 + let newWidth = targetArea.width + let newHeight = targetArea.height + + if (['left', 'right'].includes(dockZone)) { + // 左右停靠:压缩宽度 + newWidth = Math.max(150, targetArea.width * 0.6) // 最小宽度150px + } else { + // 上下停靠:压缩高度 + newHeight = Math.max(150, targetArea.height * 0.6) // 最小高度150px + } + + targetArea.width = newWidth + targetArea.height = newHeight + + console.log(`[DockLayout] 目标Area压缩完成: ${newWidth}x${newHeight}`) + + return { + success: true, + message: '目标Area空间压缩成功' + } + + } catch (error) { + console.error('[DockLayout] 压缩目标Area空间时发生错误:', error) + return { + success: false, + message: `压缩目标Area空间失败: ${error.message}` + } + } +} + +/** + * 调整源Area以适应并排布局 + * @param {Object} sourceArea - 源Area对象 + * @param {Object} targetArea - 目标Area对象 + * @param {string} dockZone - 停靠方向 + */ +const adjustSourceAreaForLayout = (sourceArea, targetArea, dockZone) => { + try { + console.log(`[DockLayout] 调整源Area适应布局: ${dockZone}`) + + // 根据停靠方向调整源Area的尺寸 + if (['left', 'right'].includes(dockZone)) { + // 左右停靠:设置源Area的宽度 + sourceArea.width = targetArea.width + sourceArea.height = targetArea.height + + // 设置源Area的位置(并排放置) + if (dockZone === 'left') { + sourceArea.x = 0 + sourceArea.y = targetArea.y + } else { + sourceArea.x = targetArea.x + targetArea.width + sourceArea.y = targetArea.y + } + } else { + // 上下停靠:设置源Area的高度 + sourceArea.width = targetArea.width + sourceArea.height = targetArea.height + + // 设置源Area的位置(并排放置) + if (dockZone === 'top') { + sourceArea.x = targetArea.x + sourceArea.y = 0 + } else { + sourceArea.x = targetArea.x + sourceArea.y = targetArea.y + targetArea.height + } + } + + console.log(`[DockLayout] 源Area调整完成: ${sourceArea.x}, ${sourceArea.y}, ${sourceArea.width}x${sourceArea.height}`) + + } catch (error) { + console.error('[DockLayout] 调整源Area布局时发生错误:', error) + } +} + // 暴露方法给父组件 defineExpose({ // 原有方法 addFloatingPanel, - // 第4步:隐藏列表管理方法 + // 统一停靠处理 + handleDockingEnding, + handleEdgeDocking, + handleSideBySideDocking, + + // 隐藏列表管理方法 getHiddenAreas, restoreAreaFromHidden, removeFromHiddenList, - clearHiddenList + clearHiddenList, + + // ResizeBar相关方法 + addResizeBarForSideBySideLayout, + handleResizeBarResize, + handleResizeBarResizeStart, + handleResizeBarResizeEnd, + + // ResizeBar尺寸调整方法 + handleHorizontalResizeWithSize, + handleVerticalResizeWithSize, + + // 查找方法 + findOrCreateMainAreaTabPage, + findFirstMainArea, }) diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue index f5b9a00..991b60b 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue @@ -70,8 +70,8 @@
- -
+ +

@@ -245,6 +245,38 @@ const onDragEnd = () => { shape-rendering: crispEdges; } +/* 内容区域滚动条样式 */ +.content-area { + /* 确保滚动条正确显示 */ + scrollbar-width: thin; + scrollbar-color: #c7d2ea #f5f7fb; +} + +/* Webkit浏览器滚动条样式 */ +.content-area::-webkit-scrollbar { + width: 12px; + height: 12px; +} + +.content-area::-webkit-scrollbar-track { + background: #f5f7fb; + border-radius: 6px; +} + +.content-area::-webkit-scrollbar-thumb { + background: linear-gradient(to bottom, #d0d6ea, #c0c7e0); + border: 1px solid #b0b6d6; + border-radius: 6px; +} + +.content-area::-webkit-scrollbar-thumb:hover { + background: linear-gradient(to bottom, #c1c7e2, #b2b8d9); +} + +.content-area::-webkit-scrollbar-corner { + background: #f5f7fb; +} + /* 禁用可能存在的旧伪元素样式 */ :deep(.icon-square::before), :deep(.icon-square::after) { diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/TabPage.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/TabPage.vue index 8e32470..b236f34 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/TabPage.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/TabPage.vue @@ -1,7 +1,7 @@ @@ -95,36 +123,8 @@

- -
-
-
- {{ panel.title }} - - -
-
-
-
- - -
+ +