Skip to content

Commit 6fd3703

Browse files
authored
fix-mindmap-outline-update-interval (#2641)
1 parent 924451b commit 6fd3703

File tree

1 file changed

+164
-8
lines changed
  • src/data/extra/web/js/mindmap/features/outline

1 file changed

+164
-8
lines changed

src/data/extra/web/js/mindmap/features/outline/outline.js

Lines changed: 164 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ class OutlineFeature {
1616
this.lastSize = null; // 记录最后的大小
1717
this.COLLAPSE_THRESHOLD = 750; // 思维导图尺寸小于这个值时自动折叠
1818
this.titleBarHeight = 45; // 标题栏高度
19+
20+
// 添加防抖和监听器管理
21+
this.updateTimer = null;
22+
this.mutationObserver = null;
23+
this.isUpdating = false;
24+
this.lastUpdateTime = 0;
25+
this.UPDATE_DEBOUNCE_DELAY = 300; // 防抖延迟300ms
1926
}
2027

2128
/**
@@ -34,6 +41,9 @@ class OutlineFeature {
3441
*/
3542
init() {
3643
console.log('OutlineFeature: init called');
44+
// 先清理之前的实例(如果有的话)
45+
this.cleanupObservers();
46+
3747
// 先检查并删除已存在的大纲窗口
3848
const existingWindow = document.getElementById('vx-outline-window');
3949
if (existingWindow) {
@@ -581,13 +591,44 @@ class OutlineFeature {
581591
}
582592

583593
/**
584-
* 更新大纲窗口内容
594+
* 更新大纲窗口内容(带防抖)
585595
* 步骤:
586596
* 1. 清空现有内容
587597
* 2. 获取根节点数据
588598
* 3. 递归渲染节点结构
589599
*/
590600
updateOutlineWindow() {
601+
// 防抖处理 - 清除之前的定时器
602+
if (this.updateTimer) {
603+
clearTimeout(this.updateTimer);
604+
}
605+
606+
// 如果正在更新,直接返回
607+
if (this.isUpdating) {
608+
return;
609+
}
610+
611+
// 检查更新频率限制
612+
const now = Date.now();
613+
const timeSinceLastUpdate = now - this.lastUpdateTime;
614+
615+
if (timeSinceLastUpdate < 500) { // 500ms内不重复更新
616+
this.updateTimer = setTimeout(() => {
617+
this.doUpdateOutlineWindow();
618+
}, this.UPDATE_DEBOUNCE_DELAY);
619+
return;
620+
}
621+
622+
// 设置延迟更新
623+
this.updateTimer = setTimeout(() => {
624+
this.doUpdateOutlineWindow();
625+
}, 50); // 短延迟确保DOM更新完成
626+
}
627+
628+
/**
629+
* 实际执行大纲窗口更新
630+
*/
631+
doUpdateOutlineWindow() {
591632
if (!this.outlineWindow) {
592633
console.warn('OutlineFeature: outlineWindow not found');
593634
return;
@@ -600,6 +641,9 @@ class OutlineFeature {
600641
}
601642

602643
try {
644+
this.isUpdating = true;
645+
this.lastUpdateTime = Date.now();
646+
603647
// 获取MindElixir数据
604648
const allData = this.core.mindElixir && this.core.mindElixir.getAllData();
605649

@@ -614,6 +658,13 @@ class OutlineFeature {
614658
} catch (error) {
615659
console.error('OutlineFeature: Error updating outline window:', error);
616660
content.innerHTML = '<div style="color: #e74c3c; text-align: center; padding: 20px;">数据加载失败</div>';
661+
} finally {
662+
this.isUpdating = false;
663+
// 清除定时器
664+
if (this.updateTimer) {
665+
clearTimeout(this.updateTimer);
666+
this.updateTimer = null;
667+
}
617668
}
618669
}
619670

@@ -948,15 +999,95 @@ class OutlineFeature {
948999
* 监听思维导图变化并更新大纲
9491000
*/
9501001
setupDOMObserver() {
951-
const observer = new MutationObserver(() => {
952-
this.updateOutlineWindow();
953-
});
1002+
// 清理之前的监听器
1003+
this.cleanupObservers();
1004+
1005+
// 监听 MindElixir 的 operation 事件
1006+
this.core.mindElixir.bus.addListener('operation', (operation) => {
1007+
console.log('OutlineFeature: Operation detected:', operation.name);
1008+
1009+
// 根据操作类型决定是否需要更新大纲
1010+
const outlineUpdateOperations = [
1011+
'addChild', // 添加子节点
1012+
'removeNode', // 删除节点
1013+
'moveNode', // 移动节点
1014+
'finishEdit' // 完成编辑(文本内容变化)
1015+
];
1016+
1017+
const immediateUpdateOperations = [
1018+
'addChild',
1019+
'removeNode',
1020+
'moveNode'
1021+
];
1022+
1023+
const delayedUpdateOperations = [
1024+
'finishEdit' // 编辑完成时再更新,避免输入过程中频繁更新
1025+
];
1026+
1027+
if (immediateUpdateOperations.includes(operation.name)) {
1028+
// 立即更新(有50ms防抖)
1029+
this.updateOutlineWindow();
1030+
} else if (delayedUpdateOperations.includes(operation.name)) {
1031+
// 延迟更新,给更多时间让用户完成编辑
1032+
if (this.updateTimer) {
1033+
clearTimeout(this.updateTimer);
1034+
}
1035+
this.updateTimer = setTimeout(() => {
1036+
this.doUpdateOutlineWindow();
1037+
}, this.UPDATE_DEBOUNCE_DELAY);
1038+
}
9541039

955-
observer.observe(document.getElementById('vx-mindmap'), {
956-
childList: true,
957-
subtree: true,
958-
characterData: true
1040+
// 不再监听 editStyle, editTags, editIcons 等,这些不影响大纲结构
9591041
});
1042+
1043+
// 创建单一的 MutationObserver 作为备用监听器
1044+
// 只监听结构性变化,不监听文本内容变化
1045+
const mindmapElement = document.getElementById('vx-mindmap');
1046+
if (mindmapElement) {
1047+
this.mutationObserver = new MutationObserver((mutations) => {
1048+
let needsUpdate = false;
1049+
1050+
mutations.forEach((mutation) => {
1051+
// 只关注子节点的添加/删除,忽略文本内容变化
1052+
if (mutation.type === 'childList' &&
1053+
(mutation.addedNodes.length > 0 || mutation.removedNodes.length > 0)) {
1054+
needsUpdate = true;
1055+
}
1056+
});
1057+
1058+
if (needsUpdate) {
1059+
console.log('OutlineFeature: DOM structure change detected via MutationObserver');
1060+
this.updateOutlineWindow();
1061+
}
1062+
});
1063+
1064+
// 只监听子节点变化,不监听characterData
1065+
this.mutationObserver.observe(mindmapElement, {
1066+
childList: true,
1067+
subtree: true
1068+
// 不包含 characterData: true,避免文本编辑时的频繁触发
1069+
});
1070+
}
1071+
}
1072+
1073+
/**
1074+
* 清理所有观察器和监听器
1075+
*/
1076+
cleanupObservers() {
1077+
// 清理定时器
1078+
if (this.updateTimer) {
1079+
clearTimeout(this.updateTimer);
1080+
this.updateTimer = null;
1081+
}
1082+
1083+
// 清理 MutationObserver
1084+
if (this.mutationObserver) {
1085+
this.mutationObserver.disconnect();
1086+
this.mutationObserver = null;
1087+
}
1088+
1089+
// 重置状态
1090+
this.isUpdating = false;
9601091
}
9611092

9621093
/**
@@ -999,4 +1130,29 @@ class OutlineFeature {
9991130
});
10001131
}
10011132
}
1133+
1134+
/**
1135+
* 销毁大纲功能,清理所有资源
1136+
*/
1137+
destroy() {
1138+
console.log('OutlineFeature: destroy called');
1139+
1140+
// 清理所有观察器和监听器
1141+
this.cleanupObservers();
1142+
1143+
// 移除大纲窗口
1144+
if (this.outlineWindow && this.outlineWindow.parentNode) {
1145+
this.outlineWindow.parentNode.removeChild(this.outlineWindow);
1146+
}
1147+
1148+
// 清理所有引用
1149+
this.outlineWindow = null;
1150+
this.core = null;
1151+
this.nodeDataMap.clear();
1152+
this.collapsedNodes = null;
1153+
this.lastPosition = null;
1154+
this.lastSize = null;
1155+
1156+
console.log('OutlineFeature: destroyed successfully');
1157+
}
10021158
}

0 commit comments

Comments
 (0)