diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue
index 498365a..8a03e20 100644
--- a/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue
+++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue
@@ -99,14 +99,52 @@
-
-
+
+
+
+
+
+
+ {}"
+ @tabDragMove="() => {}"
+ @tabDragEnd="() => {}"
+ >
+
+ {}"
+ @maximize="onPanelMaximize"
+ @close="() => {}"
+ @toggleToolbar="() => {}"
+ @dragStart="() => {}"
+ @dragMove="() => {}"
+ @dragEnd="() => {}"
+ @dragover="handleDragOver"
+ @dragleave="handleDragLeave"
+ />
+
+
\ No newline at end of file
diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue
index b8b3c56..75e619c 100644
--- a/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue
+++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue
@@ -412,7 +412,6 @@
{
}
}
+// 隐藏Area管理方法 - 第1步实现
+// 将Area添加到隐藏列表
+const addAreaToHiddenList = (area) => {
+ // 确保area有唯一标识符
+ if (!area.id) {
+ area.id = `hidden-area-${Date.now()}`
+ }
+
+ // 检查是否已经存在于隐藏列表中
+ const existingIndex = hiddenAreas.value.findIndex(h => h.id === area.id)
+ if (existingIndex === -1) {
+ // 添加到隐藏列表
+ hiddenAreas.value.push({
+ ...area,
+ hiddenAt: new Date().toISOString()
+ })
+ console.log('Area已添加到隐藏列表:', area.id)
+ } else {
+ console.warn('Area已在隐藏列表中:', area.id)
+ }
+}
+
// Panel拖拽开始
const onPanelDragStart = (areaId, event) => {
const area = floatingAreas.value.find(a => a.id === areaId)
@@ -436,17 +461,23 @@ const onPanelDragMove = (areaId, event) => {
const onPanelDragEnd = () => {
panelDragState.value.isDragging = false
+ const currentAreaId = panelDragState.value.currentAreaId
panelDragState.value.currentAreaId = null
+ // 3.1 在onPanelDragEnd方法中添加中心停靠检测
+ if (activeDockZone.value === 'center' && currentAreaId) {
+ // 处理中心停靠
+ const result = handleCenterDocking(currentAreaId)
+ if (!result.success) {
+ console.warn('中心停靠失败:', result.message)
+ } else {
+ console.log('中心停靠成功:', result.message)
+ }
+ }
+
// 隐藏停靠指示器
showDockIndicator.value = false
activeDockZone.value = null
-
- // 如果有活动的停靠区域,可以在这里处理停靠逻辑
- if (activeDockZone.value) {
-
- // 这里可以实现具体的停靠逻辑
- }
}
// Area拖拽开始
@@ -454,6 +485,24 @@ const onAreaDragStart = (areaId, event) => {
const area = floatingAreas.value.find(a => a.id === areaId)
if (!area) return
+ // 设置拖拽状态(类似TabPage拖拽)
+ panelDragState.value.isDragging = true
+ panelDragState.value.currentAreaId = areaId
+ panelDragState.value.startClientPos = {
+ x: event.clientX,
+ y: event.clientY
+ }
+ panelDragState.value.startAreaPos = {
+ x: area.x,
+ y: area.y
+ }
+
+ // 初始化鼠标位置跟踪
+ currentMousePosition.value = {
+ x: event.clientX,
+ y: event.clientY
+ }
+
// 拖拽开始时显示指示器
showDockIndicator.value = true
@@ -477,12 +526,33 @@ const onAreaDragStart = (areaId, event) => {
// Area拖拽移动
const onAreaDragMove = (areaId, event) => {
+ // 更新鼠标位置
+ currentMousePosition.value = {
+ x: event.clientX,
+ y: event.clientY
+ }
+
// 根据鼠标位置动态更新停靠区域
updateDockZoneByMousePosition(event.clientX, event.clientY)
}
// Area拖拽结束
const onAreaDragEnd = (areaId, event) => {
+ // 清理拖拽状态
+ panelDragState.value.isDragging = false
+ panelDragState.value.currentAreaId = null
+
+ // 3.1 在onAreaDragEnd方法中添加中心停靠检测
+ if (activeDockZone.value === 'center') {
+ // 处理中心停靠
+ const result = handleCenterDocking(areaId)
+ if (!result.success) {
+ console.warn('Area中心停靠失败:', result.message)
+ } else {
+ console.log('Area中心停靠成功:', result.message)
+ }
+ }
+
// 隐藏停靠指示器
showDockIndicator.value = false
activeDockZone.value = null
@@ -595,8 +665,20 @@ const onTabDragMove = (areaId, event) => {
const onTabDragEnd = () => {
tabDragState.value.isDragging = false
+ const currentAreaId = tabDragState.value.currentAreaId
tabDragState.value.currentAreaId = null
+ // 3.2 在onTabDragEnd方法中添加中心停靠检测
+ if (activeDockZone.value === 'center' && currentAreaId) {
+ // 处理中心停靠
+ const result = handleCenterDocking(currentAreaId)
+ if (!result.success) {
+ console.warn('中心停靠失败:', result.message)
+ } else {
+ console.log('中心停靠成功:', result.message)
+ }
+ }
+
// 隐藏停靠指示器
showDockIndicator.value = false
activeDockZone.value = null
@@ -642,7 +724,22 @@ watch(floatingAreas, () => {
// 当主区域内没有其他Area时,隐藏外部边缘指示器,只显示中心指示器
-// 根据鼠标位置动态更新停靠区域
+// 第5步:优化UI指示器显示逻辑(当主区域为空时)
+/**
+ * 检查主区域是否为空
+ * @returns {boolean} 主区域是否为空
+ */
+const isMainAreaEmpty = () => {
+ checkMainContentForAreas()
+ return !hasAreasInMainContent.value
+}
+
+/**
+ * 根据鼠标位置动态更新停靠区域(优化版)
+ * 当主区域为空时,只显示中心指示器
+ * @param {number} mouseX - 鼠标X坐标
+ * @param {number} mouseY - 鼠标Y坐标
+ */
const updateDockZoneByMousePosition = (mouseX, mouseY) => {
if (!dockLayoutRef.value || !targetAreaRect.value) return
@@ -658,26 +755,43 @@ const updateDockZoneByMousePosition = (mouseX, mouseY) => {
let newActiveZone = null
+ // 检查主区域是否为空
+ const mainAreaEmpty = isMainAreaEmpty()
+
if (relativeX >= 0 && relativeX <= 1 && relativeY >= 0 && relativeY <= 1) {
// 鼠标在目标区域内
- 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 (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'
+ }
}
}
// 只有当停靠区域改变时才更新,减少不必要的重新渲染
if (activeDockZone.value !== newActiveZone) {
activeDockZone.value = newActiveZone
-
+ // 如果激活的区域不为空,显示指示器;如果为空,隐藏指示器
+ if (newActiveZone) {
+ showDockIndicator.value = true
+ } else {
+ showDockIndicator.value = false
+ }
}
}
@@ -790,9 +904,243 @@ const onPanelMaximizeSync = ({ areaId, maximized }) => {
}
}
+// 第2步:Area验证逻辑 - 目标Area内容区为空的情况
+/**
+ * 检查Area是否可以停靠到中心区域
+ * @param {Object} sourceArea - 源Area对象
+ * @param {Object} targetArea - 目标Area对象(主区域)
+ * @returns {Object} 验证结果 {canDock: boolean, reason: string}
+ */
+const canDockToCenter = (sourceArea, targetArea) => {
+ // 验证1:检查源Area是否包含TabPage
+ if (!sourceArea) {
+ return {
+ canDock: false,
+ reason: '源Area不存在'
+ }
+ }
+
+ if (!sourceArea.tabPages || sourceArea.tabPages.length === 0) {
+ return {
+ canDock: false,
+ reason: '源Area不包含TabPage,无法停靠'
+ }
+ }
+
+ // 验证2:检查目标Area(主区域)- 支持Area级别停靠
+ if (targetArea === mainAreaRef.value) {
+ // Area级别停靠支持:将源Area的内容移动到主区域
+ // 不再要求主区域完全为空,允许Area停靠
+ console.log('Area级别停靠:源Area可停靠到主区域')
+ } else {
+ // 对于其他目标Area的验证逻辑可以后续扩展
+ return {
+ canDock: false,
+ reason: '暂不支持停靠到非主区域'
+ }
+ }
+
+ // 验证3:确保包含TabPage的Area才能停靠到中心
+ const sourceTabPage = sourceArea.tabPages[0]
+ if (!sourceTabPage.panels || sourceTabPage.panels.length === 0) {
+ return {
+ canDock: false,
+ reason: '源Area的TabPage不包含Panel,无法停靠'
+ }
+ }
+
+ // 所有验证通过
+ return {
+ canDock: true,
+ reason: '验证通过,可以停靠到中心区域'
+ }
+}
+
+// 第3步:中心停靠逻辑
+/**
+ * 处理中心停靠的核心函数
+ * @param {string} areaId - 源Area的ID
+ * @returns {Object} 处理结果 {success: boolean, message: string}
+ */
+const handleCenterDocking = (areaId) => {
+ try {
+ // 3.4 查找源Area
+ const sourceArea = floatingAreas.value.find(a => a.id === areaId)
+ if (!sourceArea) {
+ return {
+ success: false,
+ message: '找不到指定的Area'
+ }
+ }
+
+ // 2.1-2.5 验证是否可以停靠到中心
+ const validation = canDockToCenter(sourceArea, mainAreaRef.value)
+ if (!validation.canDock) {
+ return {
+ success: false,
+ message: `验证失败:${validation.reason}`
+ }
+ }
+
+ // 3.4 实现Area子组件移动到主区域的逻辑
+ // 将Area的内容移动到主区域(通过主区域Ref处理)
+ if (mainAreaRef.value && mainAreaRef.value.appendAreaContent) {
+ mainAreaRef.value.appendAreaContent(sourceArea)
+ } else {
+ console.warn('主区域Ref不可用,无法移动内容')
+ }
+
+ // 3.5 实现Area添加到隐藏列表的逻辑
+ addAreaToHiddenList(sourceArea)
+
+ // 3.6 实现Area从浮动区域移除的逻辑
+ const index = floatingAreas.value.findIndex(a => a.id === areaId)
+ if (index !== -1) {
+ floatingAreas.value.splice(index, 1)
+ }
+
+ // 3.7 更新主区域和浮动区域的响应式状态
+ checkMainContentForAreas()
+
+ return {
+ success: true,
+ message: '成功停靠到中心区域'
+ }
+
+ } catch (error) {
+ console.error('中心停靠处理错误:', error)
+ return {
+ success: false,
+ message: `停靠处理出错:${error.message}`
+ }
+ }
+}
+
+// 第4步:在defineExpose中暴露隐藏列表管理方法
+/**
+ * 获取隐藏Area列表
+ * @returns {Array} 隐藏Area列表
+ */
+const getHiddenAreas = () => {
+ return [...hiddenAreas.value]
+}
+
+/**
+ * 从隐藏列表恢复Area到浮动区域
+ * @param {string} areaId - Area的ID
+ * @returns {Object} 恢复结果 {success: boolean, message: string}
+ */
+const restoreAreaFromHidden = (areaId) => {
+ try {
+ const hiddenIndex = hiddenAreas.value.findIndex(area => area.id === areaId)
+ if (hiddenIndex === -1) {
+ return {
+ success: false,
+ message: '找不到指定的隐藏Area'
+ }
+ }
+
+ const areaToRestore = hiddenAreas.value[hiddenIndex]
+
+ // 检查是否已存在相同的Area(避免重复)
+ const existingIndex = floatingAreas.value.findIndex(area => area.id === areaId)
+ if (existingIndex !== -1) {
+ return {
+ success: false,
+ message: 'Area已存在于浮动区域中'
+ }
+ }
+
+ // 恢复Area到浮动区域
+ floatingAreas.value.push(areaToRestore)
+
+ // 从隐藏列表移除
+ hiddenAreas.value.splice(hiddenIndex, 1)
+
+ // 更新状态
+ checkMainContentForAreas()
+
+ return {
+ success: true,
+ message: 'Area已成功恢复到浮动区域'
+ }
+
+ } catch (error) {
+ console.error('恢复Area时出错:', error)
+ return {
+ success: false,
+ message: `恢复失败:${error.message}`
+ }
+ }
+}
+
+/**
+ * 从隐藏列表移除Area(不恢复)
+ * @param {string} areaId - Area的ID
+ * @returns {Object} 移除结果 {success: boolean, message: string}
+ */
+const removeFromHiddenList = (areaId) => {
+ try {
+ const hiddenIndex = hiddenAreas.value.findIndex(area => area.id === areaId)
+ if (hiddenIndex === -1) {
+ return {
+ success: false,
+ message: '找不到指定的隐藏Area'
+ }
+ }
+
+ // 从隐藏列表移除
+ hiddenAreas.value.splice(hiddenIndex, 1)
+
+ return {
+ success: true,
+ message: 'Area已从隐藏列表中移除'
+ }
+
+ } catch (error) {
+ console.error('移除Area时出错:', error)
+ return {
+ success: false,
+ message: `移除失败:${error.message}`
+ }
+ }
+}
+
+/**
+ * 清空隐藏列表
+ * @returns {Object} 清空结果 {success: boolean, message: string, removedCount: number}
+ */
+const clearHiddenList = () => {
+ try {
+ const count = hiddenAreas.value.length
+ hiddenAreas.value.splice(0, hiddenAreas.value.length)
+
+ return {
+ success: true,
+ message: `已清空隐藏列表,移除${count}个Area`,
+ removedCount: count
+ }
+
+ } catch (error) {
+ console.error('清空隐藏列表时出错:', error)
+ return {
+ success: false,
+ message: `清空失败:${error.message}`,
+ removedCount: 0
+ }
+ }
+}
+
// 暴露方法给父组件
defineExpose({
- addFloatingPanel
+ // 原有方法
+ addFloatingPanel,
+
+ // 第4步:隐藏列表管理方法
+ getHiddenAreas,
+ restoreAreaFromHidden,
+ removeFromHiddenList,
+ clearHiddenList
})
diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/ToDoList.md b/AutoRobot/Windows/Robot/Web/src/DockLayout/ToDoList.md
index 151deba..cbf87e7 100644
--- a/AutoRobot/Windows/Robot/Web/src/DockLayout/ToDoList.md
+++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/ToDoList.md
@@ -35,5 +35,10 @@
1. 当一个Panel被拖动时,显示停靠指示器。
2. 当拖动Panel到指示器时,显示停靠区。
3. 当主区域内没有其他Area时,隐藏外部边缘指示器、中心区域容器,只显示中心指示器。
-4. 当将Area拖动到中心指示器时
-4.1. 如果Area只有一个直接子组件(TabPage),则将Area的子组件停靠到中心区域。这个Area保存到DockLayout的的隐藏列表中。
+4. 当将源Panel拖动到中心指示器时
+4.1. 如果源Area只有一个直接子组件(TabPage),且目标Area内容区为空,则将源Area的子组件停靠到中心区域。这个源Area保存到DockLayout的的隐藏列表中。
+5. 当将源Area拖动到中心指示器时
+5.1. 如果源Area只有一个直接子组件(TabPage),且目标Area内容区为空,则将源Area的子组件停靠到中心区域。这个源Area保存到DockLayout的的隐藏列表中。
+5.2. 如果源Area只有一个直接子组件(TabPage),且目标Area内容区已经包含一个TabPage,则将源Area的TabPage组件的每个标签页移动并添加到目标Area的Tabpage中。这个源Area保存到DockLayout的的隐藏列表中。
+
+