diff --git a/src/data/extra/web/js/mindmap/features/outline/outline.js b/src/data/extra/web/js/mindmap/features/outline/outline.js index 55ed73a113..7e219c3b95 100644 --- a/src/data/extra/web/js/mindmap/features/outline/outline.js +++ b/src/data/extra/web/js/mindmap/features/outline/outline.js @@ -16,6 +16,13 @@ class OutlineFeature { this.lastSize = null; // 记录最后的大小 this.COLLAPSE_THRESHOLD = 750; // 思维导图尺寸小于这个值时自动折叠 this.titleBarHeight = 45; // 标题栏高度 + + // 添加防抖和监听器管理 + this.updateTimer = null; + this.mutationObserver = null; + this.isUpdating = false; + this.lastUpdateTime = 0; + this.UPDATE_DEBOUNCE_DELAY = 300; // 防抖延迟300ms } /** @@ -34,6 +41,9 @@ class OutlineFeature { */ init() { console.log('OutlineFeature: init called'); + // 先清理之前的实例(如果有的话) + this.cleanupObservers(); + // 先检查并删除已存在的大纲窗口 const existingWindow = document.getElementById('vx-outline-window'); if (existingWindow) { @@ -581,13 +591,44 @@ class OutlineFeature { } /** - * 更新大纲窗口内容 + * 更新大纲窗口内容(带防抖) * 步骤: * 1. 清空现有内容 * 2. 获取根节点数据 * 3. 递归渲染节点结构 */ updateOutlineWindow() { + // 防抖处理 - 清除之前的定时器 + if (this.updateTimer) { + clearTimeout(this.updateTimer); + } + + // 如果正在更新,直接返回 + if (this.isUpdating) { + return; + } + + // 检查更新频率限制 + const now = Date.now(); + const timeSinceLastUpdate = now - this.lastUpdateTime; + + if (timeSinceLastUpdate < 500) { // 500ms内不重复更新 + this.updateTimer = setTimeout(() => { + this.doUpdateOutlineWindow(); + }, this.UPDATE_DEBOUNCE_DELAY); + return; + } + + // 设置延迟更新 + this.updateTimer = setTimeout(() => { + this.doUpdateOutlineWindow(); + }, 50); // 短延迟确保DOM更新完成 + } + + /** + * 实际执行大纲窗口更新 + */ + doUpdateOutlineWindow() { if (!this.outlineWindow) { console.warn('OutlineFeature: outlineWindow not found'); return; @@ -600,6 +641,9 @@ class OutlineFeature { } try { + this.isUpdating = true; + this.lastUpdateTime = Date.now(); + // 获取MindElixir数据 const allData = this.core.mindElixir && this.core.mindElixir.getAllData(); @@ -614,6 +658,13 @@ class OutlineFeature { } catch (error) { console.error('OutlineFeature: Error updating outline window:', error); content.innerHTML = '
数据加载失败
'; + } finally { + this.isUpdating = false; + // 清除定时器 + if (this.updateTimer) { + clearTimeout(this.updateTimer); + this.updateTimer = null; + } } } @@ -948,15 +999,95 @@ class OutlineFeature { * 监听思维导图变化并更新大纲 */ setupDOMObserver() { - const observer = new MutationObserver(() => { - this.updateOutlineWindow(); - }); + // 清理之前的监听器 + this.cleanupObservers(); + + // 监听 MindElixir 的 operation 事件 + this.core.mindElixir.bus.addListener('operation', (operation) => { + console.log('OutlineFeature: Operation detected:', operation.name); + + // 根据操作类型决定是否需要更新大纲 + const outlineUpdateOperations = [ + 'addChild', // 添加子节点 + 'removeNode', // 删除节点 + 'moveNode', // 移动节点 + 'finishEdit' // 完成编辑(文本内容变化) + ]; + + const immediateUpdateOperations = [ + 'addChild', + 'removeNode', + 'moveNode' + ]; + + const delayedUpdateOperations = [ + 'finishEdit' // 编辑完成时再更新,避免输入过程中频繁更新 + ]; + + if (immediateUpdateOperations.includes(operation.name)) { + // 立即更新(有50ms防抖) + this.updateOutlineWindow(); + } else if (delayedUpdateOperations.includes(operation.name)) { + // 延迟更新,给更多时间让用户完成编辑 + if (this.updateTimer) { + clearTimeout(this.updateTimer); + } + this.updateTimer = setTimeout(() => { + this.doUpdateOutlineWindow(); + }, this.UPDATE_DEBOUNCE_DELAY); + } - observer.observe(document.getElementById('vx-mindmap'), { - childList: true, - subtree: true, - characterData: true + // 不再监听 editStyle, editTags, editIcons 等,这些不影响大纲结构 }); + + // 创建单一的 MutationObserver 作为备用监听器 + // 只监听结构性变化,不监听文本内容变化 + const mindmapElement = document.getElementById('vx-mindmap'); + if (mindmapElement) { + this.mutationObserver = new MutationObserver((mutations) => { + let needsUpdate = false; + + mutations.forEach((mutation) => { + // 只关注子节点的添加/删除,忽略文本内容变化 + if (mutation.type === 'childList' && + (mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) { + needsUpdate = true; + } + }); + + if (needsUpdate) { + console.log('OutlineFeature: DOM structure change detected via MutationObserver'); + this.updateOutlineWindow(); + } + }); + + // 只监听子节点变化,不监听characterData + this.mutationObserver.observe(mindmapElement, { + childList: true, + subtree: true + // 不包含 characterData: true,避免文本编辑时的频繁触发 + }); + } + } + + /** + * 清理所有观察器和监听器 + */ + cleanupObservers() { + // 清理定时器 + if (this.updateTimer) { + clearTimeout(this.updateTimer); + this.updateTimer = null; + } + + // 清理 MutationObserver + if (this.mutationObserver) { + this.mutationObserver.disconnect(); + this.mutationObserver = null; + } + + // 重置状态 + this.isUpdating = false; } /** @@ -999,4 +1130,29 @@ class OutlineFeature { }); } } + + /** + * 销毁大纲功能,清理所有资源 + */ + destroy() { + console.log('OutlineFeature: destroy called'); + + // 清理所有观察器和监听器 + this.cleanupObservers(); + + // 移除大纲窗口 + if (this.outlineWindow && this.outlineWindow.parentNode) { + this.outlineWindow.parentNode.removeChild(this.outlineWindow); + } + + // 清理所有引用 + this.outlineWindow = null; + this.core = null; + this.nodeDataMap.clear(); + this.collapsedNodes = null; + this.lastPosition = null; + this.lastSize = null; + + console.log('OutlineFeature: destroyed successfully'); + } } \ No newline at end of file