Files
JoyD/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue
2026-01-15 14:28:29 +08:00

1567 lines
48 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="dock-layout" ref="dockLayoutRef" style="display: flex; flex-direction: column; position: relative; width: 100%; height: 100%;">
<!-- 停靠指示器组件 - 设置高z-index确保显示在最顶层 -->
<DockIndicator
:visible="showDockIndicator"
:target-rect="targetAreaRect"
:mouse-position="currentMousePosition"
:hide-edge-indicators="hideEdgeIndicators"
@zone-active="onDockZoneActive"
style="z-index: 9999;"
/>
<!-- 主区域使用Render组件统一渲染 -->
<div class="main-area-container" style="position: relative; width: 100%; height: 100%;">
<Render
:type="'Area'"
:config="mainAreaConfig"
ref="mainAreaRef"
/>
<!-- 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>
<!-- 浮动区域使用Render组件统一渲染 -->
<Render
v-for="area in floatingAreas"
:key="area.id"
:type="'Area'"
:config="area"
/>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, defineEmits } from 'vue'
import Area from './Area.vue';
import { getAreaHandler } from './handlers/AreaHandler';
// 获取AreaHandler实例
const areaHandler = getAreaHandler();
import Panel from './Panel.vue';
import TabPage from './TabPage.vue';
import DockIndicator from './DockIndicator.vue';
import ResizeBar from './ResizeBar.vue';
import Render from './Render.vue';
import { zIndexManager } from './dockLayers';
import { eventBus, EVENT_TYPES, emitEvent } from './eventBus';
import { areaActions } from './handlers/AreaHandler';
import { dragStateActions } from './handlers/DragStateManager';
import { panelActions } from './handlers/PanelHandler';
import { tabPageActions } from './handlers/TabPageHandler';
import { globalEventActions } from './handlers/GlobalEventManager';
// 定义组件可以发出的事件
const emit = defineEmits([
'maximize', // 面板最大化事件
'toggleCollapse', // 折叠状态切换事件
'toggleToolbar', // 工具栏切换事件
'dragStart', // 拖拽开始事件
'dragMove', // 拖拽移动事件
'dragEnd' // 拖拽结束事件
])
// 主区域状态
const windowState = ref('最大化')
// 主区域配置
const mainAreaConfig = ref({
id: 'MainArea',
title: '主区域',
windowState: windowState.value,
showTitleBar: false,
children: {
type: 'TabPage',
id: `tabPage-${Date.now()}`,
children: []
}
})
// 浮动区域列表 - 每个area包含panels数组
const floatingAreas = ref([])
// 容器引用
const dockLayoutRef = ref(null)
// 主区域引用
const mainAreaRef = ref(null)
// 调试模式开关
const debugMode = ref(false)
// 停靠指示器相关状态
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)
// 主区域ResizeBar列表
const mainAreaResizeBars = ref([])
// 检查主区域内是否有其他Area简化版
const hasAreasInMainContent = ref(false)
// 添加拖拽操作缓存,避免在每次移动事件中重复检测
const dragOperationCache = new Map();
/**
* 检查是否应该操作区域而非面板
* 当只有一个面板时,操作区域而不是面板
* @param {string} areaId - Area ID
* @returns {boolean} 是否应该操作区域
*/
const shouldOperateAreaInsteadOfPanel = (areaId) => {
try {
// 从floatingAreas中查找对应areaId的区域
const area = floatingAreas.value.find(a => a.id === areaId);
if (!area) return false;
// 检查区域的子元素结构
// 如果有一个TabPage和任意数量的Panel返回true
const childrenArray = Array.isArray(area.children) ? area.children : [area.children];
for (const child of childrenArray) {
if (child.type === 'TabPage' && child.children) {
const tabChildrenArray = Array.isArray(child.children) ? child.children : [child.children];
const panelCount = tabChildrenArray.filter(tabChild => tabChild.type === 'Panel').length;
// 如果TabPage有Panel返回true
if (panelCount > 0) {
return true;
}
}
}
return false;
} catch (error) {
return false;
}
};
/**
* 检查是否应该操作区域而非面板从GlobalEventManager.js迁移
* 当有一个TabPage和任意数量的Panel时操作区域而不是面板
* @param {Object} data - 事件数据
* @returns {boolean} 是否应该操作区域
*/
const shouldOperateAreaInsteadOfPanelFromData = (data) => {
const { panelId, areaId } = data;
// 从AreaHandler获取区域状态
const areaState = areaHandler.getAreaState(areaId);
// 检查区域是否有一个TabPage和任意数量的Panel
if (areaState.children && areaState.children.type === 'TabPage') {
const tabChildren = Array.isArray(areaState.children.children) ? areaState.children.children : [areaState.children.children];
const panelCount = tabChildren.filter(child => child.type === 'Panel').length;
// 如果TabPage有Panel返回true
return panelCount > 0;
}
// 默认不是
return false;
};
const onCloseFloatingArea = (event) => {
const id = event.areaId;
areaActions.closeFloating(id);
const index = floatingAreas.value.findIndex(a => a.id === id);
if (index !== -1) {
floatingAreas.value.splice(index, 1);
}
};
// 保持旧的onUpdatePosition函数以兼容现有事件
const onUpdatePosition = (event) => {
const id = event.areaId;
// 处理不同事件类型的数据结构
const position = event.position || event;
const area = floatingAreas.value.find(a => a.id === id);
if (area) {
area.left = position.left;
area.top = position.top;
}
};
// 处理Area更新事件
const onAreaUpdated = (event) => {
const id = event.areaId;
const updates = event.updates;
const area = floatingAreas.value.find(a => a.id === id);
if (area) {
// 合并更新到Area对象
Object.assign(area, updates);
// 如果是最大化状态变化发送panel.maximize.sync事件
if ('maximized' in updates || 'windowState' in updates) {
// 查找该区域下的所有Panel
const areaState = areaHandler.areaStateManager.getState(id);
if (areaState && areaState.children) {
const childrenArray = Array.isArray(areaState.children) ? areaState.children : [areaState.children];
// 查找TabPage
childrenArray.forEach(child => {
if (child.type === 'TabPage' && child.children) {
const tabChildrenArray = Array.isArray(child.children) ? child.children : [child.children];
// 发送panel.maximize.sync事件给每个Panel
tabChildrenArray.forEach(tabChild => {
if (tabChild.type === 'Panel') {
eventBus.emit(EVENT_TYPES.PANEL_MAXIMIZE_SYNC, {
panelId: tabChild.id,
areaId: id,
maximized: updates.maximized !== undefined ? updates.maximized : (updates.windowState === '最大化' || updates.windowState === 'maximized')
}, { componentId: 'dock-layout' });
}
});
}
});
}
}
}
};
const onMaximize = (event) => {
const panelId = event.panelId;
const areaId = event.areaId;
// 检查是否应该操作区域而非面板
// 1. 只有一个面板时,操作区域
// 2. 有一个TabPage和多个Panel时也操作区域
const shouldOperateArea = shouldOperateAreaInsteadOfPanelFromData(event);
// 从AreaHandler获取区域状态检查是否有一个TabPage和多个Panel
const areaState = areaHandler.getAreaState(areaId);
const hasOneTabPageWithMultiplePanels = areaState?.children?.type === 'TabPage' &&
Array.isArray(areaState.children.children) &&
areaState.children.children.length > 1;
if (shouldOperateArea || hasOneTabPageWithMultiplePanels) {
areaActions.toggleMaximize(areaId);
} else {
panelActions.maximize(panelId);
}
};
const onPanelClose = (event) => {
const areaId = event.areaId;
const panelId = event.panelId;
// 1. 先找到要移除的面板
const area = floatingAreas.value.find(a => a.id === areaId);
if (area && area.children) {
const areaChildrenArray = Array.isArray(area.children) ? area.children : [area.children];
for (const child of areaChildrenArray) {
if (child.type === 'TabPage' && child.children) {
// 确保TabPage的children是数组方便统一处理
let isArray = Array.isArray(child.children);
if (!isArray) {
// 如果不是数组,将其转换为数组
child.children = [child.children];
isArray = true;
}
// 检查每个子项是否为Panel组件
for (let i = 0; i < child.children.length; i++) {
const item = child.children[i];
if (item.type === 'Panel' && item.id === panelId) {
// 2. 调用PanelActions关闭面板资源
panelActions.close(panelId, areaId);
// 3. 只移除指定的面板,不影响其他面板
child.children.splice(i, 1);
// 发送面板移除事件通知TabPage组件调整activeTabIndex
const tabPageId = child.id || `tabPage-${areaId}`;
eventBus.emit(EVENT_TYPES.TABPAGE_PANEL_REMOVED, {
tabPageId,
removedPanelId: panelId,
removedIndex: i,
remainingPanelCount: child.children.length
}, { componentId: 'dock-layout' });
// 4. 检查TabPage是否还有子元素
// 当child.children为空数组时认为TabPage没有子元素
if (child.children.length === 0) {
// 如果TabPage没有任何子元素移除TabPage
const tabPageIndex = areaChildrenArray.indexOf(child);
if (tabPageIndex !== -1) {
if (Array.isArray(area.children)) {
area.children.splice(tabPageIndex, 1);
} else {
// 如果area.children是单个对象直接设为null
area.children = null;
}
}
}
break;
}
}
break;
}
}
} else {
// 如果找不到Area或TabPage仍需关闭面板资源
panelActions.close(panelId, areaId);
}
// 5. 检查Area是否还有子元素如果没有关闭整个Area
const updatedArea = floatingAreas.value.find(a => a.id === areaId);
if (updatedArea) {
if (!updatedArea.children) {
// Area没有children直接关闭
onCloseFloatingArea({ areaId });
} else {
// 检查Area的children是否还有有效的TabPage
let hasValidTabPage = false;
const areaChildrenArray = Array.isArray(updatedArea.children) ? updatedArea.children : [updatedArea.children];
for (const child of areaChildrenArray) {
if (child.type === 'TabPage' && child.children) {
// 检查TabPage是否还有子元素无论是数组还是单个对象
const hasChildren = Array.isArray(child.children) ? child.children.length > 0 : true;
if (hasChildren) {
hasValidTabPage = true;
break;
}
}
}
if (!hasValidTabPage) {
onCloseFloatingArea({ areaId });
}
}
}
};
// 处理Area关闭请求事件
const onAreaCloseRequest = (event) => {
const { areaId } = event;
// 查找要关闭的Area
const areaIndex = floatingAreas.value.findIndex(a => a.id === areaId);
if (areaIndex === -1) {
console.error(`❌ 找不到要关闭的Area: ${areaId}`);
return;
}
const area = floatingAreas.value[areaIndex];
// 1. 处理Area下的所有子组件
if (area.children) {
const areaChildrenArray = Array.isArray(area.children) ? area.children : [area.children];
areaChildrenArray.forEach((child, childIndex) => {
if (child.type === 'TabPage') {
// 2. 处理TabPage下的所有Panel
if (child.children) {
const tabChildrenArray = Array.isArray(child.children) ? child.children : [child.children];
// 3. 遍历并关闭每个Panel
tabChildrenArray.forEach(panel => {
if (panel.type === 'Panel' && panel.id) {
try {
// 4. 关闭Panel资源异步执行
panelActions.close(panel.id, areaId);
// 注意panelActions.close内部已经会发送PANEL_CLOSED事件不需要手动发送
} catch (error) {
console.error(`❌ 关闭Panel ${panel.id}失败:`, error);
// 继续处理下一个Panel避免单个Panel关闭失败导致整个流程中断
}
}
});
// 6. 清理TabPage的children引用
child.children = [];
}
// 7. 从Area中移除TabPage
if (Array.isArray(area.children)) {
area.children.splice(childIndex, 1);
} else {
area.children = null;
}
}
});
}
// 8. 关闭Area资源此时Area的children已清空
areaActions.closeFloating(areaId);
// 9. 从floatingAreas中移除Area
floatingAreas.value.splice(areaIndex, 1);
// 10. 发送Area关闭事件
emitEvent(EVENT_TYPES.AREA_CLOSED, {
areaId: areaId
});
console.log(`✅ 成功关闭Area及其所有子组件: ${areaId}`);
};
// 简单的拖拽事件处理
const handleMainAreaDragOver = (event) => {
event.preventDefault();
globalEventActions.handleDragOver('main-area', event);
};
const handleMainAreaDragLeave = () => {
globalEventActions.handleDragLeave('main-area');
};
const handleAreaDragOver = (event) => {
event.preventDefault();
globalEventActions.handleDragOver('floating-area', event);
};
const handleAreaDragLeave = (event) => {
globalEventActions.handleDragLeave('floating-area');
};
// 处理面板拖拽开始事件
const onPanelDragStart = async (event) => {
console.log(`📈 收到面板拖拽开始事件:`, { event });
// 检查是否应该操作区域而非面板,并缓存结果
const shouldOperateArea = shouldOperateAreaInsteadOfPanelFromData(event);
// 缓存检测结果,用于后续的拖拽移动和结束事件
dragOperationCache.set(event.dragId, {
shouldOperateArea,
timestamp: Date.now()
});
if (shouldOperateArea) {
// 转换为区域拖拽事件
const areaDragStartData = {
...event,
eventType: 'area.drag.start',
dragId: event.dragId || `area_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
};
// 发送区域拖拽开始事件
eventBus.emit('area.drag.start', areaDragStartData, { componentId: 'dock-layout' });
} else {
// 发送面板拖拽状态更新事件(下降事件)
eventBus.emit(EVENT_TYPES.PANEL_DRAG_STATE_UPDATE, {
...event,
status: 'active'
}, { componentId: 'dock-layout' });
}
};
// 处理面板拖拽移动事件
const onPanelDragMove = async (event) => {
console.log(`📈 收到面板拖拽移动事件:`, { event });
// 从缓存中获取单面板检测结果,避免重复检测
const cache = dragOperationCache.get(event.dragId);
const shouldOperateArea = cache ? cache.shouldOperateArea : shouldOperateAreaInsteadOfPanelFromData(event);
if (shouldOperateArea) {
// 转换为区域拖拽移动事件
const areaDragMoveData = {
...event,
eventType: 'area.drag.move'
};
// 发送区域拖拽移动事件
eventBus.emit('area.drag.move', areaDragMoveData, { componentId: 'dock-layout' });
} else {
// 发送面板拖拽状态更新事件(下降事件)
eventBus.emit(EVENT_TYPES.PANEL_DRAG_STATE_UPDATE, {
...event,
status: 'moving'
}, { componentId: 'dock-layout' });
}
};
// 处理面板拖拽结束事件
const onPanelDragEnd = async (event) => {
console.log(`📈 收到面板拖拽结束事件:`, { event });
// 从缓存中获取单面板检测结果
const cache = dragOperationCache.get(event.dragId);
const shouldOperateArea = cache ? cache.shouldOperateArea : shouldOperateAreaInsteadOfPanelFromData(event);
if (shouldOperateArea) {
// 转换为区域拖拽结束事件
const areaDragEndData = {
...event,
eventType: 'area.drag.end'
};
// 发送区域拖拽结束事件
eventBus.emit('area.drag.end', areaDragEndData, { componentId: 'dock-layout' });
} else {
// 发送面板拖拽状态更新事件(下降事件)
eventBus.emit(EVENT_TYPES.PANEL_DRAG_STATE_UPDATE, {
...event,
status: 'ended'
}, { componentId: 'dock-layout' });
}
// 清理缓存
dragOperationCache.delete(event.dragId);
};
// 监听区域拖拽状态更新下降事件
const onAreaDragStateUpdate = (event) => {
const id = event.areaId;
const status = event.status;
const area = floatingAreas.value.find(a => a.id === id);
if (area) {
area.isDragging = status === 'active' || status === 'moving';
}
};
// 其他事件处理方法
const onDockZoneActive = (zone) => {
activeDockZone.value = zone;
};
const onPanelMaximizeSync = ({ areaId, maximized }) => {
// 使用areaActions.updateState来更新区域的最大化状态
areaActions.updateState(areaId, { maximized });
};
const onAreaMerged = (event) => {
areaActions.handleAreaMerged(event.areaId);
};
// 标签页切换事件处理
const onTabChange = async (data) => {
try {
await tabPageActions.switch(data.tabPageId, data.areaId, data.fromTabPageId);
} catch (error) {
console.error('Failed to handle tab change:', error);
}
};
const onTabClose = async (data) => {
try {
// 适配数据结构从data.id获取Panel ID
const panelId = data.id;
if (!panelId) {
console.error('Tab close event missing panelId');
return;
}
console.log(`📋 处理标签页关闭事件:`, { data, panelId });
// 1. 查找包含该Panel的Area
let targetArea = null;
let targetTabPage = null;
let panelIndex = -1;
let areaId = null;
for (const area of floatingAreas.value) {
if (area.children && area.children.type === 'TabPage') {
const tabPage = area.children;
if (tabPage.children) {
const childrenArray = Array.isArray(tabPage.children) ? tabPage.children : [tabPage.children];
const index = childrenArray.findIndex(child => child.id === panelId);
if (index !== -1) {
targetArea = area;
targetTabPage = tabPage;
panelIndex = index;
areaId = area.id;
break;
}
}
}
}
if (!targetArea || !targetTabPage || !areaId) {
console.error(`❌ 找不到包含Panel ${panelId}的Area`);
return;
}
const tabPageId = targetTabPage.id || `tabPage-${areaId}`;
console.log(`✅ 找到目标Area和TabPage:`, { areaId, tabPageId, panelId });
// 2. 调用PanelHandler的close方法回收Panel资源
panelActions.close(panelId, areaId);
console.log(`📌 调用PanelHandler回收Panel资源`);
// 3. 调用TabPageActions处理关闭请求
await tabPageActions.requestClose(tabPageId, areaId);
// 4. 移除Panel
if (Array.isArray(targetTabPage.children)) {
targetTabPage.children.splice(panelIndex, 1);
console.log(`📌 从TabPage移除Panel后剩余Panel数量: ${targetTabPage.children.length}`);
// 发送Panel移除事件通知TabPage组件调整activeTabIndex
eventBus.emit(EVENT_TYPES.TABPAGE_PANEL_REMOVED, {
tabPageId,
removedPanelId: panelId,
removedIndex: panelIndex,
remainingPanelCount: targetTabPage.children.length
}, { componentId: 'dock-layout' });
} else {
// 当TabPage只有一个Panel时我们将children设置为null
// 这样shouldShowTabs计算属性会返回false不显示标签栏
// 但TabPage仍然存在
targetTabPage.children = null;
console.log(`📌 移除了唯一的PanelTabPage不显示标签栏`);
// 发送Panel移除事件通知TabPage组件调整activeTabIndex
eventBus.emit(EVENT_TYPES.TABPAGE_PANEL_REMOVED, {
tabPageId,
removedPanelId: panelId,
removedIndex: panelIndex,
remainingPanelCount: 0
}, { componentId: 'dock-layout' });
}
// 5. 检查TabPage是否还有子元素
// 当targetTabPage.children为null或undefined时认为TabPage没有子元素
// 当targetTabPage.children为空数组时也认为没有子元素
let hasTabPageChildren = true;
if (Array.isArray(targetTabPage.children)) {
hasTabPageChildren = targetTabPage.children.length > 0;
} else if (targetTabPage.children === null || targetTabPage.children === undefined) {
hasTabPageChildren = false;
}
// 6. 只有当TabPage没有任何子元素时才移除TabPage
if (!hasTabPageChildren) {
console.log(`📌 TabPage已无Panel移除TabPage`);
// 7. 实际从Area的children中移除TabPage
if (Array.isArray(targetArea.children)) {
const tabPageIndex = targetArea.children.indexOf(targetTabPage);
if (tabPageIndex !== -1) {
targetArea.children.splice(tabPageIndex, 1);
}
} else {
// 如果Area的children是单个TabPage直接设为null
targetArea.children = null;
}
// 8. 检查Area是否还有子元素
let hasAreaChildren = false;
if (Array.isArray(targetArea.children)) {
hasAreaChildren = targetArea.children.length > 0;
} else {
hasAreaChildren = !!targetArea.children;
}
// 9. 只有当Area没有任何子元素时才关闭整个Area
if (!hasAreaChildren) {
console.log(`📌 Area已无子元素关闭整个Area`);
onCloseFloatingArea({ areaId });
return;
}
}
// 10. 更新floatingAreas中的Area配置
const areaIndex = floatingAreas.value.findIndex(a => a.id === areaId);
if (areaIndex !== -1) {
floatingAreas.value[areaIndex] = { ...targetArea };
console.log(`📌 更新floatingAreas中的Area配置`);
// 11. 触发Area更新事件确保状态同步
areaActions.updateState(areaId, { children: targetArea.children });
console.log(`📌 触发Area更新事件`);
console.log(`✅ 成功移除标签页和回收Panel: areaId=${areaId}, panelId=${panelId}`);
}
} catch (error) {
console.error('Failed to handle tab close:', error);
}
};
const onTabAdd = async (data) => {
try {
await tabPageActions.create(data.areaId, data.config);
} catch (error) {
console.error('Failed to handle tab add:', error);
}
};
// ResizeBar相关处理方法
const handleMainAreaResizeBar = (id, size) => {
areaActions.handleResize(id, size);
};
const handleMainAreaResizeBarStart = (id) => {
areaActions.handleResizeStart(id);
};
const handleMainAreaResizeBarEnd = (id) => {
areaActions.handleResizeEnd(id);
};
// 处理Area合并请求
const handleAreaMergeRequest = (data) => {
const { sourceArea, targetAreaId } = data;
// 查找目标Area
const targetArea = floatingAreas.value.find(area => area.id === targetAreaId);
if (!targetArea) return;
// 直接修改目标Area的children配置
if (!targetArea.children) {
targetArea.children = [];
}
// 处理源Area的children
if (sourceArea.children) {
const childrenArray = Array.isArray(sourceArea.children) ? sourceArea.children : [sourceArea.children];
childrenArray.forEach(child => {
if (child.type === 'TabPage') {
// 添加到目标Area的children中
if (!Array.isArray(targetArea.children)) {
targetArea.children = [targetArea.children];
}
targetArea.children.push(child);
}
});
}
// 从floatingAreas中移除源Area
floatingAreas.value = floatingAreas.value.filter(area => area.id !== sourceArea.id);
};
const getMainAreaResizeBarStyle = (resizeBar) => {
return areaActions.getResizeBarStyle(resizeBar);
};
// 计算属性
const hideEdgeIndicators = computed(() => {
return !hasAreasInMainContent.value;
});
// 设置事件总线监听器
const setupEventListeners = () => {
// 创建一个数组来保存所有的取消订阅函数
const unsubscribeFunctions = [];
// 上升事件处理 - 统一由handleRisingEvent处理所有上升事件
const risingEvents = [
// 面板拖拽事件
EVENT_TYPES.PANEL_DRAG_START,
EVENT_TYPES.PANEL_DRAG_MOVE,
EVENT_TYPES.PANEL_DRAG_END,
EVENT_TYPES.PANEL_DRAG_CANCEL,
// 面板调整大小事件
EVENT_TYPES.PANEL_RESIZE_START,
EVENT_TYPES.PANEL_RESIZE_MOVE,
EVENT_TYPES.PANEL_RESIZE_END,
// 区域拖拽事件
EVENT_TYPES.AREA_DRAG_START,
EVENT_TYPES.AREA_DRAG_MOVE,
EVENT_TYPES.AREA_DRAG_END,
EVENT_TYPES.AREA_DRAG_CANCEL,
// 区域调整大小事件
EVENT_TYPES.AREA_RESIZE_START,
EVENT_TYPES.AREA_RESIZE_MOVE,
EVENT_TYPES.AREA_RESIZE_END
];
// 订阅所有上升事件统一由handleRisingEvent处理
risingEvents.forEach(eventType => {
unsubscribeFunctions.push(eventBus.on(eventType, handleRisingEvent, { componentId: 'dock-layout' }));
});
// Area相关事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_OVER, handleAreaDragOver, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_LEAVE, handleAreaDragLeave, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_MERGE_REQUEST, handleAreaMergeRequest, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_UPDATED, onAreaUpdated, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_CLOSE_REQUEST, onAreaCloseRequest, { componentId: 'dock-layout' }));
// Tab相关事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_CHANGE, onTabChange, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_CLOSE, onTabClose, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_ADD, onTabAdd, { componentId: 'dock-layout' }));
// Panel相关事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_TOGGLE_COLLAPSE, () => emit('toggleCollapse'), { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_MAXIMIZE, onMaximize, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_CLOSE_REQUEST, onPanelClose, { componentId: 'dock-layout' }));
// 移除对PANEL_CLOSE事件的监听避免重复执行关闭逻辑
// unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_CLOSE, onPanelClose, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_TOGGLE_TOOLBAR, () => emit('toggleToolbar'), { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_MAXIMIZE_SYNC, onPanelMaximizeSync, { componentId: 'dock-layout' }));
// 单面板检测事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_CHECK_SINGLE_PANEL, onCheckSinglePanel, { componentId: 'dock-layout' }));
// Area位置更新事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_POSITION_UPDATE, onAreaPositionUpdate, { componentId: 'dock-layout' }));
// Area z-index更新事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.Z_INDEX_UPDATE, onZIndexUpdate, { componentId: 'dock-layout' }));
// Resize相关事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.RESIZE_START, () => emit('dragStart'), { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.RESIZE_MOVE, () => emit('dragMove'), { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.RESIZE_END, () => emit('dragEnd'), { componentId: 'dock-layout' }));
// Window相关事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.WINDOW_STATE_CHANGE, (event) => {
const areaId = event.areaId;
const state = event.state;
const position = event.position;
const area = floatingAreas.value.find(a => a.id === areaId);
if (area) {
area.windowState = state;
if (position) {
area.left = position.left;
area.top = position.top;
area.width = position.width;
area.height = position.height;
}
}
}, { componentId: 'dock-layout' }));
// 自定义事件
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_MERGED, onAreaMerged, { componentId: 'dock-layout' }));
unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.DOCK_ZONE_ACTIVE, (event) => onDockZoneActive(event.zoneId), { componentId: 'dock-layout' }));
return unsubscribeFunctions;
};
// 保存取消订阅函数数组
const unsubscribeFunctions = ref([]);
// 清理函数
const cleanup = () => {
// 清理事件监听器和其他资源
console.log('🧹 开始清理DockLayout资源');
// 清理事件监听器
console.log('🔇 开始清理事件监听器');
unsubscribeFunctions.value.forEach(unsubscribe => unsubscribe());
unsubscribeFunctions.value = [];
// 清理浮动区域
floatingAreas.value = [];
// 清理隐藏区域
hiddenAreas.value = [];
// 清理主区域ResizeBar
mainAreaResizeBars.value = [];
// 清理停靠指示器状态
showDockIndicator.value = false;
currentMousePosition.value = { x: 0, y: 0 };
targetAreaRect.value = { left: 0, top: 0, width: 0, height: 0 };
activeDockZone.value = null;
console.log('✅ DockLayout资源清理完成');
};
// 轻量级隐藏区域管理
const hiddenAreas = ref([]);
// 隐藏Area管理方法
// 将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()
})
}
}
// 移除重复的详细实现函数 - 使用轻量级代理
/**
* 处理并排停靠逻辑
* 当主区域内已有Area时压缩目标Area并创建并排布局
* @param {Object} sourceArea - 源Area对象
* @param {string} dockZone - 停靠方向
* @returns {Object} 处理结果 {success: boolean, message: string}
*/
// 轻量级并排停靠代理
const handleSideBySideDocking = () => '轻量级并排停靠代理'
// 轻量级ResizeBar添加代理
const addResizeBarForSideBySideLayout = () => '轻量级ResizeBar代理'
// 轻量级ResizeBar事件代理
const handleResizeBarResize = () => '轻量级ResizeBar调整代理'
// 轻量级ResizeBar尺寸调整代理
const handleResizeBarResizeStart = () => '轻量级ResizeBar调整开始代理'
const handleResizeBarResizeEnd = () => '轻量级ResizeBar调整结束代理'
// 轻量级尺寸调整代理
const handleHorizontalResize = () => '轻量级水平调整代理'
const handleVerticalResize = () => '轻量级垂直调整代理'
// 轻量级Area查找代理
const findFirstMainArea = () => '轻量级主区域查找代理'
const getOrCreateTargetArea = () => '轻量级目标区域获取代理'
// 轻量级并排布局代理
const createSideBySideLayout = () => '轻量级并排布局代理'
const compressTargetArea = () => '轻量级压缩目标区域代理'
const adjustSourceAreaForLayout = () => '轻量级布局调整代理'
// 添加浮动面板
const addFloatingPanel = (panel) => {
// 确保panel参数存在否则使用默认面板对象
const safePanel = panel || {
id: `panel-${Date.now()}`,
title: '新建面板',
content: {
color: '#435d9c',
title: '默认面板内容',
type: 'default',
timestamp: new Date().toLocaleString(),
data: [
{ id: 1, label: '示例数据1', value: '123' },
{ id: 2, label: '示例数据2', value: '456' },
{ id: 3, label: '示例数据3', value: '789' }
]
}
};
// 生成唯一的areaId
const areaId = `area-${Date.now()}`;
const newArea = {
id: areaId,
type: 'floating', // 添加浮动类型标识
left: 100 + Math.random() * 200,
top: 100 + Math.random() * 200,
width: 300,
height: 200,
zIndex: zIndexManager.getFloatingAreaZIndex(areaId),
// 使用children结构以兼容Render组件的渲染逻辑
children: {
type: 'TabPage',
id: `tabPage-${areaId}`,
tabPosition: ['top', 'right', 'bottom', 'left'][Math.floor(Math.random() * 4)],
children: [
{
...safePanel,
id: `panel-${Date.now()}-1`,
title: panel?.title || '面板1',
type: 'Panel',
content: {
...safePanel.content,
title: '面板1内容',
data: [
{ id: 1, label: '面板1数据1', value: '数据1' },
{ id: 2, label: '面板1数据2', value: '数据2' }
]
}
},
{
...safePanel,
id: `panel-${Date.now()}-2`,
title: '面板2',
type: 'Panel',
content: {
...safePanel.content,
title: '面板2内容',
color: '#68217A',
data: [
{ id: 3, label: '面板2数据1', value: '数据3' },
{ id: 4, label: '面板2数据2', value: '数据4' }
]
}
}
]
}
}
// 添加到DockLayout的floatingAreas数组中用于渲染
floatingAreas.value.push(newArea)
// 同时注册到AreaHandler的状态管理中确保事件处理能正常进行
areaActions.createFloating(newArea)
// 初始化面板状态
const tabPage = newArea.children
if (tabPage.children) {
const childrenArray = Array.isArray(tabPage.children) ? tabPage.children : [tabPage.children]
for (const panel of childrenArray) {
if (panel.type === 'Panel' && panel.id) {
panelActions.initializePanelState(panel.id, areaId)
}
}
}
return newArea.id
}
// 查找或创建主区域TabPage
const findOrCreateMainAreaTabPage = () => {
// 返回主区域的tabPage信息
return {
id: 'main-area-tabpage',
title: '主区域',
items: []
};
}
// 处理上升事件(用户触发的原始事件)- 从GlobalEventManager.js迁移
const handleRisingEvent = async (data, event) => {
// 从事件对象中获取事件类型
const eventType = event.type;
try {
// 总是输出日志,便于调试
console.log(`📈 收到上升事件: ${eventType}`, { data });
// 根据事件类型分发到不同的处理方法
switch (eventType) {
// 面板拖拽事件
case EVENT_TYPES.PANEL_DRAG_START:
await handlePanelDragStart(data);
break;
case EVENT_TYPES.PANEL_DRAG_MOVE:
await handlePanelDragMove(data);
break;
case EVENT_TYPES.PANEL_DRAG_END:
await handlePanelDragEnd(data);
break;
case EVENT_TYPES.PANEL_DRAG_CANCEL:
await handlePanelDragCancel(data);
break;
// 面板resize事件
case EVENT_TYPES.PANEL_RESIZE_START:
await handlePanelResizeStart(data);
break;
case EVENT_TYPES.PANEL_RESIZE_MOVE:
await handlePanelResizeMove(data);
break;
case EVENT_TYPES.PANEL_RESIZE_END:
await handlePanelResizeEnd(data);
break;
// 区域拖拽事件
case EVENT_TYPES.AREA_DRAG_START:
await handleAreaDragStart(data);
break;
case EVENT_TYPES.AREA_DRAG_MOVE:
await handleAreaDragMove(data);
break;
case EVENT_TYPES.AREA_DRAG_END:
await handleAreaDragEnd(data);
break;
case EVENT_TYPES.AREA_DRAG_CANCEL:
await handleAreaDragCancel(data);
break;
// 区域resize事件
case EVENT_TYPES.AREA_RESIZE_START:
await handleAreaResizeStart(data);
break;
case EVENT_TYPES.AREA_RESIZE_MOVE:
await handleAreaResizeMove(data);
break;
case EVENT_TYPES.AREA_RESIZE_END:
await handleAreaResizeEnd(data);
break;
// 标签页拖拽事件
case EVENT_TYPES.TABPAGE_DRAG_START:
await handleTabPageDragStart(data);
break;
case EVENT_TYPES.TABPAGE_DRAG_MOVE:
await handleTabPageDragMove(data);
break;
case EVENT_TYPES.TABPAGE_DRAG_END:
await handleTabPageDragEnd(data);
break;
case EVENT_TYPES.TABPAGE_DRAG_CANCEL:
await handleTabPageDragCancel(data);
break;
// 默认处理
default:
console.log(`🌐 全局事件: ${eventType}`, data);
}
} catch (error) {
console.error(`❌ 上升事件处理错误 (${eventType}):`, error);
}
};
// 面板拖拽开始处理
const handlePanelDragStart = async (data) => {
if (debugMode) {
console.log('👋 处理面板拖拽开始:', data);
}
// 检查是否应该操作区域而非面板,并缓存结果
const shouldOperateArea = shouldOperateAreaInsteadOfPanelFromData(data);
// 缓存检测结果,用于后续的拖拽移动和结束事件
dragOperationCache.set(data.dragId, {
shouldOperateArea,
timestamp: Date.now()
});
if (shouldOperateArea) {
// 转换为区域拖拽事件
const areaDragStartData = {
...data,
eventType: 'area.drag.start',
dragId: data.dragId || `area_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`
};
// 发送区域拖拽开始事件
eventBus.emit('area.drag.start', areaDragStartData);
} else {
// 发送面板拖拽状态更新事件(下降事件)
eventBus.emit('panel.drag.state.update', {
...data,
status: 'active'
});
}
};
// 面板拖拽移动处理
const handlePanelDragMove = async (data) => {
if (debugMode) {
console.log('✋ 处理面板拖拽移动:', data);
}
// 从缓存中获取单面板检测结果,避免重复检测
const cache = dragOperationCache.get(data.dragId);
const shouldOperateArea = cache ? cache.shouldOperateArea : shouldOperateAreaInsteadOfPanelFromData(data);
if (shouldOperateArea) {
// 转换为区域拖拽移动事件
const areaDragMoveData = {
...data,
eventType: 'area.drag.move'
};
// 发送区域拖拽移动事件
eventBus.emit('area.drag.move', areaDragMoveData);
} else {
// 发送面板拖拽状态更新事件(下降事件)
eventBus.emit('panel.drag.state.update', {
...data,
status: 'moving'
});
}
};
// 面板拖拽结束处理
const handlePanelDragEnd = async (data) => {
if (debugMode) {
console.log('✋ 处理面板拖拽结束:', data);
}
// 从缓存中获取单面板检测结果
const cache = dragOperationCache.get(data.dragId);
const shouldOperateArea = cache ? cache.shouldOperateArea : shouldOperateAreaInsteadOfPanelFromData(data);
if (shouldOperateArea) {
// 转换为区域拖拽结束事件
const areaDragEndData = {
...data,
eventType: 'area.drag.end'
};
// 发送区域拖拽结束事件
eventBus.emit('area.drag.end', areaDragEndData);
} else {
// 发送面板拖拽状态更新事件(下降事件)
eventBus.emit('panel.drag.state.update', {
...data,
status: 'ended'
});
}
// 清理缓存
dragOperationCache.delete(data.dragId);
};
// 面板拖拽取消处理
const handlePanelDragCancel = async (data) => {
if (debugMode) {
console.log('✋ 处理面板拖拽取消:', data);
}
// 发送面板拖拽状态更新事件(下降事件)
eventBus.emit('panel.drag.state.update', {
...data,
status: 'cancelled'
});
// 清理缓存
dragOperationCache.delete(data.dragId);
};
// 区域拖拽开始处理
const handleAreaDragStart = async (data) => {
if (debugMode) {
console.log('👋 处理区域拖拽开始:', data);
}
// 发送区域拖拽状态更新事件(下降事件)
eventBus.emit('area.drag.state.update', {
...data,
status: 'active'
});
};
// 区域拖拽移动处理
const handleAreaDragMove = async (data) => {
if (debugMode) {
console.log('✋ 处理区域拖拽移动:', data);
}
// 发送区域拖拽状态更新事件(下降事件)
eventBus.emit('area.drag.state.update', {
...data,
status: 'moving'
});
// 发送区域位置更新事件(下降事件)
eventBus.emit('area.position.update', {
...data,
position: data.position
});
};
// 区域拖拽结束处理
const handleAreaDragEnd = async (data) => {
if (debugMode) {
console.log('✋ 处理区域拖拽结束:', data);
}
// 发送区域拖拽状态更新事件(下降事件)
eventBus.emit('area.drag.state.update', {
...data,
status: 'ended'
});
};
// 区域拖拽取消处理
const handleAreaDragCancel = async (data) => {
if (debugMode) {
console.log('✋ 处理区域拖拽取消:', data);
}
// 发送区域拖拽状态更新事件(下降事件)
eventBus.emit('area.drag.state.update', {
...data,
status: 'cancelled'
});
};
// 面板resize处理
const handlePanelResizeStart = async (data) => {
if (debugMode) {
console.log('👋 处理面板resize开始:', data);
}
eventBus.emit(EVENT_TYPES.AREA_RESIZE_START, {
...data,
resizeType: 'panel'
});
};
const handlePanelResizeMove = async (data) => {
if (debugMode) {
console.log('✋ 处理面板resize移动:', data);
}
eventBus.emit(EVENT_TYPES.AREA_RESIZE, {
...data,
resizeType: 'panel'
});
eventBus.emit(EVENT_TYPES.AREA_RESIZE_MOVE, {
...data,
resizeType: 'panel'
});
};
const handlePanelResizeEnd = async (data) => {
if (debugMode) {
console.log('✋ 处理面板resize结束:', data);
}
eventBus.emit(EVENT_TYPES.AREA_RESIZE_END, {
...data,
resizeType: 'panel'
});
};
// 区域resize处理
const handleAreaResizeStart = async (data) => {
if (debugMode) {
console.log('👋 处理区域resize开始:', data);
}
eventBus.emit(EVENT_TYPES.AREA_RESIZE_START, {
...data,
resizeType: 'area'
});
};
const handleAreaResizeMove = async (data) => {
if (debugMode) {
console.log('✋ 处理区域resize移动:', data);
}
eventBus.emit(EVENT_TYPES.AREA_RESIZE, {
...data,
resizeType: 'area'
});
eventBus.emit(EVENT_TYPES.AREA_RESIZE_MOVE, {
...data,
resizeType: 'area'
});
};
const handleAreaResizeEnd = async (data) => {
if (debugMode) {
console.log('✋ 处理区域resize结束:', data);
}
eventBus.emit(EVENT_TYPES.AREA_RESIZE_END, {
...data,
resizeType: 'area'
});
};
// 标签页拖拽处理
const handleTabPageDragStart = async (data) => {
if (debugMode) {
console.log('👋 处理标签页拖拽开始:', data);
}
// 标签页拖拽逻辑
eventBus.emit('tabpage.drag.state.update', {
...data,
status: 'active'
});
};
const handleTabPageDragMove = async (data) => {
if (debugMode) {
console.log('✋ 处理标签页拖拽移动:', data);
}
eventBus.emit('tabpage.drag.state.update', {
...data,
status: 'moving'
});
};
const handleTabPageDragEnd = async (data) => {
if (debugMode) {
console.log('✋ 处理标签页拖拽结束:', data);
}
eventBus.emit('tabpage.drag.state.update', {
...data,
status: 'ended'
});
};
const handleTabPageDragCancel = async (data) => {
if (debugMode) {
console.log('✋ 处理标签页拖拽取消:', data);
}
eventBus.emit('tabpage.drag.state.update', {
...data,
status: 'cancelled'
});
};
// 单面板检测处理函数
const onCheckSinglePanel = (event) => {
// 使用新的shouldOperateAreaInsteadOfPanelFromData函数检查是否为单面板
const isSinglePanel = shouldOperateAreaInsteadOfPanelFromData(event);
// 发送检测结果
eventBus.emit(EVENT_TYPES.PANEL_SINGLE_PANEL_RESULT, {
areaId: event.areaId,
panelId: event.panelId,
isSinglePanel
}, { componentId: 'dock-layout' });
};
// Area resize事件处理函数
const onAreaResizeStart = (event) => {
const { areaId, direction, position } = event;
// AreaHandler已经监听了AREA_RESIZE_START事件会自动处理
// 只需要更新本地floatingAreas中的状态
};
const onAreaResizeMove = (event) => {
const { areaId, direction, size, position } = event;
const area = floatingAreas.value.find(a => a.id === areaId);
if (area) {
if (direction.includes('right') || direction.includes('left')) {
area.width = size.width;
if (direction.includes('left')) {
area.left = position.left;
}
}
if (direction.includes('bottom') || direction.includes('top')) {
area.height = size.height;
if (direction.includes('top')) {
area.top = position.top;
}
}
}
};
// 处理Area位置更新事件
const onAreaPositionUpdate = (event) => {
const { areaId, left, top, width, height } = event;
const area = floatingAreas.value.find(a => a.id === areaId);
if (area) {
area.left = left;
area.top = top;
if (width !== undefined) {
area.width = width;
}
if (height !== undefined) {
area.height = height;
}
}
};
// 处理Area z-index更新事件
const onZIndexUpdate = (event) => {
const { areaId, zIndex } = event;
const area = floatingAreas.value.find(a => a.id === areaId);
if (area) {
area.zIndex = zIndex;
}
};
const onAreaResizeEnd = (event) => {
const { areaId, direction, finalPosition } = event;
// AreaHandler已经监听了AREA_RESIZE_END事件会自动处理
// 只需要更新本地floatingAreas中的状态
};
/**
* 初始化主区域的所有面板状态
*/
const initializeMainAreaPanels = () => {
const mainTabPage = mainAreaConfig.value.children;
if (mainTabPage && mainTabPage.type === 'TabPage' && mainTabPage.children) {
const childrenArray = Array.isArray(mainTabPage.children)
? mainTabPage.children
: [mainTabPage.children];
console.log(`[DockLayout] 初始化主区域面板,共 ${childrenArray.length}`);
for (const panel of childrenArray) {
if (panel.type === 'Panel' && panel.id) {
panelActions.initializePanelState(panel.id, mainAreaConfig.value.id);
}
}
}
}
// 轻量级生命周期处理
onMounted(() => {
// 初始化轻量级状态
console.log('DockLayout component mounted');
unsubscribeFunctions.value = setupEventListeners();
// 初始化主区域的面板状态
initializeMainAreaPanels();
})
// 组件卸载时清理资源
onUnmounted(() => {
// 清理事件监听器和其他资源
console.log('DockLayout component unmounted');
cleanup();
// 逐个移除事件监听器
unsubscribeFunctions.value.forEach(unsubscribe => unsubscribe());
unsubscribeFunctions.value = [];
})
// 暴露轻量级接口给父组件
defineExpose({
// 基础数据
floatingAreas,
hiddenAreas,
// 核心方法
addFloatingPanel,
findOrCreateMainAreaTabPage,
// 轻量级代理方法(功能保留但简化)
handleDockingEnding: () => '轻量级停靠处理代理',
handleEdgeDocking: () => '轻量级边缘停靠代理',
handleSideBySideDocking: () => '轻量级并排停靠代理',
// ResizeBar轻量级代理
addResizeBarForSideBySideLayout: () => '轻量级ResizeBar代理',
handleResizeBarResize: () => '轻量级ResizeBar调整代理',
// 隐藏列表轻量级代理
getHiddenAreas: () => '轻量级隐藏列表代理',
restoreAreaFromHidden: () => '轻量级恢复代理',
removeFromHiddenList: () => '轻量级移除代理',
clearHiddenList: () => '轻量级清空代理',
})
</script>
<style scoped>
.dock-layout {
position: relative;
width: 100%;
height: 100%;
overflow: visible;
}
/* 浮动区域样式已直接应用到Area组件 */
/* 添加浮动区域按钮样式 */
.add-floating-btn {
font-size: 14px;
cursor: pointer;
user-select: none;
}
.add-floating-btn:active {
transform: scale(0.98);
}
</style>