@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.
Files changed (88) hide show
  1. package/.umirc.ts +14 -10
  2. package/dist/components/Business/AddSelectBusiness/index.d.ts +3 -4
  3. package/dist/components/Business/BsLayouts/Components/AllFunc/drawContent.d.ts +1 -2
  4. package/dist/components/Business/BsLayouts/Components/ChooseStore/index.d.ts +1 -2
  5. package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/index.d.ts +1 -1
  6. package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/leftTree.d.ts +1 -1
  7. package/dist/components/Business/BsLayouts/Components/CustomerMenu/MenuSetting/rightTree.d.ts +2 -2
  8. package/dist/components/Business/BsLayouts/Components/CustomerMenu/globalMenu/DrawContent.d.ts +1 -2
  9. package/dist/components/Business/BsLayouts/Components/CustomerMenu/globalMenu/customMenuHeader.d.ts +1 -2
  10. package/dist/components/Business/BsLayouts/Components/CustomerMenu/index.d.ts +1 -1
  11. package/dist/components/Business/BsLayouts/Components/GlobalHeader/index.d.ts +1 -2
  12. package/dist/components/Business/BsLayouts/Components/RightContent/LoginModal.d.ts +1 -2
  13. package/dist/components/Business/BsLayouts/index.d.ts +1 -1
  14. package/dist/components/Business/BsSulaQueryTable/SearchItemSetting.d.ts +19 -8
  15. package/dist/components/Business/BsSulaQueryTable/index.d.ts +1 -2
  16. package/dist/components/Business/BsSulaQueryTable/setting.d.ts +9 -17
  17. package/dist/components/Business/BsSulaQueryTable/utils.d.ts +14 -15
  18. package/dist/components/Business/CommodityEntry/index.d.ts +1 -2
  19. package/dist/components/Business/CommonAlert/index.d.ts +1 -2
  20. package/dist/components/Business/CommonGuideWrapper/index.d.ts +3 -3
  21. package/dist/components/Business/DetailPageWrapper/index.d.ts +11 -12
  22. package/dist/components/Business/HomePageWrapper/index.d.ts +1 -2
  23. package/dist/components/Business/ItemPropertySelector/index.d.ts +1 -2
  24. package/dist/components/Business/JsonQueryTable/components/FieldsModifyModal.d.ts +1 -2
  25. package/dist/components/Business/JsonQueryTable/components/FieldsSettingsTable.d.ts +1 -2
  26. package/dist/components/Business/JsonQueryTable/components/Formula.d.ts +1 -2
  27. package/dist/components/Business/JsonQueryTable/components/MaintainOptions.d.ts +1 -2
  28. package/dist/components/Business/JsonQueryTable/drawer/index.d.ts +1 -2
  29. package/dist/components/Business/PropertyModal/index.d.ts +1 -2
  30. package/dist/components/Business/PropertyModal/propertyGroup.d.ts +1 -1
  31. package/dist/components/Business/SearchSelect/index.d.ts +1 -1
  32. package/dist/components/Business/StateFlow/index.d.ts +1 -2
  33. package/dist/components/Business/TreeSearchSelect/index.d.ts +1 -1
  34. package/dist/components/Business/columnSettingTable/columnSetting.d.ts +6 -6
  35. package/dist/components/Business/columnSettingTable/components/TableSumComponent.d.ts +1 -2
  36. package/dist/components/Business/columnSettingTable/index.d.ts +3 -3
  37. package/dist/components/Business/columnSettingTable/sulaSettingTable.d.ts +3 -3
  38. package/dist/components/Business/columnSettingTable/utils.d.ts +1 -2
  39. package/dist/components/Business/moreTreeTable/FixedScrollBar.d.ts +1 -1
  40. package/dist/components/Common/ParagraphCopier/index.d.ts +1 -1
  41. package/dist/components/Common/Section/index.d.ts +1 -1
  42. package/dist/components/Functional/AddSelect/index.d.ts +1 -2
  43. package/dist/components/Functional/AuthButton/index.d.ts +1 -2
  44. package/dist/components/Functional/DataImport/index.d.ts +4 -4
  45. package/dist/components/Functional/DataValidation/index.d.ts +5 -5
  46. package/dist/components/Functional/ExportFunctions/ExportIcon/index.d.ts +1 -2
  47. package/dist/components/Functional/QueryMutipleInput/index.d.ts +1 -2
  48. package/dist/components/Functional/QueryMutipleSelect/index.d.ts +1 -2
  49. package/dist/components/Functional/SearchSelect/index.d.ts +1 -1
  50. package/dist/components/Functional/SearchSelect/utils.d.ts +2 -3
  51. package/dist/components/Functional/TreeSearchSelect/index.d.ts +1 -2
  52. package/dist/components/Solution/RuleComponent/CustomPlugin/CustomSelector/CustomSelectorModal.d.ts +1 -1
  53. package/dist/components/Solution/RuleComponent/CustomPlugin/CustomSelector/index.d.ts +1 -2
  54. package/dist/components/Solution/RuleComponent/Formula.d.ts +1 -2
  55. package/dist/components/Solution/RuleComponent/InnerSelect.d.ts +1 -2
  56. package/dist/components/Solution/RuleComponent/RenderCompItem.d.ts +1 -2
  57. package/dist/components/Solution/RuleSetter/RuleInstance.d.ts +1 -2
  58. package/dist/components/Solution/RuleSetter/baseRule.d.ts +1 -1
  59. package/dist/components/Solution/RuleSetter/index.d.ts +1 -1
  60. package/dist/index.d.ts +0 -1
  61. package/dist/index.esm.js +2349 -1832
  62. package/dist/index.js +2341 -1826
  63. package/dist/plugin/TableColumnSetting/index.d.ts +5 -5
  64. package/dist/utils/TableUtils.d.ts +18 -19
  65. package/dist/utils/luckysheetLoader.d.ts +21 -0
  66. package/dist/utils/utils.d.ts +0 -41
  67. package/package.json +1 -1
  68. package/src/components/Business/BsLayouts/index.tsx +485 -129
  69. package/src/components/Business/BsSulaQueryTable/SearchItemSetting.tsx +144 -4
  70. package/src/components/Business/BsSulaQueryTable/index.md +120 -0
  71. package/src/components/Business/BsSulaQueryTable/index.tsx +236 -22
  72. package/src/components/Business/BsSulaQueryTable/setting.tsx +232 -17
  73. package/src/components/Business/DetailPageWrapper/index.tsx +30 -27
  74. package/src/components/Business/HomePageWrapper/index.tsx +10 -8
  75. package/src/components/Business/SearchSelect/BusinessUtils.tsx +38 -234
  76. package/src/components/Business/columnSettingTable/index.tsx +6 -7
  77. package/src/components/Business/columnSettingTable/sulaSettingTable.tsx +22 -23
  78. package/src/components/Functional/AddSelect/index.tsx +0 -92
  79. package/src/components/Functional/DataImport/index.tsx +76 -3
  80. package/src/components/Functional/DataValidation/index.tsx +81 -3
  81. package/src/components/Functional/SearchSelect/index.tsx +2 -5
  82. package/src/components/Solution/RuleComponent/index.js +0 -1
  83. package/src/index.ts +0 -2
  84. package/src/utils/luckysheetLoader.ts +164 -0
  85. package/src/utils/utils.ts +1 -41
  86. package/dist/components/Business/SystemLog/index.d.ts +0 -78
  87. package/src/components/Business/SystemLog/index.md +0 -37
  88. 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
- <DndProvider backend={HTML5Backend}>
223
- <Tabs renderTabBar={renderTabBar} {...props}>
224
- {orderTabs}
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
- authMenuPathList = getAuthMenuPathAndDocsId(props.pathToRegexp)?.menuKeys || [];
332
- docsId = getAuthMenuPathAndDocsId(props.pathToRegexp)?.docsId || [];
333
- routerArray = this.updateTree(props.route.routes, authMenuPathList);
334
- const homeRouter = routerArray.filter(
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
- const breadcrumbNameMap = getBreadcrumbNameMap(
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
- window.$wujie?.bus.$on("main-route-change", function (appname, info) {
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
- let newPath = encodeUrlQuery(info.path);
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
- let notSamePageFlag = lastTwoRouterArray[0] != lastTwoRouterArray[1];
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
- // eslint-disable-next-line no-unused-expressions
611
- UN_LISTTEN_DRP && UN_LISTTEN_DRP();
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
- let globalTabsNav = document
935
- .getElementById('globalTabs')
936
- ?.getElementsByClassName('ant-tabs-nav-list')?.[0];
937
- let globalTabsNavWrap = document
938
- .getElementById('globalTabs')
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
- let maxTransX = navListWidth - wrapWidth; // 允许移动最大宽度
944
- let transXStr = document.defaultView?.getComputedStyle(
1125
+
1126
+ const maxTransX = navListWidth - wrapWidth; // 允许移动最大宽度
1127
+ const transXStr = document.defaultView?.getComputedStyle(
945
1128
  globalTabsNav,
946
1129
  null,
947
1130
  ).transform;
948
- let transx = transXStr?.split(',')[4]; //当前translateX的值
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 = 'translateX(-' + targetTransX + 'px)';
1136
+ globalTabsNav.style.transform = `translateX(-${targetTransX}px)`;
954
1137
  };
955
1138
 
956
1139
  checkisNavSlide = () => {
957
- if (window.top != window) return;
1140
+ if (window.top !== window) return;
958
1141
  //监听tabs页签总长度判断是否可点击滑动
959
- let globalTabsNav = document
960
- .getElementById('globalTabs')
961
- ?.getElementsByClassName('ant-tabs-nav-list')?.[0];
962
- let globalTabsNavWrap = document
963
- .getElementById('globalTabs')
964
- ?.getElementsByClassName('ant-tabs-nav-wrap')?.[0];
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: true,
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
- let weiqianduanProps = {};
1255
- let isWeiqianduan = false;
1256
-
1257
- if (window.top != window) {
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
- const OperationsSlot: Record<PositionType, React.ReactNode> = {
1269
- left: (
1270
- <div className={'tab_left_operate'}>
1271
- <div
1272
- onClick={() => {
1273
- history.push({
1274
- pathname: '/',
1275
- });
1276
- }}
1277
- >
1278
- <HomeOutlined />
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(-100);
1472
+ this.setTabNavTransLate(100);
1284
1473
  }}
1285
1474
  >
1286
- <DoubleLeftOutlined />
1475
+ <DoubleRightOutlined />
1287
1476
  </div>
1288
- </div>
1289
- ),
1290
- right: (
1291
- <div
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
- <DraggableTabs
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
- </DraggableTabs>
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) { // 适配wujie环境主应用下渲染打印编辑器
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,