2025-10-31 23:58:26 +08:00
|
|
|
|
<template>
|
2025-11-07 15:09:06 +08:00
|
|
|
|
<div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative;">
|
2025-11-07 15:41:44 +08:00
|
|
|
|
<!-- 停靠指示器组件 - 设置高z-index确保显示在最顶层 -->
|
2025-11-13 17:08:21 +08:00
|
|
|
|
<!-- :visible="showDockIndicator" :visible="true" -->
|
2025-11-07 15:41:44 +08:00
|
|
|
|
<DockIndicator
|
2025-11-14 09:39:59 +08:00
|
|
|
|
:visible="showDockIndicator"
|
2025-11-07 15:41:44 +08:00
|
|
|
|
:target-rect="targetAreaRect"
|
|
|
|
|
|
:mouse-position="currentMousePosition"
|
2025-11-14 14:23:10 +08:00
|
|
|
|
:hide-edge-indicators="hideEdgeIndicators"
|
2025-11-07 15:41:44 +08:00
|
|
|
|
@zone-active="onDockZoneActive"
|
|
|
|
|
|
style="z-index: 9999;"
|
|
|
|
|
|
/>
|
|
|
|
|
|
|
2025-11-13 10:50:22 +08:00
|
|
|
|
<!-- 主区域 - 添加ref引用 -->
|
2025-11-04 09:10:15 +08:00
|
|
|
|
<Area
|
2025-11-14 14:23:10 +08:00
|
|
|
|
ref="mainAreaRef"
|
2025-11-04 09:10:15 +08:00
|
|
|
|
:WindowState="windowState"
|
|
|
|
|
|
:showTitleBar="false"
|
|
|
|
|
|
title="主区域"
|
2025-11-07 14:56:09 +08:00
|
|
|
|
:style="{ position: 'relative', width: '100%', height: '100%', zIndex: 1 }"
|
2025-11-07 15:41:44 +08:00
|
|
|
|
@dragover="handleMainAreaDragOver"
|
|
|
|
|
|
@dragleave="handleMainAreaDragLeave"
|
2025-11-17 16:55:03 +08:00
|
|
|
|
@area-merged="onAreaMerged"
|
2025-11-05 16:40:27 +08:00
|
|
|
|
>
|
2025-11-19 11:31:21 +08:00
|
|
|
|
|
2025-11-18 15:39:46 +08:00
|
|
|
|
<!-- 主区域内容区 -->
|
|
|
|
|
|
<div class="main-content-container" style="position: relative; width: 100%; height: 100%;">
|
|
|
|
|
|
<!-- 这里可以放置主区域的内容 -->
|
|
|
|
|
|
|
|
|
|
|
|
<!-- ResizeBar组件渲染区 -->
|
|
|
|
|
|
<ResizeBar
|
|
|
|
|
|
v-for="resizeBar in mainAreaResizeBars"
|
|
|
|
|
|
:key="resizeBar.id"
|
|
|
|
|
|
:target-id="resizeBar.targetId"
|
|
|
|
|
|
:direction="resizeBar.direction"
|
|
|
|
|
|
:min-size="resizeBar.minSize"
|
|
|
|
|
|
:max-size="resizeBar.maxSize"
|
|
|
|
|
|
:initial-size="resizeBar.initialSize"
|
|
|
|
|
|
@resize="(size) => handleMainAreaResizeBar(resizeBar.id, size)"
|
|
|
|
|
|
@resize-start="() => handleMainAreaResizeBarStart(resizeBar.id)"
|
|
|
|
|
|
@resize-end="() => handleMainAreaResizeBarEnd(resizeBar.id)"
|
|
|
|
|
|
:style="getMainAreaResizeBarStyle(resizeBar)"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
2025-11-07 14:44:07 +08:00
|
|
|
|
</Area>
|
2025-11-19 13:57:51 +08:00
|
|
|
|
<!-- 浮动区域使用Render组件统一渲染 -->
|
|
|
|
|
|
<Render
|
2025-11-07 14:44:07 +08:00
|
|
|
|
v-for="area in floatingAreas"
|
|
|
|
|
|
:key="area.id"
|
2025-11-19 13:57:51 +08:00
|
|
|
|
:type="'area'"
|
|
|
|
|
|
:config="area"
|
|
|
|
|
|
:style="{ zIndex: area.zIndex || zIndexManager.getFloatingAreaZIndex(area.id) }"
|
|
|
|
|
|
@close="() => onCloseFloatingArea(area.id)"
|
|
|
|
|
|
@update:position="(position) => onUpdatePosition(area.id, position)"
|
2025-11-07 14:44:07 +08:00
|
|
|
|
@panelMaximizeSync="onPanelMaximizeSync"
|
2025-11-19 13:57:51 +08:00
|
|
|
|
@areaDragStart="(event) => onAreaDragStart(area.id, event)"
|
|
|
|
|
|
@areaDragMove="(event) => onAreaDragMove(area.id, event)"
|
|
|
|
|
|
@areaDragEnd="(event) => onAreaDragEnd(area.id, event)"
|
|
|
|
|
|
@tab-change="onTabChange"
|
|
|
|
|
|
@tab-close="onTabClose"
|
|
|
|
|
|
@tab-add="onTabAdd"
|
|
|
|
|
|
@tabDragStart="(event) => onTabDragStart(area.id, event)"
|
|
|
|
|
|
@tabDragMove="(event) => onTabDragMove(area.id, event)"
|
|
|
|
|
|
@tabDragEnd="onTabDragEnd"
|
|
|
|
|
|
@toggleCollapse="(panelId) => $emit('toggleCollapse', panelId)"
|
|
|
|
|
|
@maximize="(panelId) => onMaximize(panelId)"
|
|
|
|
|
|
@closePanel="(panelId) => onClosePanel(area.id, panelId)"
|
|
|
|
|
|
@toggleToolbar="(panelId) => $emit('toggleToolbar', panelId)"
|
|
|
|
|
|
@dragStart="(event) => onPanelDragStartFromTabPage(area.id, event)"
|
|
|
|
|
|
@dragMove="(event) => onPanelDragMoveFromTabPage(area.id, event)"
|
|
|
|
|
|
@dragEnd="onPanelDragEndFromTabPage"
|
|
|
|
|
|
@dragover="handleAreaDragOver"
|
|
|
|
|
|
@dragleave="handleAreaDragLeave"
|
|
|
|
|
|
/>
|
2025-11-02 17:19:53 +07:00
|
|
|
|
</div>
|
2025-10-31 23:58:26 +08:00
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup>
|
2025-11-18 13:48:13 +08:00
|
|
|
|
import { ref, defineExpose, defineEmits, nextTick, watch, computed, onMounted } from 'vue'
|
2025-10-31 23:58:26 +08:00
|
|
|
|
import Area from './Area.vue';
|
2025-11-02 17:06:40 +07:00
|
|
|
|
import Panel from './Panel.vue';
|
2025-11-05 09:02:11 +08:00
|
|
|
|
import TabPage from './TabPage.vue';
|
2025-11-07 15:41:44 +08:00
|
|
|
|
import DockIndicator from './DockIndicator.vue';
|
2025-11-18 15:39:46 +08:00
|
|
|
|
import ResizeBar from './ResizeBar.vue';
|
2025-11-19 13:57:51 +08:00
|
|
|
|
import Render from './Render.vue';
|
|
|
|
|
|
import { Z_INDEX_LAYERS, zIndexManager } from './dockLayers.js';
|
2025-11-02 17:06:40 +07:00
|
|
|
|
|
2025-11-18 13:48:13 +08:00
|
|
|
|
// 定义组件可以发出的事件
|
|
|
|
|
|
const emit = defineEmits([
|
|
|
|
|
|
'maximize', // 面板最大化事件
|
|
|
|
|
|
'toggleCollapse', // 折叠状态切换事件
|
|
|
|
|
|
'toggleToolbar', // 工具栏切换事件
|
|
|
|
|
|
'dragStart', // 拖拽开始事件
|
|
|
|
|
|
'dragMove', // 拖拽移动事件
|
|
|
|
|
|
'dragEnd' // 拖拽结束事件
|
|
|
|
|
|
])
|
|
|
|
|
|
|
2025-11-02 17:06:40 +07:00
|
|
|
|
// 主区域状态
|
2025-11-01 14:23:35 +07:00
|
|
|
|
const windowState = ref('最大化')
|
2025-11-02 17:06:40 +07:00
|
|
|
|
|
2025-11-04 10:53:22 +08:00
|
|
|
|
// 浮动区域列表 - 每个area包含panels数组
|
2025-11-02 17:06:40 +07:00
|
|
|
|
const floatingAreas = ref([])
|
|
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 隐藏区域列表 - 存储被隐藏的Area
|
|
|
|
|
|
const hiddenAreas = ref([])
|
|
|
|
|
|
|
2025-11-04 09:45:51 +08:00
|
|
|
|
// 容器引用
|
|
|
|
|
|
const dockLayoutRef = ref(null)
|
2025-11-14 14:23:10 +08:00
|
|
|
|
// 主区域引用
|
|
|
|
|
|
const mainAreaRef = ref(null)
|
2025-11-02 17:06:40 +07:00
|
|
|
|
// 区域ID计数器
|
|
|
|
|
|
let areaIdCounter = 1
|
|
|
|
|
|
|
2025-11-07 15:41:44 +08:00
|
|
|
|
// 停靠指示器相关状态
|
|
|
|
|
|
const showDockIndicator = ref(false)
|
|
|
|
|
|
const currentMousePosition = ref({ x: 0, y: 0 })
|
|
|
|
|
|
const targetAreaRect = ref({ left: 0, top: 0, width: 0, height: 0 })
|
|
|
|
|
|
const activeDockZone = ref(null)
|
|
|
|
|
|
|
2025-11-14 14:23:10 +08:00
|
|
|
|
// 检查主区域内是否有其他Area(TabPage和Panel等子组件)
|
|
|
|
|
|
const hasAreasInMainContent = ref(false)
|
|
|
|
|
|
|
2025-11-18 15:39:46 +08:00
|
|
|
|
// 主区域ResizeBar列表
|
|
|
|
|
|
const mainAreaResizeBars = ref([])
|
|
|
|
|
|
|
2025-11-14 14:23:10 +08:00
|
|
|
|
// 计算是否隐藏外部边缘指示器
|
|
|
|
|
|
const hideEdgeIndicators = computed(() => {
|
|
|
|
|
|
// 当主区域内没有其他Area时,隐藏外部边缘指示器
|
|
|
|
|
|
return !hasAreasInMainContent.value
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-04 11:05:12 +08:00
|
|
|
|
// Panel拖拽相关状态
|
|
|
|
|
|
const panelDragState = ref({
|
|
|
|
|
|
isDragging: false,
|
|
|
|
|
|
currentAreaId: null,
|
|
|
|
|
|
startClientPos: { x: 0, y: 0 },
|
|
|
|
|
|
startAreaPos: { x: 0, y: 0 }
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-06 14:57:30 +08:00
|
|
|
|
// TabPage拖拽相关状态
|
|
|
|
|
|
const tabDragState = ref({
|
|
|
|
|
|
isDragging: false,
|
|
|
|
|
|
currentAreaId: null,
|
|
|
|
|
|
startClientPos: { x: 0, y: 0 },
|
|
|
|
|
|
startAreaPos: { x: 0, y: 0 }
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-11-18 15:39:46 +08:00
|
|
|
|
// 处理从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()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-14 14:23:10 +08:00
|
|
|
|
// 检测主区域内是否有其他Area
|
|
|
|
|
|
const checkMainContentForAreas = () => {
|
|
|
|
|
|
if (!mainAreaRef.value) {
|
|
|
|
|
|
hasAreasInMainContent.value = false
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 获取主区域的DOM元素
|
|
|
|
|
|
const mainAreaElement = mainAreaRef.value.$el
|
|
|
|
|
|
if (!mainAreaElement) {
|
|
|
|
|
|
hasAreasInMainContent.value = false
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查主区域内容区内是否包含vs-area元素(子Area)
|
|
|
|
|
|
const childAreas = mainAreaElement.querySelectorAll('.vs-area')
|
|
|
|
|
|
const hasChildAreas = childAreas.length > 1 // 排除主区域本身
|
|
|
|
|
|
|
|
|
|
|
|
// 检查主区域内容区内是否包含TabPage
|
|
|
|
|
|
const tabPages = mainAreaElement.querySelectorAll('.tab-page, [class*="tab"]')
|
|
|
|
|
|
|
|
|
|
|
|
// 检查主区域内容区内是否包含Panel
|
|
|
|
|
|
const panels = mainAreaElement.querySelectorAll('.panel, [class*="panel"]')
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有任何子Area、TabPage或Panel,则认为主区域内有其他Area
|
|
|
|
|
|
hasAreasInMainContent.value = hasChildAreas || tabPages.length > 0 || panels.length > 0
|
|
|
|
|
|
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-14 14:23:10 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('检查主区域内容时出错:', error)
|
|
|
|
|
|
hasAreasInMainContent.value = false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 16:55:03 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 生成随机测试内容用于合并测试
|
|
|
|
|
|
* @param {number} panelIndex - 面板索引
|
|
|
|
|
|
* @returns {Object} 包含随机内容的对象
|
|
|
|
|
|
*/
|
|
|
|
|
|
const generateRandomContent = (panelIndex) => {
|
|
|
|
|
|
const contentTypes = ['图表', '数据', '配置', '文档', '代码', '日志']
|
|
|
|
|
|
const randomType = contentTypes[Math.floor(Math.random() * contentTypes.length)]
|
|
|
|
|
|
const colors = ['#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7', '#DDA0DD']
|
|
|
|
|
|
const randomColor = colors[Math.floor(Math.random() * colors.length)]
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
type: randomType,
|
|
|
|
|
|
color: randomColor,
|
|
|
|
|
|
index: panelIndex,
|
|
|
|
|
|
title: `${randomType}面板 ${panelIndex + 1}`,
|
|
|
|
|
|
data: generateSampleData(panelIndex),
|
|
|
|
|
|
timestamp: new Date().toLocaleString()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成样本数据
|
|
|
|
|
|
* @param {number} dataIndex - 数据索引
|
|
|
|
|
|
* @returns {Array} 样本数据数组
|
|
|
|
|
|
*/
|
|
|
|
|
|
const generateSampleData = (dataIndex) => {
|
|
|
|
|
|
const data = []
|
|
|
|
|
|
for (let i = 0; i < 5; i++) {
|
|
|
|
|
|
data.push({
|
|
|
|
|
|
label: `项目 ${String.fromCharCode(65 + i)}`,
|
|
|
|
|
|
value: Math.floor(Math.random() * 100) + 10,
|
|
|
|
|
|
id: `data-${dataIndex}-${i}`
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
return data
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 10:31:12 +08:00
|
|
|
|
// 添加新的浮动面板
|
|
|
|
|
|
const addFloatingPanel = () => {
|
2025-11-04 09:45:51 +08:00
|
|
|
|
// 获取父容器尺寸以计算居中位置
|
|
|
|
|
|
let x = 50 + (areaIdCounter - 2) * 20
|
|
|
|
|
|
let y = 50 + (areaIdCounter - 2) * 20
|
|
|
|
|
|
|
|
|
|
|
|
// 如果容器已渲染,计算居中位置
|
|
|
|
|
|
if (dockLayoutRef.value) {
|
|
|
|
|
|
const containerRect = dockLayoutRef.value.getBoundingClientRect()
|
2025-11-19 13:57:51 +08:00
|
|
|
|
const width = 280
|
|
|
|
|
|
const height = 200
|
2025-11-04 09:45:51 +08:00
|
|
|
|
x = Math.floor((containerRect.width - width) / 2)
|
|
|
|
|
|
y = Math.floor((containerRect.height - height) / 2)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-19 13:57:51 +08:00
|
|
|
|
// 获取当前ID并递增
|
|
|
|
|
|
const currentId = areaIdCounter++
|
|
|
|
|
|
const areaId = `floating-area-${currentId}`
|
|
|
|
|
|
|
|
|
|
|
|
// 使用z-index管理器为新浮动区域分配z-index
|
|
|
|
|
|
const newZIndex = zIndexManager.getFloatingAreaZIndex(areaId)
|
|
|
|
|
|
|
|
|
|
|
|
// 直接创建符合Render config的数据结构,确保响应式同步
|
2025-11-02 17:06:40 +07:00
|
|
|
|
const newArea = {
|
2025-11-19 13:57:51 +08:00
|
|
|
|
id: areaId,
|
|
|
|
|
|
title: `浮动区域 ${currentId}`,
|
2025-11-04 09:45:51 +08:00
|
|
|
|
x: x,
|
|
|
|
|
|
y: y,
|
2025-11-19 13:57:51 +08:00
|
|
|
|
width: 280,
|
|
|
|
|
|
height: 200,
|
|
|
|
|
|
windowState: '正常',
|
2025-11-04 10:53:22 +08:00
|
|
|
|
showTitleBar: true,
|
2025-11-19 13:57:51 +08:00
|
|
|
|
resizable: true,
|
|
|
|
|
|
draggable: true,
|
|
|
|
|
|
zIndex: newZIndex, // 使用z-index管理器分配的层级
|
|
|
|
|
|
// 使用Render期望的children结构
|
|
|
|
|
|
children: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'tabpage',
|
|
|
|
|
|
id: `tabpage-${currentId}-1`,
|
|
|
|
|
|
title: `标签页 1`,
|
|
|
|
|
|
tabPosition: 'bottom',
|
|
|
|
|
|
children: {
|
|
|
|
|
|
type: 'panel',
|
|
|
|
|
|
items: [
|
|
|
|
|
|
{
|
|
|
|
|
|
type: 'panel',
|
|
|
|
|
|
id: `panel-${currentId}-1-1`,
|
|
|
|
|
|
title: `面板 ${currentId}`,
|
|
|
|
|
|
x: 0,
|
|
|
|
|
|
y: 0,
|
|
|
|
|
|
width: 280,
|
|
|
|
|
|
height: 200,
|
|
|
|
|
|
collapsed: false,
|
|
|
|
|
|
toolbarExpanded: false,
|
|
|
|
|
|
maximized: false,
|
|
|
|
|
|
content: generateRandomContent(currentId)
|
|
|
|
|
|
}
|
|
|
|
|
|
]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-05 08:59:53 +08:00
|
|
|
|
]
|
2025-11-02 17:06:40 +07:00
|
|
|
|
}
|
2025-11-19 13:57:51 +08:00
|
|
|
|
|
2025-11-02 17:06:40 +07:00
|
|
|
|
floatingAreas.value.push(newArea)
|
2025-11-19 13:57:51 +08:00
|
|
|
|
console.log('✅ 创建浮动面板成功:', newArea.id, 'z-index:', newZIndex)
|
|
|
|
|
|
console.log('🔍 浮动面板数据结构:', JSON.stringify(newArea, null, 2))
|
|
|
|
|
|
|
|
|
|
|
|
// 使用nextTick确保DOM更新后再进行后续操作
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
console.log('🔍 浮动面板已添加到DOM,floatingAreas长度:', floatingAreas.value.length)
|
|
|
|
|
|
})
|
2025-11-02 17:06:40 +07:00
|
|
|
|
}
|
2025-11-04 09:45:51 +08:00
|
|
|
|
// 更新区域位置
|
|
|
|
|
|
const onUpdatePosition = (id, position) => {
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === id)
|
|
|
|
|
|
if (area) {
|
|
|
|
|
|
area.x = position.left
|
|
|
|
|
|
area.y = position.top
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 17:06:40 +07:00
|
|
|
|
// 切换折叠状态
|
|
|
|
|
|
const onToggleCollapse = (id) => {
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === id)
|
|
|
|
|
|
if (area) {
|
|
|
|
|
|
area.collapsed = !area.collapsed
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 最大化/还原
|
2025-11-04 14:43:19 +08:00
|
|
|
|
const onMaximize = (panelId) => {
|
|
|
|
|
|
// 查找包含该面板的区域
|
|
|
|
|
|
for (const area of floatingAreas.value) {
|
2025-11-19 13:57:51 +08:00
|
|
|
|
if (area.children) {
|
|
|
|
|
|
for (const child of area.children) {
|
|
|
|
|
|
if (child.type === 'tabpage' && child.children && child.children.type === 'panel') {
|
|
|
|
|
|
const panels = child.children.items || []
|
|
|
|
|
|
if (panels.length === 1 && panels[0].id === panelId) {
|
|
|
|
|
|
// 当区域只包含一个Panel时,切换Area和Panel的最大化状态
|
|
|
|
|
|
const isCurrentlyMaximized = area.windowState === '最大化' || area.windowState === 'maximized'
|
|
|
|
|
|
|
|
|
|
|
|
if (isCurrentlyMaximized) {
|
|
|
|
|
|
// 切换为正常状态
|
|
|
|
|
|
area.windowState = '正常'
|
|
|
|
|
|
// 确保Panel也恢复正常状态 - 使用展开运算符创建新对象确保响应式
|
|
|
|
|
|
child.children.items[0] = { ...panels[0], maximized: false }
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 切换为最大化状态
|
|
|
|
|
|
area.windowState = '最大化'
|
|
|
|
|
|
// 同时最大化Panel - 使用展开运算符创建新对象确保响应式
|
|
|
|
|
|
child.children.items[0] = { ...panels[0], maximized: true }
|
|
|
|
|
|
|
|
|
|
|
|
// 激活浮动区域,将其置于最顶层
|
|
|
|
|
|
zIndexManager.activateFloatingArea(area.id)
|
|
|
|
|
|
area.zIndex = zIndexManager.getFloatingAreaZIndex(area.id, true)
|
|
|
|
|
|
}
|
|
|
|
|
|
break
|
2025-11-06 13:32:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-04 14:43:19 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-02 17:06:40 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 15:26:50 +08:00
|
|
|
|
// 关闭浮动区域 - 同时移除内容区的Panel
|
2025-11-02 17:06:40 +07:00
|
|
|
|
const onCloseFloatingArea = (id) => {
|
|
|
|
|
|
const index = floatingAreas.value.findIndex(a => a.id === id)
|
|
|
|
|
|
if (index !== -1) {
|
2025-11-04 15:26:50 +08:00
|
|
|
|
// 获取要移除的Area
|
|
|
|
|
|
const areaToRemove = floatingAreas.value[index]
|
|
|
|
|
|
|
2025-11-19 13:57:51 +08:00
|
|
|
|
// 从z-index管理器中移除该区域的层级管理
|
|
|
|
|
|
zIndexManager.removeFloatingArea(id)
|
|
|
|
|
|
|
2025-11-04 15:26:50 +08:00
|
|
|
|
// 清理Panel引用,确保Panel被正确移除
|
|
|
|
|
|
if (areaToRemove.panels) {
|
|
|
|
|
|
// 这里可以添加任何需要的Panel清理逻辑
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-04 15:26:50 +08:00
|
|
|
|
// 清空panels数组,确保Panel被正确移除
|
|
|
|
|
|
areaToRemove.panels = []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从数组中移除Area
|
2025-11-02 17:06:40 +07:00
|
|
|
|
floatingAreas.value.splice(index, 1)
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-02 17:06:40 +07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-19 13:57:51 +08:00
|
|
|
|
// 关闭面板 - 适配children数据结构
|
2025-11-04 10:53:22 +08:00
|
|
|
|
const onClosePanel = (areaId, panelId) => {
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === areaId)
|
2025-11-19 13:57:51 +08:00
|
|
|
|
if (area && area.children) {
|
|
|
|
|
|
for (const child of area.children) {
|
|
|
|
|
|
if (child.type === 'tabpage' && child.children && child.children.type === 'panel') {
|
|
|
|
|
|
const panels = child.children.items || []
|
|
|
|
|
|
const panelIndex = panels.findIndex(p => p.id === panelId)
|
2025-11-06 13:32:18 +08:00
|
|
|
|
if (panelIndex !== -1) {
|
2025-11-19 13:57:51 +08:00
|
|
|
|
panels.splice(panelIndex, 1)
|
2025-11-06 13:32:18 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果区域内没有面板了,可以考虑关闭整个区域
|
2025-11-19 13:57:51 +08:00
|
|
|
|
if (panels.length === 0) {
|
2025-11-06 13:32:18 +08:00
|
|
|
|
onCloseFloatingArea(areaId)
|
|
|
|
|
|
}
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
2025-11-04 10:53:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-02 17:06:40 +07:00
|
|
|
|
// 切换工具栏
|
|
|
|
|
|
const onToggleToolbar = (id) => {
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === id)
|
|
|
|
|
|
if (area) {
|
|
|
|
|
|
area.toolbarExpanded = !area.toolbarExpanded
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-02 17:12:40 +07:00
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 隐藏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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 16:55:03 +08:00
|
|
|
|
// 处理Area合并事件
|
|
|
|
|
|
const onAreaMerged = (eventData) => {
|
|
|
|
|
|
console.log('处理Area合并事件:', eventData)
|
|
|
|
|
|
|
|
|
|
|
|
const { sourceArea, targetAreaHasContent } = eventData
|
|
|
|
|
|
|
|
|
|
|
|
// 获取源Area对象
|
|
|
|
|
|
const sourceAreaObj = floatingAreas.value.find(a => a.id === sourceArea.id)
|
|
|
|
|
|
if (!sourceAreaObj) {
|
|
|
|
|
|
console.warn('找不到源Area:', sourceArea.id)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 根据目标Area内容状态执行不同的隐藏逻辑
|
|
|
|
|
|
if (targetAreaHasContent) {
|
|
|
|
|
|
// 目标Area已有内容:保存源Area及其TabPage组件到隐藏列表
|
|
|
|
|
|
console.log('目标Area已有内容,保存源Area和TabPage组件到隐藏列表')
|
|
|
|
|
|
addAreaToHiddenList({
|
|
|
|
|
|
...sourceAreaObj,
|
|
|
|
|
|
tabPages: [...sourceAreaObj.tabPages] // 深拷贝TabPages
|
|
|
|
|
|
})
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 目标Area为空:仅保存源Area到隐藏列表
|
|
|
|
|
|
console.log('目标Area为空,保存源Area到隐藏列表')
|
|
|
|
|
|
addAreaToHiddenList(sourceAreaObj)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 从浮动区域中移除已合并的源Area
|
|
|
|
|
|
const sourceIndex = floatingAreas.value.findIndex(a => a.id === sourceArea.id)
|
|
|
|
|
|
if (sourceIndex !== -1) {
|
|
|
|
|
|
floatingAreas.value.splice(sourceIndex, 1)
|
|
|
|
|
|
console.log('源Area已从浮动区域移除:', sourceArea.id)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 11:05:12 +08:00
|
|
|
|
// Panel拖拽开始
|
|
|
|
|
|
const onPanelDragStart = (areaId, event) => {
|
2025-11-18 15:39:46 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
2025-11-04 11:05:12 +08:00
|
|
|
|
const area = floatingAreas.value.find(a => a.id === areaId)
|
2025-11-18 15:39:46 +08:00
|
|
|
|
console.log('找到的area:', area)
|
|
|
|
|
|
console.log('area.tabPages.length:', area?.tabPages?.length)
|
|
|
|
|
|
console.log('area.tabPages[0].panels.length:', area?.tabPages?.[0]?.panels?.length)
|
|
|
|
|
|
|
2025-11-05 09:07:06 +08:00
|
|
|
|
// 只有当Area中只有一个TabPage且该TabPage中只有一个Panel时才允许通过Panel标题栏移动Area
|
|
|
|
|
|
if (area && area.tabPages && area.tabPages.length === 1 && area.tabPages[0].panels && area.tabPages[0].panels.length === 1) {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
// 检查event是否为对象格式(来自Panel.vue)
|
2025-11-18 15:39:46 +08:00
|
|
|
|
const clientX = event.clientX || (typeof event === 'object' ? event.clientX : event?.clientX)
|
|
|
|
|
|
const clientY = event.clientY || (typeof event === 'object' ? event.clientY : event?.clientY)
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (clientX === undefined || clientY === undefined) {
|
|
|
|
|
|
console.error('无法获取有效的鼠标位置信息:', event)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 11:05:12 +08:00
|
|
|
|
panelDragState.value.isDragging = true
|
|
|
|
|
|
panelDragState.value.currentAreaId = areaId
|
|
|
|
|
|
panelDragState.value.startClientPos = {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
x: clientX,
|
|
|
|
|
|
y: clientY
|
2025-11-04 11:05:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
panelDragState.value.startAreaPos = {
|
|
|
|
|
|
x: area.x,
|
|
|
|
|
|
y: area.y
|
|
|
|
|
|
}
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-07 15:41:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化鼠标位置跟踪
|
|
|
|
|
|
currentMousePosition.value = {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
x: clientX,
|
|
|
|
|
|
y: clientY
|
2025-11-07 15:41:44 +08:00
|
|
|
|
}
|
2025-11-13 10:50:22 +08:00
|
|
|
|
|
|
|
|
|
|
// 拖拽开始时就显示指示器
|
|
|
|
|
|
showDockIndicator.value = true
|
|
|
|
|
|
|
2025-11-14 15:45:54 +08:00
|
|
|
|
// 检查主区域内是否有其他Area
|
|
|
|
|
|
checkMainContentForAreas()
|
|
|
|
|
|
|
2025-11-13 10:50:22 +08:00
|
|
|
|
// 使用dock-layout作为默认目标区域
|
|
|
|
|
|
if (dockLayoutRef.value) {
|
|
|
|
|
|
const rect = dockLayoutRef.value.getBoundingClientRect()
|
|
|
|
|
|
targetAreaRect.value = {
|
|
|
|
|
|
left: 0, // 使用相对于容器的位置(左上角)
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
width: rect.width,
|
|
|
|
|
|
height: rect.height
|
|
|
|
|
|
}
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 拖拽开始时立即更新停靠区域
|
|
|
|
|
|
updateDockZoneByMousePosition(clientX, clientY)
|
2025-11-13 10:50:22 +08:00
|
|
|
|
}
|
2025-11-04 11:05:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Panel拖拽移动
|
|
|
|
|
|
const onPanelDragMove = (areaId, event) => {
|
2025-11-18 15:39:46 +08:00
|
|
|
|
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)
|
|
|
|
|
|
|
2025-11-04 11:05:12 +08:00
|
|
|
|
if (panelDragState.value.isDragging && panelDragState.value.currentAreaId === areaId) {
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === areaId)
|
|
|
|
|
|
if (area) {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
// 检查event是否为对象格式(来自Panel.vue)
|
2025-11-18 15:39:46 +08:00
|
|
|
|
const clientX = event.clientX || (typeof event === 'object' ? event.clientX : event?.clientX)
|
|
|
|
|
|
const clientY = event.clientY || (typeof event === 'object' ? event.clientY : event?.clientY)
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
|
|
|
|
|
if (clientX === undefined || clientY === undefined) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 11:05:12 +08:00
|
|
|
|
// 计算移动距离
|
2025-11-14 15:45:54 +08:00
|
|
|
|
const deltaX = clientX - panelDragState.value.startClientPos.x
|
|
|
|
|
|
const deltaY = clientY - panelDragState.value.startClientPos.y
|
2025-11-04 11:05:12 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算新位置
|
|
|
|
|
|
let newLeft = panelDragState.value.startAreaPos.x + deltaX
|
|
|
|
|
|
let newTop = panelDragState.value.startAreaPos.y + deltaY
|
|
|
|
|
|
|
|
|
|
|
|
// 确保不超出父容器边界
|
|
|
|
|
|
if (dockLayoutRef.value) {
|
|
|
|
|
|
const parentRect = dockLayoutRef.value.getBoundingClientRect()
|
|
|
|
|
|
|
|
|
|
|
|
// 严格边界检查
|
|
|
|
|
|
newLeft = Math.max(0, Math.min(newLeft, parentRect.width - area.width))
|
|
|
|
|
|
newTop = Math.max(0, Math.min(newTop, parentRect.height - area.height))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新位置
|
|
|
|
|
|
area.x = newLeft
|
|
|
|
|
|
area.y = newTop
|
2025-11-04 14:34:40 +08:00
|
|
|
|
|
2025-11-07 15:41:44 +08:00
|
|
|
|
// 更新鼠标位置
|
|
|
|
|
|
currentMousePosition.value = {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
x: clientX,
|
|
|
|
|
|
y: clientY
|
2025-11-07 15:41:44 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-14 15:45:54 +08:00
|
|
|
|
// 根据鼠标位置动态更新停靠区域
|
|
|
|
|
|
updateDockZoneByMousePosition(clientX, clientY)
|
|
|
|
|
|
|
2025-11-04 14:34:40 +08:00
|
|
|
|
// 调试信息
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-04 11:05:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Panel拖拽结束
|
|
|
|
|
|
const onPanelDragEnd = () => {
|
2025-11-18 15:39:46 +08:00
|
|
|
|
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)
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-04 11:05:12 +08:00
|
|
|
|
panelDragState.value.isDragging = false
|
2025-11-17 10:59:46 +08:00
|
|
|
|
const currentAreaId = panelDragState.value.currentAreaId
|
2025-11-04 11:05:12 +08:00
|
|
|
|
panelDragState.value.currentAreaId = null
|
2025-11-07 15:41:44 +08:00
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 3.1 在onPanelDragEnd方法中添加中心停靠检测
|
2025-11-18 13:58:44 +08:00
|
|
|
|
// 确保只有在独立中心指示器区域内释放才执行停靠
|
|
|
|
|
|
if (activeDockZone.value === 'center' && currentAreaId && isMouseInCenterIndicator(currentMousePosition.value.x, currentMousePosition.value.y)) {
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 处理中心停靠
|
|
|
|
|
|
const result = handleCenterDocking(currentAreaId)
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
console.warn('中心停靠失败:', result.message)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('中心停靠成功:', result.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 15:41:44 +08:00
|
|
|
|
// 隐藏停靠指示器
|
|
|
|
|
|
showDockIndicator.value = false
|
|
|
|
|
|
activeDockZone.value = null
|
2025-11-04 11:05:12 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-14 16:02:12 +08:00
|
|
|
|
// Area拖拽开始
|
|
|
|
|
|
const onAreaDragStart = (areaId, event) => {
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === areaId)
|
|
|
|
|
|
if (!area) return
|
|
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 设置拖拽状态(类似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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-14 16:02:12 +08:00
|
|
|
|
// 拖拽开始时显示指示器
|
|
|
|
|
|
showDockIndicator.value = true
|
|
|
|
|
|
|
|
|
|
|
|
// 检查主区域内是否有其他Area
|
|
|
|
|
|
checkMainContentForAreas()
|
|
|
|
|
|
|
|
|
|
|
|
// 使用dock-layout作为默认目标区域
|
|
|
|
|
|
if (dockLayoutRef.value) {
|
|
|
|
|
|
const rect = dockLayoutRef.value.getBoundingClientRect()
|
|
|
|
|
|
targetAreaRect.value = {
|
|
|
|
|
|
left: 0,
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
width: rect.width,
|
|
|
|
|
|
height: rect.height
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 拖拽开始时立即更新停靠区域
|
|
|
|
|
|
updateDockZoneByMousePosition(event.clientX, event.clientY)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Area拖拽移动
|
|
|
|
|
|
const onAreaDragMove = (areaId, event) => {
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 更新鼠标位置
|
|
|
|
|
|
currentMousePosition.value = {
|
|
|
|
|
|
x: event.clientX,
|
|
|
|
|
|
y: event.clientY
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-14 16:02:12 +08:00
|
|
|
|
// 根据鼠标位置动态更新停靠区域
|
|
|
|
|
|
updateDockZoneByMousePosition(event.clientX, event.clientY)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Area拖拽结束
|
|
|
|
|
|
const onAreaDragEnd = (areaId, event) => {
|
2025-11-18 15:39:46 +08:00
|
|
|
|
console.log('[DockLayout] Area拖拽结束:', { areaId, event, activeDockZone: activeDockZone.value })
|
|
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 清理拖拽状态
|
|
|
|
|
|
panelDragState.value.isDragging = false
|
|
|
|
|
|
panelDragState.value.currentAreaId = null
|
|
|
|
|
|
|
|
|
|
|
|
// 3.1 在onAreaDragEnd方法中添加中心停靠检测
|
2025-11-18 13:58:44 +08:00
|
|
|
|
// 确保只有在独立中心指示器区域内释放才执行停靠
|
|
|
|
|
|
if (activeDockZone.value === 'center' && isMouseInCenterIndicator(currentMousePosition.value.x, currentMousePosition.value.y)) {
|
2025-11-18 15:39:46 +08:00
|
|
|
|
console.log('[DockLayout] 检测到中心停靠区域,鼠标位置:', currentMousePosition.value)
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 处理中心停靠
|
|
|
|
|
|
const result = handleCenterDocking(areaId)
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
console.warn('Area中心停靠失败:', result.message)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('Area中心停靠成功:', result.message)
|
|
|
|
|
|
}
|
2025-11-18 15:39:46 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
console.log('[DockLayout] 未检测到有效的中心停靠:', {
|
|
|
|
|
|
activeDockZone: activeDockZone.value,
|
|
|
|
|
|
mousePosition: currentMousePosition.value,
|
|
|
|
|
|
isInCenter: isMouseInCenterIndicator(currentMousePosition.value?.x || 0, currentMousePosition.value?.y || 0)
|
|
|
|
|
|
})
|
2025-11-17 10:59:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-14 16:02:12 +08:00
|
|
|
|
// 隐藏停靠指示器
|
|
|
|
|
|
showDockIndicator.value = false
|
|
|
|
|
|
activeDockZone.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 14:57:30 +08:00
|
|
|
|
// TabPage拖拽开始
|
|
|
|
|
|
const onTabDragStart = (areaId, event) => {
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === areaId)
|
|
|
|
|
|
// 只有当Area中只有一个TabPage时才允许通过TabPage的页标签移动Area
|
|
|
|
|
|
if (area && area.tabPages && area.tabPages.length === 1) {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
// 检查event是否为对象格式(来自TabPage.vue)
|
|
|
|
|
|
const clientX = event.clientX || (typeof event === 'object' ? event.x : event?.x)
|
|
|
|
|
|
const clientY = event.clientY || (typeof event === 'object' ? event.y : event?.y)
|
|
|
|
|
|
|
|
|
|
|
|
if (clientX === undefined || clientY === undefined) {
|
|
|
|
|
|
console.error('无法获取有效的鼠标位置信息:', event)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 14:57:30 +08:00
|
|
|
|
tabDragState.value.isDragging = true
|
|
|
|
|
|
tabDragState.value.currentAreaId = areaId
|
|
|
|
|
|
tabDragState.value.startClientPos = {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
x: clientX,
|
|
|
|
|
|
y: clientY
|
2025-11-06 14:57:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
tabDragState.value.startAreaPos = {
|
|
|
|
|
|
x: area.x,
|
|
|
|
|
|
y: area.y
|
|
|
|
|
|
}
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-13 10:50:22 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化鼠标位置跟踪
|
|
|
|
|
|
currentMousePosition.value = {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
x: clientX,
|
|
|
|
|
|
y: clientY
|
2025-11-13 10:50:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 拖拽开始时就显示指示器
|
|
|
|
|
|
showDockIndicator.value = true
|
|
|
|
|
|
|
2025-11-14 15:45:54 +08:00
|
|
|
|
// 检查主区域内是否有其他Area
|
|
|
|
|
|
checkMainContentForAreas()
|
|
|
|
|
|
|
2025-11-13 10:50:22 +08:00
|
|
|
|
// 使用dock-layout作为默认目标区域
|
|
|
|
|
|
if (dockLayoutRef.value) {
|
|
|
|
|
|
const rect = dockLayoutRef.value.getBoundingClientRect()
|
|
|
|
|
|
targetAreaRect.value = {
|
|
|
|
|
|
left: 0, // 使用相对于容器的位置(左上角)
|
|
|
|
|
|
top: 0,
|
|
|
|
|
|
width: rect.width,
|
|
|
|
|
|
height: rect.height
|
|
|
|
|
|
}
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
|
|
|
|
|
// 拖拽开始时立即更新停靠区域
|
|
|
|
|
|
updateDockZoneByMousePosition(clientX, clientY)
|
2025-11-13 10:50:22 +08:00
|
|
|
|
}
|
2025-11-06 14:57:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TabPage拖拽移动
|
|
|
|
|
|
const onTabDragMove = (areaId, event) => {
|
|
|
|
|
|
if (tabDragState.value.isDragging && tabDragState.value.currentAreaId === areaId) {
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === areaId)
|
|
|
|
|
|
if (area) {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
// 检查event是否为对象格式(来自TabPage.vue)
|
|
|
|
|
|
const clientX = event.clientX || (typeof event === 'object' ? event.x : event?.x)
|
|
|
|
|
|
const clientY = event.clientY || (typeof event === 'object' ? event.y : event?.y)
|
|
|
|
|
|
|
|
|
|
|
|
if (clientX === undefined || clientY === undefined) {
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 14:57:30 +08:00
|
|
|
|
// 计算移动距离
|
2025-11-14 15:45:54 +08:00
|
|
|
|
const deltaX = clientX - tabDragState.value.startClientPos.x
|
|
|
|
|
|
const deltaY = clientY - tabDragState.value.startClientPos.y
|
2025-11-06 14:57:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 计算新位置
|
|
|
|
|
|
let newLeft = tabDragState.value.startAreaPos.x + deltaX
|
|
|
|
|
|
let newTop = tabDragState.value.startAreaPos.y + deltaY
|
|
|
|
|
|
|
|
|
|
|
|
// 确保不超出父容器边界
|
|
|
|
|
|
if (dockLayoutRef.value) {
|
|
|
|
|
|
const parentRect = dockLayoutRef.value.getBoundingClientRect()
|
|
|
|
|
|
|
|
|
|
|
|
// 严格边界检查
|
|
|
|
|
|
newLeft = Math.max(0, Math.min(newLeft, parentRect.width - area.width))
|
|
|
|
|
|
newTop = Math.max(0, Math.min(newTop, parentRect.height - area.height))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新位置
|
|
|
|
|
|
area.x = newLeft
|
|
|
|
|
|
area.y = newTop
|
|
|
|
|
|
|
2025-11-13 10:50:22 +08:00
|
|
|
|
// 更新鼠标位置
|
|
|
|
|
|
currentMousePosition.value = {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
x: clientX,
|
|
|
|
|
|
y: clientY
|
2025-11-13 10:50:22 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-14 15:45:54 +08:00
|
|
|
|
// 根据鼠标位置动态更新停靠区域
|
|
|
|
|
|
updateDockZoneByMousePosition(clientX, clientY)
|
|
|
|
|
|
|
2025-11-06 14:57:30 +08:00
|
|
|
|
// 调试信息
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-06 14:57:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TabPage拖拽结束
|
|
|
|
|
|
const onTabDragEnd = () => {
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-06 14:57:30 +08:00
|
|
|
|
tabDragState.value.isDragging = false
|
2025-11-17 10:59:46 +08:00
|
|
|
|
const currentAreaId = tabDragState.value.currentAreaId
|
2025-11-06 14:57:30 +08:00
|
|
|
|
tabDragState.value.currentAreaId = null
|
2025-11-14 15:53:27 +08:00
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 3.2 在onTabDragEnd方法中添加中心停靠检测
|
2025-11-18 13:58:44 +08:00
|
|
|
|
// 确保只有在独立中心指示器区域内释放才执行停靠
|
|
|
|
|
|
if (activeDockZone.value === 'center' && currentAreaId && isMouseInCenterIndicator(currentMousePosition.value.x, currentMousePosition.value.y)) {
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 处理中心停靠
|
|
|
|
|
|
const result = handleCenterDocking(currentAreaId)
|
|
|
|
|
|
if (!result.success) {
|
|
|
|
|
|
console.warn('中心停靠失败:', result.message)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.log('中心停靠成功:', result.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-14 15:53:27 +08:00
|
|
|
|
// 隐藏停靠指示器
|
|
|
|
|
|
showDockIndicator.value = false
|
|
|
|
|
|
activeDockZone.value = null
|
2025-11-06 14:57:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-04 17:24:08 +08:00
|
|
|
|
// 监听floatingAreas变化,确保当Area最大化时,Panel也会自动最大化
|
|
|
|
|
|
watch(floatingAreas, (newAreas) => {
|
|
|
|
|
|
newAreas.forEach(area => {
|
2025-11-06 13:32:18 +08:00
|
|
|
|
// 正确处理层级结构:Area -> TabPage -> Panel
|
|
|
|
|
|
if (area.tabPages) {
|
|
|
|
|
|
for (const tabPage of area.tabPages) {
|
|
|
|
|
|
// 当区域只包含一个Panel且Area状态变为最大化时,Panel也应该最大化
|
|
|
|
|
|
if (tabPage.panels && tabPage.panels.length === 1) {
|
|
|
|
|
|
const isAreaMaximized = area.WindowState === '最大化' || area.WindowState === 'maximized';
|
|
|
|
|
|
const isPanelMaximized = tabPage.panels[0].maximized;
|
|
|
|
|
|
|
|
|
|
|
|
// 如果状态不一致,更新Panel的maximized属性
|
|
|
|
|
|
if (isAreaMaximized !== isPanelMaximized) {
|
|
|
|
|
|
tabPage.panels[0] = { ...tabPage.panels[0], maximized: isAreaMaximized };
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-06 13:32:18 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-04 17:24:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
}, { deep: true });
|
|
|
|
|
|
|
2025-11-14 14:23:10 +08:00
|
|
|
|
// 组件挂载后检查主区域内容
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 延迟执行,确保DOM完全渲染
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
checkMainContentForAreas()
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
// 监听主区域内容变化
|
|
|
|
|
|
watch(floatingAreas, () => {
|
|
|
|
|
|
// 当浮动区域变化时,重新检查主区域内容
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
checkMainContentForAreas()
|
|
|
|
|
|
}, 100)
|
|
|
|
|
|
}, { deep: true })
|
|
|
|
|
|
|
|
|
|
|
|
// 当主区域内没有其他Area时,隐藏外部边缘指示器,只显示中心指示器
|
|
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 第5步:优化UI指示器显示逻辑(当主区域为空时)
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查主区域是否为空
|
|
|
|
|
|
* @returns {boolean} 主区域是否为空
|
|
|
|
|
|
*/
|
|
|
|
|
|
const isMainAreaEmpty = () => {
|
|
|
|
|
|
checkMainContentForAreas()
|
|
|
|
|
|
return !hasAreasInMainContent.value
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 根据鼠标位置动态更新停靠区域(优化版)
|
|
|
|
|
|
* 当主区域为空时,只显示中心指示器
|
|
|
|
|
|
* @param {number} mouseX - 鼠标X坐标
|
|
|
|
|
|
* @param {number} mouseY - 鼠标Y坐标
|
|
|
|
|
|
*/
|
2025-11-14 15:45:54 +08:00
|
|
|
|
const updateDockZoneByMousePosition = (mouseX, mouseY) => {
|
|
|
|
|
|
if (!dockLayoutRef.value || !targetAreaRect.value) return
|
|
|
|
|
|
|
2025-11-18 15:39:46 +08:00
|
|
|
|
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'
|
|
|
|
|
|
}
|
2025-11-17 10:59:46 +08:00
|
|
|
|
}
|
2025-11-14 15:45:54 +08:00
|
|
|
|
}
|
2025-11-18 15:39:46 +08:00
|
|
|
|
|
|
|
|
|
|
// 只有当停靠区域改变时才更新,减少不必要的重新渲染
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
2025-11-17 10:59:46 +08:00
|
|
|
|
}
|
2025-11-18 15:39:46 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('更新停靠区域时出错:', error)
|
2025-11-14 15:45:54 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-07 15:41:44 +08:00
|
|
|
|
// 处理主区域的dragover事件
|
|
|
|
|
|
const handleMainAreaDragOver = (event) => {
|
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
|
|
|
|
if (panelDragState.value.isDragging || tabDragState.value.isDragging) {
|
2025-11-13 10:50:22 +08:00
|
|
|
|
// 使用dock-layout作为基准获取位置和大小
|
|
|
|
|
|
let rect
|
|
|
|
|
|
if (dockLayoutRef.value) {
|
|
|
|
|
|
rect = dockLayoutRef.value.getBoundingClientRect()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 回退到使用事件目标
|
|
|
|
|
|
rect = event.currentTarget.getBoundingClientRect()
|
|
|
|
|
|
}
|
2025-11-07 15:41:44 +08:00
|
|
|
|
|
|
|
|
|
|
// 更新目标区域信息并显示停靠指示器
|
|
|
|
|
|
targetAreaRect.value = {
|
2025-11-13 10:50:22 +08:00
|
|
|
|
left: 0, // 使用相对于容器的位置(左上角)
|
|
|
|
|
|
top: 0,
|
2025-11-07 15:41:44 +08:00
|
|
|
|
width: rect.width,
|
|
|
|
|
|
height: rect.height
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
showDockIndicator.value = true
|
|
|
|
|
|
|
|
|
|
|
|
// 更新鼠标位置
|
|
|
|
|
|
currentMousePosition.value = {
|
|
|
|
|
|
x: event.clientX,
|
|
|
|
|
|
y: event.clientY
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理主区域的dragleave事件
|
|
|
|
|
|
const handleMainAreaDragLeave = () => {
|
|
|
|
|
|
// 检查鼠标是否真的离开了区域(可能只是进入了子元素)
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const activeElement = document.activeElement
|
|
|
|
|
|
const dockLayout = dockLayoutRef.value
|
|
|
|
|
|
|
|
|
|
|
|
// 如果活动元素不是dockLayout的后代,隐藏指示器
|
2025-11-18 15:39:46 +08:00
|
|
|
|
// 但如果在拖拽过程中,不立即隐藏指示器
|
|
|
|
|
|
const isDragging = panelDragState.value.isDragging || tabDragState.value.isDragging
|
|
|
|
|
|
if (!isDragging && (!dockLayout || (activeElement && !dockLayout.contains(activeElement)))) {
|
2025-11-07 15:41:44 +08:00
|
|
|
|
showDockIndicator.value = false
|
|
|
|
|
|
activeDockZone.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 50)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理浮动区域的dragover事件
|
|
|
|
|
|
const handleAreaDragOver = (event, areaId) => {
|
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
|
|
|
|
|
|
|
if (panelDragState.value.isDragging || tabDragState.value.isDragging) {
|
|
|
|
|
|
// 避免自身停靠到自身
|
|
|
|
|
|
if (areaId !== panelDragState.value.currentAreaId && areaId !== tabDragState.value.currentAreaId) {
|
|
|
|
|
|
// 获取目标区域的位置和大小
|
|
|
|
|
|
const areaElement = event.currentTarget
|
|
|
|
|
|
const rect = areaElement.getBoundingClientRect()
|
|
|
|
|
|
|
|
|
|
|
|
// 更新目标区域信息并显示停靠指示器
|
|
|
|
|
|
targetAreaRect.value = {
|
|
|
|
|
|
left: rect.left,
|
|
|
|
|
|
top: rect.top,
|
|
|
|
|
|
width: rect.width,
|
|
|
|
|
|
height: rect.height
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
showDockIndicator.value = true
|
|
|
|
|
|
|
|
|
|
|
|
// 更新鼠标位置
|
|
|
|
|
|
currentMousePosition.value = {
|
|
|
|
|
|
x: event.clientX,
|
|
|
|
|
|
y: event.clientY
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理浮动区域的dragleave事件
|
|
|
|
|
|
const handleAreaDragLeave = () => {
|
|
|
|
|
|
// 延迟检查,避免快速移动时的闪烁
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const activeElement = document.activeElement
|
|
|
|
|
|
const dockLayout = dockLayoutRef.value
|
|
|
|
|
|
|
|
|
|
|
|
// 如果活动元素不是dockLayout的后代,隐藏指示器
|
2025-11-18 15:39:46 +08:00
|
|
|
|
// 但如果在拖拽过程中,不立即隐藏指示器
|
|
|
|
|
|
const isDragging = panelDragState.value.isDragging || tabDragState.value.isDragging
|
|
|
|
|
|
if (!isDragging && (!dockLayout || (activeElement && !dockLayout.contains(activeElement)))) {
|
2025-11-07 15:41:44 +08:00
|
|
|
|
showDockIndicator.value = false
|
|
|
|
|
|
activeDockZone.value = null
|
|
|
|
|
|
}
|
|
|
|
|
|
}, 50)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理停靠区域激活事件
|
|
|
|
|
|
const onDockZoneActive = (zone) => {
|
|
|
|
|
|
activeDockZone.value = zone
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-06 13:24:37 +08:00
|
|
|
|
// 处理Panel最大化同步事件
|
|
|
|
|
|
const onPanelMaximizeSync = ({ areaId, maximized }) => {
|
|
|
|
|
|
// 查找对应的Area
|
|
|
|
|
|
const area = floatingAreas.value.find(a => a.id === areaId);
|
2025-11-06 13:32:18 +08:00
|
|
|
|
// 正确处理层级结构:Area -> TabPage -> Panel
|
|
|
|
|
|
if (area && area.tabPages && area.tabPages.length === 1 && area.tabPages[0].panels && area.tabPages[0].panels.length === 1) {
|
|
|
|
|
|
// 更新TabPage中Panel的maximized状态
|
|
|
|
|
|
area.tabPages[0].panels[0] = { ...area.tabPages[0].panels[0], maximized };
|
2025-11-14 15:45:54 +08:00
|
|
|
|
|
2025-11-06 13:24:37 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 第2步:Area验证逻辑 - 目标Area内容区为空的情况
|
2025-11-18 13:58:44 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 检查鼠标是否在独立中心指示器区域内
|
|
|
|
|
|
* @param {number} mouseX - 鼠标X坐标
|
|
|
|
|
|
* @param {number} mouseY - 鼠标Y坐标
|
|
|
|
|
|
* @returns {boolean} 是否在独立中心指示器区域内
|
|
|
|
|
|
*/
|
|
|
|
|
|
const isMouseInCenterIndicator = (mouseX, mouseY) => {
|
|
|
|
|
|
if (!dockLayoutRef.value) return false
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 查找独立中心指示器元素
|
|
|
|
|
|
const centerIndicator = dockLayoutRef.value.querySelector('.center-main-indicator')
|
2025-11-18 15:39:46 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|
2025-11-18 13:58:44 +08:00
|
|
|
|
|
|
|
|
|
|
const rect = centerIndicator.getBoundingClientRect()
|
2025-11-18 15:39:46 +08:00
|
|
|
|
const isInIndicator = mouseX >= rect.left && mouseX <= rect.right &&
|
|
|
|
|
|
mouseY >= rect.top && mouseY <= rect.bottom
|
2025-11-18 13:58:44 +08:00
|
|
|
|
|
2025-11-18 15:39:46 +08:00
|
|
|
|
console.log('独立中心指示器检测:', {
|
|
|
|
|
|
mouseX, mouseY,
|
|
|
|
|
|
indicatorRect: rect,
|
|
|
|
|
|
isInIndicator,
|
|
|
|
|
|
showDockIndicator: showDockIndicator.value,
|
|
|
|
|
|
activeDockZone: activeDockZone.value
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return isInIndicator
|
2025-11-18 13:58:44 +08:00
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.warn('检查独立中心指示器区域时出错:', error)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 检查Area是否可以停靠到中心区域
|
|
|
|
|
|
* @param {Object} sourceArea - 源Area对象
|
|
|
|
|
|
* @param {Object} targetArea - 目标Area对象(主区域)
|
2025-11-17 16:55:03 +08:00
|
|
|
|
* @returns {Object} 验证结果 {canDock: boolean, reason: string, strategy: string}
|
2025-11-17 10:59:46 +08:00
|
|
|
|
*/
|
|
|
|
|
|
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,无法停靠'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 16:55:03 +08:00
|
|
|
|
// 5.2. 新增验证:根据目标Area状态确定停靠策略
|
|
|
|
|
|
// 检查主区域内容状态
|
|
|
|
|
|
checkMainContentForAreas()
|
|
|
|
|
|
|
|
|
|
|
|
// 如果主区域已有内容,判断停靠策略
|
|
|
|
|
|
if (hasAreasInMainContent.value) {
|
|
|
|
|
|
// 检查主区域是否已有TabPage
|
|
|
|
|
|
const mainAreaElement = mainAreaRef.value?.$el
|
|
|
|
|
|
if (mainAreaElement) {
|
|
|
|
|
|
const existingTabPages = mainAreaElement.querySelectorAll('.tab-page, [class*="tab"]')
|
|
|
|
|
|
const hasExistingTabPage = existingTabPages.length > 0
|
|
|
|
|
|
|
|
|
|
|
|
if (hasExistingTabPage && sourceArea.tabPages.length === 1) {
|
|
|
|
|
|
// 场景5.2:源Area只有一个TabPage,目标Area也有TabPage
|
|
|
|
|
|
// 策略:合并TabPage标签页
|
|
|
|
|
|
return {
|
|
|
|
|
|
canDock: true,
|
|
|
|
|
|
reason: '可以停靠 - 采用TabPage标签页合并策略',
|
|
|
|
|
|
strategy: 'merge-tabpages'
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 其他场景:采用简单Area内容移动策略
|
|
|
|
|
|
return {
|
|
|
|
|
|
canDock: true,
|
|
|
|
|
|
reason: '可以停靠 - 采用简单移动策略',
|
|
|
|
|
|
strategy: 'simple-move'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 主区域为空的情况:采用简单移动策略
|
2025-11-17 10:59:46 +08:00
|
|
|
|
return {
|
|
|
|
|
|
canDock: true,
|
2025-11-17 16:55:03 +08:00
|
|
|
|
reason: '验证通过,可以停靠到中心区域',
|
|
|
|
|
|
strategy: 'simple-move'
|
2025-11-17 10:59:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 16:55:03 +08:00
|
|
|
|
// 3.3 创建handleCenterDocking核心处理函数
|
2025-11-17 10:59:46 +08:00
|
|
|
|
/**
|
2025-11-17 16:55:03 +08:00
|
|
|
|
* 处理中心停靠逻辑
|
|
|
|
|
|
* @param {string} sourceAreaId - 源Area的ID
|
2025-11-17 10:59:46 +08:00
|
|
|
|
* @returns {Object} 处理结果 {success: boolean, message: string}
|
|
|
|
|
|
*/
|
2025-11-17 16:55:03 +08:00
|
|
|
|
const handleCenterDocking = (sourceAreaId) => {
|
|
|
|
|
|
console.log('开始处理中心停靠:', sourceAreaId)
|
|
|
|
|
|
|
|
|
|
|
|
// 1. 查找源Area
|
|
|
|
|
|
const sourceArea = floatingAreas.value.find(a => a.id === sourceAreaId)
|
|
|
|
|
|
if (!sourceArea) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: `未找到ID为 ${sourceAreaId} 的源Area`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 验证停靠条件
|
|
|
|
|
|
const validationResult = canDockToCenter(sourceArea, mainAreaRef.value)
|
|
|
|
|
|
if (!validationResult.canDock) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: `停靠验证失败: ${validationResult.reason}`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('停靠验证通过:', validationResult.reason)
|
|
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
try {
|
2025-11-17 16:55:03 +08:00
|
|
|
|
// 3. 根据策略执行停靠逻辑
|
|
|
|
|
|
if (validationResult.strategy === 'merge-tabpages') {
|
|
|
|
|
|
// 5.2. 策略:合并TabPage标签页
|
|
|
|
|
|
return handleMergeTabpagesDocking(sourceArea)
|
2025-11-17 10:59:46 +08:00
|
|
|
|
} else {
|
2025-11-17 16:55:03 +08:00
|
|
|
|
// 默认策略:简单Area内容移动
|
|
|
|
|
|
return handleSimpleAreaDocking(sourceArea)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('停靠处理过程中发生错误:', error)
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: `停靠过程中发生错误: ${error.message}`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 5.2. 新增:处理TabPage标签页合并策略
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理TabPage标签页合并策略
|
|
|
|
|
|
* 当源Area只有一个TabPage且目标Area已有TabPage时使用此策略
|
|
|
|
|
|
* @param {Object} sourceArea - 源Area对象
|
|
|
|
|
|
* @returns {Object} 处理结果 {success: boolean, message: string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
// 合并TabPage标签页的核心处理函数
|
|
|
|
|
|
const handleMergeTabpagesDocking = (sourceArea) => {
|
|
|
|
|
|
console.log(`[DockLayout] 开始合并TabPage标签页,源Area:`, sourceArea)
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 验证源Area是否有效
|
|
|
|
|
|
if (!sourceArea || !sourceArea.tabPages || sourceArea.tabPages.length === 0) {
|
|
|
|
|
|
console.warn('[DockLayout] 源Area无效或没有TabPage,无法执行合并操作')
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 查找主区域的Area组件
|
|
|
|
|
|
const mainArea = mainAreaRef.value
|
|
|
|
|
|
if (!mainArea) {
|
|
|
|
|
|
console.error('[DockLayout] 找不到主区域引用')
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('[DockLayout] 使用新的mergeAreaContent方法合并TabPage')
|
|
|
|
|
|
const success = mainArea.mergeAreaContent(sourceArea)
|
|
|
|
|
|
|
|
|
|
|
|
if (success) {
|
|
|
|
|
|
const newTabPageId = `merged-${Date.now()}`
|
|
|
|
|
|
const panelsCount = sourceArea.tabPages[0]?.panels?.length || 0
|
|
|
|
|
|
console.log(`[DockLayout] 成功合并 ${panelsCount} 个Panel到主区域TabPage: ${newTabPageId}`)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
newTabPageId: newTabPageId,
|
|
|
|
|
|
panelsAdded: panelsCount
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.error('[DockLayout] mergeAreaContent方法执行失败')
|
|
|
|
|
|
return null
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('[DockLayout] 合并TabPage时发生错误:', error)
|
|
|
|
|
|
return null
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 在主区域查找或创建TabPage
|
|
|
|
|
|
* @returns {Object} 查找结果 {success: boolean, message: string, tabPage?: Object}
|
|
|
|
|
|
*/
|
|
|
|
|
|
const findOrCreateMainAreaTabPage = () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 检查主区域是否已经接收了内容
|
|
|
|
|
|
if (mainAreaRef.value && mainAreaRef.value.receivedContent) {
|
|
|
|
|
|
const existingContent = mainAreaRef.value.receivedContent
|
|
|
|
|
|
console.log('检查主区域已接收内容,数量:', existingContent.length)
|
|
|
|
|
|
|
|
|
|
|
|
if (existingContent.length > 0) {
|
|
|
|
|
|
// 找到现有的TabPage内容
|
|
|
|
|
|
const existingTabPage = existingContent[0] // 只使用第一个TabPage
|
|
|
|
|
|
console.log('找到现有的TabPage:', existingTabPage.title)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: '找到现有的TabPage',
|
|
|
|
|
|
tabPage: existingTabPage.tabPage,
|
|
|
|
|
|
rawTabPage: existingTabPage // 返回原始TabPage对象以供修改
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-11-17 10:59:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-17 16:55:03 +08:00
|
|
|
|
// 如果主区域没有内容,现在不再主动创建空TabPage
|
|
|
|
|
|
// 因为在合并逻辑中,Area会自动根据需要创建TabPage
|
|
|
|
|
|
console.log('主区域没有现有TabPage,将通过合并操作自动创建')
|
2025-11-17 10:59:46 +08:00
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
2025-11-17 16:55:03 +08:00
|
|
|
|
message: '主区域为空,等待合并操作创建TabPage',
|
|
|
|
|
|
tabPage: null,
|
|
|
|
|
|
rawTabPage: null
|
2025-11-17 10:59:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
2025-11-17 16:55:03 +08:00
|
|
|
|
console.error('查找TabPage时发生错误:', error)
|
2025-11-17 10:59:46 +08:00
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
2025-11-17 16:55:03 +08:00
|
|
|
|
message: `查找TabPage失败: ${error.message}`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 3.4 实现简单Area停靠策略(重构为使用合并逻辑)
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理简单Area停靠策略(Area内容合并)
|
|
|
|
|
|
* 当主区域为空或不符合TabPage合并条件时使用此策略
|
|
|
|
|
|
* @param {Object} sourceArea - 源Area对象
|
|
|
|
|
|
* @returns {Object} 处理结果 {success: boolean, message: string}
|
|
|
|
|
|
*/
|
|
|
|
|
|
const handleSimpleAreaDocking = (sourceArea) => {
|
|
|
|
|
|
console.log('执行简单Area停靠策略(使用合并逻辑)')
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 1. 验证源Area结构
|
|
|
|
|
|
if (!sourceArea.tabPages || sourceArea.tabPages.length === 0) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: '源Area不包含TabPage,无法进行停靠合并'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
console.log('源Area结构验证通过,包含', sourceArea.tabPages.length, '个TabPage')
|
|
|
|
|
|
|
|
|
|
|
|
// 2. 使用mergeAreaContent方法将源Area合并到主区域
|
|
|
|
|
|
if (mainAreaRef.value && typeof mainAreaRef.value.mergeAreaContent === 'function') {
|
|
|
|
|
|
console.log('通过mergeAreaContent方法合并源Area到主区域')
|
|
|
|
|
|
const result = mainAreaRef.value.mergeAreaContent(sourceArea)
|
|
|
|
|
|
|
|
|
|
|
|
if (result) {
|
|
|
|
|
|
console.log('源Area成功合并到主区域,area-merged事件将处理隐藏列表操作')
|
|
|
|
|
|
|
|
|
|
|
|
// 3. 更新主区域状态(合并完成后由事件处理函数处理隐藏列表)
|
|
|
|
|
|
nextTick(() => {
|
|
|
|
|
|
checkMainContentForAreas()
|
|
|
|
|
|
// 确保主区域最大化显示还原按钮
|
|
|
|
|
|
if (mainAreaRef.value) {
|
|
|
|
|
|
mainAreaRef.value.WindowState = '最大化'
|
|
|
|
|
|
console.log('主区域状态已设置为最大化')
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: true,
|
|
|
|
|
|
message: `成功合并 ${sourceArea.tabPages.length} 个TabPage到主区域`
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: 'mergeAreaContent方法执行失败'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('主区域不支持mergeAreaContent方法')
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: '主区域不支持合并操作'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('简单Area停靠策略执行时发生错误:', error)
|
|
|
|
|
|
return {
|
|
|
|
|
|
success: false,
|
|
|
|
|
|
message: `简单Area停靠失败: ${error.message}`
|
2025-11-17 10:59:46 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 第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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-18 15:39:46 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 统一停靠结束处理函数
|
|
|
|
|
|
* 根据拖拽类型和目标区域执行相应的停靠逻辑
|
|
|
|
|
|
* @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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-19 13:57:51 +08:00
|
|
|
|
// 暴露数据和方法给父组件
|
2025-11-02 17:12:40 +07:00
|
|
|
|
defineExpose({
|
2025-11-19 13:57:51 +08:00
|
|
|
|
// 数据属性
|
|
|
|
|
|
floatingAreas,
|
|
|
|
|
|
hiddenAreas,
|
|
|
|
|
|
|
2025-11-17 10:59:46 +08:00
|
|
|
|
// 原有方法
|
|
|
|
|
|
addFloatingPanel,
|
|
|
|
|
|
|
2025-11-18 15:39:46 +08:00
|
|
|
|
// 统一停靠处理
|
|
|
|
|
|
handleDockingEnding,
|
|
|
|
|
|
handleEdgeDocking,
|
|
|
|
|
|
handleSideBySideDocking,
|
|
|
|
|
|
|
|
|
|
|
|
// 隐藏列表管理方法
|
2025-11-17 10:59:46 +08:00
|
|
|
|
getHiddenAreas,
|
|
|
|
|
|
restoreAreaFromHidden,
|
|
|
|
|
|
removeFromHiddenList,
|
2025-11-18 15:39:46 +08:00
|
|
|
|
clearHiddenList,
|
|
|
|
|
|
|
|
|
|
|
|
// ResizeBar相关方法
|
|
|
|
|
|
addResizeBarForSideBySideLayout,
|
|
|
|
|
|
handleResizeBarResize,
|
|
|
|
|
|
handleResizeBarResizeStart,
|
|
|
|
|
|
handleResizeBarResizeEnd,
|
|
|
|
|
|
|
|
|
|
|
|
// ResizeBar尺寸调整方法
|
|
|
|
|
|
handleHorizontalResizeWithSize,
|
|
|
|
|
|
handleVerticalResizeWithSize,
|
|
|
|
|
|
|
|
|
|
|
|
// 查找方法
|
|
|
|
|
|
findOrCreateMainAreaTabPage,
|
|
|
|
|
|
findFirstMainArea,
|
2025-11-02 17:12:40 +07:00
|
|
|
|
})
|
2025-11-02 17:06:40 +07:00
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style scoped>
|
|
|
|
|
|
.dock-layout {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
height: 100%;
|
2025-11-04 09:10:15 +08:00
|
|
|
|
overflow: visible;
|
2025-11-02 17:06:40 +07:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-11-03 17:26:28 +08:00
|
|
|
|
/* 浮动区域样式已直接应用到Area组件 */
|
2025-11-02 17:19:53 +07:00
|
|
|
|
|
2025-11-02 17:06:40 +07:00
|
|
|
|
/* 添加浮动区域按钮样式 */
|
|
|
|
|
|
.add-floating-btn {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
|
user-select: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.add-floating-btn:active {
|
|
|
|
|
|
transform: scale(0.98);
|
|
|
|
|
|
}
|
|
|
|
|
|
</style>
|