@bit-sun/business-component 4.2.0-alpha.6.9 → 4.2.5-per-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.umirc.ts +14 -10
- package/dist/components/Business/AddSelectBusiness/index.d.ts +3 -4
- package/dist/components/Business/BsLayouts/Components/AllFunc/drawContent.d.ts +1 -2
- package/dist/components/Business/BsLayouts/Components/ChooseStore/index.d.ts +1 -2
- package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/index.d.ts +1 -1
- package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/leftTree.d.ts +1 -1
- package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/rightTree.d.ts +2 -2
- package/dist/components/Business/BsLayouts/Components/CustomerMenu/globalMenu/DrawContent.d.ts +1 -2
- package/dist/components/Business/BsLayouts/Components/CustomerMenu/globalMenu/customMenuHeader.d.ts +1 -2
- package/dist/components/Business/BsLayouts/Components/CustomerMenu/index.d.ts +1 -1
- package/dist/components/Business/BsLayouts/Components/GlobalHeader/index.d.ts +1 -2
- package/dist/components/Business/BsLayouts/Components/RightContent/LoginModal.d.ts +1 -2
- package/dist/components/Business/BsLayouts/index.d.ts +1 -1
- package/dist/components/Business/BsSulaQueryTable/SearchItemSetting.d.ts +19 -8
- package/dist/components/Business/BsSulaQueryTable/index.d.ts +1 -2
- package/dist/components/Business/BsSulaQueryTable/setting.d.ts +9 -17
- package/dist/components/Business/BsSulaQueryTable/utils.d.ts +14 -15
- package/dist/components/Business/CommodityEntry/index.d.ts +1 -2
- package/dist/components/Business/CommonAlert/index.d.ts +1 -2
- package/dist/components/Business/CommonGuideWrapper/index.d.ts +3 -3
- package/dist/components/Business/DetailPageWrapper/index.d.ts +11 -12
- package/dist/components/Business/HomePageWrapper/index.d.ts +1 -2
- package/dist/components/Business/ItemPropertySelector/index.d.ts +1 -2
- package/dist/components/Business/JsonQueryTable/components/FieldsModifyModal.d.ts +1 -2
- package/dist/components/Business/JsonQueryTable/components/FieldsSettingsTable.d.ts +1 -2
- package/dist/components/Business/JsonQueryTable/components/Formula.d.ts +1 -2
- package/dist/components/Business/JsonQueryTable/components/MaintainOptions.d.ts +1 -2
- package/dist/components/Business/JsonQueryTable/drawer/index.d.ts +1 -2
- package/dist/components/Business/PropertyModal/index.d.ts +1 -2
- package/dist/components/Business/PropertyModal/propertyGroup.d.ts +1 -1
- package/dist/components/Business/SearchSelect/index.d.ts +1 -1
- package/dist/components/Business/StateFlow/index.d.ts +1 -2
- package/dist/components/Business/TreeSearchSelect/index.d.ts +1 -1
- package/dist/components/Business/columnSettingTable/columnSetting.d.ts +6 -6
- package/dist/components/Business/columnSettingTable/components/TableSumComponent.d.ts +1 -2
- package/dist/components/Business/columnSettingTable/index.d.ts +3 -3
- package/dist/components/Business/columnSettingTable/sulaSettingTable.d.ts +3 -3
- package/dist/components/Business/columnSettingTable/utils.d.ts +1 -2
- package/dist/components/Business/moreTreeTable/FixedScrollBar.d.ts +1 -1
- package/dist/components/Common/ParagraphCopier/index.d.ts +1 -1
- package/dist/components/Common/Section/index.d.ts +1 -1
- package/dist/components/Functional/AddSelect/index.d.ts +1 -2
- package/dist/components/Functional/AuthButton/index.d.ts +1 -2
- package/dist/components/Functional/DataImport/index.d.ts +4 -4
- package/dist/components/Functional/DataValidation/index.d.ts +5 -5
- package/dist/components/Functional/ExportFunctions/ExportIcon/index.d.ts +1 -2
- package/dist/components/Functional/QueryMutipleInput/index.d.ts +1 -2
- package/dist/components/Functional/QueryMutipleSelect/index.d.ts +1 -2
- package/dist/components/Functional/SearchSelect/index.d.ts +1 -1
- package/dist/components/Functional/SearchSelect/utils.d.ts +2 -3
- package/dist/components/Functional/TreeSearchSelect/index.d.ts +1 -2
- package/dist/components/Solution/RuleComponent/CustomPlugin/CustomSelector/CustomSelectorModal.d.ts +1 -1
- package/dist/components/Solution/RuleComponent/CustomPlugin/CustomSelector/index.d.ts +1 -2
- package/dist/components/Solution/RuleComponent/Formula.d.ts +1 -2
- package/dist/components/Solution/RuleComponent/InnerSelect.d.ts +1 -2
- package/dist/components/Solution/RuleComponent/RenderCompItem.d.ts +1 -2
- package/dist/components/Solution/RuleSetter/RuleInstance.d.ts +1 -2
- package/dist/components/Solution/RuleSetter/baseRule.d.ts +1 -1
- package/dist/components/Solution/RuleSetter/index.d.ts +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.esm.js +2349 -1832
- package/dist/index.js +2341 -1826
- package/dist/plugin/TableColumnSetting/index.d.ts +5 -5
- package/dist/utils/TableUtils.d.ts +18 -19
- package/dist/utils/luckysheetLoader.d.ts +21 -0
- package/dist/utils/utils.d.ts +0 -41
- package/package.json +1 -1
- package/src/components/Business/BsLayouts/index.tsx +485 -129
- package/src/components/Business/BsSulaQueryTable/SearchItemSetting.tsx +144 -4
- package/src/components/Business/BsSulaQueryTable/index.md +120 -0
- package/src/components/Business/BsSulaQueryTable/index.tsx +236 -22
- package/src/components/Business/BsSulaQueryTable/setting.tsx +232 -17
- package/src/components/Business/DetailPageWrapper/index.tsx +30 -27
- package/src/components/Business/HomePageWrapper/index.tsx +10 -8
- package/src/components/Business/SearchSelect/BusinessUtils.tsx +38 -234
- package/src/components/Business/columnSettingTable/index.tsx +6 -7
- package/src/components/Business/columnSettingTable/sulaSettingTable.tsx +22 -23
- package/src/components/Functional/AddSelect/index.tsx +0 -92
- package/src/components/Functional/DataImport/index.tsx +76 -3
- package/src/components/Functional/DataValidation/index.tsx +81 -3
- package/src/components/Functional/SearchSelect/index.tsx +2 -5
- package/src/components/Solution/RuleComponent/index.js +0 -1
- package/src/index.ts +0 -2
- package/src/utils/luckysheetLoader.ts +164 -0
- package/src/utils/utils.ts +1 -41
- package/dist/components/Business/SystemLog/index.d.ts +0 -78
- package/src/components/Business/SystemLog/index.md +0 -37
- package/src/components/Business/SystemLog/index.tsx +0 -87
|
@@ -109,12 +109,8 @@ const menuDataRender = (menuList: MenuDataItem[]): MenuDataItem[] =>
|
|
|
109
109
|
};
|
|
110
110
|
});
|
|
111
111
|
|
|
112
|
+
// 将全局变量移到类内部,避免内存泄漏
|
|
112
113
|
let UN_LISTTEN_DRP;
|
|
113
|
-
let routerArray = [];
|
|
114
|
-
let authMenuPathList = [];
|
|
115
|
-
let docsId = [];
|
|
116
|
-
let lastTwoRouterArray = [1, 2];
|
|
117
|
-
|
|
118
114
|
let draggerTabKeys = [];
|
|
119
115
|
|
|
120
116
|
const type = 'DraggableTabNode';
|
|
@@ -165,35 +161,6 @@ const DraggableTabs = (props) => {
|
|
|
165
161
|
const { children, changeListenRouterState } = props;
|
|
166
162
|
const [order, setOrder] = useState([]);
|
|
167
163
|
|
|
168
|
-
const moveTabNode = (dragKey, hoverKey) => {
|
|
169
|
-
const newOrder = order.slice();
|
|
170
|
-
React.Children.forEach(children, (c) => {
|
|
171
|
-
if (c.key && newOrder.indexOf(c.key) === -1) {
|
|
172
|
-
newOrder.push(c.key);
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
const dragIndex = newOrder.indexOf(dragKey);
|
|
176
|
-
const hoverIndex = newOrder.indexOf(hoverKey);
|
|
177
|
-
newOrder.splice(dragIndex, 1);
|
|
178
|
-
newOrder.splice(hoverIndex, 0, dragKey);
|
|
179
|
-
changeListenRouterState(dragIndex, hoverIndex);
|
|
180
|
-
setOrder(newOrder);
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const renderTabBar = (tabBarProps, DefaultTabBar) => (
|
|
184
|
-
<DefaultTabBar {...tabBarProps}>
|
|
185
|
-
{(node) => (
|
|
186
|
-
<DraggableTabNode
|
|
187
|
-
key={node.key}
|
|
188
|
-
index={node.key}
|
|
189
|
-
moveNode={moveTabNode}
|
|
190
|
-
>
|
|
191
|
-
{node}
|
|
192
|
-
</DraggableTabNode>
|
|
193
|
-
)}
|
|
194
|
-
</DefaultTabBar>
|
|
195
|
-
);
|
|
196
|
-
|
|
197
164
|
const tabs = [];
|
|
198
165
|
React.Children.forEach(children, (c) => {
|
|
199
166
|
tabs.push(c);
|
|
@@ -219,11 +186,9 @@ const DraggableTabs = (props) => {
|
|
|
219
186
|
return ia - ib;
|
|
220
187
|
});
|
|
221
188
|
return (
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
</Tabs>
|
|
226
|
-
</DndProvider>
|
|
189
|
+
<Tabs {...props}>
|
|
190
|
+
{orderTabs}
|
|
191
|
+
</Tabs>
|
|
227
192
|
);
|
|
228
193
|
};
|
|
229
194
|
|
|
@@ -326,17 +291,38 @@ class BasicLayout extends React.PureComponent {
|
|
|
326
291
|
actionRef: any = createRef<{
|
|
327
292
|
reload: () => void;
|
|
328
293
|
}>();
|
|
294
|
+
|
|
295
|
+
// 将全局变量移到实例属性,避免多实例间的内存泄漏
|
|
296
|
+
private routerArray = [];
|
|
297
|
+
private authMenuPathList = [];
|
|
298
|
+
private docsId = [];
|
|
299
|
+
private lastTwoRouterArray = [1, 2];
|
|
300
|
+
private wujieEventHandler = null;
|
|
301
|
+
private timeoutIds = new Set(); // 管理所有setTimeout
|
|
302
|
+
|
|
329
303
|
constructor(props) {
|
|
330
304
|
super(props);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
305
|
+
const authData = getAuthMenuPathAndDocsId(props.pathToRegexp);
|
|
306
|
+
this.authMenuPathList = authData?.menuKeys || [];
|
|
307
|
+
this.docsId = authData?.docsId || [];
|
|
308
|
+
this.routerArray = this.updateTree(props.route.routes, this.authMenuPathList);
|
|
309
|
+
|
|
310
|
+
// 初始化内存管理相关属性
|
|
311
|
+
this.timeoutIds = new Set();
|
|
312
|
+
this.wujieEventHandler = null;
|
|
313
|
+
this.cachedTabsElements = null;
|
|
314
|
+
this.cachedBreadcrumbNameMap = null;
|
|
315
|
+
this.cachedWeiqianduanProps = null;
|
|
316
|
+
this.cachedOperationsSlot = null;
|
|
317
|
+
this.lastIsSliderState = null;
|
|
318
|
+
const homeRouter = this.routerArray.filter(
|
|
335
319
|
(itemroute) => itemroute.key === '/',
|
|
336
320
|
)[0];
|
|
337
|
-
|
|
321
|
+
// 缓存breadcrumbNameMap,避免重复创建
|
|
322
|
+
this.cachedBreadcrumbNameMap = this.cachedBreadcrumbNameMap || getBreadcrumbNameMap(
|
|
338
323
|
memoizeOneFormatter(props.route.routes, ''),
|
|
339
324
|
);
|
|
325
|
+
const breadcrumbNameMap = this.cachedBreadcrumbNameMap;
|
|
340
326
|
const hideMenuArray = ergodicMenuRoutes(props.route.routes);
|
|
341
327
|
|
|
342
328
|
this.state = {
|
|
@@ -378,6 +364,18 @@ class BasicLayout extends React.PureComponent {
|
|
|
378
364
|
pathToRegexp,
|
|
379
365
|
} = this.props;
|
|
380
366
|
|
|
367
|
+
// 将测试方法暴露到全局,方便调试
|
|
368
|
+
window.testTabDeletion = this.testTabDeletion;
|
|
369
|
+
window.countTabPaneDOMNodes = this.countTabPaneDOMNodes;
|
|
370
|
+
window.testDeleteTab = (key) => {
|
|
371
|
+
console.log(`[测试] 手动删除页签: ${key}`);
|
|
372
|
+
this.tabActions.remove(key);
|
|
373
|
+
};
|
|
374
|
+
window.forceCleanupDOM = this.forceCleanupDOM;
|
|
375
|
+
window.analyzeMemoryLeaks = this.analyzeMemoryLeaks;
|
|
376
|
+
window.forceClearTabPanes = this.forceClearTabPanes;
|
|
377
|
+
console.log('[调试工具] 已暴露全局方法: window.testTabDeletion(), window.countTabPaneDOMNodes(), window.testDeleteTab(key), window.forceCleanupDOM(), window.analyzeMemoryLeaks(), window.forceClearTabPanes()');
|
|
378
|
+
|
|
381
379
|
let istParent = 0;
|
|
382
380
|
|
|
383
381
|
var self = this;
|
|
@@ -402,7 +400,8 @@ class BasicLayout extends React.PureComponent {
|
|
|
402
400
|
// }
|
|
403
401
|
// });
|
|
404
402
|
|
|
405
|
-
|
|
403
|
+
// 优化事件处理器,避免内存泄漏
|
|
404
|
+
this.wujieEventHandler = (appname, info) => {
|
|
406
405
|
if (appname === itemPath) {
|
|
407
406
|
if (localStorage.getItem(ENUM.BROWSER_CACHE.CHILD_APP_BACK)) {
|
|
408
407
|
localStorage.removeItem(ENUM.BROWSER_CACHE.CHILD_APP_BACK);
|
|
@@ -411,14 +410,15 @@ class BasicLayout extends React.PureComponent {
|
|
|
411
410
|
}
|
|
412
411
|
istParent = 1;
|
|
413
412
|
if (info.type === 'main') {
|
|
414
|
-
|
|
415
|
-
history.push(newPath)
|
|
413
|
+
const newPath = encodeUrlQuery(info.path);
|
|
414
|
+
history.push(newPath);
|
|
416
415
|
}
|
|
417
416
|
if (info.type === 'remove') {
|
|
418
417
|
self.tabActions['remove'](info.path);
|
|
419
418
|
}
|
|
420
419
|
}
|
|
421
|
-
}
|
|
420
|
+
};
|
|
421
|
+
window.$wujie?.bus.$on("main-route-change", this.wujieEventHandler);
|
|
422
422
|
|
|
423
423
|
UN_LISTTEN_DRP = history.listen((route) => {
|
|
424
424
|
// if ((window as any).__POWERED_BY_QIANKUN__) {
|
|
@@ -442,7 +442,7 @@ class BasicLayout extends React.PureComponent {
|
|
|
442
442
|
* @param docsId 通用单据ID的数组,用于进一步筛选路由。
|
|
443
443
|
* @returns 返回与当前路由匹配的第一个路由项,如果没有匹配项则返回undefined。
|
|
444
444
|
*/
|
|
445
|
-
let replaceRouter = routerArray.filter((itemRoute) => {
|
|
445
|
+
let replaceRouter = this.routerArray.filter((itemRoute) => {
|
|
446
446
|
// 单独处理通用单据预览
|
|
447
447
|
if (window.top !== window && !window.__POWERED_BY_WUJIE__ && route.pathname?.includes('all-general-documents')) {
|
|
448
448
|
return pathToRegexp(itemRoute.key || '').test(route.pathname);
|
|
@@ -450,7 +450,7 @@ class BasicLayout extends React.PureComponent {
|
|
|
450
450
|
// 当路由路径包含'all-general-documents'时,按通用单据处理
|
|
451
451
|
if (route.pathname?.includes('all-general-documents') && shouldUseAuth()) {
|
|
452
452
|
// 检查路由路径是否匹配路由项的键,并且路径中包含至少一个通用单据ID
|
|
453
|
-
return pathToRegexp(itemRoute.key || '').test(route.pathname) && docsId.some(item => route.pathname.includes(item));
|
|
453
|
+
return pathToRegexp(itemRoute.key || '').test(route.pathname) && this.docsId.some(item => route.pathname.includes(item));
|
|
454
454
|
}
|
|
455
455
|
// 对于不包含'all-general-documents'的路径,只检查路由路径是否匹配路由项的键
|
|
456
456
|
return pathToRegexp(itemRoute.key || '').test(route.pathname);
|
|
@@ -528,8 +528,8 @@ class BasicLayout extends React.PureComponent {
|
|
|
528
528
|
}
|
|
529
529
|
|
|
530
530
|
// -------------------处理页签关闭----------------------------
|
|
531
|
-
lastTwoRouterArray.push(route.pathname);
|
|
532
|
-
lastTwoRouterArray.shift();
|
|
531
|
+
this.lastTwoRouterArray.push(route.pathname);
|
|
532
|
+
this.lastTwoRouterArray.shift();
|
|
533
533
|
|
|
534
534
|
const {
|
|
535
535
|
thisHideInMenuDoNotClose = false,
|
|
@@ -539,18 +539,18 @@ class BasicLayout extends React.PureComponent {
|
|
|
539
539
|
|
|
540
540
|
let needRemoveKey = '';
|
|
541
541
|
|
|
542
|
-
// lastTwoRouterArray[0] != lastTwoRouterArray[1] 该判断条件用于判断是否是tab删除操作,如果是tab页删除操作,删除的是不是最后一个打开的tab页,执行history.push会导致错误的将最后打开的那个tab页也删除掉
|
|
543
|
-
|
|
542
|
+
// this.lastTwoRouterArray[0] != this.lastTwoRouterArray[1] 该判断条件用于判断是否是tab删除操作,如果是tab页删除操作,删除的是不是最后一个打开的tab页,执行history.push会导致错误的将最后打开的那个tab页也删除掉
|
|
543
|
+
const notSamePageFlag = this.lastTwoRouterArray[0] !== this.lastTwoRouterArray[1];
|
|
544
544
|
|
|
545
545
|
// 满足包含closePrevPage标识则直接删除上一页
|
|
546
546
|
if (closePrevPage) {
|
|
547
|
-
needRemoveKey = lastTwoRouterArray[0] && typeof lastTwoRouterArray[0] === 'string' && notSamePageFlag ? lastTwoRouterArray[0] : '';
|
|
547
|
+
needRemoveKey = this.lastTwoRouterArray[0] && typeof this.lastTwoRouterArray[0] === 'string' && notSamePageFlag ? this.lastTwoRouterArray[0] : '';
|
|
548
548
|
} else {
|
|
549
549
|
// 满足非tabclick或者menuClick的hideInMenu类型菜单自动关闭
|
|
550
550
|
const shouldClosePrev = (!localStorage.getItem('isTabChange') && !localStorage.getItem('isMenuClick'));
|
|
551
|
-
const needRemoveKeyArray = lastTwoRouterArray[0] && typeof lastTwoRouterArray[0] === 'string' && shouldClosePrev ?
|
|
551
|
+
const needRemoveKeyArray = this.lastTwoRouterArray[0] && typeof this.lastTwoRouterArray[0] === 'string' && shouldClosePrev ?
|
|
552
552
|
hideMenuArray.filter((itemRoute) =>
|
|
553
|
-
pathToRegexp(itemRoute.path || '').test(lastTwoRouterArray[0]),
|
|
553
|
+
pathToRegexp(itemRoute.path || '').test(this.lastTwoRouterArray[0]),
|
|
554
554
|
) : [];
|
|
555
555
|
needRemoveKey = needRemoveKeyArray.length && notSamePageFlag && !thisHideInMenuDoNotClose ? needRemoveKeyArray[0] : '';
|
|
556
556
|
}
|
|
@@ -558,19 +558,22 @@ class BasicLayout extends React.PureComponent {
|
|
|
558
558
|
if (needRemoveKey) {
|
|
559
559
|
newListenRouterState = newListenRouterState.filter((item) => {
|
|
560
560
|
const [pathname] = item.key ? item.key.split('?') : [];
|
|
561
|
-
return pathname && pathname !== lastTwoRouterArray[0];
|
|
561
|
+
return pathname && pathname !== this.lastTwoRouterArray[0];
|
|
562
562
|
});
|
|
563
563
|
newListenRouterKey = newListenRouterKey.filter((item) => {
|
|
564
564
|
const [pathname] = item ? item.split('?') : [];
|
|
565
|
-
return pathname && pathname !== lastTwoRouterArray[0];
|
|
565
|
+
return pathname && pathname !== this.lastTwoRouterArray[0];
|
|
566
566
|
});
|
|
567
567
|
}
|
|
568
568
|
|
|
569
|
-
setTimeout
|
|
569
|
+
// 优化setTimeout管理,避免内存泄漏
|
|
570
|
+
const timeoutId = setTimeout(() => {
|
|
570
571
|
// 处理页面刷新两面
|
|
571
572
|
localStorage.removeItem('isTabChange');
|
|
572
573
|
localStorage.removeItem('isMenuClick');
|
|
574
|
+
this.timeoutIds.delete(timeoutId);
|
|
573
575
|
}, 0);
|
|
576
|
+
this.timeoutIds.add(timeoutId);
|
|
574
577
|
|
|
575
578
|
// -------------------处理页签关闭 end----------------------------
|
|
576
579
|
|
|
@@ -607,8 +610,34 @@ class BasicLayout extends React.PureComponent {
|
|
|
607
610
|
}
|
|
608
611
|
|
|
609
612
|
componentWillUnmount() {
|
|
610
|
-
//
|
|
611
|
-
|
|
613
|
+
// 清理路由监听器
|
|
614
|
+
if (UN_LISTTEN_DRP) {
|
|
615
|
+
UN_LISTTEN_DRP();
|
|
616
|
+
UN_LISTTEN_DRP = null;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// 清理wujie事件监听器
|
|
620
|
+
if (this.wujieEventHandler && window.$wujie?.bus) {
|
|
621
|
+
window.$wujie.bus.$off("main-route-change", this.wujieEventHandler);
|
|
622
|
+
this.wujieEventHandler = null;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
// 清理所有setTimeout
|
|
626
|
+
this.timeoutIds.forEach(timeoutId => {
|
|
627
|
+
clearTimeout(timeoutId);
|
|
628
|
+
});
|
|
629
|
+
this.timeoutIds.clear();
|
|
630
|
+
|
|
631
|
+
// 清理实例属性
|
|
632
|
+
this.routerArray = null;
|
|
633
|
+
this.authMenuPathList = null;
|
|
634
|
+
this.docsId = null;
|
|
635
|
+
this.lastTwoRouterArray = null;
|
|
636
|
+
this.cachedTabsElements = null;
|
|
637
|
+
this.cachedBreadcrumbNameMap = null;
|
|
638
|
+
this.cachedWeiqianduanProps = null;
|
|
639
|
+
this.cachedOperationsSlot = null;
|
|
640
|
+
this.lastIsSliderState = null;
|
|
612
641
|
}
|
|
613
642
|
|
|
614
643
|
parseQueryString = (queryString) => {
|
|
@@ -809,8 +838,144 @@ class BasicLayout extends React.PureComponent {
|
|
|
809
838
|
this.tabActions[action](targetKey);
|
|
810
839
|
};
|
|
811
840
|
|
|
841
|
+
// DOM节点计数辅助函数
|
|
842
|
+
countTabPaneDOMNodes = () => {
|
|
843
|
+
const tabPanes = document.querySelectorAll('.ant-tabs-tabpane');
|
|
844
|
+
const globalTabsTabPanes = document.querySelectorAll('#globalTabs .ant-tabs-tabpane');
|
|
845
|
+
const activeTabPanes = document.querySelectorAll('#globalTabs .ant-tabs-tabpane-active');
|
|
846
|
+
const hiddenTabPanes = document.querySelectorAll('#globalTabs .ant-tabs-tabpane:not(.ant-tabs-tabpane-active)');
|
|
847
|
+
|
|
848
|
+
console.log(`[DOM计数] 全局TabPane节点数: ${tabPanes.length}, globalTabs内TabPane节点数: ${globalTabsTabPanes.length}`);
|
|
849
|
+
console.log(`[DOM计数] 活跃TabPane节点数: ${activeTabPanes.length}, 非活跃TabPane节点数: ${hiddenTabPanes.length}`);
|
|
850
|
+
|
|
851
|
+
// 详细列出每个TabPane的key和状态
|
|
852
|
+
globalTabsTabPanes.forEach((pane, index) => {
|
|
853
|
+
const isActive = pane.classList.contains('ant-tabs-tabpane-active');
|
|
854
|
+
const key = pane.getAttribute('data-node-key') || pane.id || `未知-${index}`;
|
|
855
|
+
console.log(`[DOM详情] TabPane ${index + 1}: key=${key}, 活跃=${isActive}`);
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
return {
|
|
859
|
+
total: tabPanes.length,
|
|
860
|
+
globalTabs: globalTabsTabPanes.length,
|
|
861
|
+
active: activeTabPanes.length,
|
|
862
|
+
hidden: hiddenTabPanes.length
|
|
863
|
+
};
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
// 测试方法:验证页签删除后DOM状态
|
|
867
|
+
testTabDeletion = () => {
|
|
868
|
+
console.log('=== 页签删除测试开始 ===');
|
|
869
|
+
console.log('当前页签状态:', this.state.listenRouterState.map(item => item.key));
|
|
870
|
+
console.log('当前活跃页签:', this.state.activeKey);
|
|
871
|
+
this.countTabPaneDOMNodes();
|
|
872
|
+
|
|
873
|
+
// 提供删除建议
|
|
874
|
+
const { listenRouterState } = this.state;
|
|
875
|
+
if (listenRouterState.length > 1) {
|
|
876
|
+
const testKey = listenRouterState.find(item => item.key !== '/')?.key;
|
|
877
|
+
if (testKey) {
|
|
878
|
+
console.log(`建议测试:删除页签 ${testKey}`);
|
|
879
|
+
console.log(`执行命令:window.testDeleteTab('${testKey}')`);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
console.log('=== 页签删除测试结束 ===');
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// 强制清理DOM节点的方法
|
|
886
|
+
forceCleanupDOM = () => {
|
|
887
|
+
console.log('[强制清理] 开始强制清理DOM节点...');
|
|
888
|
+
const beforeCount = this.countTabPaneDOMNodes();
|
|
889
|
+
console.log(`[强制清理] 清理前DOM节点数: ${beforeCount.total}`);
|
|
890
|
+
|
|
891
|
+
// 强制垃圾回收
|
|
892
|
+
if (window.gc) {
|
|
893
|
+
window.gc();
|
|
894
|
+
console.log('[强制清理] 已触发垃圾回收');
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// 清理所有可能的引用
|
|
898
|
+
const tabsContainer = document.getElementById('globalTabs');
|
|
899
|
+
if (tabsContainer) {
|
|
900
|
+
const tabPanes = tabsContainer.querySelectorAll('.ant-tabs-tabpane');
|
|
901
|
+
tabPanes.forEach((pane, index) => {
|
|
902
|
+
if (pane.style.display === 'none' || pane.getAttribute('aria-hidden') === 'true') {
|
|
903
|
+
console.log(`[强制清理] 发现隐藏的TabPane节点 ${index}, 尝试清理...`);
|
|
904
|
+
// 清理事件监听器
|
|
905
|
+
const clone = pane.cloneNode(true);
|
|
906
|
+
pane.parentNode?.replaceChild(clone, pane);
|
|
907
|
+
}
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
setTimeout(() => {
|
|
912
|
+
const afterCount = this.countTabPaneDOMNodes();
|
|
913
|
+
console.log(`[强制清理] 清理后DOM节点数: ${afterCount.total}`);
|
|
914
|
+
console.log(`[强制清理] DOM节点减少: ${beforeCount.total - afterCount.total}`);
|
|
915
|
+
}, 200);
|
|
916
|
+
};
|
|
917
|
+
|
|
918
|
+
// 分析内存泄漏的方法
|
|
919
|
+
analyzeMemoryLeaks = () => {
|
|
920
|
+
console.log('[内存分析] 开始分析可能的内存泄漏...');
|
|
921
|
+
|
|
922
|
+
// 检查全局事件监听器
|
|
923
|
+
const listeners = window.getEventListeners ? window.getEventListeners(document) : {};
|
|
924
|
+
console.log('[内存分析] 全局事件监听器:', listeners);
|
|
925
|
+
|
|
926
|
+
// 检查定时器
|
|
927
|
+
console.log('[内存分析] 当前活跃定时器数量:', Object.keys(window).filter(key => key.includes('timeout') || key.includes('interval')).length);
|
|
928
|
+
|
|
929
|
+
// 检查TabPane节点详情
|
|
930
|
+
const tabsContainer = document.getElementById('globalTabs');
|
|
931
|
+
if (tabsContainer) {
|
|
932
|
+
const tabPanes = tabsContainer.querySelectorAll('.ant-tabs-tabpane');
|
|
933
|
+
console.log(`[内存分析] 发现 ${tabPanes.length} 个TabPane节点:`);
|
|
934
|
+
tabPanes.forEach((pane, index) => {
|
|
935
|
+
const key = pane.getAttribute('data-node-key') || pane.id;
|
|
936
|
+
const isVisible = pane.style.display !== 'none' && pane.getAttribute('aria-hidden') !== 'true';
|
|
937
|
+
const hasContent = pane.children.length > 0;
|
|
938
|
+
console.log(` TabPane ${index}: key=${key}, visible=${isVisible}, hasContent=${hasContent}, children=${pane.children.length}`);
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
// 检查React组件实例
|
|
943
|
+
const reactInstances = document.querySelectorAll('[data-reactroot], [data-react-checksum]');
|
|
944
|
+
console.log(`[内存分析] React实例数量: ${reactInstances.length}`);
|
|
945
|
+
};
|
|
946
|
+
|
|
947
|
+
// 强制清除所有TabPane节点
|
|
948
|
+
forceClearTabPanes = () => {
|
|
949
|
+
console.log('[强制清除] 开始强制清除所有TabPane节点...');
|
|
950
|
+
const beforeCount = this.countTabPaneDOMNodes();
|
|
951
|
+
|
|
952
|
+
const tabsContainer = document.getElementById('globalTabs');
|
|
953
|
+
if (tabsContainer) {
|
|
954
|
+
const tabPanes = tabsContainer.querySelectorAll('.ant-tabs-tabpane');
|
|
955
|
+
console.log(`[强制清除] 发现 ${tabPanes.length} 个TabPane节点,准备清除...`);
|
|
956
|
+
|
|
957
|
+
tabPanes.forEach((pane, index) => {
|
|
958
|
+
const key = pane.getAttribute('data-node-key') || pane.id;
|
|
959
|
+
const isActive = !pane.getAttribute('aria-hidden') && pane.style.display !== 'none';
|
|
960
|
+
|
|
961
|
+
if (!isActive) {
|
|
962
|
+
console.log(`[强制清除] 移除非活跃TabPane ${index} (key: ${key})`);
|
|
963
|
+
pane.remove();
|
|
964
|
+
}
|
|
965
|
+
});
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
setTimeout(() => {
|
|
969
|
+
const afterCount = this.countTabPaneDOMNodes();
|
|
970
|
+
console.log(`[强制清除] 清除前: ${beforeCount.total}, 清除后: ${afterCount.total}, 减少: ${beforeCount.total - afterCount.total}`);
|
|
971
|
+
}, 100);
|
|
972
|
+
};
|
|
973
|
+
|
|
812
974
|
tabActions: any = {
|
|
813
975
|
remove: (targetKey: string) => {
|
|
976
|
+
console.log(`[页签删除] 开始删除页签: ${targetKey}`);
|
|
977
|
+
this.countTabPaneDOMNodes();
|
|
978
|
+
|
|
814
979
|
const { listenRouterState, activeKey, customerMatchs, listenRouterKey } =
|
|
815
980
|
this.state;
|
|
816
981
|
|
|
@@ -859,6 +1024,12 @@ class BasicLayout extends React.PureComponent {
|
|
|
859
1024
|
pathname: newKey,
|
|
860
1025
|
});
|
|
861
1026
|
this.checkisNavSlide();
|
|
1027
|
+
|
|
1028
|
+
// 延迟检查DOM节点变化,确保React完成渲染
|
|
1029
|
+
setTimeout(() => {
|
|
1030
|
+
console.log(`[页签删除] 删除页签 ${targetKey} 后的DOM状态:`);
|
|
1031
|
+
this.countTabPaneDOMNodes();
|
|
1032
|
+
}, 100);
|
|
862
1033
|
},
|
|
863
1034
|
);
|
|
864
1035
|
},
|
|
@@ -929,49 +1100,53 @@ class BasicLayout extends React.PureComponent {
|
|
|
929
1100
|
});
|
|
930
1101
|
}, 500);
|
|
931
1102
|
|
|
1103
|
+
// 缓存DOM元素,避免重复查询
|
|
1104
|
+
getTabsNavElements = () => {
|
|
1105
|
+
if (!this.cachedTabsElements || !this.cachedTabsElements.globalTabsNav) {
|
|
1106
|
+
const globalTabsContainer = document.getElementById('globalTabs');
|
|
1107
|
+
if (globalTabsContainer) {
|
|
1108
|
+
this.cachedTabsElements = {
|
|
1109
|
+
globalTabsNav: globalTabsContainer.getElementsByClassName('ant-tabs-nav-list')?.[0],
|
|
1110
|
+
globalTabsNavWrap: globalTabsContainer.getElementsByClassName('ant-tabs-nav-wrap')?.[0]
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return this.cachedTabsElements || {};
|
|
1115
|
+
};
|
|
1116
|
+
|
|
932
1117
|
//设置tabs标签左右滚动
|
|
933
1118
|
setTabNavTransLate = (num) => {
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
?.getElementsByClassName('ant-tabs-nav-wrap')?.[0];
|
|
940
|
-
let wrapWidth = globalTabsNavWrap.offsetWidth; //tabsNav父节点宽度
|
|
941
|
-
let navListWidth = globalTabsNav.offsetWidth; //tabsNav总宽度
|
|
1119
|
+
const { globalTabsNav, globalTabsNavWrap } = this.getTabsNavElements();
|
|
1120
|
+
if (!globalTabsNav || !globalTabsNavWrap) return;
|
|
1121
|
+
|
|
1122
|
+
const wrapWidth = globalTabsNavWrap.offsetWidth; //tabsNav父节点宽度
|
|
1123
|
+
const navListWidth = globalTabsNav.offsetWidth; //tabsNav总宽度
|
|
942
1124
|
if (navListWidth - wrapWidth <= 0) return;
|
|
943
|
-
|
|
944
|
-
|
|
1125
|
+
|
|
1126
|
+
const maxTransX = navListWidth - wrapWidth; // 允许移动最大宽度
|
|
1127
|
+
const transXStr = document.defaultView?.getComputedStyle(
|
|
945
1128
|
globalTabsNav,
|
|
946
1129
|
null,
|
|
947
1130
|
).transform;
|
|
948
|
-
|
|
1131
|
+
const transx = transXStr?.split(',')[4]; //当前translateX的值
|
|
949
1132
|
|
|
950
1133
|
let targetTransX = Math.abs(Number(transx)) + num;
|
|
951
1134
|
if (targetTransX <= 0) targetTransX = 0;
|
|
952
1135
|
if (targetTransX >= Number(maxTransX)) targetTransX = Number(maxTransX);
|
|
953
|
-
globalTabsNav.style.transform =
|
|
1136
|
+
globalTabsNav.style.transform = `translateX(-${targetTransX}px)`;
|
|
954
1137
|
};
|
|
955
1138
|
|
|
956
1139
|
checkisNavSlide = () => {
|
|
957
|
-
if (window.top
|
|
1140
|
+
if (window.top !== window) return;
|
|
958
1141
|
//监听tabs页签总长度判断是否可点击滑动
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
let wrapWidth = globalTabsNavWrap?.offsetWidth; //tabsNav父节点宽度
|
|
966
|
-
let navListWidth = globalTabsNav?.offsetWidth; //tabsNav总宽度
|
|
967
|
-
if (navListWidth - wrapWidth <= 0) {
|
|
968
|
-
this.setState({
|
|
969
|
-
isSlider: false,
|
|
970
|
-
});
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
1142
|
+
const { globalTabsNav, globalTabsNavWrap } = this.getTabsNavElements();
|
|
1143
|
+
if (!globalTabsNav || !globalTabsNavWrap) return;
|
|
1144
|
+
|
|
1145
|
+
const wrapWidth = globalTabsNavWrap.offsetWidth; //tabsNav父节点宽度
|
|
1146
|
+
const navListWidth = globalTabsNav.offsetWidth; //tabsNav总宽度
|
|
1147
|
+
|
|
973
1148
|
this.setState({
|
|
974
|
-
isSlider:
|
|
1149
|
+
isSlider: navListWidth - wrapWidth > 0,
|
|
975
1150
|
});
|
|
976
1151
|
};
|
|
977
1152
|
|
|
@@ -1251,54 +1426,58 @@ class BasicLayout extends React.PureComponent {
|
|
|
1251
1426
|
})
|
|
1252
1427
|
.filter((item) => item) as MenuDataItem[];
|
|
1253
1428
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
isWeiqianduan = true;
|
|
1259
|
-
weiqianduanProps = {
|
|
1429
|
+
// 缓存weiqianduanProps,避免重复创建
|
|
1430
|
+
const isWeiqianduan = window.top !== window;
|
|
1431
|
+
if (!this.cachedWeiqianduanProps) {
|
|
1432
|
+
this.cachedWeiqianduanProps = isWeiqianduan ? {
|
|
1260
1433
|
headerRender: false,
|
|
1261
1434
|
footerRender: false,
|
|
1262
1435
|
menuRender: false,
|
|
1263
1436
|
menuHeaderRender: false,
|
|
1264
1437
|
menuExtraRender: false,
|
|
1265
|
-
};
|
|
1438
|
+
} : {};
|
|
1266
1439
|
}
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1440
|
+
const weiqianduanProps = this.cachedWeiqianduanProps;
|
|
1441
|
+
|
|
1442
|
+
// 缓存OperationsSlot,避免重复创建
|
|
1443
|
+
if (!this.cachedOperationsSlot || this.lastIsSliderState !== this.state.isSlider) {
|
|
1444
|
+
this.lastIsSliderState = this.state.isSlider;
|
|
1445
|
+
this.cachedOperationsSlot = {
|
|
1446
|
+
left: (
|
|
1447
|
+
<div className={'tab_left_operate'}>
|
|
1448
|
+
<div
|
|
1449
|
+
onClick={() => {
|
|
1450
|
+
history.push({
|
|
1451
|
+
pathname: '/',
|
|
1452
|
+
});
|
|
1453
|
+
}}
|
|
1454
|
+
>
|
|
1455
|
+
<HomeOutlined />
|
|
1456
|
+
</div>
|
|
1457
|
+
<div
|
|
1458
|
+
style={{ opacity: this.state.isSlider ? 1 : 0.5 }}
|
|
1459
|
+
onClick={() => {
|
|
1460
|
+
this.setTabNavTransLate(-100);
|
|
1461
|
+
}}
|
|
1462
|
+
>
|
|
1463
|
+
<DoubleLeftOutlined />
|
|
1464
|
+
</div>
|
|
1279
1465
|
</div>
|
|
1466
|
+
),
|
|
1467
|
+
right: (
|
|
1280
1468
|
<div
|
|
1281
1469
|
style={{ opacity: this.state.isSlider ? 1 : 0.5 }}
|
|
1470
|
+
className={'tab_right_operate'}
|
|
1282
1471
|
onClick={() => {
|
|
1283
|
-
this.setTabNavTransLate(
|
|
1472
|
+
this.setTabNavTransLate(100);
|
|
1284
1473
|
}}
|
|
1285
1474
|
>
|
|
1286
|
-
<
|
|
1475
|
+
<DoubleRightOutlined />
|
|
1287
1476
|
</div>
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
style={{ opacity: this.state.isSlider ? 1 : 0.5 }}
|
|
1293
|
-
className={'tab_right_operate'}
|
|
1294
|
-
onClick={() => {
|
|
1295
|
-
this.setTabNavTransLate(100);
|
|
1296
|
-
}}
|
|
1297
|
-
>
|
|
1298
|
-
<DoubleRightOutlined />
|
|
1299
|
-
</div>
|
|
1300
|
-
),
|
|
1301
|
-
};
|
|
1477
|
+
),
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
const OperationsSlot = this.cachedOperationsSlot;
|
|
1302
1481
|
|
|
1303
1482
|
return (
|
|
1304
1483
|
<ProLayout
|
|
@@ -1473,7 +1652,7 @@ class BasicLayout extends React.PureComponent {
|
|
|
1473
1652
|
{...weiqianduanProps}
|
|
1474
1653
|
>
|
|
1475
1654
|
<div id="globalTabsContent" className="globalTabs">
|
|
1476
|
-
<
|
|
1655
|
+
<Tabs
|
|
1477
1656
|
activeKey={activeKey}
|
|
1478
1657
|
id="globalTabs"
|
|
1479
1658
|
onChange={this.onChange}
|
|
@@ -1496,8 +1675,15 @@ class BasicLayout extends React.PureComponent {
|
|
|
1496
1675
|
tabBarGutter={8}
|
|
1497
1676
|
onEdit={this.onEdit}
|
|
1498
1677
|
tabBarExtraContent={OperationsSlot}
|
|
1678
|
+
destroyInactiveTabPane={true}
|
|
1499
1679
|
animated={false}
|
|
1500
1680
|
hideAdd
|
|
1681
|
+
ref={(tabsRef) => {
|
|
1682
|
+
if (tabsRef && !this.tabsRef) {
|
|
1683
|
+
this.tabsRef = tabsRef;
|
|
1684
|
+
console.log('[Tabs配置] destroyInactiveTabPane已设置为true,animated设置为false');
|
|
1685
|
+
}
|
|
1686
|
+
}}
|
|
1501
1687
|
>
|
|
1502
1688
|
{listenRouterState.map((item, index) => (
|
|
1503
1689
|
<TabPane
|
|
@@ -1521,10 +1707,11 @@ class BasicLayout extends React.PureComponent {
|
|
|
1521
1707
|
timeFormat={this.timeFormat}
|
|
1522
1708
|
transparentProps={transparentProps}
|
|
1523
1709
|
activeKey={activeKey}
|
|
1710
|
+
listenRouterState={listenRouterState}
|
|
1524
1711
|
/>
|
|
1525
1712
|
</TabPane>
|
|
1526
1713
|
))}
|
|
1527
|
-
</
|
|
1714
|
+
</Tabs>
|
|
1528
1715
|
<div
|
|
1529
1716
|
className="globalTabsOper"
|
|
1530
1717
|
style={{
|
|
@@ -1567,17 +1754,186 @@ class BasicLayout extends React.PureComponent {
|
|
|
1567
1754
|
class WrapperComponent extends React.Component {
|
|
1568
1755
|
constructor(props) {
|
|
1569
1756
|
super(props);
|
|
1757
|
+
console.log(`WrapperComponent 构造函数: 页签 ${props.item.key} 创建`);
|
|
1758
|
+
|
|
1759
|
+
// 初始化MutationObserver
|
|
1760
|
+
this.domObserver = null;
|
|
1761
|
+
this.isUnmounted = false;
|
|
1762
|
+
|
|
1763
|
+
// 统计当前DOM节点数
|
|
1764
|
+
setTimeout(() => {
|
|
1765
|
+
const tabPanes = document.querySelectorAll('#globalTabs .ant-tabs-tabpane');
|
|
1766
|
+
console.log(`[组件创建] 页签 ${props.item.key} 创建后,DOM中TabPane节点数: ${tabPanes.length}`);
|
|
1767
|
+
}, 0);
|
|
1570
1768
|
}
|
|
1571
1769
|
|
|
1770
|
+
componentDidMount() {
|
|
1771
|
+
console.log(`WrapperComponent 挂载: 页签 ${this.props.item.key} 已挂载到DOM`);
|
|
1772
|
+
const tabPanes = document.querySelectorAll('#globalTabs .ant-tabs-tabpane');
|
|
1773
|
+
console.log(`[组件挂载] 页签 ${this.props.item.key} 挂载后,DOM中TabPane节点数: ${tabPanes.length}`);
|
|
1774
|
+
|
|
1775
|
+
// 设置MutationObserver监控DOM变化
|
|
1776
|
+
this.setupDOMObserver();
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
setupDOMObserver = () => {
|
|
1780
|
+
if (this.domObserver || this.isUnmounted) return;
|
|
1781
|
+
|
|
1782
|
+
const globalTabsContainer = document.getElementById('globalTabs');
|
|
1783
|
+
if (!globalTabsContainer) return;
|
|
1784
|
+
|
|
1785
|
+
this.domObserver = new MutationObserver((mutations) => {
|
|
1786
|
+
if (this.isUnmounted) return;
|
|
1787
|
+
|
|
1788
|
+
mutations.forEach((mutation) => {
|
|
1789
|
+
if (mutation.type === 'childList') {
|
|
1790
|
+
// 检查是否有TabPane节点被添加或删除
|
|
1791
|
+
const addedTabPanes = Array.from(mutation.addedNodes).filter(node =>
|
|
1792
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
1793
|
+
node.classList &&
|
|
1794
|
+
node.classList.contains('ant-tabs-tabpane')
|
|
1795
|
+
);
|
|
1796
|
+
|
|
1797
|
+
const removedTabPanes = Array.from(mutation.removedNodes).filter(node =>
|
|
1798
|
+
node.nodeType === Node.ELEMENT_NODE &&
|
|
1799
|
+
node.classList &&
|
|
1800
|
+
node.classList.contains('ant-tabs-tabpane')
|
|
1801
|
+
);
|
|
1802
|
+
|
|
1803
|
+
if (addedTabPanes.length > 0) {
|
|
1804
|
+
console.log(`[DOM监控] 检测到 ${addedTabPanes.length} 个TabPane节点被添加`);
|
|
1805
|
+
addedTabPanes.forEach(node => {
|
|
1806
|
+
const key = node.getAttribute('data-node-key') || '未知';
|
|
1807
|
+
console.log(`[DOM监控] 添加的TabPane: ${key}`);
|
|
1808
|
+
});
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
if (removedTabPanes.length > 0) {
|
|
1812
|
+
console.log(`[DOM监控] 检测到 ${removedTabPanes.length} 个TabPane节点被删除`);
|
|
1813
|
+
removedTabPanes.forEach(node => {
|
|
1814
|
+
const key = node.getAttribute('data-node-key') || '未知';
|
|
1815
|
+
console.log(`[DOM监控] 删除的TabPane: ${key}`);
|
|
1816
|
+
|
|
1817
|
+
// 检查是否是当前组件对应的TabPane
|
|
1818
|
+
if (key === this.props.item.key) {
|
|
1819
|
+
console.log(`[DOM监控] 当前组件 ${this.props.item.key} 对应的TabPane已从DOM中删除`);
|
|
1820
|
+
}
|
|
1821
|
+
});
|
|
1822
|
+
}
|
|
1823
|
+
|
|
1824
|
+
// 统计当前DOM中的TabPane数量
|
|
1825
|
+
const currentTabPanes = document.querySelectorAll('#globalTabs .ant-tabs-tabpane');
|
|
1826
|
+
console.log(`[DOM监控] 当前DOM中TabPane节点总数: ${currentTabPanes.length}`);
|
|
1827
|
+
}
|
|
1828
|
+
});
|
|
1829
|
+
});
|
|
1830
|
+
|
|
1831
|
+
// 开始观察
|
|
1832
|
+
this.domObserver.observe(globalTabsContainer, {
|
|
1833
|
+
childList: true,
|
|
1834
|
+
subtree: true
|
|
1835
|
+
});
|
|
1836
|
+
|
|
1837
|
+
console.log(`[DOM监控] 已为页签 ${this.props.item.key} 设置DOM变化监控`);
|
|
1838
|
+
};
|
|
1839
|
+
|
|
1840
|
+
componentWillUnmount() {
|
|
1841
|
+
console.log(`WrapperComponent 卸载: 页签 ${this.props.item.key} 组件正在销毁`);
|
|
1842
|
+
|
|
1843
|
+
// 设置卸载标志
|
|
1844
|
+
this.isUnmounted = true;
|
|
1845
|
+
|
|
1846
|
+
const tabPanes = document.querySelectorAll('#globalTabs .ant-tabs-tabpane');
|
|
1847
|
+
console.log(`[组件卸载] 页签 ${this.props.item.key} 卸载前,DOM中TabPane节点数: ${tabPanes.length}`);
|
|
1848
|
+
|
|
1849
|
+
// 清理MutationObserver
|
|
1850
|
+
if (this.domObserver) {
|
|
1851
|
+
this.domObserver.disconnect();
|
|
1852
|
+
this.domObserver = null;
|
|
1853
|
+
console.log(`[清理] 已断开页签 ${this.props.item.key} 的DOM监控`);
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
// 强制清理所有可能的引用和事件监听器
|
|
1857
|
+
try {
|
|
1858
|
+
// 清理组件内部的所有引用
|
|
1859
|
+
if (this.props && typeof this.props === 'object') {
|
|
1860
|
+
Object.keys(this.props).forEach(key => {
|
|
1861
|
+
if (this.props[key] && typeof this.props[key] === 'object') {
|
|
1862
|
+
// 不直接删除props,但清理可能的循环引用
|
|
1863
|
+
console.log(`[清理] 检查prop: ${key}`);
|
|
1864
|
+
}
|
|
1865
|
+
});
|
|
1866
|
+
}
|
|
1867
|
+
|
|
1868
|
+
// 清理可能的DOM事件监听器
|
|
1869
|
+
const currentElement = document.querySelector(`#globalTabs .ant-tabs-tabpane[data-node-key="${this.props.item.key}"]`);
|
|
1870
|
+
if (currentElement) {
|
|
1871
|
+
// 移除所有可能的事件监听器
|
|
1872
|
+
const events = ['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'focus', 'blur'];
|
|
1873
|
+
events.forEach(eventType => {
|
|
1874
|
+
currentElement.removeEventListener(eventType, this.handleEvent, true);
|
|
1875
|
+
currentElement.removeEventListener(eventType, this.handleEvent, false);
|
|
1876
|
+
});
|
|
1877
|
+
console.log(`[清理] 已清理页签 ${this.props.item.key} 的DOM事件监听器`);
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
// 强制垃圾回收提示
|
|
1881
|
+
if (window.gc && typeof window.gc === 'function') {
|
|
1882
|
+
window.gc();
|
|
1883
|
+
console.log(`[清理] 已触发垃圾回收`);
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
} catch (error) {
|
|
1887
|
+
console.error(`[清理错误] 页签 ${this.props.item.key} 清理过程中出错:`, error);
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1890
|
+
// 延迟检查卸载后的DOM状态
|
|
1891
|
+
setTimeout(() => {
|
|
1892
|
+
const tabPanesAfter = document.querySelectorAll('#globalTabs .ant-tabs-tabpane');
|
|
1893
|
+
console.log(`[组件卸载] 页签 ${this.props.item.key} 卸载后,DOM中TabPane节点数: ${tabPanesAfter.length}`);
|
|
1894
|
+
|
|
1895
|
+
// 强制检查内存使用情况
|
|
1896
|
+
if (window.performance && window.performance.memory) {
|
|
1897
|
+
const memory = window.performance.memory;
|
|
1898
|
+
console.log(`[内存监控] 卸载后内存使用: ${(memory.usedJSHeapSize / 1024 / 1024).toFixed(2)}MB / ${(memory.totalJSHeapSize / 1024 / 1024).toFixed(2)}MB`);
|
|
1899
|
+
}
|
|
1900
|
+
}, 100);
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
// 通用事件处理器,用于清理
|
|
1904
|
+
handleEvent = (event) => {
|
|
1905
|
+
// 空的事件处理器,仅用于清理时移除监听器
|
|
1906
|
+
};
|
|
1907
|
+
|
|
1572
1908
|
shouldComponentUpdate(nextProps) {
|
|
1573
|
-
if (window.__POWERED_BY_WUJIE__ && nextProps?.item?.key?.indexOf('edit-template-template') > -1) {
|
|
1574
|
-
return true
|
|
1909
|
+
if (window.__POWERED_BY_WUJIE__ && nextProps?.item?.key?.indexOf('edit-template-template') > -1) {
|
|
1910
|
+
return true;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
// 检测页签是否已被删除:比较当前和下一个listenRouterState
|
|
1914
|
+
const currentTabExists = this.props.listenRouterState?.some(tab => tab.key === this.props.item.key);
|
|
1915
|
+
const nextTabExists = nextProps.listenRouterState?.some(tab => tab.key === this.props.item.key);
|
|
1916
|
+
|
|
1917
|
+
// 如果页签从存在变为不存在,说明被删除了,允许更新以便正确销毁
|
|
1918
|
+
if (currentTabExists && !nextTabExists) {
|
|
1919
|
+
console.log(`页签 ${this.props.item.key} 已被删除,允许组件更新以便销毁`);
|
|
1920
|
+
return true;
|
|
1575
1921
|
}
|
|
1922
|
+
|
|
1923
|
+
// 如果页签不再是活跃状态,允许更新以便正确销毁
|
|
1924
|
+
if (nextProps.activeKey !== nextProps.item.key && this.props.activeKey === this.props.item.key) {
|
|
1925
|
+
console.log(`页签 ${this.props.item.key} 不再活跃,允许组件更新`);
|
|
1926
|
+
return true;
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
// 如果页签变为活跃状态或内容发生变化,允许更新
|
|
1576
1930
|
if (nextProps.activeKey === nextProps.item.key && JSON.stringify(nextProps.item) !== JSON.stringify(this.props.item)) {
|
|
1577
1931
|
return true;
|
|
1578
1932
|
}
|
|
1933
|
+
|
|
1579
1934
|
return false;
|
|
1580
1935
|
}
|
|
1936
|
+
|
|
1581
1937
|
render() {
|
|
1582
1938
|
const {
|
|
1583
1939
|
item,
|