@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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bit-sun/business-component",
3
- "version": "4.2.5-per-alpha.2",
3
+ "version": "4.2.5-per-alpha.3",
4
4
  "scripts": {
5
5
  "start": "dumi dev",
6
6
  "docs:build": "dumi build",
@@ -63,6 +63,7 @@
63
63
  },
64
64
  "devDependencies": {
65
65
  "@ant-design/pro-layout": "7.1.3",
66
+ "@babel/parser": "^7.21.0",
66
67
  "@types/js-cookie": "^3.0.6",
67
68
  "@types/lodash": "^4.14.185",
68
69
  "@types/react": "17.0.2",
@@ -71,18 +72,18 @@
71
72
  "acorn": "^7.2.0",
72
73
  "acorn-walk": "^7.1.1",
73
74
  "antd": "^4.17.2",
75
+ "babel-plugin-transform-remove-console": "^6.9.4",
74
76
  "bssula": "^4.2.0",
75
- "umi-plugin-bssula": "^4.2.0",
76
77
  "dumi": "^1.0.14",
77
78
  "father-build": "^1.17.2",
78
79
  "gh-pages": "^3.0.0",
79
80
  "lint-staged": "^10.0.7",
81
+ "path-to-regexp": "^2.4.0",
80
82
  "prettier": "^2.2.1",
81
83
  "react-dnd": "^16.0.1",
82
84
  "react-dnd-html5-backend": "^16.0.1",
83
85
  "rollup-plugin-commonjs": "^10.1.0",
84
- "path-to-regexp": "^2.4.0",
85
- "@babel/parser": "^7.21.0"
86
+ "umi-plugin-bssula": "^4.2.0"
86
87
  },
87
88
  "publishConfig": {
88
89
  "access": "public"
@@ -1,5 +1,5 @@
1
1
  // @ts-nocheck
2
- import React, { useEffect, useState, useLayoutEffect, useImperativeHandle, useCallback, useRef } from 'react';
2
+ import React, { useEffect, useState, useLayoutEffect, useImperativeHandle, useCallback, useRef, useMemo } from 'react';
3
3
  import { List, Tooltip, Input, Button } from 'antd';
4
4
  import { Link, formatMessage, history } from 'umi';
5
5
  import classNames from 'classnames';
@@ -33,6 +33,7 @@ const DrawContent = ({onClose, originRoutes=[], itemPath}: any) => {
33
33
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
34
34
  const resizeHandlerRef = useRef<(() => void) | null>(null);
35
35
  const debounceTimerRef = useRef<NodeJS.Timeout | null>(null);
36
+ const drawContentRef = useRef<HTMLElement | null>(null);
36
37
 
37
38
  useEffect(() => {
38
39
  getMenuContentHeight();
@@ -81,15 +82,20 @@ const DrawContent = ({onClose, originRoutes=[], itemPath}: any) => {
81
82
  clearTimeout(debounceTimerRef.current);
82
83
  debounceTimerRef.current = null;
83
84
  }
85
+ // 清理DOM引用
86
+ drawContentRef.current = null;
84
87
  };
85
88
  }, []);
86
89
 
87
90
  useLayoutEffect(() => {
88
91
  timeoutRef.current = setTimeout(() => {
89
92
  if (!isUnmountedRef.current) {
90
- const drawContentElement = document.getElementById("drawContent");
91
- if (drawContentElement) {
92
- let drawContentHeight = drawContentElement.scrollHeight;
93
+ // 使用缓存的DOM引用,避免重复查询
94
+ if (!drawContentRef.current) {
95
+ drawContentRef.current = document.getElementById("drawContent");
96
+ }
97
+ if (drawContentRef.current) {
98
+ let drawContentHeight = drawContentRef.current.scrollHeight;
93
99
  if (drawContentHeight > rightMenuHeight) {
94
100
  setMoreBtnShow(true);
95
101
  }
@@ -114,36 +120,37 @@ const DrawContent = ({onClose, originRoutes=[], itemPath}: any) => {
114
120
  }, []);
115
121
 
116
122
 
123
+ // 优化递归渲染函数,使用React.memo减少重复渲染
117
124
  const renderChildItem = useCallback((child) => {
118
125
  if (!child.hideInMenu && child.children) {
119
126
  return (
120
- <>
127
+ <React.Fragment key={child.path || child.locale}>
121
128
  <List.Item style={{ color: '#000', fontWeight: 'bold' }}>
122
129
  {formatMessage({ id: `${child.locale}` })}
123
130
  </List.Item>
124
131
  {child.children.map((menuItem: any) => {
125
132
  return renderChildItem(menuItem)
126
133
  })}
127
- </>
134
+ </React.Fragment>
128
135
  );
129
136
  } else if (!child.hideInMenu && child.path) {
137
+ const displayText = formatMessage({ id: `${child.locale}` });
138
+ const truncatedText = displayText.length > 10
139
+ ? `${formatMessage({ id: `${child.name}` }).slice(0, 10)}...`
140
+ : displayText;
141
+
130
142
  return (
131
- <List.Item style={{fontSize: '12px'}} className="allFuncOnMouserover">
143
+ <List.Item key={child.path} style={{fontSize: '12px'}} className="allFuncOnMouserover">
132
144
  <Link to={child.path} onClick={(e) => {onMenuClick(e, child);}}>
133
- <Tooltip
134
- title={formatMessage({ id: `${child.locale}` })}
135
- >
136
- {formatMessage({ id: `${child.locale}` }).length > 10
137
- ? `${formatMessage({
138
- id: `${child.name}`,
139
- }).slice(0, 10)}...`
140
- : formatMessage({ id: `${child.locale}` })}
145
+ <Tooltip title={displayText}>
146
+ {truncatedText}
141
147
  </Tooltip>
142
- <img className="allFuncOnMouseroverImg" src={right}></img>
148
+ <img className="allFuncOnMouseroverImg" src={right} alt="arrow"></img>
143
149
  </Link>
144
150
  </List.Item>
145
151
  );
146
152
  }
153
+ return null;
147
154
  }, [onMenuClick]);
148
155
 
149
156
  const onMenuClick = useCallback((e, item) => {
@@ -191,8 +198,54 @@ const DrawContent = ({onClose, originRoutes=[], itemPath}: any) => {
191
198
  }
192
199
  }, [debouncedSearch]);
193
200
 
201
+ // 优化菜单项点击处理器
202
+ const handleMenuItemClick = useCallback((item: any) => {
203
+ if (isUnmountedRef.current) return;
204
+
205
+ if (item.component) {
206
+ history.push({
207
+ pathname: item.path
208
+ });
209
+ onClose();
210
+ } else {
211
+ setCurrentOneLevel(item.path);
212
+ let currentDom = document.getElementById(item.path);
213
+ currentDom && currentDom.scrollIntoView();
214
+ }
215
+ }, [onClose]);
216
+
217
+ // 优化关闭按钮点击处理器
218
+ const handleCloseClick = useCallback(() => {
219
+ if (!isUnmountedRef.current) {
220
+ onClose();
221
+ }
222
+ }, [onClose]);
194
223
 
195
- let searchHistoryList = JSON.parse(localStorage.getItem(`${itemPath}_search_history`) || '[]');
224
+ // 优化搜索结果点击处理器
225
+ const handleSearchResultClick = useCallback((item: any) => {
226
+ if (!isUnmountedRef.current) {
227
+ onMenuClick({stopPropagation: () => {}, preventDefault: () => {}}, item);
228
+ }
229
+ }, [onMenuClick]);
230
+
231
+ // 优化更多按钮点击处理器
232
+ const handleMoreButtonClick = useCallback(() => {
233
+ if (!isUnmountedRef.current) {
234
+ setShowScroll(true);
235
+ setMoreBtnShow(false);
236
+ }
237
+ }, []);
238
+
239
+
240
+ // 使用useMemo缓存searchHistoryList,避免每次渲染都重新解析localStorage
241
+ const searchHistoryList = useMemo(() => {
242
+ try {
243
+ return JSON.parse(localStorage.getItem(`${itemPath}_search_history`) || '[]');
244
+ } catch (error) {
245
+ console.warn('Failed to parse search history:', error);
246
+ return [];
247
+ }
248
+ }, [itemPath]);
196
249
  return (
197
250
  <div style={{height: `${drawHeight}px`}} className={'global_menu_draw_content'}>
198
251
  <div className={'drawerWarp_left'}>
@@ -206,19 +259,9 @@ const DrawContent = ({onClose, originRoutes=[], itemPath}: any) => {
206
259
  color: currentOneLevel === item.path ? '#005cff' : '#000000'
207
260
  }}
208
261
  onClick={(e) => {
209
- if (isUnmountedRef.current) return;
210
262
  e.stopPropagation();
211
- e.preventDefault()
212
- if (item.component) {
213
- history.push({
214
- pathname: item.path
215
- })
216
- onClose();
217
- } else {
218
- setCurrentOneLevel(item.path);
219
- let currentDom = document.getElementById(item.path);
220
- currentDom && currentDom.scrollIntoView();
221
- }
263
+ e.preventDefault();
264
+ handleMenuItemClick(item);
222
265
  }}
223
266
  >
224
267
  {item.name}
@@ -227,11 +270,7 @@ const DrawContent = ({onClose, originRoutes=[], itemPath}: any) => {
227
270
  </div>
228
271
  <div style={{flexGrow: 1, position: 'relative'}}>
229
272
  <img
230
- onClick={() => {
231
- if (!isUnmountedRef.current) {
232
- onClose();
233
- }
234
- }}
273
+ onClick={handleCloseClick}
235
274
  style={{position: 'absolute', right: '15px', top: '17px', cursor: 'pointer'}} width={24} src={closeicon} />
236
275
 
237
276
  <div style={{ padding: '10px', marginBottom: '10px', width: '100%', }}>
@@ -259,11 +298,7 @@ const DrawContent = ({onClose, originRoutes=[], itemPath}: any) => {
259
298
  (<div className={'search_menu_content'}>
260
299
  {
261
300
  SearhData.map((item: any) => (
262
- <div onClick={(e) => {
263
- if (!isUnmountedRef.current) {
264
- onMenuClick(e, item);
265
- }
266
- }} key={item.path}>{item.name}</div>
301
+ <div onClick={() => handleSearchResultClick(item)} key={item.path}>{item.name}</div>
267
302
  ))
268
303
  }
269
304
  </div>) : (
@@ -329,12 +364,7 @@ const DrawContent = ({onClose, originRoutes=[], itemPath}: any) => {
329
364
  height: '30px'
330
365
  }}>
331
366
  <span
332
- onClick={() => {
333
- if (!isUnmountedRef.current) {
334
- setShowScroll(true);
335
- setMoreBtnShow(false);
336
- }
337
- }}
367
+ onClick={handleMoreButtonClick}
338
368
  style={{color: '#8c8c8c'}}
339
369
  >
340
370
  <CaretDownOutlined />
@@ -4,7 +4,8 @@ import React, {
4
4
  useEffect,
5
5
  forwardRef,
6
6
  useRef,
7
- useImperativeHandle
7
+ useImperativeHandle,
8
+ useCallback
8
9
  } from "react";
9
10
  import { Button, Drawer, Modal, Menu } from 'antd';
10
11
  import addIcon from '../../../../../assets/addIcon.svg';
@@ -65,19 +66,21 @@ const CustomerMenu = forwardRef(({isCollapse, handleClose, actionRef, originRout
65
66
  })
66
67
 
67
68
 
68
- const handleMenuClick = (item: any) => {
69
+ const handleMenuClick = useCallback((item: any) => {
69
70
  if (item.children || !item.component) return;
70
- setIsDrawer(!isDrawer);
71
+ setIsDrawer(false);
71
72
  history.push({
72
73
  pathname: item.path
73
74
  })
74
- }
75
+ }, []);
75
76
 
76
- const getMenuDom = (menuData) => {
77
- return menuData.map(item => (
78
- <div style={{paddingLeft: '10px'}}>
79
- <div onClick={()=> {handleMenuClick(item)}} className={`${'menu_item'} ${item.children || !item.component ? '' : 'link_style'}`}
80
- style={{fontWeight: item.children || !item.component ? 'bolder' : '400', paddingLeft: '4px'}}>
77
+ const getMenuDom = useCallback((menuData) => {
78
+ return menuData.map((item, index) => (
79
+ <div key={`${item.path || item.name}-${index}`} style={{paddingLeft: '10px'}}>
80
+ <div
81
+ onClick={item.children || !item.component ? undefined : () => handleMenuClick(item)}
82
+ className={`${'menu_item'} ${item.children || !item.component ? '' : 'link_style'}`}
83
+ style={{fontWeight: item.children || !item.component ? 'bolder' : '400', paddingLeft: '4px', cursor: item.children || !item.component ? 'default' : 'pointer'}}>
81
84
  {item.name}
82
85
  </div>
83
86
  {
@@ -85,7 +88,7 @@ const CustomerMenu = forwardRef(({isCollapse, handleClose, actionRef, originRout
85
88
  }
86
89
  </div>
87
90
  ))
88
- }
91
+ }, [handleMenuClick]);
89
92
 
90
93
  return (
91
94
  <div className={'customer_menu_content'}>
@@ -618,16 +618,71 @@ class BasicLayout extends React.PureComponent {
618
618
  });
619
619
  this.timeoutIds.clear();
620
620
 
621
+ // 清理DOM缓存引用
622
+ if (this.cachedTabsElements) {
623
+ this.cachedTabsElements.globalTabsNav = null;
624
+ this.cachedTabsElements.globalTabsNavWrap = null;
625
+ this.cachedTabsElements = null;
626
+ }
627
+
628
+ // 清理ref引用
629
+ if (this.allFunc && this.allFunc.current) {
630
+ this.allFunc.current = null;
631
+ }
632
+ if (this.customerMenuRef && this.customerMenuRef.current) {
633
+ this.customerMenuRef.current = null;
634
+ }
635
+ if (this.actionRef && this.actionRef.current) {
636
+ this.actionRef.current = null;
637
+ }
638
+
621
639
  // 清理实例属性
622
640
  this.routerArray = null;
623
641
  this.authMenuPathList = null;
624
642
  this.docsId = null;
625
643
  this.lastTwoRouterArray = null;
626
- this.cachedTabsElements = null;
627
644
  this.cachedBreadcrumbNameMap = null;
628
645
  this.cachedWeiqianduanProps = null;
629
646
  this.cachedOperationsSlot = null;
630
647
  this.lastIsSliderState = null;
648
+
649
+ // 清理菜单容器的事件监听器
650
+ const menuContainer = document.querySelector('.sub_menu_content');
651
+ if (menuContainer) {
652
+ menuContainer.removeEventListener('click', this.handleMenuContainerClick);
653
+ // 清理所有菜单项的data属性和引用
654
+ const menuItems = menuContainer.querySelectorAll('[data-menu-item]');
655
+ menuItems.forEach(item => {
656
+ // 从WeakMap中移除引用
657
+ if (this.menuItemsWeakMap && this.menuItemsWeakMap.has(item)) {
658
+ this.menuItemsWeakMap.delete(item);
659
+ }
660
+ // 清理data属性
661
+ item.removeAttribute('data-menu-item');
662
+ item.removeAttribute('data-item-path');
663
+ item.removeAttribute('data-has-children');
664
+ });
665
+ }
666
+
667
+ // 清理WeakMap
668
+ if (this.menuItemsWeakMap) {
669
+ // WeakMap会自动清理,但显式清空以确保
670
+ this.menuItemsWeakMap = null;
671
+ }
672
+
673
+ // 清理方法引用,断开闭包链
674
+ this.handleMenuClick = null;
675
+ this.handleMenuContainerClick = null;
676
+ this.getMenuDom = null;
677
+ this.setShowMenu = null;
678
+ this.getTabsNavElements = null;
679
+ this.setTabNavTransLate = null;
680
+ this.checkisNavSlide = null;
681
+
682
+ // 强制触发垃圾回收(如果可用)
683
+ if (window.gc && typeof window.gc === 'function') {
684
+ setTimeout(() => window.gc(), 0);
685
+ }
631
686
  }
632
687
 
633
688
  parseQueryString = (queryString) => {
@@ -727,14 +782,12 @@ class BasicLayout extends React.PureComponent {
727
782
  };
728
783
 
729
784
  getDictionaryTextByValue = (dicCode: string, value: string) => {
730
- let startPerformance = performance.now();
731
785
  if (window.dicDataTextValue) {
732
786
  const dicDataTextValue = window.dicDataTextValue;
733
787
  let dicData = [];
734
788
 
735
789
  dicData = dicDataTextValue[dicCode];
736
790
 
737
- let endPerformance1 = performance.now();
738
791
 
739
792
  if (value === undefined) return "-";
740
793
 
@@ -743,7 +796,6 @@ class BasicLayout extends React.PureComponent {
743
796
  return value;
744
797
  }
745
798
 
746
- let endPerformance = performance.now();
747
799
 
748
800
  return dicData[value] || value;
749
801
  }
@@ -928,29 +980,57 @@ class BasicLayout extends React.PureComponent {
928
980
  this.setShowMenu(false);
929
981
  };
930
982
 
983
+ // 使用WeakMap存储菜单项引用,避免强引用
984
+ menuItemsWeakMap = new WeakMap();
985
+
986
+ // 容器级别的事件委托处理器
987
+ handleMenuContainerClick = (event) => {
988
+ const target = event.target.closest('[data-menu-item]');
989
+ if (!target) return;
990
+
991
+ const itemPath = target.getAttribute('data-item-path');
992
+ const hasChildren = target.getAttribute('data-has-children') === 'true';
993
+
994
+ if (!hasChildren && itemPath) {
995
+ // 从WeakMap中获取完整的item对象
996
+ const item = this.menuItemsWeakMap.get(target) || { path: itemPath };
997
+ this.handleMenuClick(item);
998
+ }
999
+ };
1000
+
931
1001
  getMenuDom = (menuData) => {
932
- return menuData.map((item) => (
933
- <div>
934
- <div
935
- onClick={() => {
936
- this.handleMenuClick(item);
937
- }}
938
- className={`${'menu_item'} ${item.children || !item.component ? '' : 'link_style'
939
- }`}
940
- style={{
941
- fontWeight: item.children || !item.component ? 'bolder' : '400',
942
- paddingLeft: '10px',
943
- marginTop: item.children || !item.component ? '5px' : '0px',
944
- fontSize: item.children || !item.component ? '14px' : '12px',
945
- }}
946
- >
947
- {item.name}
1002
+ return menuData.map((item, index) => {
1003
+ const hasChildren = item.children || !item.component;
1004
+
1005
+ return (
1006
+ <div key={`${item.path || item.name}-${index}`}>
1007
+ <div
1008
+ data-menu-item="true"
1009
+ data-item-path={item.path}
1010
+ data-has-children={hasChildren}
1011
+ className={`${'menu_item'} ${hasChildren ? '' : 'link_style'}`}
1012
+ style={{
1013
+ fontWeight: hasChildren ? 'bolder' : '400',
1014
+ paddingLeft: '10px',
1015
+ marginTop: hasChildren ? '5px' : '0px',
1016
+ fontSize: hasChildren ? '14px' : '12px',
1017
+ cursor: hasChildren ? 'default' : 'pointer',
1018
+ }}
1019
+ ref={(el) => {
1020
+ // 使用WeakMap存储元素与item的关联
1021
+ if (el && !hasChildren) {
1022
+ this.menuItemsWeakMap.set(el, item);
1023
+ }
1024
+ }}
1025
+ >
1026
+ {item.name}
1027
+ </div>
1028
+ {!!item.children &&
1029
+ !!item.children.length &&
1030
+ this.getMenuDom(item.children)}
948
1031
  </div>
949
- {!!item.children &&
950
- !!item.children.length &&
951
- this.getMenuDom(item.children)}
952
- </div>
953
- ));
1032
+ );
1033
+ });
954
1034
  };
955
1035
 
956
1036
  setShowMenu = debounce((isShow) => {
@@ -1534,7 +1614,7 @@ class BasicLayout extends React.PureComponent {
1534
1614
  tabBarGutter={8}
1535
1615
  onEdit={this.onEdit}
1536
1616
  tabBarExtraContent={OperationsSlot}
1537
- destroyInactiveTabPane={true}
1617
+ // destroyInactiveTabPane={true}
1538
1618
  animated={false}
1539
1619
  hideAdd
1540
1620
  ref={(tabsRef) => {
@@ -1599,6 +1679,7 @@ class BasicLayout extends React.PureComponent {
1599
1679
  onMouseLeave={() => {
1600
1680
  this.setShowMenu(false);
1601
1681
  }}
1682
+ onClick={this.handleMenuContainerClick}
1602
1683
  className={'sub_menu_content'}
1603
1684
  style={{ display: showSubMenu && !collapse ? 'block' : 'none' }}
1604
1685
  >
@@ -1630,7 +1711,7 @@ class WrapperComponent extends React.Component {
1630
1711
 
1631
1712
  // 清理可能的DOM事件监听器
1632
1713
  try {
1633
- const currentElement = document.querySelector(`#globalTabs .ant-tabs-tabpane[data-node-key="${this.props.item.key}"]`);
1714
+ const currentElement = document.getElementById('globalTabs-panel-' + this.props.item.key);
1634
1715
  if (currentElement) {
1635
1716
  // 移除所有可能的事件监听器
1636
1717
  const events = ['click', 'mousedown', 'mouseup', 'mouseover', 'mouseout', 'focus', 'blur'];
@@ -1638,6 +1719,35 @@ class WrapperComponent extends React.Component {
1638
1719
  currentElement.removeEventListener(eventType, this.handleEvent, true);
1639
1720
  currentElement.removeEventListener(eventType, this.handleEvent, false);
1640
1721
  });
1722
+
1723
+ // 清理拖拽相关的事件监听器
1724
+ const dragEvents = ['dragstart', 'dragend', 'dragover', 'dragenter', 'dragleave', 'drop'];
1725
+ dragEvents.forEach(eventType => {
1726
+ currentElement.removeEventListener(eventType, this.handleDragEvent, true);
1727
+ currentElement.removeEventListener(eventType, this.handleDragEvent, false);
1728
+ });
1729
+
1730
+ // 清理react-dnd相关的属性和状态
1731
+ if (currentElement._reactInternalFiber) {
1732
+ delete currentElement._reactInternalFiber;
1733
+ }
1734
+ if (currentElement._reactInternalInstance) {
1735
+ delete currentElement._reactInternalInstance;
1736
+ }
1737
+
1738
+ // 清理可能的拖拽状态数据
1739
+ const dragStateKeys = Object.keys(currentElement).filter(key =>
1740
+ key.includes('drag') || key.includes('drop') || key.includes('dnd')
1741
+ );
1742
+ dragStateKeys.forEach(key => {
1743
+ try {
1744
+ delete currentElement[key];
1745
+ } catch (e) {
1746
+ // 忽略删除失败的情况
1747
+ }
1748
+ });
1749
+
1750
+ currentElement = null;
1641
1751
  }
1642
1752
 
1643
1753
  // 强制垃圾回收提示
@@ -1654,6 +1764,11 @@ class WrapperComponent extends React.Component {
1654
1764
  handleEvent = (event) => {
1655
1765
  // 空的事件处理器,仅用于清理时移除监听器
1656
1766
  };
1767
+
1768
+ // 拖拽事件处理器,用于清理
1769
+ handleDragEvent = (event) => {
1770
+ // 空的拖拽事件处理器,仅用于清理时移除监听器
1771
+ };
1657
1772
 
1658
1773
  shouldComponentUpdate(nextProps) {
1659
1774
  if (window.__POWERED_BY_WUJIE__ && nextProps?.item?.key?.indexOf('edit-template-template') > -1) {