zmdms-webui 2.3.9 → 2.4.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.
@@ -155,7 +155,6 @@ function CanvasTable(props) {
155
155
  }), maxScrollTop = _1.maxScrollTop, maxScrollLeft = _1.maxScrollLeft;
156
156
  // 滚动管理(使用预先计算的maxScrollTop和maxScrollLeft)
157
157
  var _2 = useTableScroll({
158
- canvasRef: canvasRef,
159
158
  containerRef: containerRef,
160
159
  maxScrollTop: maxScrollTop,
161
160
  maxScrollLeft: maxScrollLeft,
@@ -269,9 +268,10 @@ function CanvasTable(props) {
269
268
  return processedColumns.filter(function (item) { return !item.key.includes("__"); });
270
269
  }, [processedColumns]);
271
270
  // Excel导出功能
272
- var exportExcel = useExcelExport(processedDataSource, {
271
+ var exportExcel = useExcelExport(newDataSource, {
273
272
  columns: exportColumns,
274
273
  isAutoMerge: isAutoMerge,
274
+ summaryConfig: [],
275
275
  });
276
276
  // 交互事件处理(使用baseScrollbarMetrics的maxScrollTop/maxScrollLeft以保持稳定)
277
277
  var _5 = useTableInteraction({
@@ -289,6 +289,7 @@ function CanvasTable(props) {
289
289
  containerHeight: containerHeight,
290
290
  scrollbarSize: SCROLLBAR_SIZE,
291
291
  canvasRef: canvasRef,
292
+ containerRef: containerRef,
292
293
  totalHeight: totalHeight,
293
294
  totalWidth: totalWidth,
294
295
  // 使用scrollbarMetrics的显示相关属性(需要随滚动更新)
@@ -79,12 +79,11 @@ var useScrollbarMetrics = function (params) {
79
79
  * 表格滚动管理 Hook
80
80
  *
81
81
  * 处理滚动相关的所有事件:
82
- * - Canvas 滚轮事件(使用 RAF 节流)
83
- * - Container 滚轮事件(阻止默认行为)
82
+ * - Container 滚轮事件(统一处理滚动,支持CellOverlay等覆盖层)
84
83
  * - 全局鼠标事件(拖拽滚动条)
85
84
  */
86
85
  var useTableScroll = function (params) {
87
- var canvasRef = params.canvasRef, containerRef = params.containerRef, maxScrollTop = params.maxScrollTop, maxScrollLeft = params.maxScrollLeft, onScroll = params.onScroll;
86
+ var containerRef = params.containerRef, maxScrollTop = params.maxScrollTop, maxScrollLeft = params.maxScrollLeft, onScroll = params.onScroll;
88
87
  var _a = useState({
89
88
  scrollLeft: 0,
90
89
  scrollTop: 0,
@@ -98,17 +97,35 @@ var useTableScroll = function (params) {
98
97
  // 用于节流滚轮事件的refs
99
98
  var rafIdRef = useRef(null);
100
99
  var pendingDeltaRef = useRef({ deltaX: 0, deltaY: 0 });
101
- // 滚轮事件处理(Canvas)- 使用 requestAnimationFrame 节流
100
+ // 注意:滚轮事件统一在Container级别处理,以确保在CellOverlay等覆盖层上也能正常滚动
101
+ // 滚轮事件处理(Container)
102
102
  useEffect(function () {
103
- var canvas = canvasRef.current;
104
- if (!canvas)
103
+ var container = containerRef.current;
104
+ if (!container)
105
105
  return;
106
- var handleNativeWheel = function (e) {
106
+ var handleNativeContainerWheel = function (e) {
107
+ // 检查事件是否来自过滤弹框,如果是,允许滚动
108
+ var target = e.target;
109
+ var isInFilterPopover = target.closest(".canvas-table-filter-popover");
110
+ if (isInFilterPopover) {
111
+ // 允许过滤弹框内部滚动
112
+ return;
113
+ }
107
114
  e.preventDefault();
108
115
  e.stopPropagation();
116
+ // 当鼠标在容器内任何位置(包括CellOverlay)时都能滚动
109
117
  // 累积滚动增量
110
- pendingDeltaRef.current.deltaX += e.deltaX;
111
- pendingDeltaRef.current.deltaY += e.deltaY;
118
+ // 支持 Shift + 鼠标滚轮进行水平滚动
119
+ if (e.shiftKey) {
120
+ // 按住 Shift 键时,将垂直滚动转换为水平滚动
121
+ pendingDeltaRef.current.deltaX += e.deltaY;
122
+ pendingDeltaRef.current.deltaY += 0;
123
+ }
124
+ else {
125
+ // 正常滚动逻辑
126
+ pendingDeltaRef.current.deltaX += e.deltaX;
127
+ pendingDeltaRef.current.deltaY += e.deltaY;
128
+ }
112
129
  // 如果已经有pending的动画帧,不需要再次请求
113
130
  if (rafIdRef.current !== null) {
114
131
  return;
@@ -134,39 +151,18 @@ var useTableScroll = function (params) {
134
151
  });
135
152
  });
136
153
  };
137
- canvas.addEventListener("wheel", handleNativeWheel, { passive: false });
154
+ container.addEventListener("wheel", handleNativeContainerWheel, {
155
+ passive: false,
156
+ });
138
157
  return function () {
139
- canvas.removeEventListener("wheel", handleNativeWheel);
158
+ container.removeEventListener("wheel", handleNativeContainerWheel);
140
159
  // 清理pending的动画帧
141
160
  if (rafIdRef.current !== null) {
142
161
  cancelAnimationFrame(rafIdRef.current);
143
162
  rafIdRef.current = null;
144
163
  }
145
164
  };
146
- }, [maxScrollTop, maxScrollLeft, onScroll, canvasRef, setScrollState]);
147
- // 滚轮事件处理(Container)
148
- useEffect(function () {
149
- var container = containerRef.current;
150
- if (!container)
151
- return;
152
- var handleNativeContainerWheel = function (e) {
153
- // 检查事件是否来自过滤弹框,如果是,允许滚动
154
- var target = e.target;
155
- var isInFilterPopover = target.closest(".canvas-table-filter-popover");
156
- if (isInFilterPopover) {
157
- // 允许过滤弹框内部滚动
158
- return;
159
- }
160
- e.preventDefault();
161
- e.stopPropagation();
162
- };
163
- container.addEventListener("wheel", handleNativeContainerWheel, {
164
- passive: false,
165
- });
166
- return function () {
167
- container.removeEventListener("wheel", handleNativeContainerWheel);
168
- };
169
- }, [containerRef]);
165
+ }, [containerRef, maxScrollTop, maxScrollLeft, onScroll, setScrollState]);
170
166
  // 全局鼠标事件处理(拖拽滚动条)
171
167
  useEffect(function () {
172
168
  var handleGlobalMouseUp = function () {
@@ -191,7 +187,128 @@ var useTableScroll = function (params) {
191
187
  setScrollState: setScrollState,
192
188
  };
193
189
  };
194
- // ==================== Hook 3: 滚动位置重置 ====================
190
+ var useSelectionBoundaryScroll = function (params) {
191
+ var containerRef = params.containerRef, isSelecting = params.isSelecting,
192
+ // scrollState,
193
+ setScrollState = params.setScrollState, maxScrollTop = params.maxScrollTop, maxScrollLeft = params.maxScrollLeft, onScroll = params.onScroll, getCellFromPosition = params.getCellFromPosition, updateSelection = params.updateSelection, columnRenderInfos = params.columnRenderInfos;
194
+ var scrollIntervalRef = useRef(null);
195
+ var currentMousePosRef = useRef(null);
196
+ var SCROLL_BOUNDARY_SIZE = 50; // 触发滚动的边界距离
197
+ var SCROLL_SPEED = 10; // 每次滚动的距离
198
+ // 检查边界滚动
199
+ var checkBoundaryScroll = useMemo(function () {
200
+ return function (mouseX, mouseY) {
201
+ if (!containerRef.current || !isSelecting)
202
+ return;
203
+ var rect = containerRef.current.getBoundingClientRect();
204
+ var containerWidth = rect.width;
205
+ var containerHeight = rect.height;
206
+ var scrollDeltaX = 0;
207
+ var scrollDeltaY = 0;
208
+ // 检查水平边界
209
+ if (mouseX < SCROLL_BOUNDARY_SIZE) {
210
+ // 接近左边界,向左滚动
211
+ scrollDeltaX = -SCROLL_SPEED;
212
+ }
213
+ else if (mouseX > containerWidth - SCROLL_BOUNDARY_SIZE) {
214
+ // 接近右边界,向右滚动
215
+ scrollDeltaX = SCROLL_SPEED;
216
+ }
217
+ // 检查垂直边界
218
+ if (mouseY < SCROLL_BOUNDARY_SIZE) {
219
+ // 接近顶部边界,向上滚动
220
+ scrollDeltaY = -SCROLL_SPEED;
221
+ }
222
+ else if (mouseY > containerHeight - SCROLL_BOUNDARY_SIZE) {
223
+ // 接近底部边界,向下滚动
224
+ scrollDeltaY = SCROLL_SPEED;
225
+ }
226
+ // 保存当前鼠标位置(用于滚动时更新框选)
227
+ currentMousePosRef.current = { x: mouseX, y: mouseY };
228
+ // 如果需要滚动
229
+ if (scrollDeltaX !== 0 || scrollDeltaY !== 0) {
230
+ // 清除之前的定时器
231
+ if (scrollIntervalRef.current) {
232
+ clearInterval(scrollIntervalRef.current);
233
+ }
234
+ // 开始持续滚动
235
+ scrollIntervalRef.current = setInterval(function () {
236
+ setScrollState(function (prev) {
237
+ var _a;
238
+ var newScrollLeft = Math.max(0, Math.min(maxScrollLeft, prev.scrollLeft + scrollDeltaX));
239
+ var newScrollTop = Math.max(0, Math.min(maxScrollTop, prev.scrollTop + scrollDeltaY));
240
+ if (newScrollLeft !== prev.scrollLeft ||
241
+ newScrollTop !== prev.scrollTop) {
242
+ onScroll === null || onScroll === void 0 ? void 0 : onScroll(newScrollLeft, newScrollTop);
243
+ // 滚动后检查鼠标位置的单元格并更新框选
244
+ if (getCellFromPosition &&
245
+ updateSelection &&
246
+ columnRenderInfos &&
247
+ currentMousePosRef.current) {
248
+ var _b = currentMousePosRef.current, x = _b.x, y = _b.y;
249
+ var cell = getCellFromPosition(x, y);
250
+ if (cell) {
251
+ // 检查是否在选择框列,如果是,则不更新框选
252
+ var isSelectionColumn = ((_a = columnRenderInfos[cell.col]) === null || _a === void 0 ? void 0 : _a.column.key) === "__selection__";
253
+ if (!isSelectionColumn) {
254
+ updateSelection(cell);
255
+ }
256
+ }
257
+ }
258
+ return __assign(__assign({}, prev), { scrollLeft: newScrollLeft, scrollTop: newScrollTop });
259
+ }
260
+ return prev;
261
+ });
262
+ }, 50); // 每50毫秒滚动一次
263
+ }
264
+ else {
265
+ // 停止滚动
266
+ if (scrollIntervalRef.current) {
267
+ clearInterval(scrollIntervalRef.current);
268
+ scrollIntervalRef.current = null;
269
+ }
270
+ }
271
+ };
272
+ }, [
273
+ containerRef,
274
+ isSelecting,
275
+ setScrollState,
276
+ maxScrollTop,
277
+ maxScrollLeft,
278
+ onScroll,
279
+ getCellFromPosition,
280
+ updateSelection,
281
+ columnRenderInfos,
282
+ ]);
283
+ // 停止边界滚动
284
+ var stopBoundaryScroll = useMemo(function () {
285
+ return function () {
286
+ if (scrollIntervalRef.current) {
287
+ clearInterval(scrollIntervalRef.current);
288
+ scrollIntervalRef.current = null;
289
+ }
290
+ };
291
+ }, []);
292
+ // 当框选结束时停止滚动
293
+ useEffect(function () {
294
+ if (!isSelecting) {
295
+ stopBoundaryScroll();
296
+ }
297
+ }, [isSelecting, stopBoundaryScroll]);
298
+ // 组件卸载时清理定时器
299
+ useEffect(function () {
300
+ return function () {
301
+ if (scrollIntervalRef.current) {
302
+ clearInterval(scrollIntervalRef.current);
303
+ }
304
+ };
305
+ }, []);
306
+ return {
307
+ checkBoundaryScroll: checkBoundaryScroll,
308
+ stopBoundaryScroll: stopBoundaryScroll,
309
+ };
310
+ };
311
+ // ==================== Hook 4: 滚动位置重置 ====================
195
312
  /**
196
313
  * 滚动位置重置 Hook
197
314
  *
@@ -249,4 +366,4 @@ var useScrollReset = function (params) {
249
366
  ]);
250
367
  };
251
368
 
252
- export { useScrollReset, useScrollbarMetrics, useTableScroll };
369
+ export { useScrollReset, useScrollbarMetrics, useSelectionBoundaryScroll, useTableScroll };
@@ -22,6 +22,8 @@ var useSummaryRow = function (params) {
22
22
  if (!hasSummary)
23
23
  return null;
24
24
  var summaryRecord = {};
25
+ // 原始值,为转为千分符的
26
+ var summaryOriginalValues = {};
25
27
  // 找到第一个列(包括序号列、选择框列)
26
28
  var firstColIndex = leafColumns.length > 0 ? 0 : -1;
27
29
  leafColumns.forEach(function (column, colIndex) {
@@ -43,9 +45,11 @@ var useSummaryRow = function (params) {
43
45
  calculatedSum = sum_1;
44
46
  // 应用格式化(仅在没有自定义回调或回调返回数值时)
45
47
  var formattedSum = calculatedSum;
48
+ summaryOriginalValues[dataIndex] = calculatedSum;
46
49
  if (typeof calculatedSum === "number") {
47
50
  if (column.precision !== undefined) {
48
51
  formattedSum = exactRound(calculatedSum, column.precision);
52
+ summaryOriginalValues[dataIndex] = calculatedSum;
49
53
  }
50
54
  if (column.thousand) {
51
55
  formattedSum = addThousedSeparator(formattedSum);
@@ -64,7 +68,7 @@ var useSummaryRow = function (params) {
64
68
  leafColumns.forEach(function (column) {
65
69
  if (column.totalCalcCallback) {
66
70
  var dataIndex = column.dataIndex || column.key;
67
- summaryRecord[dataIndex] = column.totalCalcCallback(summaryRecord[dataIndex], summaryRecord);
71
+ summaryRecord[dataIndex] = column.totalCalcCallback(summaryOriginalValues[dataIndex], summaryOriginalValues);
68
72
  }
69
73
  });
70
74
  summaryRecord[IS_SUMMARY] = true;
@@ -1,6 +1,7 @@
1
1
  import { __assign, __spreadArray } from '../../_virtual/_tslib.js';
2
- import { useEffect } from 'react';
2
+ import { useRef, useEffect } from 'react';
3
3
  import { useMemoizedFn } from 'ahooks';
4
+ import { useSelectionBoundaryScroll } from './useScroll.js';
4
5
  import { FONT_SIZE, FONT_FAMILY } from '../utils/constants.js';
5
6
  import { isTextTruncated } from '../utils/canvasDrawHelpers.js';
6
7
  import { getMaxDepth, flattenHeaders, getLeafColumns } from '../utils/multiHeaderHelpers.js';
@@ -13,7 +14,7 @@ import { calculateSelectionState, toggleSelectAll, handleSortClick, calculateIco
13
14
  * 表格交互事件处理 Hook
14
15
  */
15
16
  var useTableInteraction = function (params) {
16
- var state = params.state, setState = params.setState, scrollState = params.scrollState, setScrollState = params.setScrollState, processedDataSource = params.processedDataSource, columnRenderInfos = params.columnRenderInfos, _a = params.columns, columns = _a === void 0 ? [] : _a, rowSelection = params.rowSelection, rowHeight = params.rowHeight, headerHeight = params.headerHeight, containerWidth = params.containerWidth, containerHeight = params.containerHeight, scrollbarSize = params.scrollbarSize, canvasRef = params.canvasRef, needVerticalScrollbar = params.needVerticalScrollbar, needHorizontalScrollbar = params.needHorizontalScrollbar, verticalScrollbarTop = params.verticalScrollbarTop, verticalScrollbarHeight = params.verticalScrollbarHeight, horizontalScrollbarLeft = params.horizontalScrollbarLeft, horizontalScrollbarWidth = params.horizontalScrollbarWidth, maxScrollTop = params.maxScrollTop, maxScrollLeft = params.maxScrollLeft, totalHeight = params.totalHeight, totalWidth = params.totalWidth, dataAreaHeight = params.dataAreaHeight, onSortChange = params.onSortChange, onRowClick = params.onRowClick, getRowKey = params.getRowKey, startSelection = params.startSelection, updateSelection = params.updateSelection, extendSelection = params.extendSelection, checkResizeHandle = params.checkResizeHandle, startResize = params.startResize, updateResize = params.updateResize, endResize = params.endResize, setHoverResizeColumn = params.setHoverResizeColumn, resizeState = params.resizeState, _b = params.hasSummaryRow, hasSummaryRow = _b === void 0 ? false : _b, mergeCellMap = params.mergeCellMap, menuShow = params.menuShow, fixedRowsCount = params.fixedRowsCount, fixedRowsConfig = params.fixedRowsConfig, summaryFixed = params.summaryFixed;
17
+ var state = params.state, setState = params.setState, scrollState = params.scrollState, setScrollState = params.setScrollState, processedDataSource = params.processedDataSource, columnRenderInfos = params.columnRenderInfos, _a = params.columns, columns = _a === void 0 ? [] : _a, rowSelection = params.rowSelection, rowHeight = params.rowHeight, headerHeight = params.headerHeight, containerWidth = params.containerWidth, containerHeight = params.containerHeight, scrollbarSize = params.scrollbarSize, canvasRef = params.canvasRef, containerRef = params.containerRef, needVerticalScrollbar = params.needVerticalScrollbar, needHorizontalScrollbar = params.needHorizontalScrollbar, verticalScrollbarTop = params.verticalScrollbarTop, verticalScrollbarHeight = params.verticalScrollbarHeight, horizontalScrollbarLeft = params.horizontalScrollbarLeft, horizontalScrollbarWidth = params.horizontalScrollbarWidth, maxScrollTop = params.maxScrollTop, maxScrollLeft = params.maxScrollLeft, totalHeight = params.totalHeight, totalWidth = params.totalWidth, dataAreaHeight = params.dataAreaHeight, onSortChange = params.onSortChange, onRowClick = params.onRowClick, getRowKey = params.getRowKey, startSelection = params.startSelection, updateSelection = params.updateSelection, extendSelection = params.extendSelection, checkResizeHandle = params.checkResizeHandle, startResize = params.startResize, updateResize = params.updateResize, endResize = params.endResize, setHoverResizeColumn = params.setHoverResizeColumn, resizeState = params.resizeState, _b = params.hasSummaryRow, hasSummaryRow = _b === void 0 ? false : _b, mergeCellMap = params.mergeCellMap, menuShow = params.menuShow, fixedRowsCount = params.fixedRowsCount, fixedRowsConfig = params.fixedRowsConfig, summaryFixed = params.summaryFixed;
17
18
  // 获取鼠标位置对应的单元格
18
19
  var getCellFromPosition = useMemoizedFn(function (x, y) {
19
20
  var fixedTopRowsCount = (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.topCount) || fixedRowsCount || 0;
@@ -464,6 +465,8 @@ var useTableInteraction = function (params) {
464
465
  updateSelection(cell);
465
466
  }
466
467
  }
468
+ // 检查边界滚动 - 当鼠标接近容器边界时自动滚动
469
+ checkBoundaryScroll(x, y);
467
470
  return;
468
471
  }
469
472
  // 检测列宽调整手柄hover状态
@@ -612,6 +615,8 @@ var useTableInteraction = function (params) {
612
615
  // 结束框选
613
616
  if (state.isSelecting) {
614
617
  setState(function (prev) { return (__assign(__assign({}, prev), { isSelecting: false })); });
618
+ // 停止边界滚动
619
+ stopBoundaryScroll();
615
620
  }
616
621
  });
617
622
  // 检查目标元素是否是Canvas内部渲染的元素
@@ -739,6 +744,15 @@ var useTableInteraction = function (params) {
739
744
  // 显示右键菜单
740
745
  menuShow({ event: e });
741
746
  });
747
+ // 使用ref存储最新的状态和函数引用,避免闭包问题
748
+ var stateRef = useRef(state);
749
+ stateRef.current = state;
750
+ var scrollStateRef = useRef(scrollState);
751
+ scrollStateRef.current = scrollState;
752
+ var resizeStateRef = useRef(resizeState);
753
+ resizeStateRef.current = resizeState;
754
+ var columnRenderInfosRef = useRef(columnRenderInfos);
755
+ columnRenderInfosRef.current = columnRenderInfos;
742
756
  // 在document上监听拖拽事件和框选事件,以支持鼠标移出canvas后继续操作
743
757
  useEffect(function () {
744
758
  var isDragging = scrollState.isDraggingVertical ||
@@ -756,27 +770,32 @@ var useTableInteraction = function (params) {
756
770
  var rect = canvas.getBoundingClientRect();
757
771
  var x = e.clientX - rect.left;
758
772
  var y = e.clientY - rect.top;
773
+ // 使用ref获取最新状态,避免闭包问题
774
+ var currentScrollState = scrollStateRef.current;
775
+ var currentResizeState = resizeStateRef.current;
776
+ var currentState = stateRef.current;
759
777
  // 处理列宽调整拖拽
760
- if ((resizeState === null || resizeState === void 0 ? void 0 : resizeState.isResizing) && updateResize) {
778
+ if ((currentResizeState === null || currentResizeState === void 0 ? void 0 : currentResizeState.isResizing) && updateResize) {
761
779
  updateResize(x);
762
780
  return;
763
781
  }
764
782
  // 处理垂直滚动条拖拽
765
- if (scrollState.isDraggingVertical) {
766
- handleVerticalScrollDrag(y - scrollState.dragStartY);
783
+ if (currentScrollState.isDraggingVertical) {
784
+ handleVerticalScrollDrag(y - currentScrollState.dragStartY);
767
785
  return;
768
786
  }
769
787
  // 处理水平滚动条拖拽
770
- if (scrollState.isDraggingHorizontal) {
771
- handleHorizontalScrollDrag(x - scrollState.dragStartX);
788
+ if (currentScrollState.isDraggingHorizontal) {
789
+ handleHorizontalScrollDrag(x - currentScrollState.dragStartX);
772
790
  return;
773
791
  }
774
792
  // 处理框选(在canvas外也能继续框选)
775
- if (state.isSelecting) {
793
+ if (currentState.isSelecting) {
776
794
  var cell = getCellFromPosition(x, y);
777
795
  if (cell) {
778
796
  // 检查是否在选择框列,如果是,则不更新框选
779
- var isSelectionColumn = ((_a = columnRenderInfos[cell.col]) === null || _a === void 0 ? void 0 : _a.column.key) === "__selection__";
797
+ var isSelectionColumn = ((_a = columnRenderInfosRef.current[cell.col]) === null || _a === void 0 ? void 0 : _a.column.key) ===
798
+ "__selection__";
780
799
  if (!isSelectionColumn) {
781
800
  updateSelection(cell);
782
801
  }
@@ -784,13 +803,18 @@ var useTableInteraction = function (params) {
784
803
  }
785
804
  };
786
805
  var handleDocumentMouseUp = function () {
806
+ // 使用ref获取最新状态
807
+ var currentResizeState = resizeStateRef.current;
808
+ var currentState = stateRef.current;
787
809
  // 结束列宽调整
788
- if ((resizeState === null || resizeState === void 0 ? void 0 : resizeState.isResizing) && endResize) {
810
+ if ((currentResizeState === null || currentResizeState === void 0 ? void 0 : currentResizeState.isResizing) && endResize) {
789
811
  endResize();
790
812
  }
791
813
  // 结束框选
792
- if (state.isSelecting) {
814
+ if (currentState.isSelecting) {
793
815
  setState(function (prev) { return (__assign(__assign({}, prev), { isSelecting: false })); });
816
+ // 停止边界滚动
817
+ stopBoundaryScroll();
794
818
  }
795
819
  setScrollState(function (prev) { return (__assign(__assign({}, prev), { isDraggingVertical: false, isDraggingHorizontal: false })); });
796
820
  };
@@ -800,22 +824,32 @@ var useTableInteraction = function (params) {
800
824
  document.removeEventListener("mousemove", handleDocumentMouseMove);
801
825
  document.removeEventListener("mouseup", handleDocumentMouseUp);
802
826
  };
827
+ // 优化:减少依赖项,避免频繁重注册事件监听器
803
828
  // eslint-disable-next-line react-hooks/exhaustive-deps
804
829
  }, [
805
830
  scrollState.isDraggingVertical,
806
831
  scrollState.isDraggingHorizontal,
807
832
  resizeState === null || resizeState === void 0 ? void 0 : resizeState.isResizing,
808
833
  state.isSelecting,
809
- updateResize,
810
- endResize,
811
- handleVerticalScrollDrag,
812
- handleHorizontalScrollDrag,
813
- updateSelection,
814
- setState,
815
- getCellFromPosition,
816
- columnRenderInfos,
817
- canvasRef,
834
+ // 移除函数依赖,通过 useMemoizedFn 和 useRef 来稳定引用
835
+ // updateResize, endResize, handleVerticalScrollDrag, handleHorizontalScrollDrag,
836
+ // updateSelection, setState, getCellFromPosition, columnRenderInfos,
818
837
  ]);
838
+ // 边界滚动Hook - 用于框选时的自动滚动
839
+ var _c = useSelectionBoundaryScroll({
840
+ containerRef: containerRef,
841
+ isSelecting: state.isSelecting,
842
+ scrollState: scrollState,
843
+ setScrollState: setScrollState,
844
+ maxScrollTop: maxScrollTop,
845
+ maxScrollLeft: maxScrollLeft,
846
+ onScroll: function (scrollLeft, scrollTop) {
847
+ // 边界滚动时的回调,可以在这里添加额外的逻辑
848
+ },
849
+ getCellFromPosition: getCellFromPosition,
850
+ updateSelection: updateSelection,
851
+ columnRenderInfos: columnRenderInfos,
852
+ }), checkBoundaryScroll = _c.checkBoundaryScroll, stopBoundaryScroll = _c.stopBoundaryScroll;
819
853
  return {
820
854
  handleCanvasMouseDown: handleCanvasMouseDown,
821
855
  handleCanvasMouseMove: handleCanvasMouseMove,
@@ -1,4 +1,4 @@
1
- import { useMemo, isValidElement, useEffect } from 'react';
1
+ import { useRef, useMemo, isValidElement, useEffect } from 'react';
2
2
  import { useMemoizedFn } from 'ahooks';
3
3
  import { IS_SUMMARY } from '../../table/constant.js';
4
4
  import '../../_virtual/_tslib.js';
@@ -19,6 +19,13 @@ import { calculateSelectionState, calculateIconArea } from '../utils/interaction
19
19
  * 表格渲染 Hook
20
20
  */
21
21
  var useTableRender = function (params) {
22
+ // 添加节流机制,避免频繁重绘
23
+ var rafIdRef = useRef(null);
24
+ var isDrawingRef = useRef(false);
25
+ // 内存优化:缓存上次的渲染参数,避免不必要的重绘
26
+ var lastRenderParamsRef = useRef("");
27
+ // 内存优化:缓存Canvas context,避免重复获取
28
+ var canvasContextRef = useRef(null);
22
29
  var canvasRef = params.canvasRef, processedDataSource = params.processedDataSource, columnRenderInfos = params.columnRenderInfos, columns = params.columns, state = params.state, scrollState = params.scrollState, rowSelection = params.rowSelection, containerWidth = params.containerWidth, containerHeight = params.containerHeight, headerHeight = params.headerHeight, baseHeaderHeight = params.baseHeaderHeight, rowHeight = params.rowHeight, bordered = params.bordered, striped = params.striped, _a = params.headerAlign, headerAlign = _a === void 0 ? "center" : _a, needVerticalScrollbar = params.needVerticalScrollbar, needHorizontalScrollbar = params.needHorizontalScrollbar, verticalScrollbarTop = params.verticalScrollbarTop, verticalScrollbarHeight = params.verticalScrollbarHeight, horizontalScrollbarLeft = params.horizontalScrollbarLeft, horizontalScrollbarWidth = params.horizontalScrollbarWidth, getRowKey = params.getRowKey, resizeState = params.resizeState, getColumnWidth = params.getColumnWidth, _b = params.RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_WIDTH = _b === void 0 ? 8 : _b, mergeCellMap = params.mergeCellMap, _c = params.hasSummaryRow, hasSummaryRow = _c === void 0 ? false : _c, fixedRowsCount = params.fixedRowsCount, fixedRowsConfig = params.fixedRowsConfig, _d = params.summaryFixed, summaryFixed = _d === void 0 ? false : _d;
23
30
  // 判断是否是多级表头
24
31
  var maxDepth = useMemo(function () { return getMaxDepth(columns); }, [columns]);
@@ -1323,157 +1330,245 @@ var useTableRender = function (params) {
1323
1330
  renderBorder(true);
1324
1331
  }
1325
1332
  });
1326
- // 绘制表格
1327
- var drawTable = useMemoizedFn(function () {
1328
- var canvas = canvasRef.current;
1329
- if (!canvas)
1330
- return;
1331
- var ctx = canvas.getContext("2d");
1332
- if (!ctx)
1333
- return;
1334
- // 设置 canvas 尺寸
1335
- var displayWidth = containerWidth;
1336
- var displayHeight = containerHeight;
1337
- canvas.width = displayWidth * dpr;
1338
- canvas.height = displayHeight * dpr;
1339
- canvas.style.width = "".concat(displayWidth, "px");
1340
- canvas.style.height = "".concat(displayHeight, "px");
1341
- ctx.scale(dpr, dpr);
1342
- // 优化文字渲染质量,防止模糊
1343
- ctx.imageSmoothingEnabled = true;
1344
- ctx.imageSmoothingQuality = "high";
1345
- // 清空画布
1346
- ctx.clearRect(0, 0, displayWidth, displayHeight);
1347
- // 绘制背景
1348
- ctx.fillStyle = COLORS.white;
1349
- ctx.fillRect(0, 0, displayWidth, displayHeight);
1350
- // 计算固定行的数量(支持多种配置方式)
1351
- var fixedTopRowsCount = fixedRowsCount || (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.topCount) || 0;
1352
- var fixedBottomRowsCount = (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.bottomCount) || 0;
1353
- // 计算固定行占用的高度
1354
- var fixedTopRowsHeight = fixedTopRowsCount * rowHeight;
1355
- var fixedBottomRowsHeight = fixedBottomRowsCount * rowHeight;
1356
- var fixedSummaryRowHeight = summaryFixed && hasSummaryRow ? rowHeight : 0;
1357
- // 计算可视区域的数据区域高度(不包括表头、固定行和固定合计行)
1358
- var dataAreaHeight = displayHeight -
1359
- headerHeight -
1360
- fixedTopRowsHeight -
1361
- fixedBottomRowsHeight -
1362
- (needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
1363
- fixedSummaryRowHeight;
1364
- // 计算可视区域(从固定行之后开始)
1365
- var scrollStartRow = fixedTopRowsCount + Math.floor(scrollState.scrollTop / rowHeight);
1366
- var scrollEndRow = Math.min(fixedTopRowsCount +
1367
- Math.ceil((scrollState.scrollTop + dataAreaHeight) / rowHeight), processedDataSource.length -
1368
- fixedBottomRowsCount -
1369
- (summaryFixed && hasSummaryRow ? 1 : 0) // 如果合计行固定,需要排除它
1370
- );
1371
- // 关键修复:向前扩展渲染范围,检查是否有合并单元格延伸到可视区域
1372
- // 策略:对每一列独立检查,找到该列最靠前的需要渲染的主合并单元格
1373
- if (mergeCellMap && scrollStartRow > fixedTopRowsCount) {
1374
- var originalStartRow = scrollStartRow;
1375
- var minStartRow = scrollStartRow;
1376
- // 对每一列独立检查
1377
- for (var colIndex = 0; colIndex < columnRenderInfos.length; colIndex++) {
1378
- // 向前查找这一列的主合并单元格,最多向前查找20行
1379
- for (var checkRow = originalStartRow - 1; checkRow >= fixedTopRowsCount && checkRow >= originalStartRow - 20; checkRow--) {
1380
- var mergeCellKey = "".concat(checkRow, "-").concat(colIndex);
1381
- var mergeInfo = mergeCellMap.get(mergeCellKey);
1382
- // 如果找到主单元格(不是被合并的单元格)
1383
- if (mergeInfo && !mergeInfo.skip && mergeInfo.rowSpan > 1) {
1384
- // 计算合并单元格的底部行索引
1385
- var mergeEndRow = checkRow + mergeInfo.rowSpan - 1;
1386
- // 如果这个合并单元格延伸到可视区域
1387
- if (mergeEndRow >= originalStartRow) {
1388
- // 更新最小起始行
1389
- minStartRow = Math.min(minStartRow, checkRow);
1333
+ // 绘制表格核心函数
1334
+ var drawTableCore = useMemoizedFn(function () {
1335
+ try {
1336
+ var canvas = canvasRef.current;
1337
+ if (!canvas)
1338
+ return;
1339
+ // 防止重复绘制
1340
+ if (isDrawingRef.current)
1341
+ return;
1342
+ isDrawingRef.current = true;
1343
+ // 内存优化:复用context对象
1344
+ var ctx_1 = canvasContextRef.current;
1345
+ if (!ctx_1 || ctx_1.canvas !== canvas) {
1346
+ ctx_1 = canvas.getContext("2d");
1347
+ if (!ctx_1) {
1348
+ isDrawingRef.current = false;
1349
+ return;
1350
+ }
1351
+ canvasContextRef.current = ctx_1;
1352
+ }
1353
+ // 性能优化:检查是否需要重绘(基于关键参数hash)
1354
+ var renderParamsHash = JSON.stringify({
1355
+ width: containerWidth,
1356
+ height: containerHeight,
1357
+ scrollTop: scrollState.scrollTop,
1358
+ scrollLeft: scrollState.scrollLeft,
1359
+ dataLength: processedDataSource.length,
1360
+ columnsLength: columnRenderInfos.length,
1361
+ hoverRow: state.hoverRowIndex,
1362
+ selection: state.cellSelection,
1363
+ resizing: resizeState === null || resizeState === void 0 ? void 0 : resizeState.isResizing,
1364
+ });
1365
+ // 如果关键参数没有变化,跳过重绘
1366
+ if (lastRenderParamsRef.current === renderParamsHash) {
1367
+ isDrawingRef.current = false;
1368
+ return;
1369
+ }
1370
+ lastRenderParamsRef.current = renderParamsHash;
1371
+ // 设置 canvas 尺寸
1372
+ var displayWidth_1 = containerWidth;
1373
+ var displayHeight_1 = containerHeight;
1374
+ canvas.width = displayWidth_1 * dpr;
1375
+ canvas.height = displayHeight_1 * dpr;
1376
+ canvas.style.width = "".concat(displayWidth_1, "px");
1377
+ canvas.style.height = "".concat(displayHeight_1, "px");
1378
+ ctx_1.scale(dpr, dpr);
1379
+ // 优化文字渲染质量,防止模糊
1380
+ ctx_1.imageSmoothingEnabled = true;
1381
+ ctx_1.imageSmoothingQuality = "high";
1382
+ // 清空画布
1383
+ ctx_1.clearRect(0, 0, displayWidth_1, displayHeight_1);
1384
+ // 绘制背景
1385
+ ctx_1.fillStyle = COLORS.white;
1386
+ ctx_1.fillRect(0, 0, displayWidth_1, displayHeight_1);
1387
+ // 计算固定行的数量(支持多种配置方式)
1388
+ var fixedTopRowsCount_1 = fixedRowsCount || (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.topCount) || 0;
1389
+ var fixedBottomRowsCount_1 = (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.bottomCount) || 0;
1390
+ // 计算固定行占用的高度
1391
+ var fixedTopRowsHeight = fixedTopRowsCount_1 * rowHeight;
1392
+ var fixedBottomRowsHeight = fixedBottomRowsCount_1 * rowHeight;
1393
+ var fixedSummaryRowHeight = summaryFixed && hasSummaryRow ? rowHeight : 0;
1394
+ // 计算可视区域的数据区域高度(不包括表头、固定行和固定合计行)
1395
+ var dataAreaHeight = displayHeight_1 -
1396
+ headerHeight -
1397
+ fixedTopRowsHeight -
1398
+ fixedBottomRowsHeight -
1399
+ (needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
1400
+ fixedSummaryRowHeight;
1401
+ // 计算可视区域(从固定行之后开始)
1402
+ var scrollStartRow_1 = fixedTopRowsCount_1 + Math.floor(scrollState.scrollTop / rowHeight);
1403
+ var scrollEndRow_1 = Math.min(fixedTopRowsCount_1 +
1404
+ Math.ceil((scrollState.scrollTop + dataAreaHeight) / rowHeight), processedDataSource.length -
1405
+ fixedBottomRowsCount_1 -
1406
+ (summaryFixed && hasSummaryRow ? 1 : 0) // 如果合计行固定,需要排除它
1407
+ );
1408
+ // 关键修复:向前扩展渲染范围,检查是否有合并单元格延伸到可视区域
1409
+ // 策略:对每一列独立检查,找到该列最靠前的需要渲染的主合并单元格
1410
+ if (mergeCellMap && scrollStartRow_1 > fixedTopRowsCount_1) {
1411
+ var originalStartRow = scrollStartRow_1;
1412
+ var minStartRow = scrollStartRow_1;
1413
+ // 对每一列独立检查
1414
+ for (var colIndex = 0; colIndex < columnRenderInfos.length; colIndex++) {
1415
+ // 向前查找这一列的主合并单元格,最多向前查找20行
1416
+ for (var checkRow = originalStartRow - 1; checkRow >= fixedTopRowsCount_1 && checkRow >= originalStartRow - 20; checkRow--) {
1417
+ var mergeCellKey = "".concat(checkRow, "-").concat(colIndex);
1418
+ var mergeInfo = mergeCellMap.get(mergeCellKey);
1419
+ // 如果找到主单元格(不是被合并的单元格)
1420
+ if (mergeInfo && !mergeInfo.skip && mergeInfo.rowSpan > 1) {
1421
+ // 计算合并单元格的底部行索引
1422
+ var mergeEndRow = checkRow + mergeInfo.rowSpan - 1;
1423
+ // 如果这个合并单元格延伸到可视区域
1424
+ if (mergeEndRow >= originalStartRow) {
1425
+ // 更新最小起始行
1426
+ minStartRow = Math.min(minStartRow, checkRow);
1427
+ }
1428
+ // 找到这一列的主单元格后,停止向前查找该列
1429
+ break;
1390
1430
  }
1391
- // 找到这一列的主单元格后,停止向前查找该列
1392
- break;
1431
+ // 如果是skip的单元格或没有合并信息,继续向前查找
1432
+ // 因为主单元格可能在更前面
1393
1433
  }
1394
- // 如果是skip的单元格或没有合并信息,继续向前查找
1395
- // 因为主单元格可能在更前面
1396
1434
  }
1435
+ scrollStartRow_1 = minStartRow;
1397
1436
  }
1398
- scrollStartRow = minStartRow;
1399
- }
1400
- // 1. 绘制可滚动区域的数据行(包括边框)
1401
- renderWithFixedLayer(ctx,
1402
- // 渲染行内容
1403
- function (onlyFixed) {
1404
- for (var i = scrollStartRow; i < scrollEndRow; i++) {
1405
- drawRow(ctx, i, displayWidth, displayHeight, onlyFixed);
1406
- }
1407
- },
1408
- // 渲染边框
1409
- function (onlyFixed) {
1410
- drawBorders(ctx, scrollStartRow, scrollEndRow, displayWidth, displayHeight, onlyFixed);
1411
- });
1412
- // 2. 绘制固定的顶部行(如果有)
1413
- if (fixedTopRowsCount > 0) {
1414
- ctx.save();
1415
- renderWithFixedLayer(ctx,
1437
+ // 1. 绘制可滚动区域的数据行(包括边框)
1438
+ renderWithFixedLayer(ctx_1,
1416
1439
  // 渲染行内容
1417
1440
  function (onlyFixed) {
1418
- for (var i = 0; i < fixedTopRowsCount; i++) {
1419
- drawFixedTopRow(ctx, i, displayWidth, displayHeight, onlyFixed);
1441
+ for (var i = scrollStartRow_1; i < scrollEndRow_1; i++) {
1442
+ drawRow(ctx_1, i, displayWidth_1, displayHeight_1, onlyFixed);
1420
1443
  }
1421
1444
  },
1422
1445
  // 渲染边框
1423
1446
  function (onlyFixed) {
1424
- drawFixedTopRowsBorder(ctx, fixedTopRowsCount, displayWidth, displayHeight, onlyFixed);
1447
+ drawBorders(ctx_1, scrollStartRow_1, scrollEndRow_1, displayWidth_1, displayHeight_1, onlyFixed);
1425
1448
  });
1426
- ctx.restore();
1449
+ // 2. 绘制固定的顶部行(如果有)
1450
+ if (fixedTopRowsCount_1 > 0) {
1451
+ ctx_1.save();
1452
+ renderWithFixedLayer(ctx_1,
1453
+ // 渲染行内容
1454
+ function (onlyFixed) {
1455
+ for (var i = 0; i < fixedTopRowsCount_1; i++) {
1456
+ drawFixedTopRow(ctx_1, i, displayWidth_1, displayHeight_1, onlyFixed);
1457
+ }
1458
+ },
1459
+ // 渲染边框
1460
+ function (onlyFixed) {
1461
+ drawFixedTopRowsBorder(ctx_1, fixedTopRowsCount_1, displayWidth_1, displayHeight_1, onlyFixed);
1462
+ });
1463
+ ctx_1.restore();
1464
+ }
1465
+ // 3. 绘制固定的底部行(如果有,不包括合计行)
1466
+ if (fixedBottomRowsCount_1 > 0) {
1467
+ ctx_1.save();
1468
+ var bottomRowsStartIndex_1 = processedDataSource.length -
1469
+ fixedBottomRowsCount_1 -
1470
+ (summaryFixed && hasSummaryRow ? 1 : 0);
1471
+ renderWithFixedLayer(ctx_1,
1472
+ // 渲染行内容
1473
+ function (onlyFixed) {
1474
+ for (var i = 0; i < fixedBottomRowsCount_1; i++) {
1475
+ var rowIndex = bottomRowsStartIndex_1 + i;
1476
+ drawFixedBottomRow(ctx_1, rowIndex, i, displayWidth_1, displayHeight_1, onlyFixed);
1477
+ }
1478
+ },
1479
+ // 渲染边框
1480
+ function (onlyFixed) {
1481
+ drawFixedBottomRowsBorder(ctx_1, fixedBottomRowsCount_1, displayWidth_1, displayHeight_1, onlyFixed);
1482
+ });
1483
+ ctx_1.restore();
1484
+ }
1485
+ // 7. 绘制固定列阴影
1486
+ drawFixedColumns(ctx_1, scrollStartRow_1, scrollEndRow_1, displayWidth_1, displayHeight_1);
1487
+ // 4. 绘制固定合计行(如果启用)
1488
+ if (summaryFixed && hasSummaryRow) {
1489
+ var summaryRowIndex_1 = processedDataSource.length - 1;
1490
+ var summaryRowY_1 = displayHeight_1 -
1491
+ (needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
1492
+ rowHeight;
1493
+ ctx_1.save();
1494
+ renderWithFixedLayer(ctx_1,
1495
+ // 渲染行内容
1496
+ function (onlyFixed) {
1497
+ drawFixedSummaryRow(ctx_1, summaryRowIndex_1, summaryRowY_1, displayWidth_1, displayHeight_1, onlyFixed);
1498
+ },
1499
+ // 渲染边框
1500
+ function (onlyFixed) {
1501
+ drawFixedSummaryRowBorder(ctx_1, summaryRowIndex_1, summaryRowY_1, displayWidth_1, displayHeight_1, onlyFixed);
1502
+ });
1503
+ ctx_1.restore();
1504
+ }
1505
+ // 6. 绘制表头(最后绘制,确保始终在最上层,不会被覆盖)
1506
+ drawHeader(ctx_1, displayWidth_1, displayHeight_1);
1507
+ // 绘制滚动条(最后绘制,在表头之上)
1508
+ drawScrollbars(ctx_1, displayWidth_1, displayHeight_1);
1509
+ // 绘制框选区域
1510
+ if (state.cellSelection) {
1511
+ drawCellSelection(ctx_1, displayWidth_1, displayHeight_1);
1512
+ }
1513
+ // 重置绘制标志
1514
+ isDrawingRef.current = false;
1427
1515
  }
1428
- // 3. 绘制固定的底部行(如果有,不包括合计行)
1429
- if (fixedBottomRowsCount > 0) {
1430
- ctx.save();
1431
- var bottomRowsStartIndex_1 = processedDataSource.length -
1432
- fixedBottomRowsCount -
1433
- (summaryFixed && hasSummaryRow ? 1 : 0);
1434
- renderWithFixedLayer(ctx,
1435
- // 渲染行内容
1436
- function (onlyFixed) {
1437
- for (var i = 0; i < fixedBottomRowsCount; i++) {
1438
- var rowIndex = bottomRowsStartIndex_1 + i;
1439
- drawFixedBottomRow(ctx, rowIndex, i, displayWidth, displayHeight, onlyFixed);
1516
+ catch (error) {
1517
+ // 错误边界:捕获渲染异常,防止组件崩溃
1518
+ console.error("CanvasTable: 渲染过程中发生异常:", error);
1519
+ // 重置状态,防止卡死
1520
+ isDrawingRef.current = false;
1521
+ // 清理可能的异常状态
1522
+ try {
1523
+ var canvas = canvasRef.current;
1524
+ if (canvas) {
1525
+ var ctx = canvas.getContext("2d");
1526
+ if (ctx) {
1527
+ // 清空画布,显示错误状态
1528
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
1529
+ ctx.fillStyle = "#f5f5f5";
1530
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
1531
+ // 显示错误提示
1532
+ ctx.fillStyle = "#ff4d4f";
1533
+ ctx.font = "14px Arial";
1534
+ ctx.textAlign = "center";
1535
+ ctx.fillText("表格渲染异常,请检查数据或刷新页面", canvas.width / 2, canvas.height / 2);
1536
+ }
1440
1537
  }
1441
- },
1442
- // 渲染边框
1443
- function (onlyFixed) {
1444
- drawFixedBottomRowsBorder(ctx, fixedBottomRowsCount, displayWidth, displayHeight, onlyFixed);
1445
- });
1446
- ctx.restore();
1447
- }
1448
- // 7. 绘制固定列阴影
1449
- drawFixedColumns(ctx, scrollStartRow, scrollEndRow, displayWidth, displayHeight);
1450
- // 4. 绘制固定合计行(如果启用)
1451
- if (summaryFixed && hasSummaryRow) {
1452
- var summaryRowIndex_1 = processedDataSource.length - 1;
1453
- var summaryRowY_1 = displayHeight -
1454
- (needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
1455
- rowHeight;
1456
- ctx.save();
1457
- renderWithFixedLayer(ctx,
1458
- // 渲染行内容
1459
- function (onlyFixed) {
1460
- drawFixedSummaryRow(ctx, summaryRowIndex_1, summaryRowY_1, displayWidth, displayHeight, onlyFixed);
1461
- },
1462
- // 渲染边框
1463
- function (onlyFixed) {
1464
- drawFixedSummaryRowBorder(ctx, summaryRowIndex_1, summaryRowY_1, displayWidth, displayHeight, onlyFixed);
1465
- });
1466
- ctx.restore();
1538
+ }
1539
+ catch (fallbackError) {
1540
+ console.error("CanvasTable: 错误恢复失败:", fallbackError);
1541
+ }
1542
+ // 可以触发错误回调,通知外部组件
1543
+ // props.onRenderError?.(error);
1467
1544
  }
1468
- // 6. 绘制表头(最后绘制,确保始终在最上层,不会被覆盖)
1469
- drawHeader(ctx, displayWidth, displayHeight);
1470
- // 绘制滚动条(最后绘制,在表头之上)
1471
- drawScrollbars(ctx, displayWidth, displayHeight);
1472
- // 绘制框选区域
1473
- if (state.cellSelection) {
1474
- drawCellSelection(ctx, displayWidth, displayHeight);
1545
+ });
1546
+ // 节流的绘制函数,使用 requestAnimationFrame
1547
+ var drawTable = useMemoizedFn(function () {
1548
+ // 如果已经有pending的绘制请求,取消之前的
1549
+ if (rafIdRef.current !== null) {
1550
+ cancelAnimationFrame(rafIdRef.current);
1475
1551
  }
1552
+ // 使用 requestAnimationFrame 进行节流
1553
+ rafIdRef.current = requestAnimationFrame(function () {
1554
+ drawTableCore();
1555
+ rafIdRef.current = null;
1556
+ });
1476
1557
  });
1558
+ // 清理函数 - 释放资源
1559
+ useEffect(function () {
1560
+ return function () {
1561
+ // 清理动画帧
1562
+ if (rafIdRef.current !== null) {
1563
+ cancelAnimationFrame(rafIdRef.current);
1564
+ rafIdRef.current = null;
1565
+ }
1566
+ // 清理缓存的引用,释放内存
1567
+ canvasContextRef.current = null;
1568
+ lastRenderParamsRef.current = "";
1569
+ isDrawingRef.current = false;
1570
+ };
1571
+ }, []);
1477
1572
  // 监听变化,自动重绘
1478
1573
  useEffect(function () {
1479
1574
  drawTable();
@@ -1493,7 +1588,9 @@ var useTableRender = function (params) {
1493
1588
  columnRenderInfos,
1494
1589
  bordered,
1495
1590
  striped,
1496
- resizeState,
1591
+ resizeState === null || resizeState === void 0 ? void 0 : resizeState.isResizing,
1592
+ resizeState === null || resizeState === void 0 ? void 0 : resizeState.resizingColumnIndex,
1593
+ resizeState === null || resizeState === void 0 ? void 0 : resizeState.hoverResizeColumnIndex,
1497
1594
  drawTable,
1498
1595
  ]);
1499
1596
  return {
@@ -390,7 +390,9 @@ var useNumKeys = function (columns) {
390
390
  var numKeys = useMemo(function () {
391
391
  var leafColumns = getLeafColumns(columns);
392
392
  return Array.from(new Set(leafColumns
393
- .filter(function (item) { return item.precision !== undefined; })
393
+ .filter(function (item) {
394
+ return item.precision !== undefined || item.thousand !== undefined;
395
+ })
394
396
  .map(function (item) { return item.key; })));
395
397
  }, [columns]);
396
398
  return numKeys;
@@ -415,30 +417,66 @@ function useAutoMerge(dataSource, columns, _a) {
415
417
  .filter(function (item) { return item.isDimensionSum; })
416
418
  .map(function (item) { return item.key; });
417
419
  }, [columns]);
418
- var newDataSource = useMemo(function () {
419
- if (!isAutoMerge)
420
- return dataSource;
421
- else {
422
- // 多字段合并
423
- if (!mergeKeys)
424
- return dataSource;
425
- else {
426
- var newDataSource_2 = flattenRecordsOptimized(__spreadArray([], (dataSource || []), true), mergeKeys, dimensionSummaryKeys, summaryKeys, isDimensionDynamic, order, dimensionCustomSumKeys, columns // 添加 columns 参数,支持自定义排序
427
- );
428
- return newDataSource_2;
420
+ // 添加性能监控和异常处理
421
+ var processDataWithOptimization = useCallback(function (sourceData, mergeKeysParam) {
422
+ try {
423
+ // 数据有效性检查
424
+ if (!Array.isArray(sourceData)) {
425
+ console.warn("CanvasTable: 数据源不是数组类型");
426
+ return [];
427
+ }
428
+ if (!Array.isArray(mergeKeysParam) || mergeKeysParam.length === 0) {
429
+ console.warn("CanvasTable: 合并字段配置无效");
430
+ return sourceData;
431
+ }
432
+ // 性能检查:数据量过大时启用优化策略
433
+ var dataSize = sourceData.length;
434
+ var mergeKeysCount = mergeKeysParam.length;
435
+ var complexity = dataSize * mergeKeysCount;
436
+ // 时间复杂度过高时的优化策略
437
+ if (complexity > 10000) {
438
+ console.warn("CanvasTable: \u68C0\u6D4B\u5230\u9AD8\u590D\u6742\u5EA6\u6570\u636E\u5904\u7406 (".concat(complexity, ")\uFF0C\u5EFA\u8BAE\u4F18\u5316\u6570\u636E\u91CF\u6216\u51CF\u5C11\u5408\u5E76\u5B57\u6BB5"));
439
+ // 对于超大数据,可以考虑分页处理或虚拟化
440
+ if (dataSize > 5000) {
441
+ console.warn("CanvasTable: \u6570\u636E\u91CF\u8FC7\u5927 (".concat(dataSize, " \u884C)\uFF0C\u6027\u80FD\u53EF\u80FD\u53D7\u5F71\u54CD"));
442
+ }
443
+ }
444
+ var startTime = performance.now();
445
+ var result = flattenRecordsOptimized(__spreadArray([], sourceData, true), mergeKeysParam, dimensionSummaryKeys, summaryKeys, isDimensionDynamic, order, dimensionCustomSumKeys, columns // 添加 columns 参数,支持自定义排序
446
+ );
447
+ var endTime = performance.now();
448
+ var duration = endTime - startTime;
449
+ // 性能监控:超过阈值时警告
450
+ if (duration > 100) {
451
+ console.warn("CanvasTable: \u6570\u636E\u5904\u7406\u8017\u65F6\u8F83\u957F (".concat(duration.toFixed(2), "ms)\uFF0C\u5EFA\u8BAE\u4F18\u5316"));
429
452
  }
453
+ // 结果有效性检查
454
+ if (!Array.isArray(result)) {
455
+ console.error("CanvasTable: 数据处理结果异常");
456
+ return sourceData; // 降级返回原始数据
457
+ }
458
+ return result;
459
+ }
460
+ catch (error) {
461
+ console.error("CanvasTable: 数据处理过程中发生异常:", error);
462
+ // 异常降级:返回原始数据,避免组件崩溃
463
+ return sourceData;
430
464
  }
431
465
  }, [
432
- dataSource,
433
- isAutoMerge,
434
- mergeKeys,
435
466
  dimensionSummaryKeys,
436
467
  summaryKeys,
437
468
  isDimensionDynamic,
438
469
  order,
439
470
  dimensionCustomSumKeys,
440
- columns, // 添加 columns 依赖
471
+ columns,
441
472
  ]);
473
+ var newDataSource = useMemo(function () {
474
+ if (!isAutoMerge)
475
+ return dataSource;
476
+ if (!mergeKeys)
477
+ return dataSource;
478
+ return processDataWithOptimization(dataSource || [], mergeKeys);
479
+ }, [dataSource, isAutoMerge, mergeKeys, processDataWithOptimization]);
442
480
  var newColumns = useMemo(function () {
443
481
  var _columns = columns;
444
482
  // 递归处理多级表头,自动补充render/合并/编辑等逻辑
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zmdms-webui",
3
- "version": "2.3.9",
3
+ "version": "2.4.1",
4
4
  "private": false,
5
5
  "main": "dist/index.es.js",
6
6
  "module": "dist/index.es.js",