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