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 @@
-
-
-
-
-
-
+
+
+
+
+ DEBUG: receivedContent长度 = {{ receivedContent.length }}
+
+ TabPage {{ index }}: {{ item.tabPage.id }} - {{ item.tabPage.title }} ({{ item.tabPage.panels.length }} panels)
+
+
+
+
+
+
+
+
+
+
+
+ 此处可以放置内容
+
@@ -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"
>
-
+
+
+
+
+
+ handleMainAreaResizeBar(resizeBar.id, size)"
+ @resize-start="() => handleMainAreaResizeBarStart(resizeBar.id)"
+ @resize-end="() => handleMainAreaResizeBarEnd(resizeBar.id)"
+ :style="getMainAreaResizeBarStyle(resizeBar)"
+ />
+
onMaximize(panelId)"
@close="(panelId) => onClosePanel(area.id, panelId)"
@toggle-toolbar="(panelId) => $emit('toggleToolbar', panelId)"
- @drag-start="(event) => $emit('dragStart', event)"
- @drag-move="(event) => $emit('dragMove', event)"
- @drag-end="(event) => $emit('dragEnd', event)"
+ @dragStart="onPanelDragStartFromTabPage(area.id, $event)"
+ @dragMove="onPanelDragMoveFromTabPage(area.id, $event)"
+ @dragEnd="onPanelDragEndFromTabPage"
@dragover="handleAreaDragOver"
@dragleave="handleAreaDragLeave"
>
@@ -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 @@
-