diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue index 371bb3a..816ef8c 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/Area.vue @@ -180,6 +180,7 @@ const areaRef = ref(null) const isDragging = ref(false) const dragStartPos = ref({ x: 0, y: 0 }) const areaStartPos = ref({ x: 0, y: 0 }) +const currentDragId = ref(null) // 调整大小相关状态 const isResizing = ref(false) @@ -278,18 +279,24 @@ const onPanelMaximize = (panelId) => { } else { // // console.log('🔸 非单Panel模式,转发到父组件') // 如果不是单Panel,转发给父组件处理 - emitEvent(EVENT_TYPES.PANEL_MAXIMIZE, { panelId, areaId: props.id }) + emitEvent(EVENT_TYPES.PANEL_MAXIMIZE, { panelId, areaId: props.id }, { + source: { component: 'Area', areaId: props.id } + }) } } // 处理拖拽悬停事件 const handleDragOver = (event) => { - emitEvent(EVENT_TYPES.AREA_DRAG_OVER, { event, areaId: props.id }) + emitEvent(EVENT_TYPES.AREA_DRAG_OVER, { event, areaId: props.id }, { + source: { component: 'Area', areaId: props.id } + }) } // 处理拖拽离开事件 const handleDragLeave = (event) => { - emitEvent(EVENT_TYPES.AREA_DRAG_LEAVE, { event, areaId: props.id }) + emitEvent(EVENT_TYPES.AREA_DRAG_LEAVE, { event, areaId: props.id }, { + source: { component: 'Area', areaId: props.id } + }) } // 拖拽开始 @@ -307,8 +314,12 @@ const onDragStart = (e) => { y: originalPosition.value.top || 0 } - // 使用事件总线通知拖拽开始 + // 生成统一的 dragId + currentDragId.value = `area_${props.id}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}` + + // 使用事件总线通知拖拽开始,包含 dragId 和标准化数据格式 emitEvent(EVENT_TYPES.AREA_DRAG_START, { + dragId: currentDragId.value, areaId: props.id, event: e, element: areaRef.value, @@ -316,7 +327,10 @@ const onDragStart = (e) => { clientX: e.clientX, clientY: e.clientY, startLeft: originalPosition.value.left || 0, - startTop: originalPosition.value.top || 0 + startTop: originalPosition.value.top || 0, + timestamp: Date.now() + }, { + source: { component: 'Area', areaId: props.id, dragId: currentDragId.value } }) // 使用全局事件管理器添加事件监听 @@ -329,7 +343,7 @@ const onDragStart = (e) => { // 拖拽移动 const onDragMove = (e) => { - if (!isDragging.value) return + if (!isDragging.value || !currentDragId.value) return // 计算移动距离 const deltaX = e.clientX - dragStartPos.value.x @@ -354,8 +368,9 @@ const onDragMove = (e) => { originalPosition.value.left = newLeft originalPosition.value.top = newTop - // 使用事件总线通知拖拽移动 + // 使用事件总线通知拖拽移动,包含 dragId emitEvent(EVENT_TYPES.AREA_DRAG_MOVE, { + dragId: currentDragId.value, areaId: props.id, event: e, element: areaRef.value, @@ -363,27 +378,44 @@ const onDragMove = (e) => { clientX: e.clientX, clientY: e.clientY, left: newLeft, - top: newTop + top: newTop, + timestamp: Date.now() + }, { + source: { component: 'Area', areaId: props.id, dragId: currentDragId.value } }) - // 使用事件总线通知位置变化 - emitEvent(EVENT_TYPES.AREA_POSITION_UPDATE, { areaId: props.id, left: newLeft, top: newTop }) + // 使用事件总线通知位置变化,包含 dragId + emitEvent(EVENT_TYPES.AREA_POSITION_UPDATE, { + dragId: currentDragId.value, + areaId: props.id, + left: newLeft, + top: newTop + }, { + source: { component: 'Area', areaId: props.id, dragId: currentDragId.value } + }) } // 拖拽结束 const onDragEnd = () => { - // 使用事件总线通知拖拽结束 + if (!currentDragId.value) return + + // 使用事件总线通知拖拽结束,包含 dragId 和标准化数据格式 emitEvent(EVENT_TYPES.AREA_DRAG_END, { + dragId: currentDragId.value, areaId: props.id, finalPosition: { x: originalPosition.value.left, y: originalPosition.value.top }, left: originalPosition.value.left, - top: originalPosition.value.top + top: originalPosition.value.top, + timestamp: Date.now() + }, { + source: { component: 'Area', areaId: props.id, dragId: currentDragId.value } }) isDragging.value = false + currentDragId.value = null // 使用全局事件管理器移除事件监听 globalEventListenerManager.removeGlobalListener('mousemove', onDragMove) globalEventListenerManager.removeGlobalListener('mouseup', onDragEnd) @@ -507,6 +539,8 @@ const onResizeStart = (direction, e) => { areaId: props.id, left: newLeft, top: newTop + }, { + source: { component: 'Area', areaId: props.id } }) // 防止文本选择 @@ -542,6 +576,8 @@ const onToggleMaximize = () => { areaId: props.id, left: originalPosition.value.left, top: originalPosition.value.top + }, { + source: { component: 'Area', areaId: props.id } }) } @@ -549,11 +585,15 @@ const onToggleMaximize = () => { emitEvent(EVENT_TYPES.WINDOW_STATE_CHANGE, { areaId: props.id, state: next + }, { + source: { component: 'Area', areaId: props.id } }) } const onClose = () => emitEvent(EVENT_TYPES.PANEL_CLOSE_REQUEST, { areaId: props.id +}, { + source: { component: 'Area', areaId: props.id } }) // 组件挂载后获取父容器引用并初始化位置 @@ -589,6 +629,8 @@ onMounted(() => { areaId: props.id, left: originalPosition.value.left, top: originalPosition.value.top + }, { + source: { component: 'Area', areaId: props.id } }) } }) @@ -675,6 +717,8 @@ const mergeAreaContent = (sourceArea) => { targetAreaHasContent: false, // 目标Area为空 operation: 'add-children', addedTabPages: receivedContent.value + }, { + source: { component: 'Area', areaId: props.id } }) return true @@ -734,6 +778,8 @@ const mergeAreaContent = (sourceArea) => { targetAreaHasContent: true, // 目标Area已有内容 operation: 'merge-tabpages', sourceTabPages: tabPagesData + }, { + source: { component: 'Area', areaId: props.id } }) // 更新完成 // // console.log(`[Area] 合并完成,现有TabPage共有 ${existingTabPage.tabPage.panels.length} 个Panel`) diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue index f5791b2..5d536e1 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/DockIndicator.vue @@ -716,7 +716,9 @@ const enhancedPreviewAreaStyle = computed(() => { // 监听activeDockZone变化,触发事件 watch(activeDockZone, (newZone) => { - emitEvent('dock-zone-active', { zoneId: newZone }) + emitEvent('dock-zone-active', { zoneId: newZone }, { + source: { component: 'DockIndicator' } + }) }) diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue index 1a16857..bc7aef6 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/DockLayout.vue @@ -289,48 +289,48 @@ const setupEventListeners = () => { const unsubscribeFunctions = []; // Area相关事件 - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_START, onAreaDragStart)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_MOVE, onAreaDragMove)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_END, onAreaDragEnd)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_POSITION_UPDATE, onUpdatePosition)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_OVER, handleAreaDragOver)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_LEAVE, handleAreaDragLeave)); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_START, onAreaDragStart, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_MOVE, onAreaDragMove, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_DRAG_END, onAreaDragEnd, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.AREA_POSITION_UPDATE, onUpdatePosition, { componentId: 'dock-layout' })); + 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' })); // Tab相关事件 - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_CHANGE, onTabChange)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_CLOSE, onTabClose)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_ADD, onTabAdd)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_DRAG_START, onTabDragStart)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_DRAG_MOVE, onTabDragMove)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_DRAG_END, onTabDragEnd)); + 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' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_DRAG_START, onTabDragStart, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_DRAG_MOVE, onTabDragMove, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.TAB_DRAG_END, onTabDragEnd, { componentId: 'dock-layout' })); // Panel相关事件 - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_TOGGLE_COLLAPSE, () => emit('toggleCollapse'))); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_MAXIMIZE, onMaximize)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_CLOSE_REQUEST, onCloseFloatingArea)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_CLOSE, onClosePanel)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_TOGGLE_TOOLBAR, () => emit('toggleToolbar'))); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_START, onPanelDragStart)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_MOVE, onPanelDragMove)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_END, onPanelDragEnd)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_START_FROM_TABPAGE, onPanelDragStartFromTabPage)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_MOVE_FROM_TABPAGE, onPanelDragMoveFromTabPage)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_END_FROM_TABPAGE, onPanelDragEndFromTabPage)); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_MAXIMIZE_SYNC, onPanelMaximizeSync)); + 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, onCloseFloatingArea, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_CLOSE, onClosePanel, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_TOGGLE_TOOLBAR, () => emit('toggleToolbar'), { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_START, onPanelDragStart, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_MOVE, onPanelDragMove, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_END, onPanelDragEnd, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_START_FROM_TABPAGE, onPanelDragStartFromTabPage, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_MOVE_FROM_TABPAGE, onPanelDragMoveFromTabPage, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_DRAG_END_FROM_TABPAGE, onPanelDragEndFromTabPage, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.PANEL_MAXIMIZE_SYNC, onPanelMaximizeSync, { componentId: 'dock-layout' })); // Resize相关事件 - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.RESIZE_START, () => emit('dragStart'))); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.RESIZE_MOVE, () => emit('dragMove'))); - unsubscribeFunctions.push(eventBus.on(EVENT_TYPES.RESIZE_END, () => emit('dragEnd'))); + 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) => { // 处理窗口状态变化 - })); + }, { componentId: 'dock-layout' })); // 自定义事件 - unsubscribeFunctions.push(eventBus.on('area-merged', onAreaMerged)); - unsubscribeFunctions.push(eventBus.on('dock-zone-active', (event) => onDockZoneActive(event.zoneId))); + unsubscribeFunctions.push(eventBus.on('area-merged', onAreaMerged, { componentId: 'dock-layout' })); + unsubscribeFunctions.push(eventBus.on('dock-zone-active', (event) => onDockZoneActive(event.zoneId), { componentId: 'dock-layout' })); return unsubscribeFunctions; }; diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue index 7c98a59..fe534b7 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/Panel.vue @@ -202,6 +202,8 @@ const onToggleCollapse = () => { panelId: props.id, areaId: getCurrentAreaId(), currentState: props.collapsed + }, { + source: { component: 'Panel', panelId: props.id } }) }; @@ -211,6 +213,8 @@ const onMaximize = () => { panelId: props.id, areaId: getCurrentAreaId(), currentState: props.maximized + }, { + source: { component: 'Panel', panelId: props.id } }) }; @@ -222,6 +226,8 @@ const onClose = () => { areaId: getCurrentAreaId(), panelTitle: props.title, requestTime: Date.now() + }, { + source: { component: 'Panel', panelId: props.id } }) }; @@ -231,11 +237,14 @@ const onToggleToolbar = () => { panelId: props.id, areaId: getCurrentAreaId(), currentState: props.toolbarExpanded + }, { + source: { component: 'Panel', panelId: props.id } }) }; // 拖拽相关状态 let isDragging = false +let currentDragId = null // 全局内存泄漏保护机制 if (!window.__panelMemoryProtection) { @@ -378,19 +387,25 @@ const onDragStart = (e) => { if (!e.target.closest('.title-bar-buttons') && !e.target.closest('button')) { // 1. 立即重置之前的拖拽状态 isDragging = false + currentDragId = null cleanupDragEventListeners() isDragging = true - console.log(`[Panel:${props.id}] 开始拖拽`) - // 2. 使用事件总线触发拖拽开始事件 + // 生成统一的 dragId + currentDragId = `panel_${props.id}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}` + + console.log(`[Panel:${props.id}] 开始拖拽, dragId: ${currentDragId}`) + + // 2. 使用事件总线触发拖拽开始事件,包含统一的 dragId 和标准化数据格式 emitEvent(EVENT_TYPES.PANEL_DRAG_START, { + dragId: currentDragId, panelId: props.id, areaId: getCurrentAreaId(), position: { x: e.clientX, y: e.clientY }, timestamp: Date.now() }, { - source: { component: 'Panel', panelId: props.id } + source: { component: 'Panel', panelId: props.id, dragId: currentDragId } }) // 3. 防止文本选择和默认行为 @@ -404,40 +419,46 @@ const onDragStart = (e) => { // 拖拽移动 const onDragMove = (e) => { - if (isDragging) { + if (isDragging && currentDragId) { // 防止文本选择和默认行为 e.preventDefault(); e.stopPropagation(); - // 使用事件总线触发拖拽移动事件 + // 使用事件总线触发拖拽移动事件,包含 dragId emitEvent(EVENT_TYPES.PANEL_DRAG_MOVE, { + dragId: currentDragId, panelId: props.id, areaId: getCurrentAreaId(), position: { x: e.clientX, y: e.clientY }, timestamp: Date.now() }, { - source: { component: 'Panel', panelId: props.id } + source: { component: 'Panel', panelId: props.id, dragId: currentDragId } }) } }; // 拖拽结束 const onDragEnd = () => { - if (isDragging) { + if (isDragging && currentDragId) { isDragging = false; - console.log(`[Panel:${props.id}] 结束拖拽`) - // 使用事件总线触发拖拽结束事件 + console.log(`[Panel:${props.id}] 结束拖拽, dragId: ${currentDragId}`) + + // 使用事件总线触发拖拽结束事件,包含 dragId emitEvent(EVENT_TYPES.PANEL_DRAG_END, { + dragId: currentDragId, panelId: props.id, areaId: getCurrentAreaId(), timestamp: Date.now() }, { - source: { component: 'Panel', panelId: props.id } + source: { component: 'Panel', panelId: props.id, dragId: currentDragId } }) // 使用统一的清理方法,确保一致性和完整性 cleanupDragEventListeners() + + // 重置 dragId + currentDragId = null } }; @@ -449,10 +470,9 @@ const setupEventListeners = () => { // 监听面板最大化同步事件 const unsubscribeMaximizeSync = onEvent(EVENT_TYPES.PANEL_MAXIMIZE_SYNC, (data) => { if (data.panelId === props.id) { - // 这里可以添加最大化状态同步的逻辑 console.log(`[Panel:${props.id}] 收到最大化同步事件`) } - }) + }, { componentId: `panel-${props.id}` }) const subscriptionId = `maximizeSync_${props.id}_${Date.now()}` subscriptions.add(unsubscribeMaximizeSync) diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/ResizeBar.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/ResizeBar.vue index 8b44ee2..4362de3 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/ResizeBar.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/ResizeBar.vue @@ -123,7 +123,9 @@ const handleMouseMove = (event: MouseEvent) => { // 结束调整大小 const endResize = () => { isResizing.value = false - emitEvent(EVENT_TYPES.RESIZE_END, { direction: props.direction, targetId: props.targetId }) + emitEvent(EVENT_TYPES.RESIZE_END, { direction: props.direction, targetId: props.targetId }, { + source: { component: 'ResizeBar', targetId: props.targetId, direction: props.direction } + }) // 移除全局鼠标事件监听 document.removeEventListener('mousemove', handleMouseMove) diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/TabPage.vue b/AutoRobot/Windows/Robot/Web/src/DockLayout/TabPage.vue index b6930ec..4d2616c 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/TabPage.vue +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/TabPage.vue @@ -155,6 +155,7 @@ const activeTabIndex = ref(-1) // 拖拽相关状态 let isDragging = false let dragIndex = -1 +let currentDragId = null // 计算属性:获取插槽项的props const slotItems = computed(() => { @@ -198,12 +199,18 @@ const onTabDragStart = (index, event) => { isDragging = true dragIndex = index - // 传递标签页索引和鼠标位置 + // 生成统一的 dragId + currentDragId = `tabpage_${props.id}_${index}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}` + + // 传递标签页索引和鼠标位置,包含 dragId emitEvent(EVENT_TYPES.TAB_DRAG_START, { + dragId: currentDragId, clientX: event.clientX, clientY: event.clientY, tabIndex: index, tabId: $slots.default()[index]?.props?.id + }, { + source: { component: 'TabPage', tabPageId: props.id, tabIndex: index, dragId: currentDragId } }) // 防止文本选择和默认行为 @@ -219,11 +226,12 @@ const onTabDragStart = (index, event) => { // 标签拖拽移动 const onTabDragMove = (event) => { - if (isDragging) { + if (isDragging && currentDragId) { // 防止文本选择和默认行为 event.preventDefault() event.stopPropagation() emitEvent(EVENT_TYPES.TAB_DRAG_MOVE, { + dragId: currentDragId, clientX: event.clientX, clientY: event.clientY, tabIndex: dragIndex @@ -233,10 +241,14 @@ const onTabDragMove = (event) => { // 标签拖拽结束 const onTabDragEnd = () => { - if (isDragging) { + if (isDragging && currentDragId) { isDragging = false - emitEvent(EVENT_TYPES.TAB_DRAG_END, { tabIndex: dragIndex }) + emitEvent(EVENT_TYPES.TAB_DRAG_END, { + dragId: currentDragId, + tabIndex: dragIndex + }) dragIndex = -1 + currentDragId = null // 拖拽结束后移除事件监听器 document.removeEventListener('mousemove', onTabDragMove) diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/eventBus.js b/AutoRobot/Windows/Robot/Web/src/DockLayout/eventBus.js index abcb3dc..7581919 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/eventBus.js +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/eventBus.js @@ -1,9 +1,52 @@ import mitt from 'mitt' import { nanoid } from 'nanoid' -// 事件类型常量 +/** + * 事件数据格式规范 + * + * 所有事件数据必须包含以下基本字段: + * - eventType: string - 事件类型(由 emitEvent 自动添加) + * - timestamp: number - 时间戳(由 emitEvent 自动添加) + * - source: Object - 事件来源(由 emitEvent 自动添加) + * - component: string - 组件名称 + * - [其他字段]: any - 其他来源信息 + * + * 特定事件类型的标准数据结构: + * + * 拖拽事件(AREA_DRAG_START/MOVE/END, PANEL_DRAG_START/MOVE/END, TAB_DRAG_START/MOVE/END): + * - [areaId|panelId|tabId]: string - 目标ID + * - event: Event - 原始事件对象 + * - position: {x, y} - 鼠标位置 + * - clientX: number - 鼠标X坐标 + * - clientY: number - 鼠标Y坐标 + * - [startLeft|left]: number - 左侧位置(可选) + * - [startTop|top]: number - 顶部位置(可选) + * + * 位置更新事件(AREA_POSITION_UPDATE): + * - areaId: string - Area ID + * - left: number - 左侧位置 + * - top: number - 顶部位置 + * + * 窗口状态事件(WINDOW_STATE_CHANGE): + * - areaId: string - Area ID + * - isMaximized: boolean - 是否最大化 + * + * 面板事件(PANEL_MAXIMIZE, PANEL_CLOSE_REQUEST, PANEL_TOGGLE_COLLAPSE, PANEL_TOGGLE_TOOLBAR): + * - panelId: string - Panel ID + * - areaId: string - Area ID + * + * 标签页事件(TAB_CHANGE, TAB_CLOSE): + * - index: number - 标签索引 + * - tab: Object - 标签数据(TAB_CHANGE) + * - id: string - 标签ID(TAB_CLOSE) + * + * 调整大小事件(RESIZE_START/MOVE/END): + * - direction: string - 方向('horizontal' | 'vertical') + * - targetId: string - 目标ID + * - newSize: number - 新大小(RESIZE_MOVE) + */ + export const EVENT_TYPES = { - // 区域事件 AREA_CLOSE: 'area.close', AREA_POSITION_UPDATE: 'area.position.update', AREA_DRAG_START: 'area.drag.start', @@ -13,7 +56,6 @@ export const EVENT_TYPES = { AREA_DRAG_LEAVE: 'area.drag.leave', AREA_PANEL_CLOSED: 'area.panel.closed', - // 面板事件 PANEL_MAXIMIZE_SYNC: 'panel.maximize.sync', PANEL_MAXIMIZE: 'panel.maximize', PANEL_CLOSE: 'panel.close', @@ -22,7 +64,6 @@ export const EVENT_TYPES = { PANEL_TOGGLE_COLLAPSE: 'panel.toggleCollapse', PANEL_TOGGLE_TOOLBAR: 'panel.toggleToolbar', - // 面板拖拽事件 PANEL_DRAG_START: 'panel.drag.start', PANEL_DRAG_MOVE: 'panel.drag.move', PANEL_DRAG_END: 'panel.drag.end', @@ -30,7 +71,6 @@ export const EVENT_TYPES = { PANEL_DRAG_MOVE_FROM_TABPAGE: 'panel.drag.move.fromTabPage', PANEL_DRAG_END_FROM_TABPAGE: 'panel.drag.end.fromTabPage', - // 标签页事件 TAB_CHANGE: 'tab.change', TAB_CLOSE: 'tab.close', TAB_ADD: 'tab.add', @@ -38,46 +78,37 @@ export const EVENT_TYPES = { TAB_DRAG_MOVE: 'tab.drag.move', TAB_DRAG_END: 'tab.drag.end', - // 指示器事件 INDICATOR_SHOW: 'indicator.show', INDICATOR_HIDE: 'indicator.hide', INDICATOR_UPDATE: 'indicator.update', - // 调整大小事件 RESIZE_START: 'resize.start', RESIZE_MOVE: 'resize.move', RESIZE_END: 'resize.end', - // 窗口状态事件 WINDOW_STATE_CHANGE: 'window.state.change', Z_INDEX_UPDATE: 'zIndex.update' } -// 事件优先级常量 export const PRIORITY_LEVELS = { - CRITICAL: 0, // 关键事件(如错误、安全) - HIGH: 1, // 高优先级(用户交互) - NORMAL: 2, // 普通优先级 - LOW: 3 // 低优先级(统计、日志) + CRITICAL: 0, + HIGH: 1, + NORMAL: 2, + LOW: 3 } -// 事件去重选项 export const DEDUP_OPTIONS = { - NONE: 0, // 不去重 - TTL_BASED: 1, // 基于TTL去重 - CONTENT_BASED: 2 // 基于内容去重 + NONE: 0, + TTL_BASED: 1, + CONTENT_BASED: 2 } -// 事件去重器 class EventDeduplicator { constructor(ttl = 100) { this.recentEvents = new Map() this.ttl = ttl } - /** - * 检查事件是否应该被处理(去重) - */ shouldProcess(eventType, data, dedupType = DEDUP_OPTIONS.TTL_BASED) { const now = Date.now() @@ -87,22 +118,19 @@ class EventDeduplicator { let key if (dedupType === DEDUP_OPTIONS.CONTENT_BASED) { - // 基于内容和类型去重 key = `${eventType}:${JSON.stringify(data)}` } else { - // 基于TTL的去重(仅基于事件类型) key = eventType } const lastTime = this.recentEvents.get(key) if (lastTime && (now - lastTime) < this.ttl) { - return false // 跳过重复事件 + return false } this.recentEvents.set(key, now) - // 定期清理过期的事件 if (this.recentEvents.size > 1000) { this._cleanup() } @@ -110,9 +138,6 @@ class EventDeduplicator { return true } - /** - * 清理过期的事件记录 - */ _cleanup() { const now = Date.now() for (const [key, timestamp] of this.recentEvents.entries()) { @@ -122,15 +147,11 @@ class EventDeduplicator { } } - /** - * 清除所有记录 - */ clear() { this.recentEvents.clear() } } -// 事件优先级队列管理器 class PriorityEventQueue { constructor() { this.queues = { @@ -142,23 +163,16 @@ class PriorityEventQueue { this.processing = false } - /** - * 添加事件到队列 - */ add(event, priority = PRIORITY_LEVELS.NORMAL) { this.queues[priority].push(event) this._processQueue() } - /** - * 处理事件队列 - */ async _processQueue() { if (this.processing) return this.processing = true try { - // 按优先级顺序处理 for (const priority of Object.keys(this.queues).map(Number).sort()) { const queue = this.queues[priority] while (queue.length > 0) { @@ -171,18 +185,12 @@ class PriorityEventQueue { } } - /** - * 处理单个事件 - */ async _processEvent(event) { if (event.bus && event.bus._handlePriorityEvent) { await event.bus._handlePriorityEvent(event) } } - /** - * 获取队列统计 - */ getStats() { const stats = {} let total = 0 @@ -201,9 +209,6 @@ class PriorityEventQueue { return stats } - /** - * 获取优先级名称 - */ _getPriorityName(priority) { const priorityMap = { [PRIORITY_LEVELS.CRITICAL]: 'CRITICAL', @@ -213,11 +218,14 @@ class PriorityEventQueue { } return priorityMap[priority] || 'UNKNOWN' } + + clear() { + for (const priority of Object.keys(this.queues)) { + this.queues[priority] = [] + } + } } - - -// 增强的事件总线类 class EnhancedEventBus { constructor() { this.bus = mitt() @@ -225,25 +233,43 @@ class EnhancedEventBus { this.debugMode = false this.eventHistory = [] this.maxHistorySize = 100 - this.deduplicator = new EventDeduplicator(100) // 100ms TTL + this.deduplicator = new EventDeduplicator(100) this.priorityQueue = new PriorityEventQueue() this.performanceMetrics = new Map() + this.cleanupInterval = null + this.startCleanupTimer() } - /** - * 启用/禁用调试模式 - */ setDebugMode(enabled) { this.debugMode = enabled } - /** - * 监听事件 - * @param {string} eventType 事件类型 - * @param {function} handler 事件处理函数 - * @param {object} options 监听选项 - * @returns {function} 取消监听的函数 - */ + startCleanupTimer() { + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval) + } + + this.cleanupInterval = setInterval(() => { + this._performMaintenance() + }, 60000) + } + + _performMaintenance() { + this.deduplicator._cleanup() + + if (this.eventHistory.length > this.maxHistorySize) { + this.eventHistory = this.eventHistory.slice(-this.maxHistorySize) + } + } + + checkForListenerLeaks() { + const totalListeners = Array.from(this.bus.all.values()).reduce((sum, listeners) => sum + listeners.length, 0) + + if (totalListeners > 500) { + console.warn(`[EventBus:${this.instanceId}] 检测到大量监听器: ${totalListeners},可能存在内存泄漏`) + } + } + on(eventType, handler, options = {}) { const { priority = PRIORITY_LEVELS.NORMAL, @@ -252,12 +278,10 @@ class EnhancedEventBus { retries = 0 } = options - // 包装处理函数以添加上下文和错误处理 const wrappedHandler = async (event) => { const startTime = performance.now() try { - // 调试日志 if (this.debugMode) { console.group(`📥 Event: ${eventType}`) console.log('Received data:', event.data) @@ -267,14 +291,11 @@ class EnhancedEventBus { console.groupEnd() } - // 异步处理 if (async) { - // 设置超时处理 const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error(`Handler timeout after ${timeout}ms`)), timeout) }) - // 执行处理函数,支持重试 let result let lastError @@ -284,14 +305,13 @@ class EnhancedEventBus { handler(event.data, event), timeoutPromise ]) - break // 成功,退出重试循环 + break } catch (error) { lastError = error if (attempt < retries) { if (this.debugMode) { console.log(`🔄 Retrying event handler for ${eventType} (attempt ${attempt + 1}/${retries + 1})`) } - // 等待重试间隔(指数退避) await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 100)) } } @@ -301,24 +321,19 @@ class EnhancedEventBus { throw lastError } } else { - // 同步处理 const result = handler(event.data, event) - // 如果返回Promise,等待其完成 if (result && typeof result.then === 'function') { await result } } - // 记录成功 this._recordSuccess(eventType, performance.now() - startTime) return true } catch (error) { - // 记录错误 this._recordError(eventType, error) - // 调试模式输出错误 if (this.debugMode) { console.error(`❌ Error in event handler for ${eventType}:`, error) } @@ -327,49 +342,53 @@ class EnhancedEventBus { } } - // 防止重复注册相同的监听器 if (!this.bus.all.has(eventType)) { this.bus.all.set(eventType, []) } const listeners = this.bus.all.get(eventType) - // 检查是否已经存在相同的处理函数 const existingListener = listeners.find(listener => { - // 检查是否是同一个wrappedHandler或handler return listener === wrappedHandler || (listener._originalHandler && listener._originalHandler === handler) }) if (existingListener) { - // 已经存在相同的监听器,直接返回取消订阅函数 + const handlerId = handler.toString().slice(0, 30) + '...' + const componentId = options.componentId || 'unknown' + console.log(`🔁 [EventBus] DUPLICATE: ${eventType} - ${handlerId} | Component ID: ${componentId} (already registered)`) return () => { this.off(eventType, wrappedHandler) } } - // 保存原始处理函数,用于去重检查 wrappedHandler._originalHandler = handler - // 实际监听事件 this.bus.on(eventType, wrappedHandler) - // 返回取消监听的函数 + const handlerId = handler.toString().slice(0, 30) + '...' + const componentId = options.componentId || 'unknown' + const totalListeners = Array.from(this.bus.all.values()).reduce((sum, listeners) => sum + listeners.length, 0) + console.log(`📌 [EventBus] ADD: ${eventType} - ${handlerId} | Component ID: ${componentId} | Total listeners: ${totalListeners}`) + + this.checkForListenerLeaks() + return () => { this.off(eventType, wrappedHandler) } } - /** - * 触发事件 - * @param {string} eventType 事件类型 - * @param {any} data 事件数据 - * @param {object} options 事件选项 - */ emit(eventType, data, options = {}) { const startTime = performance.now() + + // 确保事件数据中包含eventType字段 + const eventData = { + eventType, + ...data + } + const eventInfo = { type: eventType, - data, + data: eventData, timestamp: new Date().toISOString(), instanceId: this.instanceId, priority: options.priority || PRIORITY_LEVELS.NORMAL, @@ -378,7 +397,6 @@ class EnhancedEventBus { ...options } - // 检查是否需要去重 if (!this.deduplicator.shouldProcess(eventType, data, eventInfo.dedupType)) { if (this.debugMode) { console.log(`🔁 Event skipped (dedup): ${eventType}`) @@ -386,10 +404,8 @@ class EnhancedEventBus { return Promise.resolve(false) } - // 添加到事件历史 this._addToHistory(eventInfo) - // 调试日志 if (this.debugMode) { console.group(`🚀 Event: ${eventType}`) console.log('Data:', data) @@ -401,34 +417,26 @@ class EnhancedEventBus { console.groupEnd() } - // 异步事件处理 if (eventInfo.async) { return this._handleAsyncEvent(eventInfo) } - // 优先处理关键事件 if (eventInfo.priority === PRIORITY_LEVELS.CRITICAL) { return this._handlePriorityEvent({ ...eventInfo, bus: this }) } - // 添加到优先级队列 this.priorityQueue.add({ ...eventInfo, bus: this }, eventInfo.priority) - // 记录性能指标 const duration = performance.now() - startTime this._recordPerformance(eventType, duration) return Promise.resolve(true) } - /** - * 处理优先级事件 - */ async _handlePriorityEvent(event) { const startTime = performance.now() try { - // 触发事件 this.bus.emit(event.type, { type: event.type, data: event.data, @@ -437,16 +445,13 @@ class EnhancedEventBus { ...event }) - // 记录性能指标 const duration = performance.now() - startTime this._recordPerformance(event.type, duration) return true } catch (error) { - // 记录错误 this._recordError(event.type, error) - // 调试模式输出错误 if (this.debugMode) { console.error(`❌ Error in priority event ${event.type}:`, error) } @@ -455,19 +460,14 @@ class EnhancedEventBus { } } - /** - * 处理异步事件 - */ async _handleAsyncEvent(eventInfo) { const startTime = performance.now() try { - // 调试日志 if (this.debugMode) { console.log(`⚡ Async event: ${eventInfo.type}`) } - // 异步触发事件 const result = await new Promise((resolve, reject) => { this.bus.emit(eventInfo.type, { type: eventInfo.type, @@ -480,16 +480,13 @@ class EnhancedEventBus { resolve(true) }) - // 记录性能指标 const duration = performance.now() - startTime this._recordPerformance(eventInfo.type, duration) return result } catch (error) { - // 记录错误 this._recordError(eventInfo.type, error) - // 调试模式输出错误 if (this.debugMode) { console.error(`❌ Error in async event ${eventInfo.type}:`, error) } @@ -498,9 +495,6 @@ class EnhancedEventBus { } } - /** - * 触发一次性事件 - */ once(eventType, callback, options = {}) { const unsubscribe = this.on(eventType, (data, event) => { callback(data, event) @@ -510,50 +504,72 @@ class EnhancedEventBus { return unsubscribe } - /** - * 取消订阅 - */ off(eventType, callback) { + const beforeCount = Array.from(this.bus.all.values()).reduce((sum, listeners) => sum + listeners.length, 0) + this.bus.off(eventType, callback) + + const afterCount = Array.from(this.bus.all.values()).reduce((sum, listeners) => sum + listeners.length, 0) + const removed = beforeCount > afterCount + + const handlerId = callback.toString().slice(0, 30) + '...' + if (removed) { + console.log(`🗑️ [EventBus] REMOVE: ${eventType} - ${handlerId} | Total listeners: ${afterCount}`) + } else { + console.log(`❌ [EventBus] REMOVE_FAILED: ${eventType} - ${handlerId} (not found or already removed)`) + } } - /** - * 取消所有订阅 - */ offAll() { + const beforeCount = Array.from(this.bus.all.values()).reduce((sum, listeners) => sum + listeners.length, 0) this.bus.all.clear() + console.log(`🧹 [EventBus] OFF_ALL: Removed ${beforeCount} listeners`) } - /** - * 清除所有监听器 - */ clear() { - this.bus.all.clear() + if (this.bus && this.bus.all) { + this.bus.all.clear() + } + + if (this.deduplicator) { + this.deduplicator.clear() + } + + if (this.priorityQueue) { + this.priorityQueue.clear() + } + + if (this.performanceMetrics) { + this.performanceMetrics.clear() + } + this.eventHistory = [] + + if (this.cleanupInterval) { + clearInterval(this.cleanupInterval) + this.cleanupInterval = null + } + + console.log(`[EventBus:${this.instanceId}] 已清理所有监听器和资源`) + } + + destroy() { + this.clear() + console.log(`[EventBus:${this.instanceId}] 已销毁`) } - /** - * 记录事件历史 - */ recordEvent(event) { this.eventHistory.push(event) - // 限制历史记录大小 if (this.eventHistory.length > this.maxHistorySize) { this.eventHistory.shift() } } - /** - * 获取事件历史 - */ getEventHistory(limit = 20) { return this.eventHistory.slice(-limit) } - /** - * 获取监听器统计 - */ getStats() { const stats = {} for (const [eventType, listeners] of this.bus.all.entries()) { @@ -562,9 +578,6 @@ class EnhancedEventBus { return stats } - /** - * 添加事件到历史 - */ _addToHistory(eventInfo) { this.eventHistory.push({ ...eventInfo, @@ -572,15 +585,11 @@ class EnhancedEventBus { instanceId: this.instanceId }) - // 保持历史记录在指定大小内 if (this.eventHistory.length > this.maxHistorySize) { this.eventHistory.shift() } } - /** - * 记录性能指标 - */ _recordPerformance(eventType, duration) { if (!this.performanceMetrics.has(eventType)) { this.performanceMetrics.set(eventType, { @@ -600,9 +609,6 @@ class EnhancedEventBus { metrics.minDuration = Math.min(metrics.minDuration, duration) } - /** - * 记录成功事件 - */ _recordSuccess(eventType, duration) { const metrics = this.performanceMetrics.get(eventType) if (metrics) { @@ -611,9 +617,6 @@ class EnhancedEventBus { } } - /** - * 记录错误事件 - */ _recordError(eventType, error) { const metrics = this.performanceMetrics.get(eventType) if (metrics) { @@ -626,9 +629,6 @@ class EnhancedEventBus { } } - /** - * 获取性能指标 - */ getPerformanceMetrics() { const metrics = {} for (const [eventType, data] of this.performanceMetrics.entries()) { @@ -642,9 +642,6 @@ class EnhancedEventBus { return metrics } - /** - * 获取优先级名称 - */ _getPriorityName(priority) { const priorityMap = { [PRIORITY_LEVELS.CRITICAL]: 'CRITICAL', @@ -655,9 +652,6 @@ class EnhancedEventBus { return priorityMap[priority] || 'UNKNOWN' } - /** - * 获取去重类型名称 - */ _getDedupName(dedupType) { const dedupMap = { [DEDUP_OPTIONS.NONE]: 'NONE', @@ -667,136 +661,277 @@ class EnhancedEventBus { return dedupMap[dedupType] || 'UNKNOWN' } - /** - * 获取队列统计 - */ getQueueStats() { return this.priorityQueue.getStats() } - /** - * 清除去重器 - */ clearDeduplicator() { this.deduplicator.clear() } } -// 创建全局事件总线实例 -export const eventBus = new EnhancedEventBus() +const eventBusInstance = new EnhancedEventBus() -// 扩展EnhancedEventBus类,添加自动清理和泄漏检测功能 -const originalOn = EnhancedEventBus.prototype.on -const originalClear = EnhancedEventBus.prototype.clear - -// 扩展构造方法 -EnhancedEventBus.prototype.constructor = function() { - this.cleanupInterval = null - this.startCleanupTimer() +export function getEventBus() { + return eventBusInstance } -// 扩展on方法,添加泄漏检测 -EnhancedEventBus.prototype.on = function(eventType, callback, options = {}) { - const unsubscribe = originalOn.call(this, eventType, callback, options) - - // 监控监听器数量 - this.checkForListenerLeaks() - - return unsubscribe -} +export const eventBus = eventBusInstance -// 清理所有监听器和资源 -EnhancedEventBus.prototype.clear = function() { - // 清理mitt事件总线的所有监听器 - if (this.bus && this.bus.all) { - this.bus.all.clear() +class GlobalEventListenerManager { + constructor() { + this.listeners = new Map() } - - // 清理事件处理器注册表 - if (typeof handlerRegistry !== 'undefined' && handlerRegistry.destroyAll) { - handlerRegistry.destroyAll() - } - - // 清理去重器 - if (this.deduplicator) { - this.deduplicator.clear() - } - - // 清理优先级队列 - if (this.priorityQueue) { - this.priorityQueue.clear() - } - - // 清理性能指标 - if (this.performanceMetrics) { - this.performanceMetrics.clear() - } - - // 清理事件历史 - this.eventHistory = [] - - // 清理定时器 - if (this.cleanupInterval) { - clearInterval(this.cleanupInterval) - this.cleanupInterval = null - } - - console.log(`[EventBus:${this.instanceId}] 已清理所有监听器和资源`) -} -// 添加定期清理定时器 -EnhancedEventBus.prototype.startCleanupTimer = function() { - this.cleanupInterval = setInterval(() => { - if (this.eventHistory && this.eventHistory.length > this.maxHistorySize * 0.8) { - this.eventHistory = this.eventHistory.slice(-this.maxHistorySize) - if (this.debugMode) { - console.log(`[EventBus:${this.instanceId}] 自动清理事件历史,当前记录数: ${this.eventHistory.length}`) + addGlobalListener(eventType, handler) { + if (!this.listeners.has(eventType)) { + this.listeners.set(eventType, new Map()) + } + + const eventHandlers = this.listeners.get(eventType) + const handlerId = handler.toString() + const isDuplicate = eventHandlers.has(handlerId) + + if (!isDuplicate) { + document.addEventListener(eventType, handler) + eventHandlers.set(handlerId, { + handler, + count: 0 + }) + + const handlerInfo = eventHandlers.get(handlerId) + handlerInfo.count++ + + const currentCount = handlerInfo.count + const totalListeners = Array.from(this.listeners.values()).reduce((sum, map) => sum + map.size, 0) + + console.log(`📌 [Listener] ADD: ${eventType} - ${handlerId.slice(0, 30)}... (count: ${currentCount}) | Total listeners: ${totalListeners}`) + } else { + const handlerInfo = eventHandlers.get(handlerId) + handlerInfo.count++ + + const currentCount = handlerInfo.count + const totalListeners = Array.from(this.listeners.values()).reduce((sum, map) => sum + map.size, 0) + + console.log(`🔁 [Listener] DUPLICATE: ${eventType} - ${handlerId.slice(0, 30)}... (already registered, count: ${currentCount})`) + } + + return handlerId + } + + removeGlobalListener(eventType, handler) { + if (!this.listeners.has(eventType)) { + console.log(`❌ [Listener] REMOVE_FAILED: ${eventType} - ${handler.toString().slice(0, 30)}... (event type not found)`) + return false + } + + const eventHandlers = this.listeners.get(eventType) + const handlerId = handler.toString() + + if (!eventHandlers.has(handlerId)) { + console.log(`❌ [Listener] REMOVE_FAILED: ${eventType} - ${handlerId.slice(0, 30)}... (handler not found)`) + return false + } + + const handlerInfo = eventHandlers.get(handlerId) + const oldCount = handlerInfo.count + + if (handlerInfo.count > 0) { + handlerInfo.count-- + const newCount = handlerInfo.count + + if (newCount === 0) { + document.removeEventListener(eventType, handler) + eventHandlers.delete(handlerId) + + if (eventHandlers.size === 0) { + this.listeners.delete(eventType) + } + } + + const totalListeners = Array.from(this.listeners.values()).reduce((sum, map) => sum + map.size, 0) + + console.log(`🗑️ [Listener] REMOVE: ${eventType} - ${handlerId.slice(0, 30)}... (count: ${oldCount} → ${newCount}) | Total listeners: ${totalListeners}`) + return true + } + + console.log(`❌ [Listener] REMOVE_FAILED: ${eventType} - ${handlerId.slice(0, 30)}... (count already 0)`) + return false + } + + getStats() { + const stats = {} + for (const [eventType, eventHandlers] of this.listeners.entries()) { + let totalCount = 0 + for (const handlerInfo of eventHandlers.values()) { + totalCount += handlerInfo.count + } + + stats[eventType] = { + count: totalCount, + handlerCount: eventHandlers.size + } + } + return stats + } + + clearAll() { + let removedCount = 0 + let eventTypesCount = this.listeners.size + + for (const [eventType, eventHandlers] of this.listeners.entries()) { + removedCount += eventHandlers.size + for (const [handlerId, handlerInfo] of eventHandlers.entries()) { + document.removeEventListener(eventType, handlerInfo.handler) } } - // 检查监听器泄漏 - this.checkForListenerLeaks() - }, 30000) // 每30秒检查一次 -} - -// 检查监听器泄漏 -EnhancedEventBus.prototype.checkForListenerLeaks = function() { - const stats = this.getStats() - const totalListeners = Object.values(stats).reduce((sum, count) => sum + count, 0) - - // 检查是否有异常的监听器数量 - if (totalListeners > 100) { - console.warn(`[EventBus:${this.instanceId}] 检测到可能的监听器泄漏,总监听器数: ${totalListeners}`) - } - - return totalListeners -} - -// 获取增强的统计信息 -EnhancedEventBus.prototype.getStatsWithLeakDetection = function() { - const stats = this.getStats() - const totalListeners = this.checkForListenerLeaks() - - return { - ...stats, - totalListeners, - instanceId: this.instanceId, - eventHistorySize: this.eventHistory ? this.eventHistory.length : 0, - hasCleanupTimer: !!this.cleanupInterval + this.listeners.clear() + + console.log(`🧹 [Listener] CLEAR_ALL: Removed ${removedCount} listeners across ${eventTypesCount} event types`) } } -// 销毁实例方法 -EnhancedEventBus.prototype.destroy = function() { - if (this.cleanupInterval) { - clearInterval(this.cleanupInterval) - this.cleanupInterval = null - } - this.clear() - console.log(`[EventBus:${this.instanceId}] 实例已销毁`) +const globalEventListenerManagerInstance = new GlobalEventListenerManager() + +export function getGlobalEventListenerManager() { + return globalEventListenerManagerInstance +} + +export const globalEventListenerManager = globalEventListenerManagerInstance + +class EventHandlerRegistry { + constructor() { + this.handlers = new Map() + this.lifecycleHooks = { + init: new Set(), + destroy: new Set() + } + } + + registerHandler(handlerName, eventTypes, handlerInstance) { + const subscription = new Set() + + eventTypes.forEach(eventType => { + const unsubscribe = onEvent(eventType, (data, event) => { + if (handlerInstance[eventType] && typeof handlerInstance[eventType] === 'function') { + try { + handlerInstance[eventType](data, event) + } catch (error) { + console.error(`[EventHandlerRegistry] Error in handler ${handlerName} for ${eventType}:`, error) + } + } + }) + + subscription.add({ eventType, unsubscribe }) + }) + + this.handlers.set(handlerName, { + instance: handlerInstance, + subscriptions: subscription, + registeredAt: Date.now() + }) + + console.log(`[EventHandlerRegistry] Handler "${handlerName}" registered for events:`, eventTypes) + return handlerName + } + + unregisterHandler(handlerName) { + const handler = this.handlers.get(handlerName) + if (!handler) return false + + handler.subscriptions.forEach(({ unsubscribe }) => { + try { + unsubscribe() + } catch (error) { + console.warn(`[EventHandlerRegistry] Error unsubscribing handler ${handlerName}:`, error) + } + }) + + if (handler.instance.onDestroy) { + try { + handler.instance.onDestroy() + } catch (error) { + console.error(`[EventHandlerRegistry] Error in onDestroy for handler ${handlerName}:`, error) + } + } + + this.handlers.delete(handlerName) + console.log(`[EventHandlerRegistry] Handler "${handlerName}" unregistered`) + return true + } + + getHandlers() { + return Array.from(this.handlers.keys()) + } + + getHandlerInfo(handlerName) { + return this.handlers.get(handlerName) + } + + destroyAll() { + for (const handlerName of this.handlers.keys()) { + this.unregisterHandler(handlerName) + } + console.log(`[EventHandlerRegistry] All handlers destroyed`) + } + + getSnapshot() { + const snapshot = {} + for (const [name, handler] of this.handlers.entries()) { + snapshot[name] = { + registeredAt: handler.registeredAt, + subscriptionCount: handler.subscriptions.size, + hasDestroyMethod: typeof handler.instance.onDestroy === 'function' + } + } + return snapshot + } +} + +const handlerRegistryInstance = new EventHandlerRegistry() + +export function getHandlerRegistry() { + return handlerRegistryInstance +} + +export const handlerRegistry = handlerRegistryInstance + +export const emitEvent = (eventType, data, options = {}) => { + const standardizedData = { + eventType, + timestamp: Date.now(), + source: options.source || { component: 'unknown' }, + ...data + } + + return eventBus.emit(eventType, standardizedData, options) +} + +export const onEvent = (eventType, callback, options = {}) => { + return eventBus.on(eventType, callback, options) +} + +export const onceEvent = (eventType, callback, options = {}) => { + return eventBus.once(eventType, callback, options) +} + +export const offEvent = (eventType, callback) => { + return eventBus.off(eventType, callback) +} + +export const registerHandler = (handlerName, eventTypes, handlerInstance) => { + return handlerRegistry.registerHandler(handlerName, eventTypes, handlerInstance) +} + +export const unregisterHandler = (handlerName) => { + return handlerRegistry.unregisterHandler(handlerName) +} + +export const getHandlerSnapshot = () => { + return handlerRegistry.getSnapshot() } -// 便捷的事件触发函数 export const triggerDragEvent = { area: { start: (areaId, event) => { @@ -834,253 +969,3 @@ export const triggerDragEvent = { } } } - -// 事件处理器注册和管理 -class EventHandlerRegistry { - constructor() { - this.handlers = new Map() - this.lifecycleHooks = { - init: new Set(), - destroy: new Set() - } - } - - /** - * 注册事件处理器 - */ - registerHandler(handlerName, eventTypes, handlerInstance) { - const subscription = new Set() - - // 注册事件监听器 - eventTypes.forEach(eventType => { - const unsubscribe = onEvent(eventType, (data, event) => { - if (handlerInstance[eventType] && typeof handlerInstance[eventType] === 'function') { - try { - handlerInstance[eventType](data, event) - } catch (error) { - console.error(`[EventHandlerRegistry] Error in handler ${handlerName} for ${eventType}:`, error) - } - } - }) - - subscription.add({ eventType, unsubscribe }) - }) - - this.handlers.set(handlerName, { - instance: handlerInstance, - subscriptions: subscription, - registeredAt: Date.now() - }) - - console.log(`[EventHandlerRegistry] Handler "${handlerName}" registered for events:`, eventTypes) - return handlerName - } - - /** - * 注销事件处理器 - */ - unregisterHandler(handlerName) { - const handler = this.handlers.get(handlerName) - if (!handler) return false - - // 取消所有订阅 - handler.subscriptions.forEach(({ unsubscribe }) => { - try { - unsubscribe() - } catch (error) { - console.warn(`[EventHandlerRegistry] Error unsubscribing handler ${handlerName}:`, error) - } - }) - - // 调用销毁钩子 - if (handler.instance.onDestroy) { - try { - handler.instance.onDestroy() - } catch (error) { - console.error(`[EventHandlerRegistry] Error in onDestroy for handler ${handlerName}:`, error) - } - } - - this.handlers.delete(handlerName) - console.log(`[EventHandlerRegistry] Handler "${handlerName}" unregistered`) - return true - } - - /** - * 获取所有处理器 - */ - getHandlers() { - return Array.from(this.handlers.keys()) - } - - /** - * 获取处理器信息 - */ - getHandlerInfo(handlerName) { - return this.handlers.get(handlerName) - } - - /** - * 销毁所有处理器 - */ - destroyAll() { - for (const handlerName of this.handlers.keys()) { - this.unregisterHandler(handlerName) - } - console.log(`[EventHandlerRegistry] All handlers destroyed`) - } - - /** - * 获取状态快照 - */ - getSnapshot() { - const snapshot = {} - for (const [name, handler] of this.handlers.entries()) { - snapshot[name] = { - registeredAt: handler.registeredAt, - subscriptionCount: handler.subscriptions.size, - hasDestroyMethod: typeof handler.instance.onDestroy === 'function' - } - } - return snapshot - } -} - -// 创建事件处理器注册表 -export const handlerRegistry = new EventHandlerRegistry() - -// 导出便捷触发函数 -export const emitEvent = (eventType, data, options = {}) => { - return eventBus.emit(eventType, data, options) -} - -export const onEvent = (eventType, callback, options = {}) => { - return eventBus.on(eventType, callback, options) -} - -export const onceEvent = (eventType, callback, options = {}) => { - return eventBus.once(eventType, callback, options) -} - -export const offEvent = (eventType, callback) => { - return eventBus.off(eventType, callback) -} - -// 导出事件处理器注册函数 -export const registerHandler = (handlerName, eventTypes, handlerInstance) => { - return handlerRegistry.registerHandler(handlerName, eventTypes, handlerInstance) -} - -export const unregisterHandler = (handlerName) => { - return handlerRegistry.unregisterHandler(handlerName) -} - -export const getHandlerSnapshot = () => { - return handlerRegistry.getSnapshot() -} - -// 全局事件监听管理器 -class GlobalEventListenerManager { - constructor() { - this.listeners = new Map() - } - - /** - * 添加全局事件监听 - * @param {string} eventType 事件类型 - * @param {Function} handler 事件处理函数 - */ - addGlobalListener(eventType, handler) { - if (!this.listeners.has(eventType)) { - this.listeners.set(eventType, new Map()) - } - - const eventHandlers = this.listeners.get(eventType) - const handlerId = handler.toString() - - // 如果是第一次添加这个处理函数,实际添加到文档 - if (!eventHandlers.has(handlerId)) { - document.addEventListener(eventType, handler) - eventHandlers.set(handlerId, { - handler, - count: 0 - }) - } - - // 增加这个处理函数的引用计数 - eventHandlers.get(handlerId).count++ - return handlerId - } - - /** - * 移除全局事件监听 - * @param {string} eventType 事件类型 - * @param {Function} handler 事件处理函数 - */ - removeGlobalListener(eventType, handler) { - if (!this.listeners.has(eventType)) { - return false - } - - const eventHandlers = this.listeners.get(eventType) - const handlerId = handler.toString() - - if (!eventHandlers.has(handlerId)) { - return false - } - - const handlerInfo = eventHandlers.get(handlerId) - - // 减少这个处理函数的引用计数 - if (handlerInfo.count > 0) { - handlerInfo.count-- - - // 如果计数器减到0,实际从文档移除 - if (handlerInfo.count === 0) { - document.removeEventListener(eventType, handler) - eventHandlers.delete(handlerId) - - // 如果这个事件类型没有处理函数了,清理该事件类型的映射 - if (eventHandlers.size === 0) { - this.listeners.delete(eventType) - } - } - } - - return true - } - - /** - * 获取全局事件监听统计 - */ - getStats() { - const stats = {} - for (const [eventType, eventHandlers] of this.listeners.entries()) { - let totalCount = 0 - for (const handlerInfo of eventHandlers.values()) { - totalCount += handlerInfo.count - } - - stats[eventType] = { - count: totalCount, - handlerCount: eventHandlers.size - } - } - return stats - } - - /** - * 清除所有全局事件监听 - */ - clearAll() { - for (const [eventType, eventHandlers] of this.listeners.entries()) { - for (const [handlerId, handlerInfo] of eventHandlers.entries()) { - document.removeEventListener(eventType, handlerInfo.handler) - } - } - this.listeners.clear() - } -} - -// 导出全局事件管理器实例 -export const globalEventListenerManager = new GlobalEventListenerManager() \ No newline at end of file diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/AreaHandler.js b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/AreaHandler.js index 3a13657..b3db9cd 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/AreaHandler.js +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/AreaHandler.js @@ -3,9 +3,9 @@ * 专门处理Area相关的所有事件,包括浮动区域管理、拖拽、停靠、合并等 */ -import { eventBus } from '../eventBus'; +import { eventBus, EVENT_TYPES } from '../eventBus'; -// Area事件类型常量 +// Area事件类型常量(仅包含EVENT_TYPES中没有的) export const AREA_EVENT_TYPES = { // 基础事件 AREA_CREATED: 'area.created', @@ -23,16 +23,8 @@ export const AREA_EVENT_TYPES = { AREA_RESTORE: 'area.restore', AREA_COLLAPSE: 'area.collapse', AREA_EXPAND: 'area.expand', - AREA_CLOSE: 'area.close', AREA_TOGGLE_TOOLBAR: 'area.toggleToolbar', - // 拖拽相关 - AREA_DRAG_START: 'area.drag.start', - AREA_DRAG_MOVE: 'area.drag.move', - AREA_DRAG_END: 'area.drag.end', - AREA_DRAG_OVER: 'area.drag.over', - AREA_DRAG_LEAVE: 'area.drag.leave', - // 停靠相关 AREA_DOCK_CENTER: 'area.dock.center', AREA_DOCK_EDGE: 'area.dock.edge', @@ -759,12 +751,36 @@ class AreaEventHandler { * 注册事件监听器 */ _registerEventListeners() { - const events = Object.values(AREA_EVENT_TYPES); - events.forEach(eventType => { + // 监听 EVENT_TYPES 中的事件 + const eventTypes = [ + EVENT_TYPES.AREA_DRAG_START, + EVENT_TYPES.AREA_DRAG_MOVE, + EVENT_TYPES.AREA_DRAG_END, + EVENT_TYPES.AREA_DRAG_OVER, + EVENT_TYPES.AREA_DRAG_LEAVE, + EVENT_TYPES.AREA_CLOSE, + EVENT_TYPES.AREA_POSITION_UPDATE, + EVENT_TYPES.AREA_PANEL_CLOSED + ]; + + eventTypes.forEach(eventType => { const listener = this._onAreaEvent; eventBus.on(eventType, listener, { priority: 1, - deduplication: { type: 'TTL_BASED', ttl: 100 } + deduplication: { type: 'TTL_BASED', ttl: 100 }, + componentId: 'area-handler' + }); + this.areaListeners.set(eventType, listener); + }); + + // 监听 AREA_EVENT_TYPES 中的事件 + const areaEventTypes = Object.values(AREA_EVENT_TYPES); + areaEventTypes.forEach(eventType => { + const listener = this._onAreaEvent; + eventBus.on(eventType, listener, { + priority: 1, + deduplication: { type: 'TTL_BASED', ttl: 100 }, + componentId: 'area-handler' }); this.areaListeners.set(eventType, listener); }); @@ -775,17 +791,17 @@ class AreaEventHandler { * @param {Object} data - 事件数据 */ async _onAreaEvent(data) { - const { eventType } = data; + const eventType = data.eventType; try { switch (eventType) { - case AREA_EVENT_TYPES.AREA_DRAG_START: + case EVENT_TYPES.AREA_DRAG_START: await this._handleAreaDragStart(data); break; - case AREA_EVENT_TYPES.AREA_DRAG_MOVE: + case EVENT_TYPES.AREA_DRAG_MOVE: await this._handleAreaDragMove(data); break; - case AREA_EVENT_TYPES.AREA_DRAG_END: + case EVENT_TYPES.AREA_DRAG_END: await this._handleAreaDragEnd(data); break; case AREA_EVENT_TYPES.AREA_DOCK_CENTER: @@ -806,13 +822,15 @@ class AreaEventHandler { case AREA_EVENT_TYPES.AREA_FLOATING_CREATE: await this._handleFloatingAreaCreate(data); break; + case AREA_EVENT_TYPES.AREA_ZINDEX_MANAGEMENT: + await this._handleAreaZIndexManagement(data); + break; default: // 记录其他事件但不处理 - console.log(`📍 Area事件处理器: ${eventType}`, data); + console.log(`📍 Area事件处理器: ${eventType || 'unknown'}`, data); } } catch (error) { console.error(`❌ Area事件处理错误 (${eventType}):`, error); - eventBus.recordError(`areaHandler_${eventType}`, error); } } @@ -884,7 +902,7 @@ class AreaEventHandler { * @param {Object} data - 事件数据 */ async _handleAreaDragEnd(data) { - const { areaId, event } = data; + const { areaId, event, finalPosition, left, top } = data; const dragState = this.areaStateManager.getDragState(areaId); if (!dragState) return; @@ -896,7 +914,18 @@ class AreaEventHandler { // 清理拖拽状态 dragState.isDragging = false; dragState.endTime = Date.now(); - dragState.endPosition = { x: event.clientX, y: event.clientY }; + + // 使用 finalPosition 或 left/top 作为结束位置 + if (finalPosition) { + dragState.endPosition = { x: finalPosition.x, y: finalPosition.y }; + } else if (left !== undefined && top !== undefined) { + dragState.endPosition = { x: left, y: top }; + } else if (event) { + dragState.endPosition = { x: event.clientX, y: event.clientY }; + } else { + dragState.endPosition = dragState.startPosition; + } + dragState.totalDistance = distance; dragState.duration = dragDuration; @@ -1126,6 +1155,50 @@ class AreaEventHandler { }); } + /** + * 处理Area层级管理事件 + * @param {Object} data - 事件数据 + */ + async _handleAreaZIndexManagement(data) { + const { areaId, action, zIndex, reason } = data; + + try { + switch (action) { + case 'activate': + // 激活Area时更新z-index + this.activeAreas.add(areaId); + this.areaStateManager.updateState(areaId, { + lastActivatedAt: Date.now() + }); + break; + + case 'create_floating': + // 创建浮动Area时设置z-index + if (zIndex) { + this.areaStateManager.updateState(areaId, { + zIndex, + lastActivatedAt: Date.now() + }); + } + break; + + case 'update': + // 更新z-index + if (zIndex) { + this.areaStateManager.updateState(areaId, { + zIndex + }); + } + break; + + default: + console.log(`📍 Area层级管理: ${action}`, data); + } + } catch (error) { + console.error(`❌ Area层级管理错误 (${action}):`, error); + } + } + /** * 启动内存保护机制 */ @@ -1233,6 +1306,12 @@ class AreaEventHandler { // 创建单例实例 const areaHandler = new AreaEventHandler(); +/** + * 获取Area事件处理器实例 + * @returns {AreaEventHandler} AreaEventHandler实例 + */ +export const getAreaHandler = () => areaHandler; + // Area便捷操作函数 export const areaActions = { /** diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/DragStateManager.js b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/DragStateManager.js index 7bd5976..9046eb9 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/DragStateManager.js +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/DragStateManager.js @@ -495,6 +495,8 @@ class DragStateManager { }; this.isEnabled = true; this.debugMode = false; + this.eventListenersRegistered = false; // 初始化监听器注册标志 + this.cleanupScheduler = null; // 初始化清理调度器 // 绑定方法 this._onDragEvent = this._onDragEvent.bind(this); @@ -516,12 +518,24 @@ class DragStateManager { console.log('🎯 拖拽状态管理器初始化完成'); } + /** + * 生成唯一的拖拽ID + * @param {string} componentType - 组件类型 + * @returns {string} 拖拽ID + */ + _generateDragId(componentType) { + const timestamp = Date.now(); + const random = Math.random().toString(36).substring(2, 9); + return `${componentType}_${timestamp}_${random}`; + } + /** * 注册事件监听器 */ _registerEventListeners() { // 防止重复注册监听器 if (this.eventListenersRegistered) { + console.log('🚫 已经注册了监听器,跳过重复注册'); return; } @@ -537,17 +551,22 @@ class DragStateManager { dragEvents.forEach(eventType => { eventBus.on(eventType, this._onDragEvent, { priority: 1, - deduplication: { type: 'EVENT_BASED', key: 'dragState' } + deduplication: { type: 'EVENT_BASED', key: 'dragState' }, + componentId: 'drag-state-manager' }); }); this.eventListenersRegistered = true; + console.log('✅ 拖拽管理器事件监听器注册完成'); } /** * 销毁管理器 */ destroy() { + // 取消所有拖拽 + this.cancelAllDrags(); + // 清理事件监听器 const dragEvents = [ // Panel拖拽事件 @@ -565,10 +584,17 @@ class DragStateManager { // 清理清理调度器 this._stopCleanupScheduler(); - // 清理拖拽状态 - this.dragStates.clear(); + // 清理数据 + this.activeDrags.clear(); this.dragHistory = []; + this.dragTargets.clear(); + // 清理冲突检测器 + this.conflictDetector.activeDrags.clear(); + this.conflictDetector.conflictHistory = []; + + this.isEnabled = false; + this.eventListenersRegistered = false; console.log('🗑️ 拖拽状态管理器已销毁'); } @@ -579,34 +605,66 @@ class DragStateManager { async _onDragEvent(data) { if (!this.isEnabled) return; - const monitorId = globalEventActions.startMonitor(`drag_${data.eventType || data.type}`); + const monitorId = globalEventActions.startMonitor(`drag_${data.eventType}`); try { const { eventType = data.type, dragId, componentType, sourceElement } = data; + // 从事件数据中提取 dragId,如果没有则根据组件类型推断 + let actualDragId = dragId; + if (!actualDragId) { + if (data.panelId) { + actualDragId = `panel_${data.panelId}_${data.timestamp || Date.now()}`; + } else if (data.tabIndex !== undefined) { + actualDragId = `tabpage_${data.tabId}_${data.tabIndex}_${data.timestamp || Date.now()}`; + } else if (data.areaId) { + actualDragId = `area_${data.areaId}_${data.timestamp || Date.now()}`; + } + } + + // 推断组件类型 + let actualComponentType = componentType; + if (!actualComponentType) { + if (data.panelId) { + actualComponentType = 'panel'; + } else if (data.tabIndex !== undefined) { + actualComponentType = 'tabpage'; + } else if (data.areaId) { + actualComponentType = 'area'; + } + } + + // 准备标准化的拖拽数据 + const dragData = { + ...data, + dragId: actualDragId, + componentType: actualComponentType, + sourceElement: sourceElement || data.element + }; + switch (eventType) { case 'panel.drag.start': case 'tabpage.drag.start': case 'area.drag.start': - await this._handleDragStart(data); + await this._handleDragStart(dragData); break; case 'panel.drag.move': case 'tabpage.drag.move': case 'area.drag.move': - await this._handleDragMove(data); + await this._handleDragMove(dragData); break; case 'panel.drag.end': case 'tabpage.drag.end': case 'area.drag.end': - await this._handleDragEnd(data); + await this._handleDragEnd(dragData); break; case 'panel.drag.cancel': case 'tabpage.drag.cancel': case 'area.drag.cancel': - await this._handleDragCancel(data); + await this._handleDragCancel(dragData); break; } } catch (error) { @@ -628,28 +686,31 @@ class DragStateManager { async _handleDragStart(data) { const { dragId, componentType, sourceElement, position, options = {} } = data; + // 如果没有提供 dragId,生成一个唯一的 dragId + const actualDragId = dragId || this._generateDragId(componentType); + // 创建拖拽状态 - const dragState = new DragState(dragId, componentType, sourceElement, options); + const dragState = new DragState(actualDragId, componentType, sourceElement, options); dragState.startPosition = { ...position }; dragState.currentPosition = { ...position }; dragState.status = 'active'; // 保存拖拽状态 - this.activeDrags.set(dragId, dragState); + this.activeDrags.set(actualDragId, dragState); // 注册到冲突检测器 this.conflictDetector.registerDrag(dragState); - // 触发状态变更事件 + // 触发状态变更事件,包含 dragId eventBus.emit(DRAG_STATE_TYPES.DRAG_STATE_CHANGE, { - dragId, + dragId: actualDragId, newStatus: 'active', dragState: dragState.getSummary() }); // 发送初始反馈 if (this.debugMode) { - this._updateDragFeedback(dragId, { + this._updateDragFeedback(actualDragId, { visible: true, content: `开始拖拽 ${componentType}`, type: 'default', @@ -658,8 +719,10 @@ class DragStateManager { } if (this.debugMode) { - console.log(`🎯 拖拽开始: ${dragId} (${componentType})`, position); + console.log(`🎯 拖拽开始: ${actualDragId} (${componentType})`, position); } + + return actualDragId; } /** @@ -670,7 +733,10 @@ class DragStateManager { const { dragId, position, targetElement, targetArea } = data; const dragState = this.activeDrags.get(dragId); - if (!dragState || dragState.status !== 'active') return; + if (!dragState || dragState.status !== 'active') { + console.warn(`⚠️ 未找到活跃的拖拽状态: ${dragId}`); + return; + } // 更新位置 dragState.updatePosition(position.x, position.y); @@ -726,7 +792,10 @@ class DragStateManager { const { dragId, finalPosition, dropTarget, success = true } = data; const dragState = this.activeDrags.get(dragId); - if (!dragState) return; + if (!dragState) { + console.warn(`⚠️ 未找到拖拽状态: ${dragId}`); + return; + } // 完成拖拽 dragState.complete('completed', { success, finalPosition, dropTarget }); @@ -743,7 +812,10 @@ class DragStateManager { // 从冲突检测器注销 this.conflictDetector.unregisterDrag(dragId); - // 触发状态变更事件 + // 清理拖拽目标 + this.dragTargets.delete(dragId); + + // 触发状态变更事件,包含 dragId eventBus.emit(DRAG_STATE_TYPES.DRAG_STATE_CHANGE, { dragId, newStatus: 'completed', @@ -773,7 +845,10 @@ class DragStateManager { const { dragId, reason } = data; const dragState = this.activeDrags.get(dragId); - if (!dragState) return; + if (!dragState) { + console.warn(`⚠️ 未找到拖拽状态: ${dragId}`); + return; + } // 取消拖拽 dragState.complete('cancelled', { reason }); @@ -790,7 +865,10 @@ class DragStateManager { // 从冲突检测器注销 this.conflictDetector.unregisterDrag(dragId); - // 触发状态变更事件 + // 清理拖拽目标 + this.dragTargets.delete(dragId); + + // 触发状态变更事件,包含 dragId eventBus.emit(DRAG_STATE_TYPES.DRAG_STATE_CHANGE, { dragId, newStatus: 'cancelled', @@ -1024,11 +1102,24 @@ class DragStateManager { * 启动清理调度器 */ _startCleanupScheduler() { - setInterval(() => { + if (this.cleanupScheduler) { + return; + } + this.cleanupScheduler = setInterval(() => { this._cleanupExpiredData(); }, 300000); // 每5分钟清理一次 } + /** + * 停止清理调度器 + */ + _stopCleanupScheduler() { + if (this.cleanupScheduler) { + clearInterval(this.cleanupScheduler); + this.cleanupScheduler = null; + } + } + /** * 清理过期数据 */ @@ -1663,26 +1754,6 @@ class DragStateManager { } } - /** - * 销毁管理器 - */ - destroy() { - // 取消所有拖拽 - this.cancelAllDrags(); - - // 清理数据 - this.activeDrags.clear(); - this.dragHistory = []; - this.dragTargets.clear(); - - // 清理冲突检测器 - this.conflictDetector.activeDrags.clear(); - this.conflictDetector.conflictHistory = []; - - this.isEnabled = false; - console.log('🗑️ 拖拽状态管理器已销毁'); - } - /** * Area拖拽开始 * @param {Object} eventData - 拖拽事件数据 @@ -1878,18 +1949,8 @@ class DragStateManager { } } -// 延迟创建单例实例 -let dragStateManager = null; - -/** - * 确保单例实例存在 - */ -function ensureDragStateManagerInstance() { - if (!dragStateManager) { - dragStateManager = new DragStateManager(); - } - return dragStateManager; -} +// 立即创建单例实例,避免并发初始化问题 +const dragStateManager = new DragStateManager(); // 便捷操作函数 export const dragStateActions = { @@ -1898,150 +1959,153 @@ export const dragStateActions = { * @param {string} dragId - 拖拽ID * @returns {Object} 拖拽状态 */ - getDragState: (dragId) => ensureDragStateManagerInstance().getDragState(dragId), + getDragState: (dragId) => dragStateManager.getDragState(dragId), /** * 获取所有活跃拖拽 * @returns {Array} 活跃拖拽列表 */ - getActiveDrags: () => ensureDragStateManagerInstance().getActiveDrags(), + getActiveDrags: () => dragStateManager.getActiveDrags(), /** * 获取拖拽历史 * @param {number} limit - 限制数量 * @returns {Array} 历史记录 */ - getHistory: (limit = 100) => ensureDragStateManagerInstance().getDragHistory(limit), + getHistory: (limit = 100) => dragStateManager.getDragHistory(limit), /** * 获取统计信息 * @returns {Object} 统计信息 */ - getStats: () => ensureDragStateManagerInstance().getDragStats(), + getStats: () => dragStateManager.getDragStats(), /** * 设置调试模式 * @param {boolean} enabled - 是否启用 */ - setDebugMode: (enabled) => ensureDragStateManagerInstance().setDebugMode(enabled), + setDebugMode: (enabled) => dragStateManager.setDebugMode(enabled), /** * 启用/禁用管理器 * @param {boolean} enabled - 是否启用 */ - setEnabled: (enabled) => ensureDragStateManagerInstance().setEnabled(enabled), + setEnabled: (enabled) => dragStateManager.setEnabled(enabled), /** * 取消所有拖拽 */ - cancelAll: () => ensureDragStateManagerInstance().cancelAllDrags(), + cancelAll: () => dragStateManager.cancelAllDrags(), /** * 面板拖拽开始 * @param {Object} eventData - 拖拽事件数据 * @returns {string} 拖拽ID */ - onPanelDragStart: (eventData) => ensureDragStateManagerInstance().onPanelDragStart(eventData), + onPanelDragStart: (eventData) => dragStateManager.onPanelDragStart(eventData), /** * 面板拖拽移动 * @param {Object} eventData - 拖拽事件数据 * @returns {boolean} 是否成功 */ - onPanelDragMove: (eventData) => ensureDragStateManagerInstance().onPanelDragMove(eventData), + onPanelDragMove: (eventData) => dragStateManager.onPanelDragMove(eventData), /** * 面板拖拽结束 * @param {Object} eventData - 拖拽事件数据 * @returns {boolean} 是否成功 */ - onPanelDragEnd: (eventData) => ensureDragStateManagerInstance().onPanelDragEnd(eventData), + onPanelDragEnd: (eventData) => dragStateManager.onPanelDragEnd(eventData), /** * TabPage拖拽开始 * @param {Object} eventData - 拖拽事件数据 * @returns {string} 拖拽ID */ - onPanelDragStartFromTabPage: (eventData) => ensureDragStateManagerInstance().onPanelDragStartFromTabPage(eventData), + onPanelDragStartFromTabPage: (eventData) => dragStateManager.onPanelDragStartFromTabPage(eventData), /** * TabPage拖拽移动 * @param {Object} eventData - 拖拽事件数据 * @returns {boolean} 是否成功 */ - onPanelDragMoveFromTabPage: (eventData) => ensureDragStateManagerInstance().onPanelDragMoveFromTabPage(eventData), + onPanelDragMoveFromTabPage: (eventData) => dragStateManager.onPanelDragMoveFromTabPage(eventData), /** * TabPage拖拽结束 * @param {Object} eventData - 拖拽事件数据 * @returns {boolean} 是否成功 */ - onPanelDragEndFromTabPage: (eventData) => ensureDragStateManagerInstance().onPanelDragEndFromTabPage(eventData), + onPanelDragEndFromTabPage: (eventData) => dragStateManager.onPanelDragEndFromTabPage(eventData), /** * Area拖拽开始 * @param {Object} eventData - 拖拽事件数据 * @returns {string} 拖拽ID */ - onAreaDragStart: (eventData) => ensureDragStateManagerInstance().onAreaDragStart(eventData), + onAreaDragStart: (eventData) => dragStateManager.onAreaDragStart(eventData), /** * Area拖拽移动 * @param {Object} eventData - 拖拽事件数据 * @returns {boolean} 是否成功 */ - onAreaDragMove: (eventData) => ensureDragStateManagerInstance().onAreaDragMove(eventData), + onAreaDragMove: (eventData) => dragStateManager.onAreaDragMove(eventData), /** * Area拖拽结束 * @param {Object} eventData - 拖拽事件数据 * @returns {boolean} 是否成功 */ - onAreaDragEnd: (eventData) => ensureDragStateManagerInstance().onAreaDragEnd(eventData), + onAreaDragEnd: (eventData) => dragStateManager.onAreaDragEnd(eventData), /** * Tab拖拽开始 * @param {Object} eventData - 拖拽事件数据 * @returns {string} 拖拽ID */ - onTabDragStart: (eventData) => ensureDragStateManagerInstance().onPanelDragStartFromTabPage(eventData), + onTabDragStart: (eventData) => dragStateManager.onPanelDragStartFromTabPage(eventData), /** * Tab拖拽移动 * @param {Object} eventData - 拖拽事件数据 * @returns {boolean} 是否成功 */ - onTabDragMove: (eventData) => ensureDragStateManagerInstance().onPanelDragMoveFromTabPage(eventData), + onTabDragMove: (eventData) => dragStateManager.onPanelDragMoveFromTabPage(eventData), /** * Tab拖拽结束 * @param {Object} eventData - 拖拽事件数据 * @returns {boolean} 是否成功 */ - onTabDragEnd: (eventData) => ensureDragStateManagerInstance().onPanelDragEndFromTabPage(eventData), + onTabDragEnd: (eventData) => dragStateManager.onPanelDragEndFromTabPage(eventData), /** * 初始化拖拽管理器 */ - initialize: () => ensureDragStateManagerInstance(), + initialize: () => dragStateManager, /** * 销毁拖拽管理器 */ destroy: () => { if (dragStateManager) { - // 清理拖拽管理器资源 - dragStateManager.activeDrags.clear(); - dragStateManager.dragHistory = []; + // 清理拖拽管理器资源,先调用实例的destroy方法清理监听器 + try { + dragStateManager.destroy(); + } catch (e) { + console.warn('销毁拖拽状态管理器时出错:', e); + } dragStateManager = null; - console.log('🗑️ 拖拽状态管理器已销毁,所有资源已清理'); + console.log('🗑️ 拖拽状态管理器已销毁'); } } }; // 导出 export default { - getInstance: ensureDragStateManagerInstance, + getInstance: () => dragStateManager, actions: dragStateActions }; export { DragState }; \ No newline at end of file diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/EventBusManager.js b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/EventBusManager.js index ee25616..44f7306 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/EventBusManager.js +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/EventBusManager.js @@ -140,10 +140,10 @@ class EventBusManager { { name: 'tabpage', handler: tabPageHandler, events: TABPAGE_EVENT_TYPES }, { name: 'area', handler: areaHandler, events: AREA_EVENT_TYPES }, { name: 'global', handler: globalEventManager, events: GLOBAL_EVENT_TYPES }, - { name: 'drag', handler: dragStateManager, events: DRAG_STATE_TYPES } + { name: 'drag', handler: dragStateManager.getInstance(), events: DRAG_STATE_TYPES, skipEventRegistration: true } ]; - for (const { name, handler, events } of handlers) { + for (const { name, handler, events, skipEventRegistration = false } of handlers) { const handlerConfig = this.config.handlers[name]; if (!handlerConfig?.enabled) { @@ -154,15 +154,20 @@ class EventBusManager { try { console.log(`📝 注册事件处理器: ${name}`); - // 注册事件处理器到事件总线 - Object.values(events).forEach(eventType => { - if (typeof handler.handleEvent === 'function') { - eventBus.on(eventType, handler.handleEvent.bind(handler), { - priority: 1, - id: `handler-${name}` - }); - } - }); + // 注册事件处理器到事件总线(除非跳过) + if (!skipEventRegistration) { + Object.values(events).forEach(eventType => { + if (typeof handler.handleEvent === 'function') { + eventBus.on(eventType, handler.handleEvent.bind(handler), { + priority: 1, + id: `handler-${name}`, + componentId: `handler-${name}` + }); + } + }); + } else { + console.log(`⏭️ 跳过事件监听器注册(内部自动注册): ${name}`); + } // 调用初始化方法(如果存在) if (typeof handler.initialize === 'function') { @@ -236,7 +241,7 @@ class EventBusManager { routeTime: Date.now() }); }); - }, { id: 'router', priority: 0 }); + }, { id: 'router', priority: 0, componentId: 'event-router' }); }); console.log(`🔗 设置了 ${routes.length} 个事件路由`); diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/GlobalEventManager.js b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/GlobalEventManager.js index 893429e..76f2741 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/GlobalEventManager.js +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/GlobalEventManager.js @@ -4,13 +4,6 @@ */ import { eventBus } from '../eventBus'; -import { PANEL_EVENT_TYPES, panelActions, panelHandler } from './PanelHandler'; -import tabPageHandlerModule, { TABPAGE_EVENT_TYPES, tabPageActions } from './TabPageHandler'; -import areaHandlerModule, { AREA_EVENT_TYPES, areaActions } from './AreaHandler'; - -// 获取处理器实例 -const tabPageHandler = tabPageHandlerModule.getInstance ? tabPageHandlerModule.getInstance() : tabPageHandlerModule; -const areaHandler = areaHandlerModule; // 全局事件类型常量 export const GLOBAL_EVENT_TYPES = { @@ -50,59 +43,7 @@ export const GLOBAL_EVENT_TYPES = { DEBUG_MEMORY: 'debug.memory' }; -// 事件路由配置 -const EVENT_ROUTES = { - // Panel事件路由 - [PANEL_EVENT_TYPES.PANEL_DRAG_START]: { - handlers: ['panelHandler', 'areaHandler'], - priority: 1, - timeout: 5000 - }, - [PANEL_EVENT_TYPES.PANEL_MAXIMIZE]: { - handlers: ['panelHandler', 'areaHandler'], - priority: 1, - timeout: 3000 - }, - [PANEL_EVENT_TYPES.PANEL_CLOSE_REQUEST]: { - handlers: ['panelHandler', 'tabPageHandler'], - priority: 2, - timeout: 5000 - }, - - // TabPage事件路由 - [TABPAGE_EVENT_TYPES.TABPAGE_DRAG_START]: { - handlers: ['tabPageHandler', 'areaHandler'], - priority: 1, - timeout: 5000 - }, - [TABPAGE_EVENT_TYPES.TABPAGE_SWITCH]: { - handlers: ['tabPageHandler', 'panelHandler'], - priority: 2, - timeout: 3000 - }, - [TABPAGE_EVENT_TYPES.TABPAGE_CLOSE_REQUEST]: { - handlers: ['tabPageHandler', 'panelHandler'], - priority: 2, - timeout: 5000 - }, - - // Area事件路由 - [AREA_EVENT_TYPES.AREA_DRAG_START]: { - handlers: ['areaHandler', 'tabPageHandler'], - priority: 1, - timeout: 5000 - }, - [AREA_EVENT_TYPES.AREA_MERGE]: { - handlers: ['areaHandler', 'tabPageHandler', 'panelHandler'], - priority: 1, - timeout: 10000 - }, - [AREA_EVENT_TYPES.AREA_DOCK_CENTER]: { - handlers: ['areaHandler', 'tabPageHandler'], - priority: 1, - timeout: 8000 - } -}; +// 事件路由配置将在构造函数中动态创建 // 事件执行监控 class EventExecutionMonitor { @@ -297,31 +238,26 @@ class GlobalEventManager { static instance = null; constructor() { - // 单例模式实现:如果已经有实例,直接返回现有实例 if (GlobalEventManager.instance) { return GlobalEventManager.instance; } this.eventHandlers = new Map(); - this.eventRoutes = new Map(Object.entries(EVENT_ROUTES)); + this.eventRoutes = new Map(); this.executionMonitor = new EventExecutionMonitor(); this.eventChains = new Map(); this.crossComponentChannels = new Map(); this.isInitialized = false; + this.isDestroyed = false; this.eventListenersRegistered = false; this.componentListenersRegistered = false; this.debugMode = false; - this.handlerMap = {}; this.eventListenerUnsubscribers = []; - // 绑定方法 this._onGlobalEvent = this._onGlobalEvent.bind(this); this._onSystemError = this._handleSystemError.bind(this); this._cleanupExpiredData = this._cleanupExpiredData.bind(this); - // 延迟初始化,先不调用_initialize() - - // 保存实例到静态属性 GlobalEventManager.instance = this; } @@ -329,41 +265,29 @@ class GlobalEventManager { * 初始化事件管理器 */ async _initialize() { - // 防止重复初始化 if (this.isInitialized) { console.warn('⚠️ 全局事件管理器已初始化,跳过重复初始化'); return; } - // 初始化处理器映射 - this.handlerMap = { - panelHandler, - tabPageHandler, - areaHandler - }; + this.eventRoutes = new Map(); - // 注册全局事件监听器 this._registerGlobalEventListeners(); - - // 启动定期清理 this._startCleanupScheduler(); - - // 启动调试模式监听 this._startDebugMode(); this.isInitialized = true; console.log('✅ 全局事件管理器初始化完成'); - // 触发系统就绪事件 eventBus.emit(GLOBAL_EVENT_TYPES.SYSTEM_READY, { manager: 'globalEventManager', - timestamp: Date.now(), - handlers: Object.keys(this.handlerMap) + timestamp: Date.now() }); } /** * 注册全局事件监听器 + * 添加初始化锁防止并发场景下的重复注册 */ _registerGlobalEventListeners() { // 检查是否已经注册过事件监听器 @@ -372,236 +296,46 @@ class GlobalEventManager { return; } - const globalEvents = Object.values(GLOBAL_EVENT_TYPES); - globalEvents.forEach(eventType => { - const unsubscribe = eventBus.on(eventType, this._onGlobalEvent, { - priority: 0, // 最高优先级 - deduplication: { type: 'TTL_BASED', ttl: 50 } - }); - this.eventListenerUnsubscribers.push(unsubscribe); - }); - - // 注册所有组件事件监听器 - this._registerComponentEventListeners(); - - // 标记为已注册 + // 先标记为正在注册,防止并发 this.eventListenersRegistered = true; - } - - /** - * 注册组件事件监听器 - */ - _registerComponentEventListeners() { - // 防止重复注册组件事件监听器 - if (this.componentListenersRegistered) { - return; - } - - // Panel事件监听 - Object.values(PANEL_EVENT_TYPES).forEach(eventType => { - const unsubscribe = eventBus.on(eventType, this._routeEvent.bind(this, eventType, null, 'panel'), { priority: 1 }); - this.eventListenerUnsubscribers.push(unsubscribe); - }); - - // TabPage事件监听 - Object.values(TABPAGE_EVENT_TYPES).forEach(eventType => { - const unsubscribe = eventBus.on(eventType, this._routeEvent.bind(this, eventType, null, 'tabPage'), { priority: 1 }); - this.eventListenerUnsubscribers.push(unsubscribe); - }); - - // Area事件监听 - Object.values(AREA_EVENT_TYPES).forEach(eventType => { - const unsubscribe = eventBus.on(eventType, this._routeEvent.bind(this, eventType, null, 'area'), { priority: 1 }); - this.eventListenerUnsubscribers.push(unsubscribe); - }); - - this.componentListenersRegistered = true; - } - - /** - * 路由事件到相应的处理器 - * @param {string} eventType - 事件类型 - * @param {Object} eventData - 事件数据 - * @param {string} source - 事件源 - */ - async _routeEvent(eventType, eventData, source) { - const eventId = `${eventType}-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; - const routeConfig = this.eventRoutes.get(eventType); - - // 开始监控 - this.executionMonitor.startMonitoring(eventId, { ...eventData, eventType }); - - eventBus.emit(GLOBAL_EVENT_TYPES.EVENT_ROUTE_START, { - eventId, - eventType, - source, - routeConfig - }); try { - if (routeConfig) { - // 使用配置的路由 - await this._executeRoutedHandlers(eventId, eventType, eventData, routeConfig); - } else { - // 默认路由到相应组件处理器 - await this._executeDefaultHandler(eventId, eventType, eventData, source); - } - - // 完成监控 - this.executionMonitor.completeMonitoring(eventId); - - eventBus.emit(GLOBAL_EVENT_TYPES.EVENT_ROUTE_SUCCESS, { - eventId, - eventType, - source, - timestamp: Date.now() + const globalEvents = Object.values(GLOBAL_EVENT_TYPES); + globalEvents.forEach(eventType => { + const unsubscribe = eventBus.on(eventType, this._onGlobalEvent, { + priority: 0, // 最高优先级 + deduplication: { type: 'TTL_BASED', ttl: 50 }, + componentId: 'global-event-manager' + }); + this.eventListenerUnsubscribers.push(unsubscribe); }); + // 注册所有组件事件监听器 + this._registerComponentEventListeners(); } catch (error) { - // 记录错误 - this.executionMonitor.recordError(eventId, error); - - eventBus.emit(GLOBAL_EVENT_TYPES.EVENT_ROUTE_ERROR, { - eventId, - eventType, - source, - error: error.message, - timestamp: Date.now() - }); - - // 触发系统错误事件 - eventBus.emit(GLOBAL_EVENT_TYPES.SYSTEM_ERROR, { - eventId, - eventType, - source, - error, - timestamp: Date.now() - }); - } - } - - /** - * 执行路由的处理器 - * @param {string} eventId - 事件ID - * @param {string} eventType - 事件类型 - * @param {Object} eventData - 事件数据 - * @param {Object} routeConfig - 路由配置 - */ - async _executeRoutedHandlers(eventId, eventType, eventData, routeConfig) { - const { handlers, priority, timeout = 5000 } = routeConfig; - const handlerPromises = handlers.map(async (handlerName) => { - const startTime = Date.now(); - this.executionMonitor.recordHandlerStart(eventId, handlerName); - - try { - const handler = this.handlerMap[handlerName]; - if (!handler) { - throw new Error(`Handler ${handlerName} not found`); - } - - // 执行处理器 - const result = await this._executeHandlerWithTimeout( - handler, - eventType, - eventData, - timeout - ); - - const duration = Date.now() - startTime; - this.executionMonitor.recordHandlerComplete(eventId, handlerName, true, duration); - - return { handlerName, result, duration }; - - } catch (error) { - const duration = Date.now() - startTime; - this.executionMonitor.recordHandlerComplete(eventId, handlerName, false, duration, error); - throw error; - } - }); - - // 等待所有处理器完成 - const results = await Promise.allSettled(handlerPromises); - - // 检查是否有失败的处理器 - const failures = results.filter(result => result.status === 'rejected'); - if (failures.length > 0) { - throw new Error(`Handler execution failed: ${failures.map(f => f.reason.message).join(', ')}`); - } - - return results.map(result => result.value); - } - - /** - * 执行默认处理器 - * @param {string} eventId - 事件ID - * @param {string} eventType - 事件类型 - * @param {Object} eventData - 事件数据 - * @param {string} source - 事件源 - */ - async _executeDefaultHandler(eventId, eventType, eventData, source) { - const handlerName = `${source}Handler`; - const handler = this.handlerMap[handlerName]; - - if (!handler) { - throw new Error(`Default handler ${handlerName} not found`); - } - - this.executionMonitor.recordHandlerStart(eventId, handlerName); - - try { - const result = await this._executeHandlerWithTimeout(handler, eventType, eventData, 5000); - const duration = Date.now() - this.executionMonitor.getExecutionDetail(eventId).startTime; - this.executionMonitor.recordHandlerComplete(eventId, handlerName, true, duration); - return result; - } catch (error) { - const duration = Date.now() - this.executionMonitor.getExecutionDetail(eventId).startTime; - this.executionMonitor.recordHandlerComplete(eventId, handlerName, false, duration, error); + // 如果注册失败,重置标志位 + this.eventListenersRegistered = false; throw error; } } /** - * 带超时的处理器执行 - * @param {Object} handler - 处理器对象 - * @param {string} eventType - 事件类型 - * @param {Object} eventData - 事件数据 - * @param {number} timeout - 超时时间 - * @returns {Promise} 执行结果 + * 注册组件事件监听器 + * 添加初始化锁防止并发场景下的重复注册 */ - async _executeHandlerWithTimeout(handler, eventType, eventData, timeout) { - return new Promise((resolve, reject) => { - const timer = setTimeout(() => { - reject(new Error(`Handler execution timeout after ${timeout}ms`)); - }, timeout); - - try { - // 假设处理器有处理事件的方法 - if (typeof handler._onEvent === 'function') { - const result = handler._onEvent({ eventType, ...eventData }); - if (result instanceof Promise) { - result.then(resolve).catch(reject).finally(() => clearTimeout(timer)); - } else { - resolve(result); - clearTimeout(timer); - } - } else { - // 如果处理器没有_onEvent方法,直接解析 - resolve(true); - clearTimeout(timer); - } - } catch (error) { - reject(error); - clearTimeout(timer); - } - }); + _registerComponentEventListeners() { + if (this.componentListenersRegistered) { + console.warn('⚠️ 组件事件监听器已经注册,跳过重复注册'); + return; + } + + this.componentListenersRegistered = true; + + console.log('✅ 组件事件监听器注册完成(Handler自行订阅事件)'); } - /** - * 处理全局事件 - * @param {Object} data - 事件数据 - */ async _onGlobalEvent(data) { - const { eventType } = data; + const eventType = data.eventType; try { switch (eventType) { @@ -634,18 +368,8 @@ class GlobalEventManager { async _handleSystemInit(data) { console.log('🚀 系统初始化开始...', data); - // 检查所有处理器是否就绪 - const handlersStatus = {}; - for (const [name, handler] of Object.entries(this.handlerMap)) { - handlersStatus[name] = { - ready: typeof handler === 'object', - methods: Object.getOwnPropertyNames(Object.getPrototypeOf(handler)) - }; - } - eventBus.emit(GLOBAL_EVENT_TYPES.SYSTEM_READY, { manager: 'globalEventManager', - handlersStatus, timestamp: Date.now() }); } @@ -714,37 +438,13 @@ class GlobalEventManager { async _handleCrossComponentRequest(data) { const { requestId, targetComponent, action, payload } = data; - try { - // 查找目标组件的处理器 - const handler = this.handlerMap[`${targetComponent}Handler`]; - if (!handler) { - throw new Error(`Target component handler ${targetComponent}Handler not found`); - } - - // 执行请求 - const result = await handler[action](payload); - - // 发送响应 - eventBus.emit(GLOBAL_EVENT_TYPES.CROSS_COMPONENT_RESPONSE, { - requestId, - targetComponent, - action, - result, - success: true, - timestamp: Date.now() - }); - - } catch (error) { - // 发送错误响应 - eventBus.emit(GLOBAL_EVENT_TYPES.CROSS_COMPONENT_RESPONSE, { - requestId, - targetComponent, - action, - error: error.message, - success: false, - timestamp: Date.now() - }); - } + // 通过事件总线转发跨组件请求 + // 目标Handler应该监听这个事件并处理 + eventBus.emit(`${targetComponent}.${action}`, { + requestId, + payload, + isCrossComponent: true + }); } /** @@ -1032,41 +732,52 @@ class GlobalEventManager { /** * 销毁事件管理器 + * 添加销毁锁防止重复销毁,确保正确清理所有监听器 */ destroy() { - // 清理自己注册的所有事件监听器 - this.eventListenerUnsubscribers.forEach(unsubscribe => { - try { - unsubscribe(); - } catch (error) { - console.warn('清理监听器时出错:', error); - } - }); - this.eventListenerUnsubscribers = []; + if (this.isDestroyed) { + console.warn('⚠️ 全局事件管理器已经被销毁,跳过重复销毁'); + return; + } - // 销毁各个处理器 - Object.values(this.handlerMap).forEach(handler => { - if (typeof handler.destroy === 'function') { - handler.destroy(); - } - }); + this.isDestroyed = true; + + // 清理自己注册的所有事件监听器 + if (this.eventListenerUnsubscribers.length > 0) { + this.eventListenerUnsubscribers.forEach(unsubscribe => { + try { + if (typeof unsubscribe === 'function') { + unsubscribe(); + } + } catch (error) { + console.warn('清理监听器时出错:', error); + } + }); + this.eventListenerUnsubscribers = []; + } // 清理数据 - this.eventHandlers.clear(); - this.eventChains.clear(); - this.crossComponentChannels.clear(); - this.handlerMap = {}; - this.eventRoutes.clear(); + if (this.eventHandlers) this.eventHandlers.clear(); + if (this.eventChains) this.eventChains.clear(); + if (this.crossComponentChannels) this.crossComponentChannels.clear(); + if (this.eventRoutes) this.eventRoutes.clear(); + // 清理执行监控器 + if (this.executionMonitor) { + this.executionMonitor.cleanup(); + } + + // 重置状态标志 this.isInitialized = false; this.eventListenersRegistered = false; this.componentListenersRegistered = false; + console.log('🗑️ 全局事件管理器已销毁,所有监听器已清理'); } } -// 延迟创建单例实例 -let globalEventManager = null; +// 立即创建单例实例,避免并发初始化问题 +const globalEventManager = new GlobalEventManager(); // 全局便捷API export const globalEventActions = { @@ -1076,7 +787,6 @@ export const globalEventActions = { * @returns {string} 监控ID */ startMonitor: (operation) => { - ensureInstance(); return globalEventManager.startPerformanceMonitor(operation); }, @@ -1086,7 +796,6 @@ export const globalEventActions = { * @param {Object} metadata - 元数据 */ endMonitor: (monitorId, metadata = {}) => { - ensureInstance(); globalEventManager.endPerformanceMonitor(monitorId, metadata); }, @@ -1097,7 +806,6 @@ export const globalEventActions = { * @returns {string} 链ID */ createChain: (chainName, events) => { - ensureInstance(); return globalEventManager.createEventChain(chainName, events); }, @@ -1108,7 +816,6 @@ export const globalEventActions = { * @param {Array} targets - 目标组件 */ broadcast: (message, data = {}, targets = null) => { - ensureInstance(); globalEventManager.broadcast(message, data, targets); }, @@ -1120,7 +827,6 @@ export const globalEventActions = { * @returns {Promise} 响应 */ request: (component, action, payload) => { - ensureInstance(); return globalEventManager.request(component, action, payload); }, @@ -1136,7 +842,6 @@ export const globalEventActions = { * @returns {Object} 统计信息 */ getStats: () => { - ensureInstance(); return globalEventManager.getExecutionStats(); }, @@ -1145,7 +850,6 @@ export const globalEventActions = { * @returns {Array} 链状态 */ getChainStatus: () => { - ensureInstance(); return globalEventManager.getEventChainStatus(); }, @@ -1153,7 +857,7 @@ export const globalEventActions = { * 初始化事件管理器 */ initialize: () => { - ensureInstance(); + return globalEventManager; }, /** @@ -1162,31 +866,15 @@ export const globalEventActions = { destroy: () => { if (globalEventManager) { globalEventManager.destroy(); - globalEventManager = null; } } }; -/** - * 确保单例实例存在 - */ -async function ensureInstance() { - if (!globalEventManager) { - globalEventManager = new GlobalEventManager(); - // 异步初始化 - await globalEventManager._initialize(); - } else if (!globalEventManager.isInitialized) { - // 如果实例存在但未初始化,完成初始化 - await globalEventManager._initialize(); - } - return globalEventManager; -} - // 导出单例实例访问方法 -export const getGlobalEventManager = ensureInstance; +export const getGlobalEventManager = () => globalEventManager; // 导出事件管理器和相关API export default { - getInstance: getGlobalEventManager, + getInstance: () => globalEventManager, actions: globalEventActions }; \ No newline at end of file diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/PanelHandler.js b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/PanelHandler.js index 1b6d8d0..7dc3a06 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/PanelHandler.js +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/PanelHandler.js @@ -1,5 +1,5 @@ -import { emitEvent, onEvent, onceEvent, registerHandler, unregisterHandler, getHandlerSnapshot } from '../eventBus' import { nanoid } from 'nanoid' +import { emitEvent, onEvent, onceEvent, registerHandler, unregisterHandler, getHandlerSnapshot } from '../eventBus' // Panel事件类型常量 export const PANEL_EVENT_TYPES = { @@ -151,9 +151,11 @@ class PanelEventHandler { // 触发最大化事件(false表示还原) await emitEvent(PANEL_EVENT_TYPES.MAXIMIZE, { panelId, - maximized: false, - source: 'PanelHandler' - }, { priority }) + maximized: false + }, { + priority, + source: { component: 'PanelHandler', panelId } + }) // 如果需要同步到Area if (syncToArea) { @@ -171,10 +173,12 @@ class PanelEventHandler { */ requestClose(eventData) { const { areaId, panelId } = eventData; - this.emitEvent(PANEL_EVENT_TYPES.CLOSE_REQUEST, { + emitEvent(PANEL_EVENT_TYPES.CLOSE_REQUEST, { areaId, panelId, timestamp: Date.now() + }, { + source: { component: 'PanelHandler', panelId, areaId } }); } @@ -231,9 +235,11 @@ class PanelEventHandler { return emitEvent(PANEL_EVENT_TYPES.TOGGLE_TOOLBAR, { panelId, - toolbarExpanded: panelState.toolbarExpanded, - source: 'PanelHandler' - }, { priority }) + toolbarExpanded: panelState.toolbarExpanded + }, { + priority, + source: { component: 'PanelHandler', panelId } + }) } /** @@ -261,9 +267,11 @@ class PanelEventHandler { return emitEvent(PANEL_EVENT_TYPES.DRAG_START, { panelId, event: dragEvent, - dragState, - source: 'PanelHandler' - }, { priority }) + dragState + }, { + priority, + source: { component: 'PanelHandler', panelId } + }) } /** @@ -325,9 +333,11 @@ class PanelEventHandler { panelId, event: dragEvent, dragState, - duration: dragState.endTime - dragState.startTime, - source: 'PanelHandler' - }, { priority }) + duration: dragState.endTime - dragState.startTime + }, { + priority, + source: { component: 'PanelHandler', panelId } + }) // 清理拖拽状态 this.dragStates.delete(panelId) @@ -353,10 +363,11 @@ class PanelEventHandler { this.panelStates.set(panelId, panelState) return emitEvent(PANEL_EVENT_TYPES.ACTIVATED, { - panelId, - source: 'PanelHandler', - timestamp: Date.now() - }, { priority }) + panelId + }, { + priority, + source: { component: 'PanelHandler', panelId } + }) } /** @@ -377,10 +388,11 @@ class PanelEventHandler { this.panelStates.set(panelId, panelState) return emitEvent(PANEL_EVENT_TYPES.DEACTIVATED, { - panelId, - source: 'PanelHandler', - timestamp: Date.now() - }, { priority }) + panelId + }, { + priority, + source: { component: 'PanelHandler', panelId } + }) } /** @@ -441,10 +453,12 @@ class PanelEventHandler { */ onExpand(eventData) { const { areaId, panelId } = eventData; - this.emitEvent(PANEL_EVENT_TYPES.EXPAND, { + emitEvent(PANEL_EVENT_TYPES.EXPAND, { areaId, panelId, timestamp: Date.now() + }, { + source: { component: 'PanelHandler', panelId, areaId } }); } @@ -454,10 +468,12 @@ class PanelEventHandler { */ onMaximize(eventData) { const { areaId, panelId } = eventData; - this.emitEvent(PANEL_EVENT_TYPES.MAXIMIZE, { + emitEvent(PANEL_EVENT_TYPES.MAXIMIZE, { areaId, panelId, timestamp: Date.now() + }, { + source: { component: 'PanelHandler', panelId, areaId } }); } @@ -480,10 +496,12 @@ class PanelEventHandler { */ onToggleToolbar(eventData) { const { areaId, panelId } = eventData; - this.emitEvent(PANEL_EVENT_TYPES.TOGGLE_TOOLBAR, { + emitEvent(PANEL_EVENT_TYPES.TOGGLE_TOOLBAR, { areaId, panelId, timestamp: Date.now() + }, { + source: { component: 'PanelHandler', panelId, areaId } }); } @@ -507,11 +525,13 @@ class PanelEventHandler { */ onSizeUpdate(eventData) { const { areaId, panelId, size } = eventData; - this.emitEvent('panel.size.update', { + emitEvent('panel.size.update', { areaId, panelId, size, timestamp: Date.now() + }, { + source: { component: 'PanelHandler', panelId, areaId } }); } @@ -567,8 +587,9 @@ class PanelEventHandler { return emitEvent(PANEL_EVENT_TYPES.MAXIMIZE_SYNC, { panelId, maximized, - areaId: panelState.areaId, - source: 'PanelHandler' + areaId: panelState.areaId + }, { + source: { component: 'PanelHandler', panelId, areaId: panelState.areaId } }) } diff --git a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/TabPageHandler.js b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/TabPageHandler.js index e52a16b..f036718 100644 --- a/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/TabPageHandler.js +++ b/AutoRobot/Windows/Robot/Web/src/DockLayout/handlers/TabPageHandler.js @@ -188,7 +188,7 @@ class TabPageEventHandler { * @param {string} data.eventType - 事件类型 */ async _onTabPageEvent(data) { - const { eventType } = data; + const eventType = data.eventType; try { switch (eventType) { @@ -222,7 +222,6 @@ class TabPageEventHandler { } } catch (error) { console.error(`❌ TabPage事件处理错误 (${eventType}):`, error); - eventBus.recordError(`tabPageHandler_${eventType}`, error); } } @@ -591,6 +590,9 @@ function ensureInstance() { return tabPageHandler; } +// 导出单例实例访问方法 +export const getTabPageHandler = ensureInstance; + // TabPage便捷操作函数 export const tabPageActions = { /**