检查Panel的关闭按钮执行关闭的整个过程,解决可能的内存泄漏
This commit is contained in:
@@ -166,8 +166,9 @@ const props = defineProps({
|
||||
// 改为通过DOM动态获取当前所在区域
|
||||
});
|
||||
|
||||
// 事件订阅管理
|
||||
const subscriptions = new Map();
|
||||
// 事件订阅管理 - 使用Set避免key冲突,并添加唯一标识符
|
||||
const subscriptions = new Set();
|
||||
const subscriptionRegistry = new Map(); // 用于追踪订阅详细信息
|
||||
|
||||
// 动态获取当前面板所在的Area ID
|
||||
const getCurrentAreaId = () => {
|
||||
@@ -234,16 +235,155 @@ const onToggleToolbar = () => {
|
||||
};
|
||||
|
||||
// 拖拽相关状态
|
||||
let isDragging = false;
|
||||
let isDragging = false
|
||||
|
||||
// 全局内存泄漏保护机制
|
||||
if (!window.__panelMemoryProtection) {
|
||||
window.__panelMemoryProtection = {
|
||||
// 存储所有面板组件实例追踪信息
|
||||
panelInstances: new Map(),
|
||||
|
||||
// 定时检测内存泄漏(开发环境)
|
||||
startLeakDetection() {
|
||||
if (import.meta.env.DEV) {
|
||||
setInterval(() => {
|
||||
this.detectMemoryLeaks()
|
||||
}, 30000) // 每30秒检测一次
|
||||
}
|
||||
},
|
||||
|
||||
// 检测内存泄漏
|
||||
detectMemoryLeaks() {
|
||||
const activePanels = window.__panelDragHandlers ? window.__panelDragHandlers.size : 0
|
||||
const registeredPanels = this.panelInstances.size
|
||||
|
||||
if (activePanels !== registeredPanels) {
|
||||
console.warn(`[内存泄漏检测] 发现面板内存不一致 - 活动拖拽: ${activePanels}, 注册实例: ${registeredPanels}`)
|
||||
|
||||
// 清理 orphaned handlers
|
||||
if (window.__panelDragHandlers && activePanels > 0) {
|
||||
window.__panelDragHandlers.forEach((handlers, panelId) => {
|
||||
if (!this.panelInstances.has(panelId)) {
|
||||
console.warn(`[内存泄漏检测] 清理orphaned handler: ${panelId}`)
|
||||
document.removeEventListener('mousemove', handlers.dragMoveHandler, false)
|
||||
document.removeEventListener('mouseup', handlers.dragEndHandler, false)
|
||||
document.removeEventListener('mouseleave', handlers.dragEndHandler, false)
|
||||
window.__panelDragHandlers.delete(panelId)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 注册面板实例
|
||||
registerPanel(panelId) {
|
||||
this.panelInstances.set(panelId, {
|
||||
createdAt: Date.now(),
|
||||
lastActivity: Date.now()
|
||||
})
|
||||
},
|
||||
|
||||
// 注销面板实例
|
||||
unregisterPanel(panelId) {
|
||||
this.panelInstances.delete(panelId)
|
||||
},
|
||||
|
||||
// 更新活动状态
|
||||
updateActivity(panelId) {
|
||||
const panel = this.panelInstances.get(panelId)
|
||||
if (panel) {
|
||||
panel.lastActivity = Date.now()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 启动内存泄漏检测
|
||||
window.__panelMemoryProtection.startLeakDetection()
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加Document拖拽事件监听器
|
||||
*/
|
||||
const addDocumentDragListeners = () => {
|
||||
// 移除可能存在的旧监听器
|
||||
cleanupDragEventListeners()
|
||||
|
||||
// 使用组件实例标识符确保清理正确性
|
||||
const componentId = `panel_${props.id}`
|
||||
const dragMoveHandler = (e) => onDragMove(e)
|
||||
const dragEndHandler = (e) => onDragEnd(e)
|
||||
|
||||
// 将处理函数绑定到组件作用域,避免匿名函数导致的清理问题
|
||||
if (!window.__panelDragHandlers) {
|
||||
window.__panelDragHandlers = new Map()
|
||||
}
|
||||
|
||||
window.__panelDragHandlers.set(componentId, {
|
||||
dragMoveHandler,
|
||||
dragEndHandler
|
||||
})
|
||||
|
||||
document.addEventListener('mousemove', dragMoveHandler, false)
|
||||
document.addEventListener('mouseup', dragEndHandler, false)
|
||||
document.addEventListener('mouseleave', dragEndHandler, false)
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`[Panel:${props.id}] Document拖拽事件监听器已添加: ${componentId}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理Document拖拽事件监听器
|
||||
*/
|
||||
const cleanupDragEventListeners = () => {
|
||||
try {
|
||||
const componentId = `panel_${props.id}`
|
||||
|
||||
// 从全局拖拽处理函数映射中获取处理函数
|
||||
const handlers = window.__panelDragHandlers?.get(componentId)
|
||||
|
||||
if (handlers) {
|
||||
// 使用正确的处理函数引用进行清理
|
||||
document.removeEventListener('mousemove', handlers.dragMoveHandler, false)
|
||||
document.removeEventListener('mouseup', handlers.dragEndHandler, false)
|
||||
document.removeEventListener('mouseleave', handlers.dragEndHandler, false)
|
||||
|
||||
// 从映射中移除
|
||||
window.__panelDragHandlers.delete(componentId)
|
||||
|
||||
// 清理映射,如果为空则删除整个映射
|
||||
if (window.__panelDragHandlers.size === 0) {
|
||||
delete window.__panelDragHandlers
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`[Panel:${props.id}] Document拖拽事件监听器已清理: ${componentId}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 立即重置拖拽状态,确保清理完整性
|
||||
isDragging = false
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`[Panel:${props.id}] 清理拖拽事件监听器时出错:`, error)
|
||||
|
||||
// 发生错误时仍然重置状态
|
||||
isDragging = false
|
||||
}
|
||||
}
|
||||
|
||||
// 拖拽开始
|
||||
const onDragStart = (e) => {
|
||||
// 只有当点击的是标题栏区域(不是按钮)时才触发拖拽
|
||||
if (!e.target.closest('.title-bar-buttons') && !e.target.closest('button')) {
|
||||
isDragging = true;
|
||||
// 1. 立即重置之前的拖拽状态
|
||||
isDragging = false
|
||||
cleanupDragEventListeners()
|
||||
|
||||
isDragging = true
|
||||
console.log(`[Panel:${props.id}] 开始拖拽`)
|
||||
|
||||
// 使用事件总线触发拖拽开始事件
|
||||
// 2. 使用事件总线触发拖拽开始事件
|
||||
emitEvent(EVENT_TYPES.PANEL_DRAG_START, {
|
||||
panelId: props.id,
|
||||
areaId: getCurrentAreaId(),
|
||||
@@ -253,14 +393,12 @@ const onDragStart = (e) => {
|
||||
source: { component: 'Panel', panelId: props.id }
|
||||
})
|
||||
|
||||
// 防止文本选择和默认行为
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// 3. 防止文本选择和默认行为
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
||||
// 将鼠标移动和释放事件绑定到document,确保拖拽的连续性
|
||||
document.addEventListener('mousemove', onDragMove);
|
||||
document.addEventListener('mouseup', onDragEnd);
|
||||
document.addEventListener('mouseleave', onDragEnd);
|
||||
// 4. 添加Document事件监听器,使用一次性变量避免内存泄漏
|
||||
addDocumentDragListeners()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -298,10 +436,8 @@ const onDragEnd = () => {
|
||||
source: { component: 'Panel', panelId: props.id }
|
||||
})
|
||||
|
||||
// 拖拽结束后移除事件监听器
|
||||
document.removeEventListener('mousemove', onDragMove);
|
||||
document.removeEventListener('mouseup', onDragEnd);
|
||||
document.removeEventListener('mouseleave', onDragEnd);
|
||||
// 使用统一的清理方法,确保一致性和完整性
|
||||
cleanupDragEventListeners()
|
||||
}
|
||||
};
|
||||
|
||||
@@ -309,33 +445,119 @@ const onDragEnd = () => {
|
||||
* 监听面板关闭事件,更新组件状态(可选)
|
||||
*/
|
||||
const setupEventListeners = () => {
|
||||
// 监听面板最大化同步事件
|
||||
const unsubscribeMaximizeSync = onEvent(EVENT_TYPES.PANEL_MAXIMIZE_SYNC, (data) => {
|
||||
if (data.panelId === props.id) {
|
||||
// 这里可以添加最大化状态同步的逻辑
|
||||
console.log(`[Panel:${props.id}] 收到最大化同步事件`)
|
||||
}
|
||||
})
|
||||
|
||||
subscriptions.set('maximizeSync', unsubscribeMaximizeSync)
|
||||
try {
|
||||
// 监听面板最大化同步事件
|
||||
const unsubscribeMaximizeSync = onEvent(EVENT_TYPES.PANEL_MAXIMIZE_SYNC, (data) => {
|
||||
if (data.panelId === props.id) {
|
||||
// 这里可以添加最大化状态同步的逻辑
|
||||
console.log(`[Panel:${props.id}] 收到最大化同步事件`)
|
||||
}
|
||||
})
|
||||
|
||||
const subscriptionId = `maximizeSync_${props.id}_${Date.now()}`
|
||||
subscriptions.add(unsubscribeMaximizeSync)
|
||||
subscriptionRegistry.set(subscriptionId, {
|
||||
unsubscribe: unsubscribeMaximizeSync,
|
||||
name: 'maximizeSync',
|
||||
createdAt: Date.now()
|
||||
})
|
||||
|
||||
console.log(`[Panel:${props.id}] 事件监听器注册完成,ID: ${subscriptionId}`)
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Panel:${props.id}] 注册事件监听器失败:`, error)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理所有事件订阅
|
||||
* 增强版清理事件监听器,返回清理结果统计
|
||||
*/
|
||||
const cleanupEventListeners = () => {
|
||||
subscriptions.forEach((unsubscribe) => {
|
||||
if (typeof unsubscribe === 'function') {
|
||||
unsubscribe()
|
||||
const cleanupResult = {
|
||||
eventSubscriptions: 0,
|
||||
documentListeners: 0,
|
||||
errors: []
|
||||
}
|
||||
|
||||
try {
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`[Panel:${props.id}] 开始清理所有事件监听器...`)
|
||||
}
|
||||
})
|
||||
subscriptions.clear()
|
||||
|
||||
// 1. 清理事件订阅
|
||||
let unsubscribeCount = 0
|
||||
const subscriptionsToCleanup = Array.from(subscriptions)
|
||||
|
||||
subscriptionsToCleanup.forEach((subscription, index) => {
|
||||
try {
|
||||
if (subscription && typeof subscription === 'function') {
|
||||
subscription() // 执行取消订阅函数
|
||||
unsubscribeCount++
|
||||
} else {
|
||||
console.warn(`[Panel:${props.id}] 发现无效的订阅函数,索引: ${index}`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(`[Panel:${props.id}] 取消订阅时出错,索引: ${index}:`, error)
|
||||
cleanupResult.errors.push(`取消订阅错误 (${index}): ${error.message}`)
|
||||
}
|
||||
})
|
||||
|
||||
// 清空订阅集合和注册表
|
||||
subscriptions.clear()
|
||||
subscriptionRegistry.clear()
|
||||
|
||||
cleanupResult.eventSubscriptions = unsubscribeCount
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`[Panel:${props.id}] 已清理 ${unsubscribeCount} 个事件订阅`)
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Panel:${props.id}] 清理事件订阅时发生严重错误:`, error)
|
||||
cleanupResult.errors.push(`事件订阅清理错误: ${error.message}`)
|
||||
}
|
||||
|
||||
try {
|
||||
// 2. 清理Document事件监听器
|
||||
cleanupDragEventListeners()
|
||||
cleanupResult.documentListeners = 3 // mousemove, mouseup, mouseleave
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Panel:${props.id}] 清理Document事件监听器时发生错误:`, error)
|
||||
cleanupResult.errors.push(`Document事件清理错误: ${error.message}`)
|
||||
}
|
||||
|
||||
// 3. 重置状态
|
||||
try {
|
||||
isDragging = false
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`[Panel:${props.id}] 拖拽状态已重置`)
|
||||
}
|
||||
} catch (error) {
|
||||
cleanupResult.errors.push(`状态重置错误: ${error.message}`)
|
||||
}
|
||||
|
||||
// 4. 输出清理结果摘要
|
||||
if (import.meta.env.DEV) {
|
||||
const totalErrors = cleanupResult.errors.length
|
||||
if (totalErrors > 0) {
|
||||
console.warn(`[Panel:${props.id}] 清理完成,存在 ${totalErrors} 个错误:`, cleanupResult.errors)
|
||||
} else {
|
||||
console.log(`[Panel:${props.id}] 清理完全成功,无错误`)
|
||||
}
|
||||
}
|
||||
|
||||
return cleanupResult
|
||||
}
|
||||
|
||||
// 生命周期钩子
|
||||
onMounted(() => {
|
||||
console.log(`[Panel:${props.id}] 组件已挂载`)
|
||||
|
||||
// 注册到全局内存保护机制
|
||||
if (window.__panelMemoryProtection) {
|
||||
window.__panelMemoryProtection.registerPanel(`panel_${props.id}`)
|
||||
}
|
||||
|
||||
// 启用调试模式(开发环境)
|
||||
if (import.meta.env.DEV) {
|
||||
eventBus.setDebugMode(true)
|
||||
@@ -343,20 +565,61 @@ onMounted(() => {
|
||||
|
||||
// 设置事件监听器
|
||||
setupEventListeners()
|
||||
|
||||
// 设置拖拽相关事件监听器
|
||||
addDocumentDragListeners()
|
||||
|
||||
// 更新活动状态
|
||||
if (window.__panelMemoryProtection) {
|
||||
window.__panelMemoryProtection.updateActivity(`panel_${props.id}`)
|
||||
}
|
||||
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`[Panel:${props.id}] 所有监听器设置完成`)
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
console.log(`[Panel:${props.id}] 组件即将卸载`)
|
||||
|
||||
// 清理事件监听器
|
||||
cleanupEventListeners()
|
||||
// 立即注销全局内存保护机制
|
||||
if (window.__panelMemoryProtection) {
|
||||
window.__panelMemoryProtection.unregisterPanel(`panel_${props.id}`)
|
||||
}
|
||||
|
||||
// 确保拖拽状态已清理
|
||||
if (isDragging) {
|
||||
document.removeEventListener('mousemove', onDragMove);
|
||||
document.removeEventListener('mouseup', onDragEnd);
|
||||
document.removeEventListener('mouseleave', onDragEnd);
|
||||
try {
|
||||
// 1. 立即设置标志位,防止新的异步操作
|
||||
isDragging = false
|
||||
|
||||
// 2. 同步清理所有可以直接清理的资源
|
||||
const cleanupResult = cleanupEventListeners()
|
||||
|
||||
// 3. 记录清理结果
|
||||
if (import.meta.env.DEV) {
|
||||
console.log(`[Panel:${props.id}] 组件清理结果:`, cleanupResult)
|
||||
}
|
||||
|
||||
// 4. 清理超时保护 - 简化版本,防止无限等待
|
||||
const cleanupTimeout = setTimeout(() => {
|
||||
console.warn(`[Panel:${props.id}] 清理超时,但继续卸载`)
|
||||
}, 200) // 缩短超时时间
|
||||
|
||||
// 5. 清理超时定时器,确保不会泄露
|
||||
setTimeout(() => {
|
||||
clearTimeout(cleanupTimeout)
|
||||
}, 250)
|
||||
|
||||
} catch (error) {
|
||||
console.error(`[Panel:${props.id}] 清理过程中出现异常:`, error)
|
||||
|
||||
// 即使出现异常,也要尝试强制清理
|
||||
try {
|
||||
isDragging = false
|
||||
// 强制清理事件监听器
|
||||
cleanupEventListeners()
|
||||
} catch (forceError) {
|
||||
console.error(`[Panel:${props.id}] 强制清理也失败:`, forceError)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user