zmdms-webui 2.3.5 → 2.3.7

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,9 +8,9 @@ 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_SIZE, FONT_FAMILY, SCROLLBAR_SIZE, SCROLLBAR_PADDING } from '../utils/constants.js';
11
+ import { COLORS, FONT_WEIGHT, FONT_SIZE, FONT_FAMILY, SCROLLBAR_SIZE, SCROLLBAR_PADDING } from '../utils/constants.js';
12
12
  import { drawCheckbox, wrapText, truncateText, drawFilterIcon, drawSortIcon } from '../utils/canvasDrawHelpers.js';
13
- import { getMaxDepth, flattenHeaders, getLeafColumns } from '../utils/multiHeaderHelpers.js';
13
+ import { getMaxDepth, flattenHeaders, calculateLayerHeights, getLeafColumns } from '../utils/multiHeaderHelpers.js';
14
14
  import { extractCellText } from '../utils/cellHelpers.js';
15
15
  import { formatCellValue } from '../utils/formatHelpers.js';
16
16
  import { calculateSelectionState, calculateIconArea } from '../utils/interactionHelpers.js';
@@ -19,12 +19,21 @@ 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, 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, 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;
23
23
  // 判断是否是多级表头
24
24
  var maxDepth = useMemo(function () { return getMaxDepth(columns); }, [columns]);
25
25
  var isMultiHeader = maxDepth > 1;
26
26
  // 扁平化多级表头(如果需要)
27
27
  var flattenedHeaderRows = useMemo(function () { return (isMultiHeader ? flattenHeaders(columns) : []); }, [columns, isMultiHeader]);
28
+ // 计算多级表头每一层的高度(考虑换行)
29
+ // 使用 baseHeaderHeight 或默认计算出的基础高度
30
+ var _d = useMemo(function () {
31
+ var maxDepth = getMaxDepth(columns);
32
+ // 如果提供了 baseHeaderHeight,使用它;否则根据 headerHeight 和深度计算
33
+ var baseHeight = baseHeaderHeight ||
34
+ (maxDepth > 1 ? Math.floor(headerHeight / maxDepth) : headerHeight);
35
+ return calculateLayerHeights(columns, baseHeight, columnRenderInfos);
36
+ }, [columns, headerHeight, baseHeaderHeight, columnRenderInfos]), layerHeights = _d.layerHeights, layerStartY = _d.layerStartY;
28
37
  var dpr = window.devicePixelRatio || 1;
29
38
  // ==================== 通用辅助函数 ====================
30
39
  /**
@@ -45,7 +54,7 @@ var useTableRender = function (params) {
45
54
  */
46
55
  var calculateTextX = function (drawX, actualWidth, align, padding) {
47
56
  if (align === void 0) { align = "left"; }
48
- if (padding === void 0) { padding = 16; }
57
+ if (padding === void 0) { padding = 5; }
49
58
  if (align === "center") {
50
59
  return drawX + actualWidth / 2;
51
60
  }
@@ -63,8 +72,6 @@ var useTableRender = function (params) {
63
72
  };
64
73
  // 绘制角标的辅助函数(三角形标记,类似Excel批注)
65
74
  var drawBadge = useMemoizedFn(function (ctx, badge, cellX, cellY, cellWidth, cellHeight) {
66
- if (!badge.text)
67
- return;
68
75
  var triangleSize = 12; // 三角形边长
69
76
  var position = badge.position || "top-right";
70
77
  var color = badge.color || "#ff4d4f";
@@ -120,9 +127,8 @@ var useTableRender = function (params) {
120
127
  ctx.fillRect(drawX, 0, width, headerHeight);
121
128
  // 表头文本
122
129
  ctx.fillStyle = COLORS.text;
123
- ctx.font = "".concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
130
+ ctx.font = "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
124
131
  ctx.textBaseline = "middle";
125
- var textX = drawX + 16;
126
132
  var textY = headerHeight / 2;
127
133
  // 绘制选择框
128
134
  if (column.key === "__selection__") {
@@ -135,26 +141,65 @@ var useTableRender = function (params) {
135
141
  else {
136
142
  // 计算图标占用的宽度
137
143
  var iconArea = calculateIconArea(column, width);
138
- // 计算文本可用宽度(左边距16px + 图标宽度 )
139
- var textMaxWidth = width - 16 - iconArea.iconsWidth;
144
+ // 确定表头对齐方式,优先使用列的 headerAlign,其次是 align,最后是默认值
145
+ var headerAlign = column.headerAlign || column.align || "center";
140
146
  // 绘制文本(如果 title 是 React 元素,跳过Canvas渲染,在覆盖层中渲染)
141
147
  if (!isValidElement(column.title)) {
142
148
  var titleText = String(column.title || "");
149
+ // 根据对齐方式和图标情况设置文本位置
150
+ var textX_1;
151
+ var textMaxWidth = void 0;
152
+ if (iconArea.iconsWidth > 0) {
153
+ // 有图标时,根据headerAlign调整文本位置和最大宽度
154
+ if (headerAlign === "right") {
155
+ ctx.textAlign = "right";
156
+ textX_1 = drawX + width - 5 - iconArea.iconsWidth;
157
+ textMaxWidth = width - 10 - iconArea.iconsWidth;
158
+ }
159
+ else if (headerAlign === "center") {
160
+ ctx.textAlign = "center";
161
+ textX_1 = drawX + (width - iconArea.iconsWidth) / 2;
162
+ textMaxWidth = width - 10 - iconArea.iconsWidth;
163
+ }
164
+ else {
165
+ ctx.textAlign = "left";
166
+ textX_1 = drawX + 5;
167
+ textMaxWidth = width - 5 - iconArea.iconsWidth;
168
+ }
169
+ }
170
+ else {
171
+ // 没有图标时,根据headerAlign设置对齐方式
172
+ if (headerAlign === "left") {
173
+ ctx.textAlign = "left";
174
+ textX_1 = drawX + 5;
175
+ textMaxWidth = width - 10;
176
+ }
177
+ else if (headerAlign === "right") {
178
+ ctx.textAlign = "right";
179
+ textX_1 = drawX + width - 5;
180
+ textMaxWidth = width - 10;
181
+ }
182
+ else {
183
+ ctx.textAlign = "center";
184
+ textX_1 = drawX + width / 2;
185
+ textMaxWidth = width - 10;
186
+ }
187
+ }
143
188
  // 根据 ellipsis 和 wrap 属性处理文本
144
189
  if (column.wrap) {
145
190
  // 换行显示(支持\n和自动换行)
146
191
  var lines = wrapText(ctx, titleText, textMaxWidth);
147
- var lineHeight_1 = 20;
192
+ var lineHeight_1 = FONT_SIZE + 5; // 字体大小 + 行间距5px
148
193
  var startY_1 = textY - ((lines.length - 1) * lineHeight_1) / 2;
149
194
  lines.forEach(function (line, index) {
150
195
  var lineY = startY_1 + index * lineHeight_1;
151
- ctx.fillText(line, textX, lineY);
196
+ ctx.fillText(line, textX_1, lineY);
152
197
  });
153
198
  }
154
199
  else {
155
200
  // 默认截断显示
156
201
  var truncatedText = truncateText(ctx, titleText, textMaxWidth);
157
- ctx.fillText(truncatedText, textX, textY);
202
+ ctx.fillText(truncatedText, textX_1, textY);
158
203
  }
159
204
  }
160
205
  // 绘制筛选图标(最右侧,距离右边5px)
@@ -171,6 +216,7 @@ var useTableRender = function (params) {
171
216
  drawSortIcon(ctx, iconX, textY, column.key === state.sortField ? state.sortOrder : null);
172
217
  }
173
218
  }
219
+ ctx.textAlign = "left"; // 重置对齐方式
174
220
  ctx.restore();
175
221
  // 绘制表头列之间的边框
176
222
  if (bordered) {
@@ -205,7 +251,8 @@ var useTableRender = function (params) {
205
251
  }
206
252
  });
207
253
  // 绘制多级表头单元格
208
- var drawMultiHeaderCell = useMemoizedFn(function (ctx, headerCell, rowHeight, y, displayWidth, currentDepth, onlyFixed // 是否只绘制fixed列
254
+ var drawMultiHeaderCell = useMemoizedFn(function (ctx, headerCell, layerHeight, y, displayWidth, currentDepth, totalCellHeight, // 单元格的总高度(考虑rowSpan)
255
+ onlyFixed // 是否只绘制fixed列
209
256
  ) {
210
257
  if (onlyFixed === void 0) { onlyFixed = false; }
211
258
  var column = headerCell.column, colSpan = headerCell.colSpan, rowSpan = headerCell.rowSpan, colIndex = headerCell.colIndex;
@@ -245,19 +292,19 @@ var useTableRender = function (params) {
245
292
  }
246
293
  // 绘制单元格背景
247
294
  ctx.fillStyle = COLORS.headerBg;
248
- ctx.fillRect(drawX, y, width, rowHeight * rowSpan);
295
+ ctx.fillRect(drawX, y, width, totalCellHeight);
249
296
  // 绘制边框
250
297
  if (bordered) {
251
298
  ctx.strokeStyle = COLORS.border;
252
299
  ctx.lineWidth = 1;
253
- ctx.strokeRect(drawX, y, width, rowHeight * rowSpan);
300
+ ctx.strokeRect(drawX, y, width, totalCellHeight);
254
301
  }
255
302
  // 判断是否是最末级表头(当前行+rowSpan 是否等于最大深度)
256
303
  var isLastLevel = currentDepth + rowSpan === maxDepth;
257
304
  // 判断是否是特殊列(选择框列、序号列等不需要排序和筛选的列)
258
305
  var isSpecialColumn = column.key === "__selection__" || column.key === "__index__";
259
306
  // 绘制单元格内容
260
- var textY = y + (rowHeight * rowSpan) / 2;
307
+ var textY = y + totalCellHeight / 2;
261
308
  // 绘制选择框(只在第一行显示全选框)
262
309
  if (column.key === "__selection__" && currentDepth === 0) {
263
310
  // 计算选中状态
@@ -269,7 +316,7 @@ var useTableRender = function (params) {
269
316
  else {
270
317
  // 绘制文本
271
318
  ctx.fillStyle = COLORS.text;
272
- ctx.font = "".concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
319
+ ctx.font = "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
273
320
  ctx.textBaseline = "middle";
274
321
  if (!isValidElement(column.title)) {
275
322
  var titleText = String(column.title || "");
@@ -278,44 +325,73 @@ var useTableRender = function (params) {
278
325
  var iconArea = shouldShowIcons
279
326
  ? calculateIconArea(column, width)
280
327
  : { hasOrder: false, hasFilter: false, iconsWidth: 0 };
281
- // 如果是最末级且有图标,文本左对齐;否则居中
328
+ // 确定表头对齐方式,优先使用列的 headerAlign,其次是 align,最后是默认值
329
+ var headerAlign = column.headerAlign || column.align || "center";
330
+ // 如果是最末级且有图标,需要调整对齐方式
282
331
  if (isLastLevel && iconArea.iconsWidth > 0) {
283
- ctx.textAlign = "left";
284
- var textX_1 = drawX + 16;
285
- var textMaxWidth = width - 16 - iconArea.iconsWidth;
332
+ // 有图标时,根据headerAlign调整文本位置和最大宽度
333
+ var textX_2;
334
+ var textMaxWidth = void 0;
335
+ if (headerAlign === "right") {
336
+ ctx.textAlign = "right";
337
+ textX_2 = drawX + width - 5 - iconArea.iconsWidth;
338
+ textMaxWidth = width - 10 - iconArea.iconsWidth;
339
+ }
340
+ else if (headerAlign === "center") {
341
+ ctx.textAlign = "center";
342
+ textX_2 = drawX + (width - iconArea.iconsWidth) / 2;
343
+ textMaxWidth = width - 10 - iconArea.iconsWidth;
344
+ }
345
+ else {
346
+ ctx.textAlign = "left";
347
+ textX_2 = drawX + 5;
348
+ textMaxWidth = width - 5 - iconArea.iconsWidth;
349
+ }
286
350
  // 根据 wrap 属性处理文本
287
351
  if (column.wrap) {
288
352
  // 换行显示(支持\n和自动换行)
289
353
  var lines = wrapText(ctx, titleText, textMaxWidth);
290
- var lineHeight_2 = 20;
354
+ var lineHeight_2 = FONT_SIZE + 5; // 字体大小 + 行间距5px
291
355
  var startY_2 = textY - ((lines.length - 1) * lineHeight_2) / 2;
292
356
  lines.forEach(function (line, index) {
293
357
  var lineY = startY_2 + index * lineHeight_2;
294
- ctx.fillText(line, textX_1, lineY);
358
+ ctx.fillText(line, textX_2, lineY);
295
359
  });
296
360
  }
297
361
  else {
298
362
  var truncated = truncateText(ctx, titleText, textMaxWidth);
299
- ctx.fillText(truncated, textX_1, textY);
363
+ ctx.fillText(truncated, textX_2, textY);
300
364
  }
301
365
  }
302
366
  else {
303
- ctx.textAlign = "center";
304
- var textX_2 = drawX + width / 2;
367
+ // 没有图标时,根据headerAlign设置对齐方式
368
+ var textX_3;
369
+ if (headerAlign === "left") {
370
+ ctx.textAlign = "left";
371
+ textX_3 = drawX + 5;
372
+ }
373
+ else if (headerAlign === "right") {
374
+ ctx.textAlign = "right";
375
+ textX_3 = drawX + width - 5;
376
+ }
377
+ else {
378
+ ctx.textAlign = "center";
379
+ textX_3 = drawX + width / 2;
380
+ }
305
381
  // 根据 wrap 属性处理文本
306
382
  if (column.wrap) {
307
383
  // 换行显示(支持\n和自动换行)
308
- var lines = wrapText(ctx, titleText, width - 16);
309
- var lineHeight_3 = 20;
384
+ var lines = wrapText(ctx, titleText, width - 10); // 左右各5px
385
+ var lineHeight_3 = FONT_SIZE + 5; // 字体大小 + 行间距5px
310
386
  var startY_3 = textY - ((lines.length - 1) * lineHeight_3) / 2;
311
387
  lines.forEach(function (line, index) {
312
388
  var lineY = startY_3 + index * lineHeight_3;
313
- ctx.fillText(line, textX_2, lineY);
389
+ ctx.fillText(line, textX_3, lineY);
314
390
  });
315
391
  }
316
392
  else {
317
- var truncated = truncateText(ctx, titleText, width - 16);
318
- ctx.fillText(truncated, textX_2, textY);
393
+ var truncated = truncateText(ctx, titleText, width - 10); // 左右各5px
394
+ ctx.fillText(truncated, textX_3, textY);
319
395
  }
320
396
  }
321
397
  // 绘制筛选图标(仅在最末级表头显示)
@@ -341,20 +417,31 @@ var useTableRender = function (params) {
341
417
  ctx.fillStyle = COLORS.headerBg;
342
418
  ctx.fillRect(0, 0, displayWidth, headerHeight);
343
419
  if (isMultiHeader) {
344
- // 多级表头渲染
345
- var rowHeight_1 = headerHeight / maxDepth;
420
+ // 多级表头渲染,使用计算出的每一层高度
346
421
  // 先绘制非fixed列
347
422
  flattenedHeaderRows.forEach(function (headerRow, rowIndex) {
348
- var y = rowIndex * rowHeight_1;
423
+ var y = layerStartY[rowIndex];
424
+ var currentLayerHeight = layerHeights[rowIndex];
349
425
  headerRow.forEach(function (headerCell) {
350
- drawMultiHeaderCell(ctx, headerCell, rowHeight_1, y, displayWidth, rowIndex, false);
426
+ // 计算单元格的总高度(如果跨越多层)
427
+ var totalCellHeight = 0;
428
+ for (var i = rowIndex; i < rowIndex + headerCell.rowSpan; i++) {
429
+ totalCellHeight += layerHeights[i];
430
+ }
431
+ drawMultiHeaderCell(ctx, headerCell, currentLayerHeight, y, displayWidth, rowIndex, totalCellHeight, false);
351
432
  });
352
433
  });
353
434
  // 再绘制fixed列(覆盖在非fixed列之上)
354
435
  flattenedHeaderRows.forEach(function (headerRow, rowIndex) {
355
- var y = rowIndex * rowHeight_1;
436
+ var y = layerStartY[rowIndex];
437
+ var currentLayerHeight = layerHeights[rowIndex];
356
438
  headerRow.forEach(function (headerCell) {
357
- drawMultiHeaderCell(ctx, headerCell, rowHeight_1, y, displayWidth, rowIndex, true);
439
+ // 计算单元格的总高度(如果跨越多层)
440
+ var totalCellHeight = 0;
441
+ for (var i = rowIndex; i < rowIndex + headerCell.rowSpan; i++) {
442
+ totalCellHeight += layerHeights[i];
443
+ }
444
+ drawMultiHeaderCell(ctx, headerCell, currentLayerHeight, y, displayWidth, rowIndex, totalCellHeight, true);
358
445
  });
359
446
  });
360
447
  }
@@ -514,10 +601,30 @@ var useTableRender = function (params) {
514
601
  }
515
602
  // 单元格内容
516
603
  if (column.key === "__selection__") {
517
- var checkboxProps = ((_a = rowSelection === null || rowSelection === void 0 ? void 0 : rowSelection.getCheckboxProps) === null || _a === void 0 ? void 0 : _a.call(rowSelection, record)) || {};
518
- var disabled = checkboxProps.disabled || false;
519
- // 对于合并单元格,使用合并后的单元格高度进行垂直居中
520
- 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
+ }
521
628
  }
522
629
  else {
523
630
  var dataIndex = column.dataIndex || column.key;
@@ -562,29 +669,43 @@ var useTableRender = function (params) {
562
669
  // 合计行字体加粗
563
670
  ctx.font = record[IS_SUMMARY]
564
671
  ? "bold ".concat(FONT_SIZE, "px ").concat(FONT_FAMILY)
565
- : "".concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
672
+ : "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
566
673
  ctx.textBaseline = "middle";
567
674
  var align = column.align || "left";
568
- var textX_3 = drawX + 16;
675
+ var textX_4 = drawX + 5;
569
676
  if (align === "center") {
570
677
  ctx.textAlign = "center";
571
- textX_3 = drawX + width / 2;
678
+ textX_4 = drawX + width / 2;
572
679
  }
573
680
  else if (align === "right") {
574
681
  ctx.textAlign = "right";
575
- textX_3 = drawX + width - 16;
682
+ textX_4 = drawX + width - 5;
576
683
  }
577
684
  else {
578
685
  ctx.textAlign = "left";
579
686
  }
580
687
  // 处理文本显示(ellipsis 或 wrap)
581
- var maxWidth = width - 32;
688
+ var maxWidth = width - 10; // 左右各5px
582
689
  var ellipsis = column.ellipsis !== false; // 默认为true
583
690
  var wrap = column.wrap === true; // 默认为false
584
691
  if (wrap) {
585
- // 换行显示
586
- var lines = wrapText(ctx, cellText, maxWidth);
587
- 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
588
709
  var totalHeight = lines.length * lineHeight_4;
589
710
  // 使用合并后的单元格高度计算垂直居中位置
590
711
  var startY_4 = y + (cellHeight - totalHeight) / 2 + lineHeight_4 / 2;
@@ -592,7 +713,7 @@ var useTableRender = function (params) {
592
713
  var lineY = startY_4 + lineIndex * lineHeight_4;
593
714
  // 只绘制在单元格可见区域内的文本
594
715
  if (lineY >= y && lineY <= y + cellHeight) {
595
- ctx.fillText(line, textX_3, lineY);
716
+ ctx.fillText(line, textX_4, lineY);
596
717
  }
597
718
  });
598
719
  }
@@ -600,12 +721,12 @@ var useTableRender = function (params) {
600
721
  // 省略显示 - 使用合并后的单元格高度
601
722
  var textY = y + cellHeight / 2;
602
723
  var truncatedText = truncateText(ctx, cellText, maxWidth);
603
- ctx.fillText(truncatedText, textX_3, textY);
724
+ ctx.fillText(truncatedText, textX_4, textY);
604
725
  }
605
726
  else {
606
727
  // 不处理,直接显示 - 使用合并后的单元格高度
607
728
  var textY = y + cellHeight / 2;
608
- ctx.fillText(cellText, textX_3, textY);
729
+ ctx.fillText(cellText, textX_4, textY);
609
730
  }
610
731
  if (column.badge && !isSummaryRow) {
611
732
  var badgeProps = typeof column.badge === "function"
@@ -814,28 +935,30 @@ var useTableRender = function (params) {
814
935
  ctx.fillRect(drawX, fixedY, actualWidth, rowHeight);
815
936
  // 处理序号列
816
937
  if (column.key === "__index__") {
817
- // 合计行序号列显示空
818
- 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);
819
948
  return;
820
- var indexText = String(rowIndex + 1);
821
- ctx.fillStyle = COLORS.text;
822
- ctx.font = "".concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
823
- ctx.textBaseline = "middle";
824
- setTextAlign(ctx, "center");
825
- var textX = drawX + actualWidth / 2;
826
- var textY = fixedY + rowHeight / 2;
827
- ctx.fillText(indexText, textX, textY);
828
- return;
949
+ }
950
+ // 合计行序号列继续执行后面的逻辑,可能显示"合计"文字
829
951
  }
830
952
  // 处理选择框列
831
953
  if (column.key === "__selection__") {
832
- // 合计行选择框列显示空
833
- 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);
834
959
  return;
835
- var checkboxX = drawX + actualWidth / 2;
836
- var textY = fixedY + rowHeight / 2;
837
- drawCheckbox(ctx, checkboxX, textY, isSelected, false, false);
838
- return;
960
+ }
961
+ // 合计行选择框列继续执行后面的逻辑,可能显示"合计"文字
839
962
  }
840
963
  // 获取单元格值
841
964
  var dataIndex = column.dataIndex || column.key;
@@ -863,11 +986,11 @@ var useTableRender = function (params) {
863
986
  ctx.font =
864
987
  rowType === "summary"
865
988
  ? "bold ".concat(FONT_SIZE, "px ").concat(FONT_FAMILY)
866
- : "".concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
989
+ : "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
867
990
  ctx.textBaseline = "middle";
868
991
  var align = column.align || "left";
869
992
  setTextAlign(ctx, align);
870
- var padding = 16;
993
+ var padding = 5;
871
994
  var maxWidth = actualWidth - padding * 2;
872
995
  var textY = fixedY + rowHeight / 2;
873
996
  var textX = calculateTextX(drawX, actualWidth, align, padding);
@@ -1241,8 +1364,9 @@ var useTableRender = function (params) {
1241
1364
  // 计算可视区域(从固定行之后开始)
1242
1365
  var scrollStartRow = fixedTopRowsCount + Math.floor(scrollState.scrollTop / rowHeight);
1243
1366
  var scrollEndRow = Math.min(fixedTopRowsCount +
1244
- Math.ceil((scrollState.scrollTop + dataAreaHeight) / rowHeight), processedDataSource.length - fixedBottomRowsCount
1245
- // 注意:不再减去合计行数量,因为合计行的空间已在 dataAreaHeight 中考虑
1367
+ Math.ceil((scrollState.scrollTop + dataAreaHeight) / rowHeight), processedDataSource.length -
1368
+ fixedBottomRowsCount -
1369
+ (summaryFixed && hasSummaryRow ? 1 : 0) // 如果合计行固定,需要排除它
1246
1370
  );
1247
1371
  // 关键修复:向前扩展渲染范围,检查是否有合并单元格延伸到可视区域
1248
1372
  // 策略:对每一列独立检查,找到该列最靠前的需要渲染的主合并单元格
@@ -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
  */
@@ -182,13 +186,13 @@ interface ICanvasColumnType<RecordType = any> {
182
186
  type ICanvasColumnsType<RecordType = any> = ICanvasColumnType<RecordType>[];
183
187
  interface IBadgeProps {
184
188
  /**
185
- * 角标颜色
189
+ * 角标提示文本(鼠标悬停时显示)
186
190
  */
187
- color?: string;
191
+ title?: string;
188
192
  /**
189
- * 角标文本
193
+ * 角标颜色
190
194
  */
191
- text?: string;
195
+ color?: string;
192
196
  /**
193
197
  * 角标位置
194
198
  */
@@ -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>;
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;