@bit-sun/business-component 4.2.5-per-alpha.2 → 4.2.5-per-alpha.3

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.
@@ -54,15 +54,8 @@ const ResizeableTitle = (props: any) => {
54
54
 
55
55
  const prevWidthRef = useRef(width);
56
56
 
57
- const handleMouseDown = (e: any) => {
58
- currentStart.current = e.clientX;
59
- markerPosition.current = { left: e.clientX, top: e.clientY }
60
- setIsResizing(true);
61
- document.addEventListener('mousemove', handleMouseMove);
62
- document.addEventListener('mouseup', handleMouseUp);
63
- };
64
-
65
- const handleMouseMove = (e: any) => {
57
+ // 使用useCallback优化事件处理函数,避免重复创建
58
+ const handleMouseMove = useCallback((e: any) => {
66
59
  e.stopPropagation();
67
60
  e.preventDefault();
68
61
  // 更新标记位置
@@ -73,13 +66,21 @@ const ResizeableTitle = (props: any) => {
73
66
  dom.style.left = `${e.clientX}px`;
74
67
  dom.style.top = `${e.clientY - 20}px`;
75
68
  }
76
- };
69
+ }, []);
77
70
 
78
- const handleMouseUp = (e: any) => {
71
+ const handleMouseUp = useCallback((e: any) => {
79
72
  document.removeEventListener('mousemove', handleMouseMove);
80
73
  document.removeEventListener('mouseup', handleMouseUp);
81
74
  setIsResizing(false);
82
- };
75
+ }, [handleMouseMove]);
76
+
77
+ const handleMouseDown = useCallback((e: any) => {
78
+ currentStart.current = e.clientX;
79
+ markerPosition.current = { left: e.clientX, top: e.clientY }
80
+ setIsResizing(true);
81
+ document.addEventListener('mousemove', handleMouseMove);
82
+ document.addEventListener('mouseup', handleMouseUp);
83
+ }, [handleMouseMove, handleMouseUp]);
83
84
 
84
85
  const handleresize = (e: any, data: any, title: string) => {
85
86
  const newWidth = data?.size?.width || 0;
@@ -104,6 +105,15 @@ const ResizeableTitle = (props: any) => {
104
105
  }
105
106
  }, [width]);
106
107
 
108
+ // 组件卸载时清理事件监听器
109
+ useEffect(() => {
110
+ return () => {
111
+ // 确保在组件卸载时移除可能残留的事件监听器
112
+ document.removeEventListener('mousemove', handleMouseMove);
113
+ document.removeEventListener('mouseup', handleMouseUp);
114
+ };
115
+ }, [handleMouseMove, handleMouseUp]);
116
+
107
117
  const thStyle = {
108
118
  boxShadow: isResizing ? '2px 2px 10px rgba(0, 0, 0, 0.3)' : 'none',
109
119
  };
@@ -173,12 +183,9 @@ export default (props: any) => {
173
183
  // 定时器引用,用于清理
174
184
  const debounceTimer = useRef(null);
175
185
  const resizeTimer = useRef(null);
186
+ const tableHeightTimer = useRef(null); // 新增:管理getTableHeight的定时器
176
187
 
177
- // 事件处理函数引用,用于清理
178
- const handleBeforeUnload = useRef(null);
179
- const handleGlobalClick = useRef(null);
180
- const handleGlobalKeydown = useRef(null);
181
- const handleStorageChange = useRef(null);
188
+ // 移除未使用的事件处理函数引用,避免内存泄漏
182
189
 
183
190
  // 获取 table columns中所有的 key 防止有的地方是 dataindex
184
191
  const checkedList = useMemo(
@@ -291,54 +298,73 @@ export default (props: any) => {
291
298
  const [showExportColumn, setShowExportColumns] = useState([]); // 导出列字段
292
299
 
293
300
  const [height, setHeight]: any = useState('');
301
+ // 使用ref避免setHeight的闭包引用
302
+ const heightRef = useRef(height);
303
+
304
+ // 同步height状态到ref
305
+ useEffect(() => {
306
+ heightRef.current = height;
307
+ }, [height]);
294
308
 
295
309
  const bsTableCodeExport = `${bsTableCode}___Export`; //设置导出列字段的唯一标识
296
310
 
297
311
  // 获取table高度
298
- const getTableHeight = () => {
299
- setTimeout(() => {
300
- const cancelHeight = window.top == window ? 303 : 223;
301
- const isFullScreen: any =
302
- window.top.document.fullScreen ||
303
- window.top.document.webkitIsFullScreen ||
304
- window.top.document.mozFullScreen;
305
-
306
- // wujie子应用iframe首次加载获取不到client以及dom元素高度兼容处理
307
- let realIframeClientHeight = document.body.clientHeight
308
- ? document.body.clientHeight
309
- : window.top?.document.body.clientHeight - 76;
310
- let summaryHeight = document.querySelector(
311
- `.ant-tabs-tabpane-active .table-bssula-summary`,
312
- )
313
- ? document.querySelector(
314
- `.ant-tabs-tabpane-active .table-bssula-summary`,
315
- )?.clientHeight || 22
316
- : 0;
317
- let listTabHeight = document.querySelector(
318
- `.ant-tabs-tabpane-active .list_top_tab .ant-tabs-nav`,
319
- )
320
- ? document.querySelector(
321
- `.ant-tabs-tabpane-active .list_top_tab .ant-tabs-nav`,
322
- )?.clientHeight || 48
323
- : 0;
324
-
325
- const h =
326
- realIframeClientHeight -
327
- summaryHeight -
328
- listTabHeight -
329
- (document.querySelector(
330
- `.ant-tabs-tabpane-active .ant-form ant-form-horizontal`,
331
- )?.clientHeight || 0) -
332
- (isFullScreen
333
- ? 0
334
- : document.querySelector(
335
- `.ant-tabs-tabpane-active .ant-pro-page-container-warp`,
336
- )?.clientHeight || 0) -
337
- cancelHeight +
338
- 'px';
339
- setHeight(h);
312
+ const getTableHeight = useCallback(() => {
313
+ // 清理之前的定时器
314
+ if (tableHeightTimer.current) {
315
+ clearTimeout(tableHeightTimer.current);
316
+ tableHeightTimer.current = null;
317
+ }
318
+
319
+ tableHeightTimer.current = setTimeout(() => {
320
+ try {
321
+ const cancelHeight = window.top == window ? 303 : 223;
322
+ const isFullScreen: any =
323
+ window.top.document.fullScreen ||
324
+ window.top.document.webkitIsFullScreen ||
325
+ window.top.document.mozFullScreen;
326
+
327
+ // wujie子应用iframe首次加载获取不到client以及dom元素高度兼容处理
328
+ let realIframeClientHeight = document.body.clientHeight
329
+ ? document.body.clientHeight
330
+ : window.top?.document.body.clientHeight - 76;
331
+ let summaryHeight = document.querySelector(
332
+ `.ant-tabs-tabpane-active .table-bssula-summary`,
333
+ )
334
+ ? document.querySelector(
335
+ `.ant-tabs-tabpane-active .table-bssula-summary`,
336
+ )?.clientHeight || 22
337
+ : 0;
338
+ let listTabHeight = document.querySelector(
339
+ `.ant-tabs-tabpane-active .list_top_tab .ant-tabs-nav`,
340
+ )
341
+ ? document.querySelector(
342
+ `.ant-tabs-tabpane-active .list_top_tab .ant-tabs-nav`,
343
+ )?.clientHeight || 48
344
+ : 0;
345
+
346
+ const h =
347
+ realIframeClientHeight -
348
+ summaryHeight -
349
+ listTabHeight -
350
+ (document.querySelector(
351
+ `.ant-tabs-tabpane-active .ant-form ant-form-horizontal`,
352
+ )?.clientHeight || 0) -
353
+ (isFullScreen
354
+ ? 0
355
+ : document.querySelector(
356
+ `.ant-tabs-tabpane-active .ant-pro-page-container-warp`,
357
+ )?.clientHeight || 0) -
358
+ cancelHeight +
359
+ 'px';
360
+ setHeight(h);
361
+ } catch (error) {
362
+ console.warn('Failed to calculate table height:', error);
363
+ } finally {
364
+ tableHeightTimer.current = null;
365
+ }
340
366
  }, 0);
341
- };
367
+ }, []);
342
368
 
343
369
  //监测是否按下esc键
344
370
  function checkFull() {
@@ -483,24 +509,15 @@ export default (props: any) => {
483
509
  resizeTimer.current = null;
484
510
  }
485
511
 
486
- // 清理所有事件监听器
487
- window.removeEventListener('resize', handleWindowResize);
488
-
489
- if (handleBeforeUnload.current) {
490
- window.removeEventListener('beforeunload', handleBeforeUnload.current);
491
- }
492
-
493
- if (handleGlobalClick.current) {
494
- document.removeEventListener('click', handleGlobalClick.current);
512
+ if (tableHeightTimer.current) {
513
+ clearTimeout(tableHeightTimer.current);
514
+ tableHeightTimer.current = null;
495
515
  }
496
516
 
497
- if (handleGlobalKeydown.current) {
498
- document.removeEventListener('keydown', handleGlobalKeydown.current);
499
- }
517
+ // 清理所有事件监听器
518
+ window.removeEventListener('resize', handleWindowResize);
500
519
 
501
- if (handleStorageChange.current) {
502
- window.removeEventListener('storage', handleStorageChange.current);
503
- }
520
+ // 移除未使用的事件监听器清理代码
504
521
 
505
522
  // 优化的子组件卸载时序控制
506
523
  const cleanupPromises: Promise<void>[] = [];
@@ -570,6 +587,10 @@ export default (props: any) => {
570
587
  // 继续处理其他组件
571
588
  }
572
589
  });
590
+
591
+ // 清理临时数组和WeakSet
592
+ tempInstancesRef.current = [];
593
+ summaryInstancesRef.current = new WeakSet();
573
594
 
574
595
  resolve();
575
596
  } catch (error) {
@@ -586,21 +607,7 @@ export default (props: any) => {
586
607
  // 清理全局事件监听器
587
608
  window.removeEventListener('resize', handleWindowResize);
588
609
 
589
- if (handleBeforeUnload.current) {
590
- window.removeEventListener('beforeunload', handleBeforeUnload.current);
591
- }
592
-
593
- if (handleGlobalClick.current) {
594
- document.removeEventListener('click', handleGlobalClick.current);
595
- }
596
-
597
- if (handleGlobalKeydown.current) {
598
- document.removeEventListener('keydown', handleGlobalKeydown.current);
599
- }
600
-
601
- if (handleStorageChange.current) {
602
- window.removeEventListener('storage', handleStorageChange.current);
603
- }
610
+ // 全局事件监听器已在上层清理
604
611
 
605
612
  resolve();
606
613
  } catch (error) {
@@ -633,6 +640,166 @@ export default (props: any) => {
633
640
  console.error('[BsSulaQueryTable] 清理任务队列执行失败:', error);
634
641
  });
635
642
 
643
+ // 设置卸载标志,防止后续创建新的Summary组件
644
+ isUnmountedRef.current = true;
645
+
646
+ // 清理Summary相关的DOM节点和React Fiber引用
647
+ try {
648
+ // 清理Summary组件实例
649
+ if (tempInstancesRef.current && tempInstancesRef.current.length > 0) {
650
+ console.log('[BsSulaQueryTable] 开始清理Summary组件实例:', tempInstancesRef.current.length);
651
+
652
+ tempInstancesRef.current.forEach((instance) => {
653
+ try {
654
+ // 首先调用实例的自定义清理方法
655
+ if (typeof instance._bsQueryTableCleanup === 'function') {
656
+ instance._bsQueryTableCleanup();
657
+ }
658
+
659
+ // 清理React Fiber节点引用
660
+ if (instance._reactInternalFiber) {
661
+ // 断开stateNode引用链
662
+ if (instance._reactInternalFiber.stateNode) {
663
+ instance._reactInternalFiber.stateNode = null;
664
+ }
665
+ // 断开child引用链
666
+ if (instance._reactInternalFiber.child) {
667
+ instance._reactInternalFiber.child = null;
668
+ }
669
+ // 断开memoizedProps引用链
670
+ if (instance._reactInternalFiber.memoizedProps) {
671
+ instance._reactInternalFiber.memoizedProps = null;
672
+ }
673
+ // 清理updateQueue
674
+ if (instance._reactInternalFiber.updateQueue) {
675
+ instance._reactInternalFiber.updateQueue = null;
676
+ }
677
+ // 清理lastEffect
678
+ if (instance._reactInternalFiber.lastEffect) {
679
+ instance._reactInternalFiber.lastEffect = null;
680
+ }
681
+ }
682
+
683
+ // 清理React 18+ Fiber节点引用
684
+ if (instance._reactInternals) {
685
+ if (instance._reactInternals.stateNode) {
686
+ instance._reactInternals.stateNode = null;
687
+ }
688
+ if (instance._reactInternals.child) {
689
+ instance._reactInternals.child = null;
690
+ }
691
+ if (instance._reactInternals.memoizedProps) {
692
+ instance._reactInternals.memoizedProps = null;
693
+ }
694
+ if (instance._reactInternals.updateQueue) {
695
+ instance._reactInternals.updateQueue = null;
696
+ }
697
+ }
698
+
699
+ // 清理React实例的内部引用
700
+ if (instance && typeof instance === 'object') {
701
+ // 清理可能的DOM引用
702
+ if (instance.current && typeof instance.current === 'object') {
703
+ const domNode = instance.current;
704
+ // 清理DOM节点上的React Fiber引用
705
+ const reactKeys = Object.keys(domNode).filter(key =>
706
+ key.startsWith('__reactFiber') ||
707
+ key.startsWith('__reactInternalInstance') ||
708
+ key.startsWith('__reactEventHandlers')
709
+ );
710
+ reactKeys.forEach(key => {
711
+ try {
712
+ delete domNode[key];
713
+ } catch (e) {
714
+ console.warn('[BsSulaQueryTable] 清理DOM React引用失败:', e);
715
+ }
716
+ });
717
+ }
718
+ }
719
+
720
+ } catch (error) {
721
+ console.warn('[BsSulaQueryTable] 清理Summary实例失败:', error);
722
+ }
723
+ });
724
+
725
+ }
726
+
727
+ // DOM节点引用已简化,无需额外清理
728
+
729
+ // 清理context相关的闭包引用
730
+ try {
731
+ console.log('[BsSulaQueryTable] 开始清理context闭包引用');
732
+
733
+ // 清理getTableSummaryInfo函数中可能的context引用
734
+ if (typeof getTableSummaryInfo === 'function') {
735
+ // 尝试清理函数闭包中的context引用
736
+ Object.defineProperty(getTableSummaryInfo, 'context', {
737
+ value: null,
738
+ writable: true,
739
+ configurable: true
740
+ });
741
+ }
742
+
743
+ // 清理可能的全局context引用
744
+ if (typeof window !== 'undefined') {
745
+ // 清理可能存在的全局context缓存
746
+ const contextKeys = Object.keys(window).filter(key =>
747
+ key.includes('context') ||
748
+ key.includes('Context') ||
749
+ key.includes('restProps') ||
750
+ key.includes('setPagePath')
751
+ );
752
+
753
+ contextKeys.forEach(key => {
754
+ try {
755
+ if (window[key] && typeof window[key] === 'object') {
756
+ // 清理context对象的引用
757
+ Object.keys(window[key]).forEach(contextKey => {
758
+ if (window[key][contextKey] && typeof window[key][contextKey] === 'object') {
759
+ window[key][contextKey] = null;
760
+ }
761
+ });
762
+ }
763
+ } catch (e) {
764
+ console.warn(`[BsSulaQueryTable] 清理全局context ${key} 失败:`, e);
765
+ }
766
+ });
767
+ }
768
+
769
+ // 清理组件内部可能的context引用
770
+ if (config && typeof config === 'object') {
771
+ // 清理config中可能的context引用
772
+ Object.keys(config).forEach(key => {
773
+ if (config[key] && typeof config[key] === 'object' && config[key].context) {
774
+ config[key].context = null;
775
+ }
776
+ });
777
+ }
778
+
779
+ } catch (contextError) {
780
+ console.warn('[BsSulaQueryTable] 清理context引用失败:', contextError);
781
+ }
782
+
783
+ } catch (error) {
784
+ console.error('[BsSulaQueryTable] Summary清理过程中发生错误:', error);
785
+ }
786
+
787
+ // 清理memoConfig引用,断开与外部库的连接
788
+ if (memoConfigRef.current) {
789
+ console.log('[BsSulaQueryTable] 清理memoConfig引用');
790
+ // 将summaryList设置为null,通知外部库释放引用
791
+ if (memoConfigRef.current.summaryList) {
792
+ memoConfigRef.current.summaryList = null;
793
+ }
794
+ // 清理其他可能的函数引用
795
+ Object.keys(memoConfigRef.current).forEach(key => {
796
+ if (typeof memoConfigRef.current[key] === 'function') {
797
+ memoConfigRef.current[key] = null;
798
+ }
799
+ });
800
+ memoConfigRef.current = null;
801
+ }
802
+
636
803
  // 清理所有ref引用
637
804
  if (rowsRef.current) {
638
805
  rowsRef.current = null;
@@ -650,10 +817,44 @@ export default (props: any) => {
650
817
  exportTableRef.current = null;
651
818
  }
652
819
 
820
+ // 强制断开与外部库的引用关系
821
+ try {
822
+ // 清理全局事件监听器
823
+ if (typeof window !== 'undefined') {
824
+ window.removeEventListener('resize', watchWinResize);
825
+ window.removeEventListener('keydown', handleKeyDown);
826
+ }
827
+
828
+ // 清理可能的全局变量引用
829
+ if (typeof window !== 'undefined' && window.__bsSulaQueryTableInstances) {
830
+ const instances = window.__bsSulaQueryTableInstances;
831
+ const instanceIndex = instances.findIndex(instance => instance.id === componentId);
832
+ if (instanceIndex > -1) {
833
+ instances.splice(instanceIndex, 1);
834
+ }
835
+ }
836
+
837
+ // 强制垃圾回收提示(仅开发环境)
838
+ if (process.env.NODE_ENV === 'development' && typeof window !== 'undefined' && window.gc) {
839
+ setTimeout(() => {
840
+ try {
841
+ window.gc();
842
+ console.log('[BsSulaQueryTable] 手动触发垃圾回收');
843
+ } catch (e) {
844
+ // 忽略垃圾回收错误
845
+ }
846
+ }, 100);
847
+ }
848
+ } catch (error) {
849
+ console.warn('[BsSulaQueryTable] 外部库引用断开过程中出现错误:', error);
850
+ }
851
+
653
852
  // 最终状态验证
654
853
  console.log('[BsSulaQueryTable] 组件卸载流程完成,清理状态:', {
655
854
  refsCleared: !rowsRef.current && !sortTableRef.current && !searchTableRef.current,
656
- timersCleared: !debounceTimer.current && !resizeTimer.current
855
+ timersCleared: !debounceTimer.current && !resizeTimer.current,
856
+ memoConfigCleared: !memoConfigRef.current,
857
+ tempInstancesCleared: tempInstancesRef.current.length === 0
657
858
  });
658
859
 
659
860
  console.log('[BsSulaQueryTable] 主组件及所有子组件已完全卸载,内存已清理');
@@ -698,23 +899,34 @@ export default (props: any) => {
698
899
  // 清理之前的定时器
699
900
  if (debounceTimer.current) {
700
901
  clearTimeout(debounceTimer.current);
902
+ debounceTimer.current = null;
701
903
  }
702
904
 
703
905
  // 设置新的定时器
704
906
  debounceTimer.current = setTimeout(() => {
705
- // getTableHeight();
706
- if (!checkFull()) {
707
- // addTabsNavStyle(true);
708
- // 全屏下按键esc后要执行的动作
709
- // isFullScreen 为true 此时为全屏状态 false 为非全屏状态
710
- if (!isFullScreen) {
711
- // 按下esc键退出全屏
712
- setIsFnllScreen(false);
713
- } else {
714
- setIsFnllScreen(false);
907
+ try {
908
+ // 检查组件是否已卸载
909
+ if (isUnmountedRef.current) {
910
+ return;
715
911
  }
912
+
913
+ // getTableHeight();
914
+ if (!checkFull()) {
915
+ // addTabsNavStyle(true);
916
+ // 全屏下按键esc后要执行的动作
917
+ // isFullScreen 为true 此时为全屏状态 false 为非全屏状态
918
+ if (!isFullScreen) {
919
+ // 按下esc键退出全屏
920
+ setIsFnllScreen(false);
921
+ } else {
922
+ setIsFnllScreen(false);
923
+ }
924
+ }
925
+ } catch (error) {
926
+ console.warn('Error in watchWinResize callback:', error);
927
+ } finally {
928
+ debounceTimer.current = null;
716
929
  }
717
- debounceTimer.current = null;
718
930
  }, 10);
719
931
  }, [isFullScreen]);
720
932
 
@@ -726,8 +938,8 @@ export default (props: any) => {
726
938
  });
727
939
  };
728
940
 
729
- // 处理 table 基本参数
730
- const setTableProps = () => {
941
+ // 处理 table 基本参数 - 使用useCallback避免闭包引用
942
+ const setTableProps = useCallback(() => {
731
943
  const defaultPageSize = 20;
732
944
  const baseTableProps = {
733
945
  size: value.size || 'middle',
@@ -737,7 +949,7 @@ export default (props: any) => {
737
949
  expandable: props.expandable,
738
950
  scroll: {
739
951
  x: props.overScrollX || 'max-content',
740
- y: props?.overScrollY || height,
952
+ y: props?.overScrollY || heightRef.current,
741
953
  },
742
954
  bordered: typeof value.bordered === 'boolean' ? value.bordered : true,
743
955
  sticky: typeof value.sticky === 'boolean' ? value.sticky : true,
@@ -769,7 +981,7 @@ export default (props: any) => {
769
981
  ...value.tableProps?.initialPaging?.pagination,
770
982
  };
771
983
 
772
- const handleRowClick = (record) => {
984
+ const handleRowClick = useCallback((record) => {
773
985
  const { rowKey } = value;
774
986
 
775
987
  let newSelectedRowKeys = [...(rowsRef?.current?.selectedRowKeys || [])];
@@ -797,15 +1009,15 @@ export default (props: any) => {
797
1009
  // 直接将 record 作为数组元素传递
798
1010
  rowSelection.onChange(newSelectedRowKeys, newSelectedRows);
799
1011
  }
800
- };
1012
+ }, [value.rowKey, value.rowSelection]);
801
1013
 
802
- const handleRowDoubleClick = (record) => {
1014
+ const handleRowDoubleClick = useCallback((record) => {
803
1015
  console.log('handleRowDoubleClick', record);
804
1016
  if (props.viewPagePath) {
805
1017
  const path = eval(`\`${props.viewPagePath.replace(/'/g, '`').replace(/#{(.*?)}/g, (match, p) => `\${${p}}`)}\``);
806
1018
  history.push(path);
807
1019
  }
808
- };
1020
+ }, [props.viewPagePath, history]);
809
1021
 
810
1022
  const tableProps = {
811
1023
  ...baseTableProps,
@@ -827,7 +1039,7 @@ export default (props: any) => {
827
1039
  tableProps.initialPaging.pagination.showSizeChanger = true;
828
1040
  }
829
1041
  return tableProps;
830
- };
1042
+ }, [value, props, rowsRef, history]); // 不包含height依赖,使用heightRef.current
831
1043
 
832
1044
  const ShowFullScreen = () => {
833
1045
  const isFullScreen: any =
@@ -1072,14 +1284,87 @@ export default (props: any) => {
1072
1284
  config.ref?.current?.tableRef?.current?.refreshTable();
1073
1285
  }
1074
1286
  }
1075
- setTimeout(() => {
1076
- // 处理页面刷新两面
1077
- localStorage.removeItem('isTabChange');
1287
+ const timeoutId = setTimeout(() => {
1288
+ try {
1289
+ // 检查组件是否已卸载
1290
+ if (isUnmountedRef.current) {
1291
+ return;
1292
+ }
1293
+ // 处理页面刷新两面
1294
+ localStorage.removeItem('isTabChange');
1295
+ } catch (error) {
1296
+ console.warn('Error in localStorage cleanup:', error);
1297
+ }
1078
1298
  }, 0);
1299
+
1300
+ return () => {
1301
+ clearTimeout(timeoutId);
1302
+ };
1079
1303
  }, [pathname]);
1304
+
1305
+ // 增强的外部库引用管理
1306
+ useEffect(() => {
1307
+ // 生成唯一组件ID
1308
+ const componentId = `bsSulaQueryTable_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
1309
+
1310
+ // 注册到全局实例管理器(如果存在)
1311
+ if (typeof window !== 'undefined') {
1312
+ if (!window.__bsSulaQueryTableInstances) {
1313
+ window.__bsSulaQueryTableInstances = [];
1314
+ }
1315
+ window.__bsSulaQueryTableInstances.push({
1316
+ id: componentId,
1317
+ cleanup: () => {
1318
+ // 提供给外部库的清理接口
1319
+ isUnmountedRef.current = true;
1320
+ if (memoConfigRef.current) {
1321
+ memoConfigRef.current.summaryList = null;
1322
+ }
1323
+ }
1324
+ });
1325
+ }
1326
+
1327
+ return () => {
1328
+ // 从全局实例管理器中移除
1329
+ if (typeof window !== 'undefined' && window.__bsSulaQueryTableInstances) {
1330
+ const instances = window.__bsSulaQueryTableInstances;
1331
+ const instanceIndex = instances.findIndex(instance => instance.id === componentId);
1332
+ if (instanceIndex > -1) {
1333
+ instances.splice(instanceIndex, 1);
1334
+ }
1335
+ }
1336
+ };
1337
+ }, []);
1080
1338
 
1081
1339
  const expandedRowKeys = props?.expandable?.expandedRowKeys;
1082
1340
 
1341
+ // 组件卸载状态跟踪
1342
+ const isUnmountedRef = useRef(false);
1343
+ // 使用WeakSet减少强引用,避免内存泄漏
1344
+ const summaryInstancesRef = useRef(new WeakSet());
1345
+ // 临时存储强引用用于清理,清理完成后立即清空
1346
+ const tempInstancesRef = useRef([]);
1347
+ // 存储memoConfig引用,用于卸载时清理
1348
+ const memoConfigRef = useRef(null);
1349
+
1350
+ // 使用ref存储状态,避免getTableSummaryInfo函数的闭包依赖
1351
+ const stateRef = useRef({
1352
+ summaryList: props.summaryList,
1353
+ rowSelection: props.rowSelection,
1354
+ expandable: props.expandable,
1355
+ showColumn: showColumn
1356
+ });
1357
+
1358
+ // 更新stateRef
1359
+ useEffect(() => {
1360
+ stateRef.current = {
1361
+ summaryList: props.summaryList,
1362
+ rowSelection: props.rowSelection,
1363
+ expandable: props.expandable,
1364
+ showColumn: showColumn
1365
+ };
1366
+ }, [props.summaryList, props.rowSelection, props.expandable, showColumn]);
1367
+
1083
1368
  //todo summary属性已经被使用,为了兼容之前的,现在使用 summaryList
1084
1369
  //结构为了实现多行总结栏 定义如下(lableShow: boolean 是否显示列文本)
1085
1370
  /**
@@ -1091,56 +1376,181 @@ export default (props: any) => {
1091
1376
  * @returns []
1092
1377
  */
1093
1378
  const getTableSummaryInfo = useCallback(() => {
1094
- const { summaryList, rowSelection, expandable }: any = props;
1379
+ // 检查组件是否已卸载,避免创建新的JSX元素
1380
+ if (isUnmountedRef.current) {
1381
+ console.warn('[BsSulaQueryTable] 组件已卸载,跳过Summary创建');
1382
+ return undefined;
1383
+ }
1384
+ // 使用ref访问状态,避免闭包依赖
1385
+ const { summaryList, rowSelection, expandable, showColumn } = stateRef.current;
1095
1386
  if (summaryList && Array.isArray(summaryList)) {
1096
1387
  const summaryRow = rowSelection ? [{}, ...showColumn] : [...showColumn];
1097
1388
  if (expandable) {
1098
1389
  summaryRow.unshift({});
1099
1390
  }
1100
1391
  // let summaryInitial = summary().cont;
1101
- return (
1102
- <Table.Summary fixed>
1103
- {Array.isArray(summaryList) &&
1104
- summaryList.map((summaryItem, summaryIndex) => (
1105
- <Table.Summary.Row key={summaryIndex}>
1106
- {summaryRow.map((item: any, index: number) => {
1107
- const { cout = [] } = summaryItem;
1108
- const target = cout.filter(
1109
- (inner: any) =>
1110
- inner.key &&
1111
- (inner.key === item.dataIndex || inner.key === item.key),
1112
- )[0];
1113
- if (target) {
1114
- const labelText = target.labelShow ? `${item.title}:` : ``;
1115
- return (
1116
- <Table.Summary.Cell
1117
- index={index}
1118
- key={`Table.Summary.Cell_${item.index}`}
1119
- >
1120
- <Text type="danger">
1121
- {`${labelText} ${target.value ?? ''}`}
1122
- </Text>
1123
- </Table.Summary.Cell>
1124
- );
1125
- } else {
1126
- return (
1127
- <Table.Summary.Cell
1128
- index={index}
1129
- key={`Table.Summary.Cell_${item.index}`}
1130
- >
1131
- <Text type="danger">{` `}</Text>
1132
- </Table.Summary.Cell>
1133
- );
1392
+ // 创建Summary组件并跟踪实例,添加强化的清理逻辑
1393
+ const summaryElement = (
1394
+ <Table.Summary
1395
+ fixed
1396
+ ref={(ref) => {
1397
+ if (ref && !isUnmountedRef.current) {
1398
+ summaryInstancesRef.current.add(ref);
1399
+ tempInstancesRef.current.push(ref);
1400
+
1401
+ // 为每个Summary实例添加清理标记
1402
+ if (ref && typeof ref === 'object') {
1403
+ ref._bsQueryTableCleanup = () => {
1404
+ try {
1405
+ // 清理可能的context引用
1406
+ if (ref.context) {
1407
+ ref.context = null;
1408
+ }
1409
+ // 清理可能的props引用
1410
+ if (ref.props) {
1411
+ Object.keys(ref.props).forEach(key => {
1412
+ if (typeof ref.props[key] === 'function') {
1413
+ ref.props[key] = null;
1414
+ }
1415
+ });
1416
+ }
1417
+ // 清理可能的state引用
1418
+ if (ref.state) {
1419
+ ref.state = null;
1420
+ }
1421
+ } catch (error) {
1422
+ console.warn('[BsSulaQueryTable] Summary实例清理失败:', error);
1134
1423
  }
1135
- })}
1136
- </Table.Summary.Row>
1137
- ))}
1424
+ };
1425
+ }
1426
+ } else if (ref && isUnmountedRef.current) {
1427
+ // 如果组件已卸载但仍有ref回调,立即清理
1428
+ console.warn('[BsSulaQueryTable] 检测到卸载后的Summary ref回调,立即清理');
1429
+ if (typeof ref._bsQueryTableCleanup === 'function') {
1430
+ ref._bsQueryTableCleanup();
1431
+ }
1432
+ }
1433
+ }}
1434
+ >
1435
+ {Array.isArray(summaryList) &&
1436
+ summaryList.map((summaryItem, summaryIndex) => {
1437
+ // 再次检查卸载状态
1438
+ if (isUnmountedRef.current) return null;
1439
+
1440
+ return (
1441
+ <Table.Summary.Row
1442
+ key={summaryIndex}
1443
+ ref={(ref) => {
1444
+ if (ref && !isUnmountedRef.current) {
1445
+ summaryInstancesRef.current.add(ref);
1446
+ tempInstancesRef.current.push(ref);
1447
+ // 为Summary.Row添加清理标记
1448
+ if (ref && typeof ref === 'object') {
1449
+ ref._bsQueryTableCleanup = () => {
1450
+ try {
1451
+ if (ref.context) ref.context = null;
1452
+ if (ref.props) {
1453
+ Object.keys(ref.props).forEach(key => {
1454
+ if (typeof ref.props[key] === 'function') {
1455
+ ref.props[key] = null;
1456
+ }
1457
+ });
1458
+ }
1459
+ if (ref.state) ref.state = null;
1460
+ } catch (error) {
1461
+ console.warn('[BsSulaQueryTable] Summary.Row清理失败:', error);
1462
+ }
1463
+ };
1464
+ }
1465
+ } else if (ref && isUnmountedRef.current) {
1466
+ if (typeof ref._bsQueryTableCleanup === 'function') {
1467
+ ref._bsQueryTableCleanup();
1468
+ }
1469
+ }
1470
+ }}
1471
+ >
1472
+ {summaryRow.map((item: any, index: number) => {
1473
+ // 检查卸载状态
1474
+ if (isUnmountedRef.current) return null;
1475
+
1476
+ const { cout = [] } = summaryItem;
1477
+ const target = cout.filter(
1478
+ (inner: any) =>
1479
+ inner.key &&
1480
+ (inner.key === item.dataIndex || inner.key === item.key),
1481
+ )[0];
1482
+
1483
+ const cellRef = (ref) => {
1484
+ if (ref && !isUnmountedRef.current) {
1485
+ summaryInstancesRef.current.add(ref);
1486
+ tempInstancesRef.current.push(ref);
1487
+ // 为Summary.Cell添加清理标记
1488
+ if (ref && typeof ref === 'object') {
1489
+ ref._bsQueryTableCleanup = () => {
1490
+ try {
1491
+ if (ref.context) ref.context = null;
1492
+ if (ref.props) {
1493
+ Object.keys(ref.props).forEach(key => {
1494
+ if (typeof ref.props[key] === 'function') {
1495
+ ref.props[key] = null;
1496
+ }
1497
+ });
1498
+ }
1499
+ if (ref.state) ref.state = null;
1500
+ } catch (error) {
1501
+ console.warn('[BsSulaQueryTable] Summary.Cell清理失败:', error);
1502
+ }
1503
+ };
1504
+ }
1505
+ } else if (ref && isUnmountedRef.current) {
1506
+ if (typeof ref._bsQueryTableCleanup === 'function') {
1507
+ ref._bsQueryTableCleanup();
1508
+ }
1509
+ }
1510
+ };
1511
+
1512
+ if (target) {
1513
+ const labelText = target.labelShow ? `${item.title}:` : ``;
1514
+ return (
1515
+ <Table.Summary.Cell
1516
+ index={index}
1517
+ key={`Table.Summary.Cell_${item.index}`}
1518
+ ref={cellRef}
1519
+ >
1520
+ <Text
1521
+ type="danger"
1522
+ >
1523
+ {`${labelText} ${target.value ?? ''}`}
1524
+ </Text>
1525
+ </Table.Summary.Cell>
1526
+ );
1527
+ } else {
1528
+ return (
1529
+ <Table.Summary.Cell
1530
+ index={index}
1531
+ key={`Table.Summary.Cell_${item.index}`}
1532
+ ref={cellRef}
1533
+ >
1534
+ <Text
1535
+ type="danger"
1536
+ >
1537
+ {` `}
1538
+ </Text>
1539
+ </Table.Summary.Cell>
1540
+ );
1541
+ }
1542
+ })}
1543
+ </Table.Summary.Row>
1544
+ );
1545
+ })}
1138
1546
  </Table.Summary>
1139
1547
  );
1548
+
1549
+ return summaryElement;
1140
1550
  } else {
1141
1551
  return undefined;
1142
1552
  }
1143
- }, [props.summaryList, props.rowSelection, props.expandable, showColumn]);
1553
+ }, []); // 移除所有依赖项,使用stateRef访问最新状态
1144
1554
 
1145
1555
  const columnsDom = <span className="ant-dropdown-link">
1146
1556
  <div onClick={sortTableRef?.current?.showModal} className="ant-dropdown-link">
@@ -1157,17 +1567,30 @@ export default (props: any) => {
1157
1567
  />
1158
1568
 
1159
1569
  const memoConfig = useMemo(
1160
- () => ({
1161
- ...config,
1162
- summary: props.summary,
1163
- summaryList: props.summaryList ? getTableSummaryInfo : undefined,
1164
- statusMapping: props.statusMapping,
1165
- isBsSulaQueryTable: true,
1166
- columnsDom:columnsDom,
1167
- queryFieldsDom,
1168
- }),
1570
+ () => {
1571
+ // 检查组件是否已卸载
1572
+ if (isUnmountedRef.current) {
1573
+ console.warn('[BsSulaQueryTable] 组件已卸载,跳过memoConfig创建');
1574
+ return {};
1575
+ }
1576
+
1577
+ const memoConfigObject = {
1578
+ ...config,
1579
+ summary: props.summary,
1580
+ summaryList: props.summaryList ? getTableSummaryInfo : undefined,
1581
+ statusMapping: props.statusMapping,
1582
+ isBsSulaQueryTable: true,
1583
+ columnsDom:columnsDom,
1584
+ queryFieldsDom,
1585
+ };
1586
+
1587
+ // 存储memoConfig引用用于清理
1588
+ memoConfigRef.current = memoConfigObject;
1589
+
1590
+ return memoConfigObject;
1591
+ },
1169
1592
  [
1170
- checkedList,
1593
+ // 只保留必要的依赖项,避免不必要的重新计算
1171
1594
  showColumn,
1172
1595
  props.statusMapping,
1173
1596
  showSearchFields,
@@ -1175,6 +1598,10 @@ export default (props: any) => {
1175
1598
  getTableSummaryInfo,
1176
1599
  props.summaryList,
1177
1600
  props.summary,
1601
+ props.rowSelection,
1602
+ props.expandable,
1603
+ config,
1604
+ bsTableCode,
1178
1605
  ],
1179
1606
  );
1180
1607