zmdms-webui 2.3.6 → 2.3.8

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.
@@ -8,7 +8,7 @@ import 'react/jsx-runtime';
8
8
  import 'zmdms-utils';
9
9
  import '../../node_modules/exceljs/dist/exceljs.min.js';
10
10
  import 'dayjs';
11
- import { COLORS, FONT_WEIGHT, FONT_SIZE, FONT_FAMILY, SCROLLBAR_SIZE, SCROLLBAR_PADDING } from '../utils/constants.js';
11
+ import { COLORS, FONT_WEIGHT, FONT_SIZE, FONT_FAMILY, TEXT_PADDING, SCROLLBAR_SIZE, SCROLLBAR_PADDING } from '../utils/constants.js';
12
12
  import { drawCheckbox, wrapText, truncateText, drawFilterIcon, drawSortIcon } from '../utils/canvasDrawHelpers.js';
13
13
  import { getMaxDepth, flattenHeaders, calculateLayerHeights, getLeafColumns } from '../utils/multiHeaderHelpers.js';
14
14
  import { extractCellText } from '../utils/cellHelpers.js';
@@ -19,7 +19,7 @@ import { calculateSelectionState, calculateIconArea } from '../utils/interaction
19
19
  * 表格渲染 Hook
20
20
  */
21
21
  var useTableRender = function (params) {
22
- 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, 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, _a = params.RESIZE_HANDLE_WIDTH, RESIZE_HANDLE_WIDTH = _a === void 0 ? 8 : _a, mergeCellMap = params.mergeCellMap, _b = params.hasSummaryRow, hasSummaryRow = _b === void 0 ? false : _b, fixedRowsCount = params.fixedRowsCount, fixedRowsConfig = params.fixedRowsConfig, _c = params.summaryFixed, summaryFixed = _c === void 0 ? false : _c;
22
+ 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
23
  // 判断是否是多级表头
24
24
  var maxDepth = useMemo(function () { return getMaxDepth(columns); }, [columns]);
25
25
  var isMultiHeader = maxDepth > 1;
@@ -27,13 +27,13 @@ var useTableRender = function (params) {
27
27
  var flattenedHeaderRows = useMemo(function () { return (isMultiHeader ? flattenHeaders(columns) : []); }, [columns, isMultiHeader]);
28
28
  // 计算多级表头每一层的高度(考虑换行)
29
29
  // 使用 baseHeaderHeight 或默认计算出的基础高度
30
- var _d = useMemo(function () {
30
+ var _e = useMemo(function () {
31
31
  var maxDepth = getMaxDepth(columns);
32
32
  // 如果提供了 baseHeaderHeight,使用它;否则根据 headerHeight 和深度计算
33
33
  var baseHeight = baseHeaderHeight ||
34
34
  (maxDepth > 1 ? Math.floor(headerHeight / maxDepth) : headerHeight);
35
35
  return calculateLayerHeights(columns, baseHeight, columnRenderInfos);
36
- }, [columns, headerHeight, baseHeaderHeight, columnRenderInfos]), layerHeights = _d.layerHeights, layerStartY = _d.layerStartY;
36
+ }, [columns, headerHeight, baseHeaderHeight, columnRenderInfos]), layerHeights = _e.layerHeights, layerStartY = _e.layerStartY;
37
37
  var dpr = window.devicePixelRatio || 1;
38
38
  // ==================== 通用辅助函数 ====================
39
39
  /**
@@ -129,7 +129,6 @@ var useTableRender = function (params) {
129
129
  ctx.fillStyle = COLORS.text;
130
130
  ctx.font = "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
131
131
  ctx.textBaseline = "middle";
132
- var textX = drawX + 5;
133
132
  var textY = headerHeight / 2;
134
133
  // 绘制选择框
135
134
  if (column.key === "__selection__") {
@@ -142,26 +141,65 @@ var useTableRender = function (params) {
142
141
  else {
143
142
  // 计算图标占用的宽度
144
143
  var iconArea = calculateIconArea(column, width);
145
- // 计算文本可用宽度(左边距5px + 图标宽度 )
146
- var textMaxWidth = width - 5 - iconArea.iconsWidth;
144
+ // 确定表头对齐方式,优先使用列的 headerAlign,其次是全局 headerAlign
145
+ var columnHeaderAlign = column.headerAlign || headerAlign;
147
146
  // 绘制文本(如果 title 是 React 元素,跳过Canvas渲染,在覆盖层中渲染)
148
147
  if (!isValidElement(column.title)) {
149
148
  var titleText = String(column.title || "");
149
+ // 根据对齐方式和图标情况设置文本位置
150
+ var textX_1;
151
+ var textMaxWidth = void 0;
152
+ if (iconArea.iconsWidth > 0) {
153
+ // 有图标时,根据columnHeaderAlign调整文本位置和最大宽度
154
+ if (columnHeaderAlign === "right") {
155
+ ctx.textAlign = "right";
156
+ textX_1 = drawX + width - TEXT_PADDING - iconArea.iconsWidth;
157
+ textMaxWidth = width - TEXT_PADDING * 2 - iconArea.iconsWidth;
158
+ }
159
+ else if (columnHeaderAlign === "center") {
160
+ ctx.textAlign = "center";
161
+ textX_1 = drawX + (width - iconArea.iconsWidth) / 2;
162
+ textMaxWidth = width - TEXT_PADDING * 2 - iconArea.iconsWidth;
163
+ }
164
+ else {
165
+ ctx.textAlign = "left";
166
+ textX_1 = drawX + TEXT_PADDING;
167
+ textMaxWidth = width - TEXT_PADDING - iconArea.iconsWidth;
168
+ }
169
+ }
170
+ else {
171
+ // 没有图标时,根据columnHeaderAlign设置对齐方式
172
+ if (columnHeaderAlign === "left") {
173
+ ctx.textAlign = "left";
174
+ textX_1 = drawX + TEXT_PADDING;
175
+ textMaxWidth = width - TEXT_PADDING * 2;
176
+ }
177
+ else if (columnHeaderAlign === "right") {
178
+ ctx.textAlign = "right";
179
+ textX_1 = drawX + width - TEXT_PADDING;
180
+ textMaxWidth = width - TEXT_PADDING * 2;
181
+ }
182
+ else {
183
+ ctx.textAlign = "center";
184
+ textX_1 = drawX + width / 2;
185
+ textMaxWidth = width - TEXT_PADDING * 2;
186
+ }
187
+ }
150
188
  // 根据 ellipsis 和 wrap 属性处理文本
151
189
  if (column.wrap) {
152
190
  // 换行显示(支持\n和自动换行)
153
191
  var lines = wrapText(ctx, titleText, textMaxWidth);
154
- var lineHeight_1 = 20;
192
+ var lineHeight_1 = FONT_SIZE + 5; // 字体大小 + 行间距5px
155
193
  var startY_1 = textY - ((lines.length - 1) * lineHeight_1) / 2;
156
194
  lines.forEach(function (line, index) {
157
195
  var lineY = startY_1 + index * lineHeight_1;
158
- ctx.fillText(line, textX, lineY);
196
+ ctx.fillText(line, textX_1, lineY);
159
197
  });
160
198
  }
161
199
  else {
162
200
  // 默认截断显示
163
201
  var truncatedText = truncateText(ctx, titleText, textMaxWidth);
164
- ctx.fillText(truncatedText, textX, textY);
202
+ ctx.fillText(truncatedText, textX_1, textY);
165
203
  }
166
204
  }
167
205
  // 绘制筛选图标(最右侧,距离右边5px)
@@ -178,6 +216,7 @@ var useTableRender = function (params) {
178
216
  drawSortIcon(ctx, iconX, textY, column.key === state.sortField ? state.sortOrder : null);
179
217
  }
180
218
  }
219
+ ctx.textAlign = "left"; // 重置对齐方式
181
220
  ctx.restore();
182
221
  // 绘制表头列之间的边框
183
222
  if (bordered) {
@@ -286,44 +325,73 @@ var useTableRender = function (params) {
286
325
  var iconArea = shouldShowIcons
287
326
  ? calculateIconArea(column, width)
288
327
  : { hasOrder: false, hasFilter: false, iconsWidth: 0 };
289
- // 如果是最末级且有图标,文本左对齐;否则居中
328
+ // 确定表头对齐方式,优先使用列的 headerAlign,其次是全局 headerAlign
329
+ var columnHeaderAlign = column.headerAlign || headerAlign;
330
+ // 如果是最末级且有图标,需要调整对齐方式
290
331
  if (isLastLevel && iconArea.iconsWidth > 0) {
291
- ctx.textAlign = "left";
292
- var textX_1 = drawX + 5;
293
- var textMaxWidth = width - 5 - iconArea.iconsWidth;
332
+ // 有图标时,根据columnHeaderAlign调整文本位置和最大宽度
333
+ var textX_2;
334
+ var textMaxWidth = void 0;
335
+ if (columnHeaderAlign === "right") {
336
+ ctx.textAlign = "right";
337
+ textX_2 = drawX + width - TEXT_PADDING - iconArea.iconsWidth;
338
+ textMaxWidth = width - TEXT_PADDING * 2 - iconArea.iconsWidth;
339
+ }
340
+ else if (columnHeaderAlign === "center") {
341
+ ctx.textAlign = "center";
342
+ textX_2 = drawX + (width - iconArea.iconsWidth) / 2;
343
+ textMaxWidth = width - TEXT_PADDING * 2 - iconArea.iconsWidth;
344
+ }
345
+ else {
346
+ ctx.textAlign = "left";
347
+ textX_2 = drawX + TEXT_PADDING;
348
+ textMaxWidth = width - TEXT_PADDING - iconArea.iconsWidth;
349
+ }
294
350
  // 根据 wrap 属性处理文本
295
351
  if (column.wrap) {
296
352
  // 换行显示(支持\n和自动换行)
297
353
  var lines = wrapText(ctx, titleText, textMaxWidth);
298
- var lineHeight_2 = 20;
354
+ var lineHeight_2 = FONT_SIZE + 5; // 字体大小 + 行间距5px
299
355
  var startY_2 = textY - ((lines.length - 1) * lineHeight_2) / 2;
300
356
  lines.forEach(function (line, index) {
301
357
  var lineY = startY_2 + index * lineHeight_2;
302
- ctx.fillText(line, textX_1, lineY);
358
+ ctx.fillText(line, textX_2, lineY);
303
359
  });
304
360
  }
305
361
  else {
306
362
  var truncated = truncateText(ctx, titleText, textMaxWidth);
307
- ctx.fillText(truncated, textX_1, textY);
363
+ ctx.fillText(truncated, textX_2, textY);
308
364
  }
309
365
  }
310
366
  else {
311
- ctx.textAlign = "center";
312
- var textX_2 = drawX + width / 2;
367
+ // 没有图标时,根据columnHeaderAlign设置对齐方式
368
+ var textX_3;
369
+ if (columnHeaderAlign === "left") {
370
+ ctx.textAlign = "left";
371
+ textX_3 = drawX + TEXT_PADDING;
372
+ }
373
+ else if (columnHeaderAlign === "right") {
374
+ ctx.textAlign = "right";
375
+ textX_3 = drawX + width - TEXT_PADDING;
376
+ }
377
+ else {
378
+ ctx.textAlign = "center";
379
+ textX_3 = drawX + width / 2;
380
+ }
313
381
  // 根据 wrap 属性处理文本
314
382
  if (column.wrap) {
315
383
  // 换行显示(支持\n和自动换行)
316
- var lines = wrapText(ctx, titleText, width - 10); // 左右各5px
317
- var lineHeight_3 = 20;
384
+ var lines = wrapText(ctx, titleText, width - TEXT_PADDING * 2); // 左右各TEXT_PADDING
385
+ var lineHeight_3 = FONT_SIZE + 5; // 字体大小 + 行间距5px
318
386
  var startY_3 = textY - ((lines.length - 1) * lineHeight_3) / 2;
319
387
  lines.forEach(function (line, index) {
320
388
  var lineY = startY_3 + index * lineHeight_3;
321
- ctx.fillText(line, textX_2, lineY);
389
+ ctx.fillText(line, textX_3, lineY);
322
390
  });
323
391
  }
324
392
  else {
325
- var truncated = truncateText(ctx, titleText, width - 10); // 左右各5px
326
- ctx.fillText(truncated, textX_2, textY);
393
+ var truncated = truncateText(ctx, titleText, width - TEXT_PADDING * 2); // 左右各TEXT_PADDING
394
+ ctx.fillText(truncated, textX_3, textY);
327
395
  }
328
396
  }
329
397
  // 绘制筛选图标(仅在最末级表头显示)
@@ -533,10 +601,30 @@ var useTableRender = function (params) {
533
601
  }
534
602
  // 单元格内容
535
603
  if (column.key === "__selection__") {
536
- var checkboxProps = ((_a = rowSelection === null || rowSelection === void 0 ? void 0 : rowSelection.getCheckboxProps) === null || _a === void 0 ? void 0 : _a.call(rowSelection, record)) || {};
537
- var disabled = checkboxProps.disabled || false;
538
- // 对于合并单元格,使用合并后的单元格高度进行垂直居中
539
- drawCheckbox(ctx, drawX + width / 2, y + cellHeight / 2, isSelected, disabled, false);
604
+ // 检查是否是合计行
605
+ var isSummaryRow = record[IS_SUMMARY];
606
+ if (!isSummaryRow) {
607
+ // 非合计行显示选择框
608
+ var checkboxProps = ((_a = rowSelection === null || rowSelection === void 0 ? void 0 : rowSelection.getCheckboxProps) === null || _a === void 0 ? void 0 : _a.call(rowSelection, record)) || {};
609
+ var disabled = checkboxProps.disabled || false;
610
+ // 对于合并单元格,使用合并后的单元格高度进行垂直居中
611
+ drawCheckbox(ctx, drawX + width / 2, y + cellHeight / 2, isSelected, disabled, false);
612
+ }
613
+ else {
614
+ // 合计行选择框列显示文本(比如"合计")
615
+ var dataIndex = column.dataIndex || column.key;
616
+ var cellValue = record[dataIndex];
617
+ var cellText = formatCellValue(cellValue, column);
618
+ if (cellText) {
619
+ ctx.fillStyle = COLORS.text;
620
+ ctx.font = "bold ".concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
621
+ ctx.textBaseline = "middle";
622
+ ctx.textAlign = "center";
623
+ var textX = drawX + width / 2;
624
+ var textY = y + cellHeight / 2;
625
+ ctx.fillText(cellText, textX, textY);
626
+ }
627
+ }
540
628
  }
541
629
  else {
542
630
  var dataIndex = column.dataIndex || column.key;
@@ -584,26 +672,40 @@ var useTableRender = function (params) {
584
672
  : "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
585
673
  ctx.textBaseline = "middle";
586
674
  var align = column.align || "left";
587
- var textX_3 = drawX + 5;
675
+ var textX_4 = drawX + TEXT_PADDING;
588
676
  if (align === "center") {
589
677
  ctx.textAlign = "center";
590
- textX_3 = drawX + width / 2;
678
+ textX_4 = drawX + width / 2;
591
679
  }
592
680
  else if (align === "right") {
593
681
  ctx.textAlign = "right";
594
- textX_3 = drawX + width - 5;
682
+ textX_4 = drawX + width - TEXT_PADDING;
595
683
  }
596
684
  else {
597
685
  ctx.textAlign = "left";
598
686
  }
599
687
  // 处理文本显示(ellipsis 或 wrap)
600
- var maxWidth = width - 10; // 左右各5px
688
+ var maxWidth = width - TEXT_PADDING * 2; // 左右各TEXT_PADDING
601
689
  var ellipsis = column.ellipsis !== false; // 默认为true
602
690
  var wrap = column.wrap === true; // 默认为false
603
691
  if (wrap) {
604
- // 换行显示
605
- var lines = wrapText(ctx, cellText, maxWidth);
606
- var lineHeight_4 = FONT_SIZE + 4; // 行高
692
+ // 换行显示,最多显示2行
693
+ var allLines = wrapText(ctx, cellText, maxWidth);
694
+ var maxLines = 2; // 限制最多显示2行
695
+ var lines = allLines.slice(0, maxLines);
696
+ // 如果超过2行,在第2行末尾强制添加省略号
697
+ if (allLines.length > maxLines && lines.length === maxLines) {
698
+ // 确保第2行有足够空间显示省略号
699
+ var lastLine = lines[maxLines - 1];
700
+ var ellipsisText = "...";
701
+ // 如果最后一行加上省略号会超出宽度,需要截断
702
+ while (lastLine.length > 0 &&
703
+ ctx.measureText(lastLine + ellipsisText).width > maxWidth) {
704
+ lastLine = lastLine.slice(0, -1);
705
+ }
706
+ lines[maxLines - 1] = lastLine + ellipsisText;
707
+ }
708
+ var lineHeight_4 = FONT_SIZE + 5; // 字体大小 + 行间距5px
607
709
  var totalHeight = lines.length * lineHeight_4;
608
710
  // 使用合并后的单元格高度计算垂直居中位置
609
711
  var startY_4 = y + (cellHeight - totalHeight) / 2 + lineHeight_4 / 2;
@@ -611,7 +713,7 @@ var useTableRender = function (params) {
611
713
  var lineY = startY_4 + lineIndex * lineHeight_4;
612
714
  // 只绘制在单元格可见区域内的文本
613
715
  if (lineY >= y && lineY <= y + cellHeight) {
614
- ctx.fillText(line, textX_3, lineY);
716
+ ctx.fillText(line, textX_4, lineY);
615
717
  }
616
718
  });
617
719
  }
@@ -619,12 +721,12 @@ var useTableRender = function (params) {
619
721
  // 省略显示 - 使用合并后的单元格高度
620
722
  var textY = y + cellHeight / 2;
621
723
  var truncatedText = truncateText(ctx, cellText, maxWidth);
622
- ctx.fillText(truncatedText, textX_3, textY);
724
+ ctx.fillText(truncatedText, textX_4, textY);
623
725
  }
624
726
  else {
625
727
  // 不处理,直接显示 - 使用合并后的单元格高度
626
728
  var textY = y + cellHeight / 2;
627
- ctx.fillText(cellText, textX_3, textY);
729
+ ctx.fillText(cellText, textX_4, textY);
628
730
  }
629
731
  if (column.badge && !isSummaryRow) {
630
732
  var badgeProps = typeof column.badge === "function"
@@ -833,28 +935,30 @@ var useTableRender = function (params) {
833
935
  ctx.fillRect(drawX, fixedY, actualWidth, rowHeight);
834
936
  // 处理序号列
835
937
  if (column.key === "__index__") {
836
- // 合计行序号列显示空
837
- if (rowType === "summary")
938
+ // 非合计行显示序号
939
+ if (rowType !== "summary") {
940
+ var indexText = String(rowIndex + 1);
941
+ ctx.fillStyle = COLORS.text;
942
+ ctx.font = "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
943
+ ctx.textBaseline = "middle";
944
+ setTextAlign(ctx, "center");
945
+ var textX = drawX + actualWidth / 2;
946
+ var textY = fixedY + rowHeight / 2;
947
+ ctx.fillText(indexText, textX, textY);
838
948
  return;
839
- var indexText = String(rowIndex + 1);
840
- ctx.fillStyle = COLORS.text;
841
- ctx.font = "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
842
- ctx.textBaseline = "middle";
843
- setTextAlign(ctx, "center");
844
- var textX = drawX + actualWidth / 2;
845
- var textY = fixedY + rowHeight / 2;
846
- ctx.fillText(indexText, textX, textY);
847
- return;
949
+ }
950
+ // 合计行序号列继续执行后面的逻辑,可能显示"合计"文字
848
951
  }
849
952
  // 处理选择框列
850
953
  if (column.key === "__selection__") {
851
- // 合计行选择框列显示空
852
- if (rowType === "summary")
954
+ // 非合计行显示选择框
955
+ if (rowType !== "summary") {
956
+ var checkboxX = drawX + actualWidth / 2;
957
+ var textY = fixedY + rowHeight / 2;
958
+ drawCheckbox(ctx, checkboxX, textY, isSelected, false, false);
853
959
  return;
854
- var checkboxX = drawX + actualWidth / 2;
855
- var textY = fixedY + rowHeight / 2;
856
- drawCheckbox(ctx, checkboxX, textY, isSelected, false, false);
857
- return;
960
+ }
961
+ // 合计行选择框列继续执行后面的逻辑,可能显示"合计"文字
858
962
  }
859
963
  // 获取单元格值
860
964
  var dataIndex = column.dataIndex || column.key;
@@ -9,7 +9,7 @@ import { getAllLeafColumns, findColumnByKey } from '../utils/columnHelpers.js';
9
9
  * 表格状态管理 Hook
10
10
  */
11
11
  var useTableState = function (params) {
12
- var dataSource = params.dataSource, columns = params.columns, rowSelection = params.rowSelection, onFilterChange = params.onFilterChange;
12
+ var dataSource = params.dataSource, columns = params.columns, rowSelection = params.rowSelection, onFilterChange = params.onFilterChange, isAutoMerge = params.isAutoMerge;
13
13
  // 表格状态
14
14
  var _a = useState({
15
15
  sortField: null,
@@ -157,7 +157,7 @@ var useTableState = function (params) {
157
157
  }
158
158
  });
159
159
  // 应用排序
160
- if (state.sortField && state.sortOrder) {
160
+ if (state.sortField && state.sortOrder && !isAutoMerge) {
161
161
  var column_2 = findColumnByKey(columns, state.sortField);
162
162
  if (column_2 && column_2.isOrder !== false) {
163
163
  var sortFn_1 = typeof column_2.sorter === "function"
@@ -187,7 +187,14 @@ var useTableState = function (params) {
187
187
  }
188
188
  }
189
189
  return data;
190
- }, [dataSource, columns, state.filters, state.sortField, state.sortOrder]);
190
+ }, [
191
+ dataSource,
192
+ columns,
193
+ state.filters,
194
+ state.sortField,
195
+ state.sortOrder,
196
+ isAutoMerge,
197
+ ]);
191
198
  // 处理过滤变化
192
199
  var handleFilterChange = useMemoizedFn(function (columnKey, values) {
193
200
  var _a;
@@ -36,6 +36,10 @@ interface ICanvasColumnType<RecordType = any> {
36
36
  * 对齐方式
37
37
  */
38
38
  align?: "left" | "center" | "right";
39
+ /**
40
+ * 表头对齐方式(如果不设置,使用全局 headerAlign 或 align)
41
+ */
42
+ headerAlign?: "left" | "center" | "right";
39
43
  /**
40
44
  * 是否固定列
41
45
  */
@@ -249,6 +253,10 @@ interface ICanvasTableProps<RecordType = any> {
249
253
  * 表头换行
250
254
  */
251
255
  headerWrap?: boolean;
256
+ /**
257
+ * 表头对齐方式(默认居中)
258
+ */
259
+ headerAlign?: "left" | "center" | "right";
252
260
  /**
253
261
  * 勾选配置
254
262
  */
@@ -394,6 +402,86 @@ interface ICanvasTableProps<RecordType = any> {
394
402
  * 表格id(用于自动高度计算时定位元素)
395
403
  */
396
404
  canvasTableId?: string;
405
+ /**
406
+ * 表格ref句柄,用于暴露表格的一些方法
407
+ */
408
+ tableRefHandle?: React__default.Ref<ICanvasTableRefHandle | undefined>;
409
+ /**
410
+ * 导出excel的一些配置
411
+ */
412
+ exportExcelConfig?: {
413
+ topDescription?: string;
414
+ time?: string;
415
+ topDescriptionRowHeight?: number;
416
+ fileName: string;
417
+ };
418
+ }
419
+ /**
420
+ * CanvasTable 暴露的方法
421
+ */
422
+ interface ICanvasTableRefHandle {
423
+ /**
424
+ * 获取当前表格的配置信息(动态列配置)
425
+ */
426
+ getDynamicList: () => any[];
427
+ /**
428
+ * 动态列配置重置
429
+ */
430
+ onResetDynamicList: () => void;
431
+ /**
432
+ * 动态列配置设置
433
+ */
434
+ onSetDynamicList: (list: any[]) => void;
435
+ /**
436
+ * 导出Excel
437
+ */
438
+ exportExcel?: (fileName: string, config?: {
439
+ topDescription?: string;
440
+ time?: string;
441
+ topDescriptionRowHeight?: number;
442
+ }) => void;
443
+ /**
444
+ * 获取表格容器
445
+ */
446
+ getContainer: () => HTMLDivElement | null;
447
+ /**
448
+ * 全屏展示表格
449
+ */
450
+ toggleFullScreen: () => void;
451
+ /**
452
+ * 获取选中的行keys
453
+ */
454
+ getSelectedRowKeys: () => React__default.Key[];
455
+ /**
456
+ * 设置选中的行keys
457
+ */
458
+ setSelectedRowKeys: (keys: React__default.Key[]) => void;
459
+ /**
460
+ * 滚动到指定位置
461
+ */
462
+ scrollTo: (params: {
463
+ x?: number;
464
+ y?: number;
465
+ }) => void;
466
+ /**
467
+ * 获取当前排序状态
468
+ */
469
+ getSortState: () => {
470
+ field: string | null;
471
+ order: SortOrder;
472
+ };
473
+ /**
474
+ * 获取当前过滤状态
475
+ */
476
+ getFilterState: () => Record<string, any[]>;
477
+ /**
478
+ * 清除排序
479
+ */
480
+ clearSort: () => void;
481
+ /**
482
+ * 清除过滤
483
+ */
484
+ clearFilter: () => void;
397
485
  }
398
486
 
399
- export { ColumnDataType, FilterConfig, IBadgeProps, ICanvasColumnType, ICanvasColumnsType, ICanvasRowSelection, ICanvasTableProps, SortOrder };
487
+ export { ColumnDataType, FilterConfig, IBadgeProps, ICanvasColumnType, ICanvasColumnsType, ICanvasRowSelection, ICanvasTableProps, ICanvasTableRefHandle, SortOrder };
@@ -24,13 +24,17 @@ var findColumnByKey = function (columns, key) {
24
24
  /**
25
25
  * 递归处理列的render函数(转换为统一的调用格式)
26
26
  */
27
- var processColumnRender = function (column, renderMode, headerWrap) {
27
+ var processColumnRender = function (column, renderMode, headerWrap, headerAlign) {
28
28
  if (renderMode === void 0) { renderMode = "object"; }
29
29
  var processedColumn = __assign({}, column);
30
30
  if (headerWrap) {
31
31
  processedColumn.wrap =
32
32
  processedColumn.wrap === undefined ? headerWrap : processedColumn.wrap;
33
33
  }
34
+ // 应用表头对齐方式(仅当列没有设置 headerAlign 或 align 时)
35
+ if (headerAlign && !processedColumn.headerAlign && !processedColumn.align) {
36
+ processedColumn.headerAlign = headerAlign;
37
+ }
34
38
  // 处理当前列的render函数
35
39
  if (column.render) {
36
40
  var render_1 = column.render;
@@ -44,7 +48,7 @@ var processColumnRender = function (column, renderMode, headerWrap) {
44
48
  // 递归处理children
45
49
  if (column.children && Array.isArray(column.children)) {
46
50
  processedColumn.children = column.children.map(function (child) {
47
- return processColumnRender(child, renderMode, headerWrap);
51
+ return processColumnRender(child, renderMode, headerWrap, headerAlign);
48
52
  });
49
53
  }
50
54
  return processedColumn;
@@ -38,6 +38,13 @@ var FONT_SIZE = 13;
38
38
  var FONT_WEIGHT = "400"; // 正常字重,避免过细导致不清晰
39
39
  // 图标尺寸
40
40
  var CHECKBOX_SIZE = 16;
41
- var SORT_ICON_SIZE = 6;
41
+ var SORT_ICON_SIZE = 6;
42
+ var SORT_ICON_WIDTH = 15; // 排序图标宽度(用于表头宽度计算)
43
+ var FILTER_ICON_WIDTH = 15; // 筛选图标宽度(用于表头宽度计算)
44
+ // 间距和内边距
45
+ var ICON_SPACING = 4; // 图标之间的间距
46
+ var TEXT_PADDING = 5; // 文本内边距
47
+ // 行高计算
48
+ var LINE_HEIGHT = FONT_SIZE + 5; // 字体大小 + 行间距5px
42
49
 
43
- export { CHECKBOX_SIZE, COLORS, DEFAULT_COLUMN_WIDTH, DEFAULT_SELECTION_COLUMN_WIDTH, FONT_FAMILY, FONT_SIZE, FONT_WEIGHT, MIN_SCROLLBAR_SIZE, SCROLLBAR_PADDING, SCROLLBAR_SIZE, SORT_ICON_SIZE };
50
+ export { CHECKBOX_SIZE, COLORS, DEFAULT_COLUMN_WIDTH, DEFAULT_SELECTION_COLUMN_WIDTH, FILTER_ICON_WIDTH, FONT_FAMILY, FONT_SIZE, FONT_WEIGHT, ICON_SPACING, LINE_HEIGHT, MIN_SCROLLBAR_SIZE, SCROLLBAR_PADDING, SCROLLBAR_SIZE, SORT_ICON_SIZE, SORT_ICON_WIDTH, TEXT_PADDING };
@@ -104,26 +104,43 @@ var toggleSelectAll = function (dataSource, isAllSelected, getRowKey, getCheckbo
104
104
  };
105
105
  /**
106
106
  * 处理排序点击
107
+ * 优化后的逻辑:
108
+ * - 点击上箭头:升序 → 降序 → 取消排序 → 升序...(循环)
109
+ * - 点击下箭头:降序 → 升序 → 取消排序 → 降序...(循环)
107
110
  */
108
111
  var handleSortClick = function (clickedUpper, currentSortField, currentSortOrder, columnKey) {
109
112
  var newOrder = null;
113
+ // 判断当前列是否已经是排序列
114
+ var isCurrentColumn = currentSortField === columnKey;
110
115
  if (clickedUpper) {
111
- // 点击上箭头:升序
112
- if (currentSortField === columnKey && currentSortOrder === "ascend") {
113
- newOrder = null; // 取消排序
114
- }
115
- else {
116
+ // 点击上箭头的循环逻辑:升序 → 降序 → 取消排序
117
+ if (!isCurrentColumn || currentSortOrder === null) {
118
+ // 没有排序或不是当前列,设置为升序
116
119
  newOrder = "ascend";
117
120
  }
121
+ else if (currentSortOrder === "ascend") {
122
+ // 已经是升序,切换到降序
123
+ newOrder = "descend";
124
+ }
125
+ else if (currentSortOrder === "descend") {
126
+ // 已经是降序,取消排序
127
+ newOrder = null;
128
+ }
118
129
  }
119
130
  else {
120
- // 点击下箭头:降序
121
- if (currentSortField === columnKey && currentSortOrder === "descend") {
122
- newOrder = null; // 取消排序
123
- }
124
- else {
131
+ // 点击下箭头的循环逻辑:降序 → 升序 → 取消排序
132
+ if (!isCurrentColumn || currentSortOrder === null) {
133
+ // 没有排序或不是当前列,设置为降序
125
134
  newOrder = "descend";
126
135
  }
136
+ else if (currentSortOrder === "descend") {
137
+ // 已经是降序,切换到升序
138
+ newOrder = "ascend";
139
+ }
140
+ else if (currentSortOrder === "ascend") {
141
+ // 已经是升序,取消排序
142
+ newOrder = null;
143
+ }
127
144
  }
128
145
  return {
129
146
  sortField: newOrder ? columnKey : null,
@@ -1,3 +1,5 @@
1
+ import { FONT_SIZE, FONT_FAMILY, TEXT_PADDING, LINE_HEIGHT, FILTER_ICON_WIDTH, SORT_ICON_WIDTH, ICON_SPACING } from './constants.js';
2
+
1
3
  /**
2
4
  * 多级表头工具函数
3
5
  */
@@ -96,13 +98,6 @@ function calculateLayerHeights(columns, baseHeaderHeight, columnRenderInfos) {
96
98
  }
97
99
  var flattenedHeaderRows = flattenHeaders(columns);
98
100
  var leafColumns = getLeafColumns(columns);
99
- var FONT_SIZE = 13;
100
- var FONT_FAMILY = '"Microsoft YaHei", 微软雅黑, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif';
101
- var LINE_HEIGHT = 20;
102
- var FILTER_ICON_WIDTH = 15;
103
- var SORT_ICON_WIDTH = 15;
104
- var ICON_SPACING = 4;
105
- var TEXT_PADDING = 5;
106
101
  // 创建临时canvas用于测量文本
107
102
  var tempCanvas = document.createElement("canvas");
108
103
  var ctx = tempCanvas.getContext("2d");
@@ -187,9 +182,10 @@ function calculateLayerHeights(columns, baseHeaderHeight, columnRenderInfos) {
187
182
  }
188
183
  maxLines = Math.max(maxLines, totalLines);
189
184
  });
190
- // 这一层的高度 = 基础高度 + (额外行数 * 行高)
185
+ // 当有换行时:上边距(5px) + 行数 * 行高 + 下边距(5px)
186
+ // 当没有换行时:使用原始的 baseHeaderHeight
191
187
  var layerHeight = maxLines > 1
192
- ? baseHeaderHeight + (maxLines - 1) * LINE_HEIGHT
188
+ ? 10 + maxLines * LINE_HEIGHT // 上下边距各5px,共10px
193
189
  : baseHeaderHeight;
194
190
  layerHeights.push(layerHeight);
195
191
  });