zmdms-webui 2.6.1 → 2.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/es/canvastable/canvasTable.js +63 -21
- package/dist/es/canvastable/hooks/renderers/BadgeRenderer.js +58 -0
- package/dist/es/canvastable/hooks/renderers/BorderRenderer.js +229 -0
- package/dist/es/canvastable/hooks/renderers/DataRowRenderer.js +358 -0
- package/dist/es/canvastable/hooks/renderers/ExpandIconRenderer.js +97 -0
- package/dist/es/canvastable/hooks/renderers/FixedColumnRenderer.js +71 -0
- package/dist/es/canvastable/hooks/renderers/FixedRowRenderer.js +192 -0
- package/dist/es/canvastable/hooks/renderers/HeaderRenderer.js +395 -0
- package/dist/es/canvastable/hooks/renderers/ScrollbarRenderer.js +85 -0
- package/dist/es/canvastable/hooks/renderers/SelectionRenderer.js +181 -0
- package/dist/es/canvastable/hooks/useExpandable.js +185 -0
- package/dist/es/canvastable/hooks/useTableInteraction.js +37 -1
- package/dist/es/canvastable/hooks/useTableRender.js +172 -1464
- package/dist/es/canvastable/interface.d.ts +71 -1
- package/dist/es/canvastable/utils/renderHelpers.js +118 -0
- package/dist/es/modal/interface.d.ts +0 -4
- package/dist/es/modal/modal.js +13 -13
- package/dist/es/modal/useResizable.js +1 -1
- package/dist/es/table/excel.js +1 -1
- package/package.json +1 -1
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import { useRef, useMemo,
|
|
2
|
-
import {
|
|
3
|
-
import { IS_SUMMARY } from '../../table/constant.js';
|
|
4
|
-
import '../../_virtual/_tslib.js';
|
|
5
|
-
import 'lodash/isEqual';
|
|
6
|
-
import '../../node_modules/immutability-helper/index.js';
|
|
7
|
-
import 'react/jsx-runtime';
|
|
1
|
+
import { useRef, useMemo, useEffect } from 'react';
|
|
2
|
+
import { SCROLLBAR_SIZE } from '../utils/constants.js';
|
|
8
3
|
import 'zmdms-utils';
|
|
9
|
-
import '../../node_modules/exceljs/dist/exceljs.min.js';
|
|
10
4
|
import 'dayjs';
|
|
11
|
-
import
|
|
12
|
-
import {
|
|
13
|
-
import
|
|
14
|
-
import
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
5
|
+
import '../../_virtual/_tslib.js';
|
|
6
|
+
import { useMemoizedFn } from 'ahooks';
|
|
7
|
+
import './renderers/BadgeRenderer.js';
|
|
8
|
+
import './renderers/ExpandIconRenderer.js';
|
|
9
|
+
import { HeaderRenderer } from './renderers/HeaderRenderer.js';
|
|
10
|
+
import { DataRowRenderer } from './renderers/DataRowRenderer.js';
|
|
11
|
+
import { FixedRowRenderer } from './renderers/FixedRowRenderer.js';
|
|
12
|
+
import { BorderRenderer } from './renderers/BorderRenderer.js';
|
|
13
|
+
import { SelectionRenderer } from './renderers/SelectionRenderer.js';
|
|
14
|
+
import { ScrollbarRenderer } from './renderers/ScrollbarRenderer.js';
|
|
15
|
+
import { FixedColumnRenderer } from './renderers/FixedColumnRenderer.js';
|
|
17
16
|
|
|
18
17
|
/**
|
|
19
18
|
* 表格渲染 Hook
|
|
19
|
+
* 重构版本:使用渲染器模式,职责分离
|
|
20
20
|
*/
|
|
21
21
|
var useTableRender = function (params) {
|
|
22
22
|
// 添加节流机制,避免频繁重绘
|
|
@@ -26,1342 +26,119 @@ var useTableRender = function (params) {
|
|
|
26
26
|
var lastRenderParamsRef = useRef("");
|
|
27
27
|
// 内存优化:缓存Canvas context,避免重复获取
|
|
28
28
|
var canvasContextRef = useRef(null);
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return calculateLayerHeights(columns, baseHeight, columnRenderInfos);
|
|
43
|
-
}, [columns, headerHeight, baseHeaderHeight, columnRenderInfos]), layerHeights = _e.layerHeights, layerStartY = _e.layerStartY;
|
|
29
|
+
// 初始化渲染器(使用 useMemo 避免重复创建)
|
|
30
|
+
var renderers = useMemo(function () {
|
|
31
|
+
return {
|
|
32
|
+
header: new HeaderRenderer(),
|
|
33
|
+
dataRow: new DataRowRenderer(),
|
|
34
|
+
fixedRow: new FixedRowRenderer(),
|
|
35
|
+
border: new BorderRenderer(),
|
|
36
|
+
selection: new SelectionRenderer(),
|
|
37
|
+
scrollbar: new ScrollbarRenderer(),
|
|
38
|
+
fixedColumn: new FixedColumnRenderer(),
|
|
39
|
+
};
|
|
40
|
+
}, []);
|
|
41
|
+
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, expandable = params.expandable, flattenedData = params.flattenedData;
|
|
44
42
|
var dpr = window.devicePixelRatio || 1;
|
|
45
|
-
// ==================== 通用辅助函数 ====================
|
|
46
|
-
/**
|
|
47
|
-
* 计算固定列的右边界
|
|
48
|
-
*/
|
|
49
|
-
var calculateFixedColumnsRightEdge = useMemoizedFn(function () {
|
|
50
|
-
var rightEdge = 0;
|
|
51
|
-
columnRenderInfos.forEach(function (info, index) {
|
|
52
|
-
if (info.fixed) {
|
|
53
|
-
var actualWidth = getColumnWidth ? getColumnWidth(index) : info.width;
|
|
54
|
-
rightEdge = Math.max(rightEdge, info.fixedLeft + actualWidth);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
return rightEdge;
|
|
58
|
-
});
|
|
59
43
|
/**
|
|
60
|
-
*
|
|
44
|
+
* 通用渲染函数:先绘制非fixed,再绘制fixed(包括边框)
|
|
61
45
|
*/
|
|
62
|
-
var
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
46
|
+
var renderWithFixedLayer = useMemoizedFn(function (renderContent, renderBorder) {
|
|
47
|
+
// 1. 绘制非fixed层
|
|
48
|
+
renderContent(false);
|
|
49
|
+
if (bordered && renderBorder) {
|
|
50
|
+
renderBorder(false);
|
|
67
51
|
}
|
|
68
|
-
|
|
69
|
-
|
|
52
|
+
// 2. 绘制fixed层(覆盖在非fixed之上)
|
|
53
|
+
renderContent(true);
|
|
54
|
+
if (bordered && renderBorder) {
|
|
55
|
+
renderBorder(true);
|
|
70
56
|
}
|
|
71
|
-
|
|
72
|
-
};
|
|
57
|
+
});
|
|
73
58
|
/**
|
|
74
|
-
*
|
|
59
|
+
* 计算可视区域的行范围
|
|
75
60
|
*/
|
|
76
|
-
var
|
|
77
|
-
if (align === void 0) { align = "left"; }
|
|
78
|
-
ctx.textAlign = align;
|
|
79
|
-
};
|
|
80
|
-
// 绘制角标的辅助函数(三角形标记,类似Excel批注)
|
|
81
|
-
var drawBadge = useMemoizedFn(function (ctx, badge, cellX, cellY, cellWidth, cellHeight) {
|
|
82
|
-
var triangleSize = 12; // 三角形边长
|
|
83
|
-
var position = badge.position || "top-right";
|
|
84
|
-
var color = badge.color || "#ff4d4f";
|
|
85
|
-
ctx.fillStyle = color;
|
|
86
|
-
ctx.beginPath();
|
|
87
|
-
// 根据位置绘制不同方向的三角形
|
|
88
|
-
switch (position) {
|
|
89
|
-
case "top-left":
|
|
90
|
-
// 左上角三角形
|
|
91
|
-
ctx.moveTo(cellX, cellY);
|
|
92
|
-
ctx.lineTo(cellX + triangleSize, cellY);
|
|
93
|
-
ctx.lineTo(cellX, cellY + triangleSize);
|
|
94
|
-
break;
|
|
95
|
-
case "top-right":
|
|
96
|
-
// 右上角三角形(默认)
|
|
97
|
-
ctx.moveTo(cellX + cellWidth, cellY);
|
|
98
|
-
ctx.lineTo(cellX + cellWidth - triangleSize, cellY);
|
|
99
|
-
ctx.lineTo(cellX + cellWidth, cellY + triangleSize);
|
|
100
|
-
break;
|
|
101
|
-
case "bottom-left":
|
|
102
|
-
// 左下角三角形
|
|
103
|
-
ctx.moveTo(cellX, cellY + cellHeight);
|
|
104
|
-
ctx.lineTo(cellX, cellY + cellHeight - triangleSize);
|
|
105
|
-
ctx.lineTo(cellX + triangleSize, cellY + cellHeight);
|
|
106
|
-
break;
|
|
107
|
-
case "bottom-right":
|
|
108
|
-
// 右下角三角形
|
|
109
|
-
ctx.moveTo(cellX + cellWidth, cellY + cellHeight);
|
|
110
|
-
ctx.lineTo(cellX + cellWidth, cellY + cellHeight - triangleSize);
|
|
111
|
-
ctx.lineTo(cellX + cellWidth - triangleSize, cellY + cellHeight);
|
|
112
|
-
break;
|
|
113
|
-
}
|
|
114
|
-
ctx.closePath();
|
|
115
|
-
ctx.fill();
|
|
116
|
-
});
|
|
117
|
-
// 绘制表头单元格的辅助函数
|
|
118
|
-
var drawHeaderCell = useMemoizedFn(function (ctx, info, index, displayWidth, displayHeight) {
|
|
119
|
-
var column = info.column, x = info.x, fixed = info.fixed, fixedLeft = info.fixedLeft;
|
|
120
|
-
// 使用 getColumnWidth 获取列宽(考虑拖拽时的临时宽度)
|
|
121
|
-
var width = getColumnWidth ? getColumnWidth(index) : info.width;
|
|
122
|
-
var drawX = fixed ? fixedLeft : x - scrollState.scrollLeft;
|
|
123
|
-
// 跳过不在可视区域的列
|
|
124
|
-
if (!fixed && (drawX + width < 0 || drawX > displayWidth)) {
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
// 绘制表头单元格
|
|
128
|
-
ctx.save();
|
|
129
|
-
ctx.beginPath();
|
|
130
|
-
ctx.rect(drawX, 0, width, headerHeight);
|
|
131
|
-
ctx.clip();
|
|
132
|
-
// 绘制单元格背景
|
|
133
|
-
ctx.fillStyle = COLORS.headerBg;
|
|
134
|
-
ctx.fillRect(drawX, 0, width, headerHeight);
|
|
135
|
-
// 表头文本
|
|
136
|
-
ctx.fillStyle = COLORS.text;
|
|
137
|
-
ctx.font = "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
|
|
138
|
-
ctx.textBaseline = "middle";
|
|
139
|
-
var textY = headerHeight / 2;
|
|
140
|
-
// 绘制选择框
|
|
141
|
-
if (column.key === "__selection__") {
|
|
142
|
-
// 计算选中状态
|
|
143
|
-
var selectionState = calculateSelectionState(processedDataSource, state.selectedRowKeys, getRowKey, rowSelection === null || rowSelection === void 0 ? void 0 : rowSelection.getCheckboxProps);
|
|
144
|
-
// 居中绘制复选框
|
|
145
|
-
var checkboxX = drawX + width / 2;
|
|
146
|
-
drawCheckbox(ctx, checkboxX, textY, selectionState.isAllSelected, false, selectionState.isIndeterminate);
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// 计算图标占用的宽度
|
|
150
|
-
var iconArea = calculateIconArea(column, width);
|
|
151
|
-
// 确定表头对齐方式,优先使用列的 headerAlign,其次是全局 headerAlign
|
|
152
|
-
var columnHeaderAlign = column.headerAlign || headerAlign;
|
|
153
|
-
// 绘制文本(如果 title 是 React 元素,跳过Canvas渲染,在覆盖层中渲染)
|
|
154
|
-
if (!isValidElement(column.title)) {
|
|
155
|
-
var titleText = String(column.title || "");
|
|
156
|
-
// 根据对齐方式和图标情况设置文本位置
|
|
157
|
-
var textX_1;
|
|
158
|
-
var textMaxWidth = void 0;
|
|
159
|
-
if (iconArea.iconsWidth > 0) {
|
|
160
|
-
// 有图标时,根据columnHeaderAlign调整文本位置和最大宽度
|
|
161
|
-
if (columnHeaderAlign === "right") {
|
|
162
|
-
ctx.textAlign = "right";
|
|
163
|
-
textX_1 = drawX + width - TEXT_PADDING - iconArea.iconsWidth;
|
|
164
|
-
textMaxWidth = width - TEXT_PADDING * 2 - iconArea.iconsWidth;
|
|
165
|
-
}
|
|
166
|
-
else if (columnHeaderAlign === "center") {
|
|
167
|
-
ctx.textAlign = "center";
|
|
168
|
-
textX_1 = drawX + (width - iconArea.iconsWidth) / 2;
|
|
169
|
-
textMaxWidth = width - TEXT_PADDING * 2 - iconArea.iconsWidth;
|
|
170
|
-
}
|
|
171
|
-
else {
|
|
172
|
-
ctx.textAlign = "left";
|
|
173
|
-
textX_1 = drawX + TEXT_PADDING;
|
|
174
|
-
textMaxWidth = width - TEXT_PADDING - iconArea.iconsWidth;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
// 没有图标时,根据columnHeaderAlign设置对齐方式
|
|
179
|
-
if (columnHeaderAlign === "left") {
|
|
180
|
-
ctx.textAlign = "left";
|
|
181
|
-
textX_1 = drawX + TEXT_PADDING;
|
|
182
|
-
textMaxWidth = width - TEXT_PADDING * 2;
|
|
183
|
-
}
|
|
184
|
-
else if (columnHeaderAlign === "right") {
|
|
185
|
-
ctx.textAlign = "right";
|
|
186
|
-
textX_1 = drawX + width - TEXT_PADDING;
|
|
187
|
-
textMaxWidth = width - TEXT_PADDING * 2;
|
|
188
|
-
}
|
|
189
|
-
else {
|
|
190
|
-
ctx.textAlign = "center";
|
|
191
|
-
textX_1 = drawX + width / 2;
|
|
192
|
-
textMaxWidth = width - TEXT_PADDING * 2;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// 根据 ellipsis 和 wrap 属性处理文本
|
|
196
|
-
if (column.wrap) {
|
|
197
|
-
// 换行显示(支持\n和自动换行)
|
|
198
|
-
var lines = wrapText(ctx, titleText, textMaxWidth);
|
|
199
|
-
var lineHeight_1 = FONT_SIZE + 5; // 字体大小 + 行间距5px
|
|
200
|
-
var startY_1 = textY - ((lines.length - 1) * lineHeight_1) / 2;
|
|
201
|
-
lines.forEach(function (line, index) {
|
|
202
|
-
var lineY = startY_1 + index * lineHeight_1;
|
|
203
|
-
ctx.fillText(line, textX_1, lineY);
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
else {
|
|
207
|
-
// 默认截断显示
|
|
208
|
-
var truncatedText = truncateText(ctx, titleText, textMaxWidth);
|
|
209
|
-
ctx.fillText(truncatedText, textX_1, textY);
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
// 绘制筛选图标(最右侧,距离右边5px)
|
|
213
|
-
if (iconArea.hasFilter) {
|
|
214
|
-
var iconX = drawX + width - 10; // 右边距5px + 图标中心偏移5px
|
|
215
|
-
var hasFilterValue = state.filters[column.key] && state.filters[column.key].length > 0;
|
|
216
|
-
drawFilterIcon(ctx, iconX, textY, hasFilterValue);
|
|
217
|
-
}
|
|
218
|
-
// 绘制排序图标(筛选图标左边,间距4px)
|
|
219
|
-
if (iconArea.hasOrder) {
|
|
220
|
-
// 如果有筛选:右边距5px + 筛选10px + 间距4px = 19px,所以图标中心在 width - 24
|
|
221
|
-
// 如果无筛选:右边距5px + 图标中心5px = 10px,所以图标中心在 width - 10
|
|
222
|
-
var iconX = drawX + width - (iconArea.hasFilter ? 24 : 10);
|
|
223
|
-
drawSortIcon(ctx, iconX, textY, column.key === state.sortField ? state.sortOrder : null);
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
ctx.textAlign = "left"; // 重置对齐方式
|
|
227
|
-
ctx.restore();
|
|
228
|
-
// 绘制表头列之间的边框
|
|
229
|
-
if (bordered) {
|
|
230
|
-
ctx.strokeStyle = COLORS.border;
|
|
231
|
-
ctx.lineWidth = 2;
|
|
232
|
-
ctx.beginPath();
|
|
233
|
-
ctx.moveTo(drawX + width, 0);
|
|
234
|
-
ctx.lineTo(drawX + width, headerHeight);
|
|
235
|
-
ctx.stroke();
|
|
236
|
-
}
|
|
237
|
-
// 绘制列宽调整手柄(跳过选择框列)
|
|
238
|
-
if (column.key !== "__selection__" && resizeState) {
|
|
239
|
-
var isHover = resizeState.hoverResizeColumnIndex === index;
|
|
240
|
-
var isResizing = resizeState.resizingColumnIndex === index;
|
|
241
|
-
if (isHover || isResizing) {
|
|
242
|
-
var handleX = drawX + width - RESIZE_HANDLE_WIDTH / 2;
|
|
243
|
-
// 绘制调整手柄背景
|
|
244
|
-
ctx.fillStyle = isResizing ? "#1890ff" : "rgba(24, 144, 255, 0.3)";
|
|
245
|
-
ctx.fillRect(handleX, 0, RESIZE_HANDLE_WIDTH, headerHeight);
|
|
246
|
-
// 如果正在拖拽,绘制虚线
|
|
247
|
-
if (isResizing) {
|
|
248
|
-
ctx.strokeStyle = "#1890ff";
|
|
249
|
-
ctx.lineWidth = 1;
|
|
250
|
-
ctx.setLineDash([4, 4]);
|
|
251
|
-
ctx.beginPath();
|
|
252
|
-
ctx.moveTo(drawX + width, 0);
|
|
253
|
-
ctx.lineTo(drawX + width, displayHeight);
|
|
254
|
-
ctx.stroke();
|
|
255
|
-
ctx.setLineDash([]);
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
// 绘制多级表头单元格
|
|
261
|
-
var drawMultiHeaderCell = useMemoizedFn(function (ctx, headerCell, layerHeight, y, displayWidth, currentDepth, totalCellHeight, // 单元格的总高度(考虑rowSpan)
|
|
262
|
-
onlyFixed // 是否只绘制fixed列
|
|
263
|
-
) {
|
|
264
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
265
|
-
var column = headerCell.column, colSpan = headerCell.colSpan, rowSpan = headerCell.rowSpan, colIndex = headerCell.colIndex;
|
|
266
|
-
// 根据列索引计算x位置和宽度,并判断是否是fixed列
|
|
267
|
-
var x = 0;
|
|
268
|
-
var width = 0;
|
|
269
|
-
var isFixed = false;
|
|
270
|
-
var fixedLeft = 0;
|
|
271
|
-
var leafColumns = getLeafColumns(columns);
|
|
272
|
-
// 找到对应的叶子列并计算位置
|
|
273
|
-
for (var i = 0; i < leafColumns.length; i++) {
|
|
274
|
-
var leafInfo = columnRenderInfos[i];
|
|
275
|
-
if (i < colIndex) {
|
|
276
|
-
x += getColumnWidth ? getColumnWidth(i) : leafInfo.width;
|
|
277
|
-
}
|
|
278
|
-
else if (i < colIndex + colSpan) {
|
|
279
|
-
width += getColumnWidth ? getColumnWidth(i) : leafInfo.width;
|
|
280
|
-
// 如果跨越的列中有任何一个是fixed的,整个合并单元格都视为fixed
|
|
281
|
-
if (leafInfo.fixed) {
|
|
282
|
-
isFixed = true;
|
|
283
|
-
if (i === colIndex) {
|
|
284
|
-
fixedLeft = leafInfo.fixedLeft;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
// 根据 onlyFixed 参数过滤要绘制的列
|
|
290
|
-
if (onlyFixed && !isFixed)
|
|
291
|
-
return; // 只绘制fixed列时,跳过非fixed列
|
|
292
|
-
if (!onlyFixed && isFixed)
|
|
293
|
-
return; // 只绘制非fixed列时,跳过fixed列
|
|
294
|
-
// 应用横向滚动(fixed列不滚动)
|
|
295
|
-
var drawX = isFixed ? fixedLeft : x - scrollState.scrollLeft;
|
|
296
|
-
// 跳过不在可视区域的列(fixed列始终绘制)
|
|
297
|
-
if (!isFixed && (drawX + width < 0 || drawX > displayWidth)) {
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
// 绘制单元格背景
|
|
301
|
-
ctx.fillStyle = COLORS.headerBg;
|
|
302
|
-
ctx.fillRect(drawX, y, width, totalCellHeight);
|
|
303
|
-
// 绘制边框
|
|
304
|
-
if (bordered) {
|
|
305
|
-
ctx.strokeStyle = COLORS.border;
|
|
306
|
-
ctx.lineWidth = 1;
|
|
307
|
-
ctx.strokeRect(drawX, y, width, totalCellHeight);
|
|
308
|
-
}
|
|
309
|
-
// 判断是否是最末级表头(当前行+rowSpan 是否等于最大深度)
|
|
310
|
-
var isLastLevel = currentDepth + rowSpan === maxDepth;
|
|
311
|
-
// 判断是否是特殊列(选择框列、序号列等不需要排序和筛选的列)
|
|
312
|
-
var isSpecialColumn = column.key === "__selection__" || column.key === "__index__";
|
|
313
|
-
// 绘制单元格内容
|
|
314
|
-
var textY = y + totalCellHeight / 2;
|
|
315
|
-
// 绘制选择框(只在第一行显示全选框)
|
|
316
|
-
if (column.key === "__selection__" && currentDepth === 0) {
|
|
317
|
-
// 计算选中状态
|
|
318
|
-
var selectionState = calculateSelectionState(processedDataSource, state.selectedRowKeys, getRowKey, rowSelection === null || rowSelection === void 0 ? void 0 : rowSelection.getCheckboxProps);
|
|
319
|
-
// 居中绘制复选框
|
|
320
|
-
var checkboxX = drawX + width / 2;
|
|
321
|
-
drawCheckbox(ctx, checkboxX, textY, selectionState.isAllSelected, false, selectionState.isIndeterminate);
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
// 绘制文本
|
|
325
|
-
ctx.fillStyle = COLORS.text;
|
|
326
|
-
ctx.font = "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
|
|
327
|
-
ctx.textBaseline = "middle";
|
|
328
|
-
if (!isValidElement(column.title)) {
|
|
329
|
-
var titleText = String(column.title || "");
|
|
330
|
-
// 计算图标占用的宽度(仅在最末级表头且非特殊列时显示)
|
|
331
|
-
var shouldShowIcons = isLastLevel && !isSpecialColumn;
|
|
332
|
-
var iconArea = shouldShowIcons
|
|
333
|
-
? calculateIconArea(column, width)
|
|
334
|
-
: { hasOrder: false, hasFilter: false, iconsWidth: 0 };
|
|
335
|
-
// 确定表头对齐方式,优先使用列的 headerAlign,其次是全局 headerAlign
|
|
336
|
-
var columnHeaderAlign = column.headerAlign || headerAlign;
|
|
337
|
-
// 如果是最末级且有图标,需要调整对齐方式
|
|
338
|
-
if (isLastLevel && iconArea.iconsWidth > 0) {
|
|
339
|
-
// 有图标时,根据columnHeaderAlign调整文本位置和最大宽度
|
|
340
|
-
var textX_2;
|
|
341
|
-
var textMaxWidth = void 0;
|
|
342
|
-
if (columnHeaderAlign === "right") {
|
|
343
|
-
ctx.textAlign = "right";
|
|
344
|
-
textX_2 = drawX + width - TEXT_PADDING - iconArea.iconsWidth;
|
|
345
|
-
textMaxWidth = width - TEXT_PADDING * 2 - iconArea.iconsWidth;
|
|
346
|
-
}
|
|
347
|
-
else if (columnHeaderAlign === "center") {
|
|
348
|
-
ctx.textAlign = "center";
|
|
349
|
-
textX_2 = drawX + (width - iconArea.iconsWidth) / 2;
|
|
350
|
-
textMaxWidth = width - TEXT_PADDING * 2 - iconArea.iconsWidth;
|
|
351
|
-
}
|
|
352
|
-
else {
|
|
353
|
-
ctx.textAlign = "left";
|
|
354
|
-
textX_2 = drawX + TEXT_PADDING;
|
|
355
|
-
textMaxWidth = width - TEXT_PADDING - iconArea.iconsWidth;
|
|
356
|
-
}
|
|
357
|
-
// 根据 wrap 属性处理文本
|
|
358
|
-
if (column.wrap) {
|
|
359
|
-
// 换行显示(支持\n和自动换行)
|
|
360
|
-
var lines = wrapText(ctx, titleText, textMaxWidth);
|
|
361
|
-
var lineHeight_2 = FONT_SIZE + 5; // 字体大小 + 行间距5px
|
|
362
|
-
var startY_2 = textY - ((lines.length - 1) * lineHeight_2) / 2;
|
|
363
|
-
lines.forEach(function (line, index) {
|
|
364
|
-
var lineY = startY_2 + index * lineHeight_2;
|
|
365
|
-
ctx.fillText(line, textX_2, lineY);
|
|
366
|
-
});
|
|
367
|
-
}
|
|
368
|
-
else {
|
|
369
|
-
var truncated = truncateText(ctx, titleText, textMaxWidth);
|
|
370
|
-
ctx.fillText(truncated, textX_2, textY);
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
// 没有图标时,根据columnHeaderAlign设置对齐方式
|
|
375
|
-
var textX_3;
|
|
376
|
-
if (columnHeaderAlign === "left") {
|
|
377
|
-
ctx.textAlign = "left";
|
|
378
|
-
textX_3 = drawX + TEXT_PADDING;
|
|
379
|
-
}
|
|
380
|
-
else if (columnHeaderAlign === "right") {
|
|
381
|
-
ctx.textAlign = "right";
|
|
382
|
-
textX_3 = drawX + width - TEXT_PADDING;
|
|
383
|
-
}
|
|
384
|
-
else {
|
|
385
|
-
ctx.textAlign = "center";
|
|
386
|
-
textX_3 = drawX + width / 2;
|
|
387
|
-
}
|
|
388
|
-
// 根据 wrap 属性处理文本
|
|
389
|
-
if (column.wrap) {
|
|
390
|
-
// 换行显示(支持\n和自动换行)
|
|
391
|
-
var lines = wrapText(ctx, titleText, width - TEXT_PADDING * 2); // 左右各TEXT_PADDING
|
|
392
|
-
var lineHeight_3 = FONT_SIZE + 5; // 字体大小 + 行间距5px
|
|
393
|
-
var startY_3 = textY - ((lines.length - 1) * lineHeight_3) / 2;
|
|
394
|
-
lines.forEach(function (line, index) {
|
|
395
|
-
var lineY = startY_3 + index * lineHeight_3;
|
|
396
|
-
ctx.fillText(line, textX_3, lineY);
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
var truncated = truncateText(ctx, titleText, width - TEXT_PADDING * 2); // 左右各TEXT_PADDING
|
|
401
|
-
ctx.fillText(truncated, textX_3, textY);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
// 绘制筛选图标(仅在最末级表头显示)
|
|
405
|
-
if (iconArea.hasFilter) {
|
|
406
|
-
var iconX = drawX + width - 10; // 右边距5px + 图标中心偏移5px
|
|
407
|
-
var hasFilterValue = state.filters[column.key] && state.filters[column.key].length > 0;
|
|
408
|
-
drawFilterIcon(ctx, iconX, textY, hasFilterValue);
|
|
409
|
-
}
|
|
410
|
-
// 绘制排序图标(仅在最末级表头显示)
|
|
411
|
-
if (iconArea.hasOrder) {
|
|
412
|
-
// 如果有筛选:右边距5px + 筛选10px + 间距4px = 19px,所以图标中心在 width - 24
|
|
413
|
-
// 如果无筛选:右边距5px + 图标中心5px = 10px,所以图标中心在 width - 10
|
|
414
|
-
var iconX = drawX + width - (iconArea.hasFilter ? 24 : 10);
|
|
415
|
-
drawSortIcon(ctx, iconX, textY, column.key === state.sortField ? state.sortOrder : null);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
}
|
|
419
|
-
ctx.textAlign = "left"; // 重置对齐方式
|
|
420
|
-
});
|
|
421
|
-
// 绘制表头
|
|
422
|
-
var drawHeader = useMemoizedFn(function (ctx, displayWidth, displayHeight) {
|
|
423
|
-
// 表头背景
|
|
424
|
-
ctx.fillStyle = COLORS.headerBg;
|
|
425
|
-
ctx.fillRect(0, 0, displayWidth, headerHeight);
|
|
426
|
-
if (isMultiHeader) {
|
|
427
|
-
// 多级表头渲染,使用计算出的每一层高度
|
|
428
|
-
// 先绘制非fixed列
|
|
429
|
-
flattenedHeaderRows.forEach(function (headerRow, rowIndex) {
|
|
430
|
-
var y = layerStartY[rowIndex];
|
|
431
|
-
var currentLayerHeight = layerHeights[rowIndex];
|
|
432
|
-
headerRow.forEach(function (headerCell) {
|
|
433
|
-
// 计算单元格的总高度(如果跨越多层)
|
|
434
|
-
var totalCellHeight = 0;
|
|
435
|
-
for (var i = rowIndex; i < rowIndex + headerCell.rowSpan; i++) {
|
|
436
|
-
totalCellHeight += layerHeights[i];
|
|
437
|
-
}
|
|
438
|
-
drawMultiHeaderCell(ctx, headerCell, currentLayerHeight, y, displayWidth, rowIndex, totalCellHeight, false);
|
|
439
|
-
});
|
|
440
|
-
});
|
|
441
|
-
// 再绘制fixed列(覆盖在非fixed列之上)
|
|
442
|
-
flattenedHeaderRows.forEach(function (headerRow, rowIndex) {
|
|
443
|
-
var y = layerStartY[rowIndex];
|
|
444
|
-
var currentLayerHeight = layerHeights[rowIndex];
|
|
445
|
-
headerRow.forEach(function (headerCell) {
|
|
446
|
-
// 计算单元格的总高度(如果跨越多层)
|
|
447
|
-
var totalCellHeight = 0;
|
|
448
|
-
for (var i = rowIndex; i < rowIndex + headerCell.rowSpan; i++) {
|
|
449
|
-
totalCellHeight += layerHeights[i];
|
|
450
|
-
}
|
|
451
|
-
drawMultiHeaderCell(ctx, headerCell, currentLayerHeight, y, displayWidth, rowIndex, totalCellHeight, true);
|
|
452
|
-
});
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
// 单层表头渲染(原有逻辑)
|
|
457
|
-
// 先绘制非fixed列
|
|
458
|
-
columnRenderInfos.forEach(function (info, index) {
|
|
459
|
-
if (!info.fixed) {
|
|
460
|
-
drawHeaderCell(ctx, info, index, displayWidth, displayHeight);
|
|
461
|
-
}
|
|
462
|
-
});
|
|
463
|
-
// 再绘制fixed列(覆盖在非fixed列之上)
|
|
464
|
-
columnRenderInfos.forEach(function (info, index) {
|
|
465
|
-
if (info.fixed) {
|
|
466
|
-
drawHeaderCell(ctx, info, index, displayWidth, displayHeight);
|
|
467
|
-
}
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
});
|
|
471
|
-
// 绘制数据行
|
|
472
|
-
var drawRow = useMemoizedFn(function (ctx, rowIndex, displayWidth, displayHeight, onlyFixed // 是否只绘制fixed列
|
|
473
|
-
) {
|
|
474
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
475
|
-
var record = processedDataSource[rowIndex];
|
|
476
|
-
if (!record)
|
|
477
|
-
return;
|
|
478
|
-
// 计算固定行数量
|
|
479
|
-
var fixedTopCount = fixedRowsCount || (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.topCount) || 0;
|
|
480
|
-
// 计算Y坐标:对于固定行之后的行,需要加上固定行的高度并减去滚动偏移
|
|
481
|
-
var y = headerHeight +
|
|
482
|
-
fixedTopCount * rowHeight +
|
|
483
|
-
(rowIndex - fixedTopCount) * rowHeight -
|
|
484
|
-
scrollState.scrollTop;
|
|
485
|
-
// 计算实际可用的渲染区域高度(考虑固定底部行和合计行)
|
|
486
|
-
var fixedBottomRowsCount = (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.bottomCount) || 0;
|
|
487
|
-
var fixedBottomHeight = fixedBottomRowsCount * rowHeight;
|
|
488
|
-
var fixedSummaryHeight = summaryFixed && hasSummaryRow ? rowHeight : 0;
|
|
489
|
-
var effectiveHeight = displayHeight -
|
|
490
|
-
(needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
|
|
491
|
-
fixedBottomHeight -
|
|
492
|
-
fixedSummaryHeight;
|
|
493
|
-
// 提前检查这一行是否有合并单元格,找出最大的rowSpan
|
|
494
|
-
var maxRowSpanInRow = 1;
|
|
495
|
-
if (mergeCellMap) {
|
|
496
|
-
columnRenderInfos.forEach(function (_, colIndex) {
|
|
497
|
-
var mergeCellKey = "".concat(rowIndex, "-").concat(colIndex);
|
|
498
|
-
var mergeInfo = mergeCellMap.get(mergeCellKey);
|
|
499
|
-
// 只考虑主单元格(不是被合并的单元格)
|
|
500
|
-
if (mergeInfo &&
|
|
501
|
-
!mergeInfo.skip &&
|
|
502
|
-
mergeInfo.rowSpan > maxRowSpanInRow) {
|
|
503
|
-
maxRowSpanInRow = mergeInfo.rowSpan;
|
|
504
|
-
}
|
|
505
|
-
});
|
|
506
|
-
}
|
|
507
|
-
// 计算这一行的实际高度(考虑合并单元格)
|
|
508
|
-
var actualRowHeight = rowHeight * maxRowSpanInRow;
|
|
509
|
-
// 跳过不在可视区域的行
|
|
510
|
-
// 关键:使用合并后的实际高度判断,确保合并单元格的最后一行还在可视区域内时继续渲染
|
|
511
|
-
if (y + actualRowHeight < headerHeight || y >= effectiveHeight) {
|
|
512
|
-
return;
|
|
513
|
-
}
|
|
514
|
-
var recordKey = getRowKey(record, rowIndex);
|
|
515
|
-
var isSelected = state.selectedRowKeys.includes(recordKey);
|
|
516
|
-
var isHover = state.hoverRowIndex === rowIndex;
|
|
517
|
-
// 计算fixed列的右边界,用于裁剪非fixed列
|
|
518
|
-
var fixedColumnsRightEdge = 0;
|
|
519
|
-
if (!onlyFixed) {
|
|
520
|
-
columnRenderInfos.forEach(function (info) {
|
|
521
|
-
if (info.fixed) {
|
|
522
|
-
var actualWidth = getColumnWidth
|
|
523
|
-
? getColumnWidth(columnRenderInfos.indexOf(info))
|
|
524
|
-
: info.width;
|
|
525
|
-
var rightEdge = info.fixedLeft + actualWidth;
|
|
526
|
-
fixedColumnsRightEdge = Math.max(fixedColumnsRightEdge, rightEdge);
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
// 绘制单元格
|
|
531
|
-
columnRenderInfos.forEach(function (info, colIndex) {
|
|
532
|
-
var _a;
|
|
533
|
-
var column = info.column, x = info.x, fixed = info.fixed, fixedLeft = info.fixedLeft;
|
|
534
|
-
// 根据 onlyFixed 参数过滤要绘制的列
|
|
535
|
-
if (onlyFixed && !fixed)
|
|
536
|
-
return; // 只绘制fixed列时,跳过非fixed列
|
|
537
|
-
if (!onlyFixed && fixed)
|
|
538
|
-
return; // 只绘制非fixed列时,跳过fixed列
|
|
539
|
-
// 检查合并单元格
|
|
540
|
-
var mergeCellKey = "".concat(rowIndex, "-").concat(colIndex);
|
|
541
|
-
var mergeInfo = mergeCellMap === null || mergeCellMap === void 0 ? void 0 : mergeCellMap.get(mergeCellKey);
|
|
542
|
-
// 如果是被合并的单元格,跳过渲染
|
|
543
|
-
if (mergeInfo && mergeInfo.skip) {
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
// 使用 getColumnWidth 获取列宽(考虑拖拽时的临时宽度)
|
|
547
|
-
var width = getColumnWidth ? getColumnWidth(colIndex) : info.width;
|
|
548
|
-
var drawX = fixed ? fixedLeft : x - scrollState.scrollLeft;
|
|
549
|
-
// 跳过不在可视区域的列
|
|
550
|
-
if (!fixed && (drawX + width < 0 || drawX > displayWidth)) {
|
|
551
|
-
return;
|
|
552
|
-
}
|
|
553
|
-
// 对于非fixed列,检查是否被fixed列遮挡,如果完全被遮挡则跳过绘制
|
|
554
|
-
if (!fixed &&
|
|
555
|
-
fixedColumnsRightEdge > 0 &&
|
|
556
|
-
drawX < fixedColumnsRightEdge) {
|
|
557
|
-
// 如果非fixed列完全在fixed列的左侧(被完全遮挡),则跳过
|
|
558
|
-
if (drawX + width <= fixedColumnsRightEdge) {
|
|
559
|
-
return;
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
// 计算单元格高度(考虑合并)
|
|
563
|
-
var cellHeight = mergeInfo && mergeInfo.rowSpan > 1
|
|
564
|
-
? rowHeight * mergeInfo.rowSpan
|
|
565
|
-
: rowHeight;
|
|
566
|
-
ctx.save();
|
|
567
|
-
ctx.beginPath();
|
|
568
|
-
// 裁剪区域:确保不覆盖表头,从 headerHeight 开始
|
|
569
|
-
var clipY = Math.max(y, headerHeight);
|
|
570
|
-
// 计算单元格实际底部位置,并限制在可视区域内
|
|
571
|
-
var cellBottom = Math.min(y + cellHeight, effectiveHeight);
|
|
572
|
-
// 裁剪高度 = 单元格可见底部 - 裁剪起始位置
|
|
573
|
-
var clipHeight = Math.max(0, cellBottom - clipY);
|
|
574
|
-
// 对于非fixed列,还需要裁剪掉被fixed列遮挡的部分
|
|
575
|
-
var clipX = drawX;
|
|
576
|
-
var clipWidth = width;
|
|
577
|
-
if (!fixed &&
|
|
578
|
-
fixedColumnsRightEdge > 0 &&
|
|
579
|
-
drawX < fixedColumnsRightEdge) {
|
|
580
|
-
// 调整裁剪区域,去掉被fixed列遮挡的部分
|
|
581
|
-
var overlap = fixedColumnsRightEdge - drawX;
|
|
582
|
-
if (overlap < width) {
|
|
583
|
-
clipX = fixedColumnsRightEdge;
|
|
584
|
-
clipWidth = width - overlap;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
ctx.rect(clipX, clipY, clipWidth, clipHeight);
|
|
588
|
-
ctx.clip();
|
|
589
|
-
// 绘制单元格背景
|
|
590
|
-
if (clipHeight > 0 && clipWidth > 0) {
|
|
591
|
-
if (isSelected) {
|
|
592
|
-
ctx.fillStyle = COLORS.selectedBg;
|
|
593
|
-
}
|
|
594
|
-
else if (record[IS_SUMMARY]) {
|
|
595
|
-
// 合计行使用表头背景色
|
|
596
|
-
ctx.fillStyle = COLORS.headerBg;
|
|
597
|
-
}
|
|
598
|
-
else if (isHover) {
|
|
599
|
-
ctx.fillStyle = COLORS.hoverBg;
|
|
600
|
-
}
|
|
601
|
-
else if (striped && rowIndex % 2 === 1) {
|
|
602
|
-
ctx.fillStyle = COLORS.stripedBg;
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
ctx.fillStyle = COLORS.white;
|
|
606
|
-
}
|
|
607
|
-
ctx.fillRect(clipX, clipY, clipWidth, clipHeight);
|
|
608
|
-
}
|
|
609
|
-
// 单元格内容
|
|
610
|
-
if (column.key === "__selection__") {
|
|
611
|
-
// 检查是否是合计行
|
|
612
|
-
var isSummaryRow = record[IS_SUMMARY];
|
|
613
|
-
if (!isSummaryRow) {
|
|
614
|
-
// 非合计行显示选择框
|
|
615
|
-
var checkboxProps = ((_a = rowSelection === null || rowSelection === void 0 ? void 0 : rowSelection.getCheckboxProps) === null || _a === void 0 ? void 0 : _a.call(rowSelection, record)) || {};
|
|
616
|
-
var disabled = checkboxProps.disabled || false;
|
|
617
|
-
// 对于合并单元格,使用合并后的单元格高度进行垂直居中
|
|
618
|
-
drawCheckbox(ctx, drawX + width / 2, y + cellHeight / 2, isSelected, disabled, false);
|
|
619
|
-
}
|
|
620
|
-
else {
|
|
621
|
-
// 合计行选择框列显示文本(比如"合计")
|
|
622
|
-
var dataIndex = column.dataIndex || column.key;
|
|
623
|
-
var cellValue = record[dataIndex];
|
|
624
|
-
var cellText = formatCellValue(cellValue, column, true);
|
|
625
|
-
if (cellText) {
|
|
626
|
-
ctx.fillStyle = COLORS.text;
|
|
627
|
-
ctx.font = "bold ".concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
|
|
628
|
-
ctx.textBaseline = "middle";
|
|
629
|
-
ctx.textAlign = "center";
|
|
630
|
-
var textX = drawX + width / 2;
|
|
631
|
-
var textY = y + cellHeight / 2;
|
|
632
|
-
ctx.fillText(cellText, textX, textY);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
var dataIndex = column.dataIndex || column.key;
|
|
638
|
-
var cellValue = record[dataIndex];
|
|
639
|
-
// 检查是否是合计行
|
|
640
|
-
var isSummaryRow = record[IS_SUMMARY];
|
|
641
|
-
// 检查是否有 render 函数且返回自定义 ReactNode
|
|
642
|
-
var shouldRenderInCanvas = true;
|
|
643
|
-
// 对于合计行,不应用render函数,直接显示原始数据
|
|
644
|
-
if (column.render && !isSummaryRow) {
|
|
645
|
-
var rendered = column.render(cellValue, record, rowIndex);
|
|
646
|
-
// 如果返回的不是字符串或数字,说明是自定义组件,应该在覆盖层渲染,不在 Canvas 渲染
|
|
647
|
-
if (typeof rendered !== "string" &&
|
|
648
|
-
typeof rendered !== "number" &&
|
|
649
|
-
rendered !== null &&
|
|
650
|
-
rendered !== undefined) {
|
|
651
|
-
shouldRenderInCanvas = false;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
// 只有在需要在 Canvas 中渲染时才绘制文本
|
|
655
|
-
if (shouldRenderInCanvas) {
|
|
656
|
-
// 应用格式化(日期、精度、千分符)
|
|
657
|
-
var cellText = void 0;
|
|
658
|
-
// 对于合计行,直接使用格式化函数,不应用render
|
|
659
|
-
if (isSummaryRow && column.key !== "__index__") {
|
|
660
|
-
cellText = formatCellValue(cellValue, column);
|
|
661
|
-
}
|
|
662
|
-
else {
|
|
663
|
-
cellText = extractCellText({
|
|
664
|
-
cellValue: cellValue,
|
|
665
|
-
record: record,
|
|
666
|
-
rowIndex: rowIndex,
|
|
667
|
-
renderFn: column.render,
|
|
668
|
-
});
|
|
669
|
-
// 如果没有自定义render,应用格式化
|
|
670
|
-
if (!column.render) {
|
|
671
|
-
cellText = formatCellValue(cellValue, column);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
// 绘制文本
|
|
675
|
-
ctx.fillStyle = COLORS.text;
|
|
676
|
-
// 合计行字体加粗
|
|
677
|
-
ctx.font = record[IS_SUMMARY]
|
|
678
|
-
? "bold ".concat(FONT_SIZE, "px ").concat(FONT_FAMILY)
|
|
679
|
-
: "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
|
|
680
|
-
ctx.textBaseline = "middle";
|
|
681
|
-
var align = column.align || "left";
|
|
682
|
-
var textX_4 = drawX + TEXT_PADDING;
|
|
683
|
-
if (align === "center") {
|
|
684
|
-
ctx.textAlign = "center";
|
|
685
|
-
textX_4 = drawX + width / 2;
|
|
686
|
-
}
|
|
687
|
-
else if (align === "right") {
|
|
688
|
-
ctx.textAlign = "right";
|
|
689
|
-
textX_4 = drawX + width - TEXT_PADDING;
|
|
690
|
-
}
|
|
691
|
-
else {
|
|
692
|
-
ctx.textAlign = "left";
|
|
693
|
-
}
|
|
694
|
-
// 处理文本显示(ellipsis 或 wrap)
|
|
695
|
-
var maxWidth = width - TEXT_PADDING * 2; // 左右各TEXT_PADDING
|
|
696
|
-
var ellipsis = column.ellipsis !== false; // 默认为true
|
|
697
|
-
var wrap = column.wrap === true; // 默认为false
|
|
698
|
-
if (wrap) {
|
|
699
|
-
// 换行显示,最多显示2行
|
|
700
|
-
var allLines = wrapText(ctx, cellText, maxWidth);
|
|
701
|
-
var maxLines = 2; // 限制最多显示2行
|
|
702
|
-
var lines = allLines.slice(0, maxLines);
|
|
703
|
-
// 如果超过2行,在第2行末尾强制添加省略号
|
|
704
|
-
if (allLines.length > maxLines && lines.length === maxLines) {
|
|
705
|
-
// 确保第2行有足够空间显示省略号
|
|
706
|
-
var lastLine = lines[maxLines - 1];
|
|
707
|
-
var ellipsisText = "...";
|
|
708
|
-
// 如果最后一行加上省略号会超出宽度,需要截断
|
|
709
|
-
while (lastLine.length > 0 &&
|
|
710
|
-
ctx.measureText(lastLine + ellipsisText).width > maxWidth) {
|
|
711
|
-
lastLine = lastLine.slice(0, -1);
|
|
712
|
-
}
|
|
713
|
-
lines[maxLines - 1] = lastLine + ellipsisText;
|
|
714
|
-
}
|
|
715
|
-
var lineHeight_4 = FONT_SIZE + 5; // 字体大小 + 行间距5px
|
|
716
|
-
var totalHeight = lines.length * lineHeight_4;
|
|
717
|
-
// 使用合并后的单元格高度计算垂直居中位置
|
|
718
|
-
var startY_4 = y + (cellHeight - totalHeight) / 2 + lineHeight_4 / 2;
|
|
719
|
-
lines.forEach(function (line, lineIndex) {
|
|
720
|
-
var lineY = startY_4 + lineIndex * lineHeight_4;
|
|
721
|
-
// 只绘制在单元格可见区域内的文本
|
|
722
|
-
if (lineY >= y && lineY <= y + cellHeight) {
|
|
723
|
-
ctx.fillText(line, textX_4, lineY);
|
|
724
|
-
}
|
|
725
|
-
});
|
|
726
|
-
}
|
|
727
|
-
else if (ellipsis) {
|
|
728
|
-
// 省略显示 - 使用合并后的单元格高度
|
|
729
|
-
var textY = y + cellHeight / 2;
|
|
730
|
-
var truncatedText = truncateText(ctx, cellText, maxWidth);
|
|
731
|
-
ctx.fillText(truncatedText, textX_4, textY);
|
|
732
|
-
}
|
|
733
|
-
else {
|
|
734
|
-
// 不处理,直接显示 - 使用合并后的单元格高度
|
|
735
|
-
var textY = y + cellHeight / 2;
|
|
736
|
-
ctx.fillText(cellText, textX_4, textY);
|
|
737
|
-
}
|
|
738
|
-
if (column.badge && !isSummaryRow) {
|
|
739
|
-
var badgeProps = typeof column.badge === "function"
|
|
740
|
-
? column.badge(record)
|
|
741
|
-
: column.badge;
|
|
742
|
-
if (badgeProps) {
|
|
743
|
-
// 使用原始的单元格坐标,而不是裁剪后的坐标
|
|
744
|
-
drawBadge(ctx, badgeProps, drawX, y, width, cellHeight);
|
|
745
|
-
}
|
|
746
|
-
}
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
ctx.restore();
|
|
750
|
-
});
|
|
751
|
-
});
|
|
752
|
-
// 绘制固定列阴影和边界
|
|
753
|
-
var drawFixedColumns = useMemoizedFn(function (ctx, startRow, endRow, displayWidth, displayHeight) {
|
|
754
|
-
var fixedColumns = columnRenderInfos.filter(function (info) { return info.fixed; });
|
|
755
|
-
if (fixedColumns.length === 0)
|
|
756
|
-
return;
|
|
757
|
-
// 计算固定列的实际右边界(考虑列宽调整)
|
|
758
|
-
var fixedRightEdge = 0;
|
|
759
|
-
fixedColumns.forEach(function (info, index) {
|
|
760
|
-
var actualWidth = getColumnWidth
|
|
761
|
-
? getColumnWidth(columnRenderInfos.indexOf(info))
|
|
762
|
-
: info.width;
|
|
763
|
-
var rightEdge = info.fixedLeft + actualWidth;
|
|
764
|
-
fixedRightEdge = Math.max(fixedRightEdge, rightEdge);
|
|
765
|
-
});
|
|
766
|
-
// 绘制固定列阴影(只在有横向滚动时显示)
|
|
767
|
-
if (scrollState.scrollLeft > 0 && fixedRightEdge < displayWidth) {
|
|
768
|
-
var shadowWidth = 12; // 增加阴影宽度,使边界更明显
|
|
769
|
-
var gradient = ctx.createLinearGradient(fixedRightEdge, 0, fixedRightEdge + shadowWidth, 0);
|
|
770
|
-
gradient.addColorStop(0, "rgba(0, 0, 0, 0.12)"); // 加深阴影
|
|
771
|
-
gradient.addColorStop(0.6, "rgba(0, 0, 0, 0.04)");
|
|
772
|
-
gradient.addColorStop(1, "rgba(0, 0, 0, 0)");
|
|
773
|
-
ctx.fillStyle = gradient;
|
|
774
|
-
ctx.fillRect(fixedRightEdge, headerHeight, shadowWidth, displayHeight - headerHeight);
|
|
775
|
-
}
|
|
776
|
-
// 绘制固定列右边界线(增强边界视觉效果)
|
|
777
|
-
if (fixedRightEdge > 0 && fixedRightEdge < displayWidth) {
|
|
778
|
-
ctx.strokeStyle = "rgba(0, 0, 0, 0.06)"; // 淡边界线
|
|
779
|
-
ctx.lineWidth = 1;
|
|
780
|
-
ctx.beginPath();
|
|
781
|
-
ctx.moveTo(fixedRightEdge, headerHeight);
|
|
782
|
-
ctx.lineTo(fixedRightEdge, displayHeight);
|
|
783
|
-
ctx.stroke();
|
|
784
|
-
}
|
|
785
|
-
});
|
|
786
|
-
// 绘制边框
|
|
787
|
-
var drawBorders = useMemoizedFn(function (ctx, startRow, endRow, displayWidth, displayHeight, onlyFixed // 是否只绘制fixed列的边框
|
|
788
|
-
) {
|
|
789
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
790
|
-
ctx.strokeStyle = COLORS.border;
|
|
791
|
-
ctx.lineWidth = 1;
|
|
792
|
-
// 计算实际数据行数(只绘制到有数据的地方)
|
|
793
|
-
var dataRowCount = processedDataSource.length;
|
|
794
|
-
// 绘制表头底部边框(只在非fixed时绘制一次,使用2px更明显)
|
|
795
|
-
if (!onlyFixed) {
|
|
796
|
-
ctx.lineWidth = 1;
|
|
797
|
-
ctx.beginPath();
|
|
798
|
-
ctx.moveTo(0, headerHeight);
|
|
799
|
-
ctx.lineTo(displayWidth, headerHeight);
|
|
800
|
-
ctx.stroke();
|
|
801
|
-
ctx.lineWidth = 1; // 恢复为1px
|
|
802
|
-
}
|
|
803
|
-
// 计算固定行占用的高度
|
|
61
|
+
var calculateVisibleRows = useMemoizedFn(function (displayHeight) {
|
|
804
62
|
var fixedTopRowsCount = fixedRowsCount || (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.topCount) || 0;
|
|
805
63
|
var fixedBottomRowsCount = (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.bottomCount) || 0;
|
|
806
|
-
var
|
|
807
|
-
var
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
fixedBottomRowsCount -
|
|
812
|
-
(summaryFixed && hasSummaryRow ? 1 : 0);
|
|
813
|
-
// 计算实际数据区域的底部位置(考虑固定底部行和固定合计行)
|
|
814
|
-
var dataBottomY = Math.min(headerHeight +
|
|
815
|
-
fixedTopRowsCount * rowHeight +
|
|
816
|
-
scrollableDataRowCount * rowHeight -
|
|
817
|
-
scrollState.scrollTop, displayHeight -
|
|
64
|
+
var fixedSummaryRowHeight = summaryFixed && hasSummaryRow ? rowHeight : 0;
|
|
65
|
+
var dataAreaHeight = displayHeight -
|
|
66
|
+
headerHeight -
|
|
67
|
+
fixedTopRowsCount * rowHeight -
|
|
68
|
+
fixedBottomRowsCount * rowHeight -
|
|
818
69
|
(needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
var x = info.x, width = info.width, fixed = info.fixed, fixedLeft = info.fixedLeft;
|
|
824
|
-
// 根据 onlyFixed 参数过滤要绘制边框的列
|
|
825
|
-
if (onlyFixed && !fixed)
|
|
826
|
-
return;
|
|
827
|
-
if (!onlyFixed && fixed)
|
|
828
|
-
return;
|
|
829
|
-
// 使用 getColumnWidth 获取列宽(考虑拖拽时的临时宽度)
|
|
830
|
-
var actualWidth = getColumnWidth ? getColumnWidth(colIndex) : width;
|
|
831
|
-
var drawX = fixed
|
|
832
|
-
? fixedLeft + actualWidth
|
|
833
|
-
: x + actualWidth - scrollState.scrollLeft;
|
|
834
|
-
if (drawX >= 0 && drawX <= displayWidth) {
|
|
835
|
-
ctx.beginPath();
|
|
836
|
-
ctx.moveTo(drawX, headerHeight);
|
|
837
|
-
// 只绘制到数据区域的底部,而不是整个容器高度
|
|
838
|
-
ctx.lineTo(drawX, dataBottomY);
|
|
839
|
-
ctx.stroke();
|
|
840
|
-
}
|
|
841
|
-
});
|
|
842
|
-
// 绘制水平边框(只绘制实际数据行的边框)
|
|
843
|
-
var maxRow = Math.min(endRow, dataRowCount, fixedTopRowsCount + scrollableDataRowCount);
|
|
844
|
-
var _loop_1 = function (i) {
|
|
845
|
-
// 计算Y坐标时需要考虑固定顶部行
|
|
846
|
-
var y = headerHeight +
|
|
847
|
-
fixedTopRowsCount * rowHeight +
|
|
848
|
-
(i - fixedTopRowsCount) * rowHeight -
|
|
849
|
-
scrollState.scrollTop;
|
|
850
|
-
// 只绘制在可滚动数据区域内的边框
|
|
851
|
-
if (y >= headerHeight + fixedTopRowsCount * rowHeight &&
|
|
852
|
-
y <= dataBottomY) {
|
|
853
|
-
// 分段绘制,只绘制当前范围(fixed或非fixed)的边框
|
|
854
|
-
columnRenderInfos.forEach(function (info, colIndex) {
|
|
855
|
-
// 根据 onlyFixed 参数过滤
|
|
856
|
-
if (onlyFixed && !info.fixed)
|
|
857
|
-
return;
|
|
858
|
-
if (!onlyFixed && info.fixed)
|
|
859
|
-
return;
|
|
860
|
-
var mergeCellKey = "".concat(i, "-").concat(colIndex);
|
|
861
|
-
var mergeInfo = mergeCellMap === null || mergeCellMap === void 0 ? void 0 : mergeCellMap.get(mergeCellKey);
|
|
862
|
-
// 只有被合并的单元格(skip=true)才跳过绘制上边框
|
|
863
|
-
// 合并单元格的起始行应该正常绘制上边框
|
|
864
|
-
if (mergeInfo && mergeInfo.skip) {
|
|
865
|
-
return;
|
|
866
|
-
}
|
|
867
|
-
var x = info.x, width = info.width, fixed = info.fixed, fixedLeft = info.fixedLeft;
|
|
868
|
-
var actualWidth = getColumnWidth
|
|
869
|
-
? getColumnWidth(colIndex)
|
|
870
|
-
: width;
|
|
871
|
-
var drawX = fixed ? fixedLeft : x - scrollState.scrollLeft;
|
|
872
|
-
// 只绘制可见区域的边框
|
|
873
|
-
if (!fixed && (drawX + actualWidth < 0 || drawX > displayWidth)) {
|
|
874
|
-
return;
|
|
875
|
-
}
|
|
876
|
-
ctx.beginPath();
|
|
877
|
-
ctx.moveTo(Math.max(drawX, 0), y);
|
|
878
|
-
ctx.lineTo(Math.min(drawX + actualWidth, displayWidth), y);
|
|
879
|
-
ctx.stroke();
|
|
880
|
-
});
|
|
881
|
-
}
|
|
882
|
-
};
|
|
883
|
-
for (var i = startRow; i <= maxRow; i++) {
|
|
884
|
-
_loop_1(i);
|
|
885
|
-
}
|
|
886
|
-
// 为合并单元格绘制底部边框
|
|
887
|
-
if (mergeCellMap && mergeCellMap.size > 0) {
|
|
888
|
-
mergeCellMap.forEach(function (mergeInfo, key) {
|
|
889
|
-
if (!mergeInfo.skip && mergeInfo.rowSpan > 1) {
|
|
890
|
-
var rowIndex = mergeInfo.rowIndex, colIndex = mergeInfo.colIndex, rowSpan = mergeInfo.rowSpan;
|
|
891
|
-
// 只绘制在可视范围内的且在数据区域内的
|
|
892
|
-
if (rowIndex >= startRow && rowIndex <= maxRow) {
|
|
893
|
-
var columnInfo = columnRenderInfos[colIndex];
|
|
894
|
-
if (!columnInfo)
|
|
895
|
-
return;
|
|
896
|
-
// 根据 onlyFixed 参数过滤
|
|
897
|
-
if (onlyFixed && !columnInfo.fixed)
|
|
898
|
-
return;
|
|
899
|
-
if (!onlyFixed && columnInfo.fixed)
|
|
900
|
-
return;
|
|
901
|
-
var x = columnInfo.x, width = columnInfo.width, fixed = columnInfo.fixed, fixedLeft = columnInfo.fixedLeft;
|
|
902
|
-
var actualWidth = getColumnWidth
|
|
903
|
-
? getColumnWidth(colIndex)
|
|
904
|
-
: width;
|
|
905
|
-
var drawX = fixed ? fixedLeft : x - scrollState.scrollLeft;
|
|
906
|
-
// 绘制合并单元格的底边框,考虑固定顶部行
|
|
907
|
-
var bottomY = headerHeight +
|
|
908
|
-
fixedTopRowsCount * rowHeight +
|
|
909
|
-
(rowIndex + rowSpan - fixedTopRowsCount) * rowHeight -
|
|
910
|
-
scrollState.scrollTop;
|
|
911
|
-
// 只在数据区域内绘制
|
|
912
|
-
if (bottomY >= headerHeight + fixedTopRowsCount * rowHeight &&
|
|
913
|
-
bottomY <= dataBottomY) {
|
|
914
|
-
ctx.beginPath();
|
|
915
|
-
ctx.moveTo(drawX, bottomY);
|
|
916
|
-
ctx.lineTo(drawX + actualWidth, bottomY);
|
|
917
|
-
ctx.stroke();
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
});
|
|
922
|
-
}
|
|
923
|
-
});
|
|
924
|
-
/**
|
|
925
|
-
* 通用的固定行绘制函数
|
|
926
|
-
* @param yCalculator - Y坐标计算函数
|
|
927
|
-
* @param rowType - 行类型:'top' | 'bottom' | 'summary'
|
|
928
|
-
*/
|
|
929
|
-
var drawFixedRow = useMemoizedFn(function (ctx, rowIndex, yCalculator, displayWidth, displayHeight, onlyFixed, rowType) {
|
|
930
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
931
|
-
if (rowType === void 0) { rowType = "top"; }
|
|
932
|
-
var record = processedDataSource[rowIndex];
|
|
933
|
-
if (!record)
|
|
934
|
-
return;
|
|
935
|
-
var fixedY = yCalculator(rowIndex);
|
|
936
|
-
ctx.save();
|
|
937
|
-
var recordKey = getRowKey(record, rowIndex);
|
|
938
|
-
var isSelected = state.selectedRowKeys.includes(recordKey);
|
|
939
|
-
var isHover = state.hoverRowIndex === rowIndex;
|
|
940
|
-
columnRenderInfos.forEach(function (info, colIndex) {
|
|
941
|
-
var _a;
|
|
942
|
-
var column = info.column, x = info.x, width = info.width, fixed = info.fixed, fixedLeft = info.fixedLeft;
|
|
943
|
-
// 根据 onlyFixed 参数过滤
|
|
944
|
-
if (onlyFixed && !fixed)
|
|
945
|
-
return;
|
|
946
|
-
if (!onlyFixed && fixed)
|
|
947
|
-
return;
|
|
948
|
-
var actualWidth = getColumnWidth ? getColumnWidth(colIndex) : width;
|
|
949
|
-
var drawX = fixed ? fixedLeft : x - scrollState.scrollLeft;
|
|
950
|
-
// 跳过不可见的列
|
|
951
|
-
if (!fixed && (drawX + actualWidth < 0 || drawX > displayWidth)) {
|
|
952
|
-
return;
|
|
953
|
-
}
|
|
954
|
-
// 绘制背景
|
|
955
|
-
var bgColor = COLORS.white;
|
|
956
|
-
// 如果有自定义样式
|
|
957
|
-
if ((_a = fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.style) === null || _a === void 0 ? void 0 : _a.backgroundColor) {
|
|
958
|
-
bgColor = fixedRowsConfig.style.backgroundColor;
|
|
959
|
-
}
|
|
960
|
-
else {
|
|
961
|
-
// 使用默认逻辑(斑马纹、选中、hover)
|
|
962
|
-
if (isSelected) {
|
|
963
|
-
bgColor = COLORS.selectedBg;
|
|
964
|
-
}
|
|
965
|
-
else if (isHover) {
|
|
966
|
-
bgColor = COLORS.hoverBg;
|
|
967
|
-
}
|
|
968
|
-
else if (striped && rowIndex % 2 === 1) {
|
|
969
|
-
bgColor = COLORS.stripedBg;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
ctx.fillStyle = bgColor;
|
|
973
|
-
ctx.fillRect(drawX, fixedY, actualWidth, rowHeight);
|
|
974
|
-
// 处理序号列
|
|
975
|
-
if (column.key === "__index__") {
|
|
976
|
-
// 非合计行显示序号
|
|
977
|
-
if (rowType !== "summary") {
|
|
978
|
-
var indexText = String(rowIndex + 1);
|
|
979
|
-
ctx.fillStyle = COLORS.text;
|
|
980
|
-
ctx.font = "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
|
|
981
|
-
ctx.textBaseline = "middle";
|
|
982
|
-
setTextAlign(ctx, "center");
|
|
983
|
-
var textX = drawX + actualWidth / 2;
|
|
984
|
-
var textY = fixedY + rowHeight / 2;
|
|
985
|
-
ctx.fillText(indexText, textX, textY);
|
|
986
|
-
return;
|
|
987
|
-
}
|
|
988
|
-
// 合计行序号列继续执行后面的逻辑,可能显示"合计"文字
|
|
989
|
-
}
|
|
990
|
-
// 处理选择框列
|
|
991
|
-
if (column.key === "__selection__") {
|
|
992
|
-
// 非合计行显示选择框
|
|
993
|
-
if (rowType !== "summary") {
|
|
994
|
-
var checkboxX = drawX + actualWidth / 2;
|
|
995
|
-
var textY = fixedY + rowHeight / 2;
|
|
996
|
-
drawCheckbox(ctx, checkboxX, textY, isSelected, false, false);
|
|
997
|
-
return;
|
|
998
|
-
}
|
|
999
|
-
// 合计行选择框列继续执行后面的逻辑,可能显示"合计"文字
|
|
1000
|
-
}
|
|
1001
|
-
// 获取单元格值
|
|
1002
|
-
var dataIndex = column.dataIndex || column.key;
|
|
1003
|
-
var cellValue = record[dataIndex];
|
|
1004
|
-
// 渲染单元格内容
|
|
1005
|
-
var cellText = formatCellValue(cellValue, column);
|
|
1006
|
-
// 检查是否有render函数且返回自定义ReactNode
|
|
1007
|
-
var shouldRenderInCanvas = true;
|
|
1008
|
-
// 对于合计行,不应用render函数,直接显示原始数据
|
|
1009
|
-
var isSummaryRowFixed = rowType === "summary";
|
|
1010
|
-
if (column.render && !isSummaryRowFixed) {
|
|
1011
|
-
var rendered = column.render(cellValue, record, rowIndex);
|
|
1012
|
-
// 如果返回的不是字符串或数字,说明是自定义组件,应该在覆盖层渲染,不在 Canvas 渲染
|
|
1013
|
-
if (typeof rendered !== "string" &&
|
|
1014
|
-
typeof rendered !== "number" &&
|
|
1015
|
-
rendered !== null &&
|
|
1016
|
-
rendered !== undefined) {
|
|
1017
|
-
shouldRenderInCanvas = false;
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
// 只有在需要在Canvas中渲染时才绘制文本
|
|
1021
|
-
if (cellText && shouldRenderInCanvas) {
|
|
1022
|
-
ctx.fillStyle = COLORS.text;
|
|
1023
|
-
// 合计行字体加粗
|
|
1024
|
-
ctx.font =
|
|
1025
|
-
rowType === "summary"
|
|
1026
|
-
? "bold ".concat(FONT_SIZE, "px ").concat(FONT_FAMILY)
|
|
1027
|
-
: "".concat(FONT_WEIGHT, " ").concat(FONT_SIZE, "px ").concat(FONT_FAMILY);
|
|
1028
|
-
ctx.textBaseline = "middle";
|
|
1029
|
-
var align = column.align || "left";
|
|
1030
|
-
setTextAlign(ctx, align);
|
|
1031
|
-
var padding = 5;
|
|
1032
|
-
var maxWidth = actualWidth - padding * 2;
|
|
1033
|
-
var textY = fixedY + rowHeight / 2;
|
|
1034
|
-
var textX = calculateTextX(drawX, actualWidth, align, padding);
|
|
1035
|
-
var truncatedText = truncateText(ctx, cellText, maxWidth);
|
|
1036
|
-
ctx.fillText(truncatedText, textX, textY);
|
|
1037
|
-
}
|
|
1038
|
-
});
|
|
1039
|
-
ctx.restore();
|
|
1040
|
-
});
|
|
1041
|
-
// 绘制固定顶部行(在固定位置)
|
|
1042
|
-
var drawFixedTopRow = useMemoizedFn(function (ctx, rowIndex, displayWidth, displayHeight, onlyFixed) {
|
|
1043
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
1044
|
-
drawFixedRow(ctx, rowIndex, function (idx) { return headerHeight + idx * rowHeight; }, displayWidth, displayHeight, onlyFixed, "top");
|
|
1045
|
-
});
|
|
1046
|
-
/**
|
|
1047
|
-
* 通用的固定行边框绘制函数
|
|
1048
|
-
* @param startYCalculator - 起始Y坐标计算函数
|
|
1049
|
-
* @param borderType - 边框类型:'normal' | 'summary'(summary会加粗)
|
|
1050
|
-
*/
|
|
1051
|
-
var drawFixedRowsBorder = useMemoizedFn(function (ctx, rowCount, startYCalculator, displayWidth, displayHeight, onlyFixed, borderType) {
|
|
1052
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
1053
|
-
if (borderType === void 0) { borderType = "normal"; }
|
|
1054
|
-
ctx.save();
|
|
1055
|
-
ctx.strokeStyle = COLORS.border;
|
|
1056
|
-
ctx.lineWidth = borderType === "summary" ? 2 : 1; // 合计行边框加粗
|
|
1057
|
-
var startY = startYCalculator();
|
|
1058
|
-
// 计算固定列的右边界
|
|
1059
|
-
var fixedColumnsRightEdge = 0;
|
|
1060
|
-
if (onlyFixed) {
|
|
1061
|
-
fixedColumnsRightEdge = calculateFixedColumnsRightEdge();
|
|
1062
|
-
}
|
|
1063
|
-
// 绘制水平边框
|
|
1064
|
-
for (var i = 0; i <= rowCount; i++) {
|
|
1065
|
-
var y = startY + i * rowHeight;
|
|
1066
|
-
ctx.beginPath();
|
|
1067
|
-
if (onlyFixed) {
|
|
1068
|
-
// 固定列区域:只绘制固定列宽度的水平边框
|
|
1069
|
-
ctx.moveTo(0, y);
|
|
1070
|
-
ctx.lineTo(fixedColumnsRightEdge, y);
|
|
1071
|
-
}
|
|
1072
|
-
else {
|
|
1073
|
-
// 非固定列区域:绘制整行水平边框
|
|
1074
|
-
ctx.moveTo(0, y);
|
|
1075
|
-
ctx.lineTo(displayWidth, y);
|
|
1076
|
-
}
|
|
1077
|
-
ctx.stroke();
|
|
1078
|
-
}
|
|
1079
|
-
// 绘制垂直边框
|
|
1080
|
-
ctx.lineWidth = 1; // 垂直边框始终为1px
|
|
1081
|
-
columnRenderInfos.forEach(function (info, colIndex) {
|
|
1082
|
-
var x = info.x, width = info.width, fixed = info.fixed, fixedLeft = info.fixedLeft;
|
|
1083
|
-
// 根据 onlyFixed 参数过滤要绘制边框的列
|
|
1084
|
-
if (onlyFixed && !fixed)
|
|
1085
|
-
return;
|
|
1086
|
-
if (!onlyFixed && fixed)
|
|
1087
|
-
return;
|
|
1088
|
-
var actualWidth = getColumnWidth ? getColumnWidth(colIndex) : width;
|
|
1089
|
-
var drawX = fixed
|
|
1090
|
-
? fixedLeft + actualWidth
|
|
1091
|
-
: x + actualWidth - scrollState.scrollLeft;
|
|
1092
|
-
if (drawX >= 0 && drawX <= displayWidth) {
|
|
1093
|
-
ctx.beginPath();
|
|
1094
|
-
ctx.moveTo(drawX, startY);
|
|
1095
|
-
ctx.lineTo(drawX, startY + rowCount * rowHeight);
|
|
1096
|
-
ctx.stroke();
|
|
1097
|
-
}
|
|
1098
|
-
});
|
|
1099
|
-
ctx.restore();
|
|
1100
|
-
});
|
|
1101
|
-
// 绘制固定顶部行的边框
|
|
1102
|
-
var drawFixedTopRowsBorder = useMemoizedFn(function (ctx, rowCount, displayWidth, displayHeight, onlyFixed) {
|
|
1103
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
1104
|
-
drawFixedRowsBorder(ctx, rowCount, function () { return headerHeight; }, displayWidth, displayHeight, onlyFixed, "normal");
|
|
1105
|
-
});
|
|
1106
|
-
// 绘制固定底部行(在固定位置)
|
|
1107
|
-
var drawFixedBottomRow = useMemoizedFn(function (ctx, rowIndex, relativeIndex, displayWidth, displayHeight, onlyFixed) {
|
|
1108
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
1109
|
-
drawFixedRow(ctx, rowIndex, function () {
|
|
1110
|
-
var fixedSummaryHeight = summaryFixed && hasSummaryRow ? rowHeight : 0;
|
|
1111
|
-
return (displayHeight -
|
|
1112
|
-
(needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
|
|
1113
|
-
fixedSummaryHeight -
|
|
1114
|
-
((fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.bottomCount) || 0) * rowHeight +
|
|
1115
|
-
relativeIndex * rowHeight);
|
|
1116
|
-
}, displayWidth, displayHeight, onlyFixed, "bottom");
|
|
1117
|
-
});
|
|
1118
|
-
// 绘制固定底部行的边框
|
|
1119
|
-
var drawFixedBottomRowsBorder = useMemoizedFn(function (ctx, rowCount, displayWidth, displayHeight, onlyFixed) {
|
|
1120
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
1121
|
-
drawFixedRowsBorder(ctx, rowCount, function () {
|
|
1122
|
-
var fixedSummaryHeight = summaryFixed && hasSummaryRow ? rowHeight : 0;
|
|
1123
|
-
return (displayHeight -
|
|
1124
|
-
(needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
|
|
1125
|
-
fixedSummaryHeight -
|
|
1126
|
-
rowCount * rowHeight);
|
|
1127
|
-
}, displayWidth, displayHeight, onlyFixed, "normal");
|
|
1128
|
-
});
|
|
1129
|
-
// 绘制固定合计行(在固定位置)
|
|
1130
|
-
var drawFixedSummaryRow = useMemoizedFn(function (ctx, rowIndex, fixedY, displayWidth, displayHeight, onlyFixed) {
|
|
1131
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
1132
|
-
drawFixedRow(ctx, rowIndex, function () { return fixedY; }, displayWidth, displayHeight, onlyFixed, "summary");
|
|
1133
|
-
});
|
|
1134
|
-
// 绘制固定合计行的边框
|
|
1135
|
-
var drawFixedSummaryRowBorder = useMemoizedFn(function (ctx, rowIndex, fixedY, displayWidth, displayHeight, onlyFixed) {
|
|
1136
|
-
if (onlyFixed === void 0) { onlyFixed = false; }
|
|
1137
|
-
drawFixedRowsBorder(ctx, 1, // 合计行只有一行
|
|
1138
|
-
function () { return fixedY; }, displayWidth, displayHeight, onlyFixed, "summary" // 使用summary类型,边框会加粗
|
|
1139
|
-
);
|
|
1140
|
-
});
|
|
1141
|
-
// 绘制框选区域
|
|
1142
|
-
var drawCellSelection = useMemoizedFn(function (ctx, displayWidth, displayHeight) {
|
|
1143
|
-
if (!state.cellSelection)
|
|
1144
|
-
return;
|
|
1145
|
-
var _a = state.cellSelection, startRow = _a.startRow, endRow = _a.endRow, startCol = _a.startCol, endCol = _a.endCol;
|
|
1146
|
-
// 计算选择区域的边界
|
|
1147
|
-
var minRow = Math.min(startRow, endRow);
|
|
1148
|
-
var maxRow = Math.max(startRow, endRow);
|
|
1149
|
-
var minCol = Math.min(startCol, endCol);
|
|
1150
|
-
var maxCol = Math.max(startCol, endCol);
|
|
1151
|
-
// 计算fixed列的右边界(用于裁剪非fixed列的选中效果)
|
|
1152
|
-
var fixedColumnsRightEdge = 0;
|
|
1153
|
-
columnRenderInfos.forEach(function (info) {
|
|
1154
|
-
if (info.fixed) {
|
|
1155
|
-
var rightEdge = info.fixedLeft + info.width;
|
|
1156
|
-
fixedColumnsRightEdge = Math.max(fixedColumnsRightEdge, rightEdge);
|
|
1157
|
-
}
|
|
1158
|
-
});
|
|
1159
|
-
// 记录已绘制的合并单元格,避免重复绘制
|
|
1160
|
-
var drawnMergeCells = new Set();
|
|
1161
|
-
// 记录选区的边界坐标
|
|
1162
|
-
var selectionTop = Infinity;
|
|
1163
|
-
var selectionBottom = -Infinity;
|
|
1164
|
-
var selectionLeft = Infinity;
|
|
1165
|
-
var selectionRight = -Infinity;
|
|
1166
|
-
// 计算固定行的范围
|
|
1167
|
-
var fixedTopRowsCount = (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.topCount) || fixedRowsCount || 0;
|
|
1168
|
-
var fixedBottomRowsCount = (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.bottomCount) || 0;
|
|
1169
|
-
var bottomRowsStartIndex = processedDataSource.length -
|
|
70
|
+
fixedSummaryRowHeight;
|
|
71
|
+
var scrollStartRow = fixedTopRowsCount + Math.floor(scrollState.scrollTop / rowHeight);
|
|
72
|
+
var scrollEndRow = Math.min(fixedTopRowsCount +
|
|
73
|
+
Math.ceil((scrollState.scrollTop + dataAreaHeight) / rowHeight), processedDataSource.length -
|
|
1170
74
|
fixedBottomRowsCount -
|
|
1171
|
-
(summaryFixed && hasSummaryRow ? 1 : 0);
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
else if (row < fixedTopRowsCount) {
|
|
1187
|
-
// 固定顶部行
|
|
1188
|
-
y = headerHeight + row * rowHeight;
|
|
1189
|
-
cellHeight = rowHeight;
|
|
1190
|
-
isFixedRow = true;
|
|
1191
|
-
}
|
|
1192
|
-
else if (fixedBottomRowsCount > 0 &&
|
|
1193
|
-
row >= bottomRowsStartIndex &&
|
|
1194
|
-
row < bottomRowsStartIndex + fixedBottomRowsCount) {
|
|
1195
|
-
// 固定底部行
|
|
1196
|
-
var relativeIndex = row - bottomRowsStartIndex;
|
|
1197
|
-
var fixedSummaryHeight = summaryFixed && hasSummaryRow ? rowHeight : 0;
|
|
1198
|
-
y =
|
|
1199
|
-
displayHeight -
|
|
1200
|
-
(needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
|
|
1201
|
-
fixedSummaryHeight -
|
|
1202
|
-
fixedBottomRowsCount * rowHeight +
|
|
1203
|
-
relativeIndex * rowHeight;
|
|
1204
|
-
cellHeight = rowHeight;
|
|
1205
|
-
isFixedRow = true;
|
|
1206
|
-
}
|
|
1207
|
-
else if (summaryFixed && row === summaryRowIndex) {
|
|
1208
|
-
// 固定合计行
|
|
1209
|
-
var summaryRowY = displayHeight -
|
|
1210
|
-
(needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
|
|
1211
|
-
rowHeight;
|
|
1212
|
-
y = summaryRowY;
|
|
1213
|
-
cellHeight = rowHeight;
|
|
1214
|
-
isFixedRow = true;
|
|
1215
|
-
}
|
|
1216
|
-
else {
|
|
1217
|
-
// 普通数据行
|
|
1218
|
-
// 需要调整行索引,减去固定顶部行的数量
|
|
1219
|
-
var adjustedRow = row - fixedTopRowsCount;
|
|
1220
|
-
y =
|
|
1221
|
-
headerHeight +
|
|
1222
|
-
fixedTopRowsCount * rowHeight +
|
|
1223
|
-
adjustedRow * rowHeight -
|
|
1224
|
-
scrollState.scrollTop;
|
|
1225
|
-
cellHeight = rowHeight;
|
|
1226
|
-
// 数据行的可见性检查
|
|
1227
|
-
if (y + cellHeight < headerHeight + fixedTopRowsCount * rowHeight ||
|
|
1228
|
-
y > displayHeight)
|
|
1229
|
-
continue;
|
|
1230
|
-
}
|
|
1231
|
-
for (var col = minCol; col <= maxCol; col++) {
|
|
1232
|
-
var columnInfo = columnRenderInfos[col];
|
|
1233
|
-
if (!columnInfo)
|
|
1234
|
-
continue;
|
|
1235
|
-
// 表头行不处理合并单元格
|
|
1236
|
-
if (row !== -1) {
|
|
1237
|
-
// 检查是否是合并单元格
|
|
1238
|
-
var mergeCellKey = "".concat(row, "-").concat(col);
|
|
1239
|
-
var mergeInfo = mergeCellMap === null || mergeCellMap === void 0 ? void 0 : mergeCellMap.get(mergeCellKey);
|
|
1240
|
-
// 如果是被合并的单元格,跳过(由主单元格统一绘制)
|
|
1241
|
-
if (mergeInfo && mergeInfo.skip) {
|
|
1242
|
-
continue;
|
|
1243
|
-
}
|
|
1244
|
-
// 如果是合并单元格的主单元格,检查是否已绘制
|
|
1245
|
-
if (mergeInfo && !mergeInfo.skip) {
|
|
1246
|
-
var mergeKey = "".concat(mergeInfo.rowIndex, "-").concat(mergeInfo.colIndex);
|
|
1247
|
-
if (drawnMergeCells.has(mergeKey)) {
|
|
1248
|
-
continue;
|
|
75
|
+
(summaryFixed && hasSummaryRow ? 1 : 0));
|
|
76
|
+
// 关键修复:向前扩展渲染范围,检查是否有合并单元格延伸到可视区域
|
|
77
|
+
if (mergeCellMap && scrollStartRow > fixedTopRowsCount) {
|
|
78
|
+
var originalStartRow = scrollStartRow;
|
|
79
|
+
var minStartRow = scrollStartRow;
|
|
80
|
+
// 对每一列独立检查
|
|
81
|
+
for (var colIndex = 0; colIndex < columnRenderInfos.length; colIndex++) {
|
|
82
|
+
// 向前查找这一列的主合并单元格,最多向前查找20行
|
|
83
|
+
for (var checkRow = originalStartRow - 1; checkRow >= fixedTopRowsCount && checkRow >= originalStartRow - 20; checkRow--) {
|
|
84
|
+
var mergeCellKey = "".concat(checkRow, "-").concat(colIndex);
|
|
85
|
+
var mergeInfo = mergeCellMap.get(mergeCellKey);
|
|
86
|
+
if (mergeInfo && !mergeInfo.skip && mergeInfo.rowSpan > 1) {
|
|
87
|
+
var mergeEndRow = checkRow + mergeInfo.rowSpan - 1;
|
|
88
|
+
if (mergeEndRow >= originalStartRow) {
|
|
89
|
+
minStartRow = Math.min(minStartRow, checkRow);
|
|
1249
90
|
}
|
|
1250
|
-
|
|
1251
|
-
}
|
|
1252
|
-
// 计算单元格高度(考虑合并)
|
|
1253
|
-
if (mergeInfo && mergeInfo.rowSpan > 1) {
|
|
1254
|
-
cellHeight = rowHeight * mergeInfo.rowSpan;
|
|
91
|
+
break;
|
|
1255
92
|
}
|
|
1256
93
|
}
|
|
1257
|
-
var x = columnInfo.x, width = columnInfo.width, fixed = columnInfo.fixed, fixedLeft = columnInfo.fixedLeft;
|
|
1258
|
-
var drawX = fixed ? fixedLeft : x - scrollState.scrollLeft;
|
|
1259
|
-
var drawWidth = width;
|
|
1260
|
-
if (!fixed && (drawX + width < 0 || drawX > displayWidth))
|
|
1261
|
-
continue;
|
|
1262
|
-
// 对于非fixed列,需要裁剪掉被fixed列遮挡的部分
|
|
1263
|
-
if (!fixed && drawX < fixedColumnsRightEdge) {
|
|
1264
|
-
var overlap = fixedColumnsRightEdge - drawX;
|
|
1265
|
-
if (overlap >= width) {
|
|
1266
|
-
// 完全被遮挡,跳过
|
|
1267
|
-
continue;
|
|
1268
|
-
}
|
|
1269
|
-
// 部分被遮挡,裁剪掉被遮挡的部分
|
|
1270
|
-
drawX = fixedColumnsRightEdge;
|
|
1271
|
-
drawWidth = width - overlap;
|
|
1272
|
-
}
|
|
1273
|
-
// 绘制选中背景
|
|
1274
|
-
ctx.fillStyle = COLORS.selectionBg;
|
|
1275
|
-
var clipY = void 0;
|
|
1276
|
-
var clipHeight = void 0;
|
|
1277
|
-
if (row === -1 || isFixedRow) {
|
|
1278
|
-
// 表头行或固定行,直接使用y和height
|
|
1279
|
-
clipY = y;
|
|
1280
|
-
clipHeight = cellHeight;
|
|
1281
|
-
}
|
|
1282
|
-
else {
|
|
1283
|
-
// 普通数据行,需要裁剪到可见区域(避免被表头或固定行遮挡)
|
|
1284
|
-
var minVisibleY = headerHeight + fixedTopRowsCount * rowHeight;
|
|
1285
|
-
clipY = Math.max(y, minVisibleY);
|
|
1286
|
-
var actualBottom = y + cellHeight;
|
|
1287
|
-
clipHeight = Math.max(0, Math.min(actualBottom - clipY, displayHeight - clipY));
|
|
1288
|
-
}
|
|
1289
|
-
if (clipHeight > 0) {
|
|
1290
|
-
ctx.fillRect(drawX, clipY, drawWidth, clipHeight);
|
|
1291
|
-
// 更新选区边界
|
|
1292
|
-
selectionTop = Math.min(selectionTop, clipY);
|
|
1293
|
-
selectionBottom = Math.max(selectionBottom, clipY + clipHeight);
|
|
1294
|
-
selectionLeft = Math.min(selectionLeft, drawX);
|
|
1295
|
-
selectionRight = Math.max(selectionRight, drawX + drawWidth);
|
|
1296
|
-
}
|
|
1297
94
|
}
|
|
95
|
+
scrollStartRow = minStartRow;
|
|
1298
96
|
}
|
|
1299
|
-
|
|
1300
|
-
if (selectionLeft !== Infinity && selectionTop !== Infinity) {
|
|
1301
|
-
ctx.strokeStyle = COLORS.primary;
|
|
1302
|
-
ctx.lineWidth = 2;
|
|
1303
|
-
ctx.strokeRect(selectionLeft, selectionTop, selectionRight - selectionLeft, selectionBottom - selectionTop);
|
|
1304
|
-
}
|
|
1305
|
-
});
|
|
1306
|
-
// 绘制滚动条
|
|
1307
|
-
var drawScrollbars = useMemoizedFn(function (ctx, displayWidth, displayHeight) {
|
|
1308
|
-
var scrollbarSize = SCROLLBAR_SIZE;
|
|
1309
|
-
var scrollbarPadding = SCROLLBAR_PADDING;
|
|
1310
|
-
// 绘制垂直滚动条
|
|
1311
|
-
if (needVerticalScrollbar) {
|
|
1312
|
-
var scrollbarX = displayWidth - scrollbarSize;
|
|
1313
|
-
var scrollbarY = headerHeight;
|
|
1314
|
-
var scrollbarHeight = needHorizontalScrollbar
|
|
1315
|
-
? displayHeight - scrollbarSize - headerHeight
|
|
1316
|
-
: displayHeight - headerHeight;
|
|
1317
|
-
// 滚动条轨道
|
|
1318
|
-
ctx.fillStyle = COLORS.scrollbarTrack;
|
|
1319
|
-
ctx.fillRect(scrollbarX, scrollbarY, scrollbarSize, scrollbarHeight);
|
|
1320
|
-
// 滚动条滑块
|
|
1321
|
-
ctx.fillStyle = scrollState.isDraggingVertical
|
|
1322
|
-
? COLORS.scrollbarThumbActive
|
|
1323
|
-
: COLORS.scrollbarThumb;
|
|
1324
|
-
ctx.fillRect(scrollbarX + scrollbarPadding, verticalScrollbarTop + headerHeight + scrollbarPadding, scrollbarSize - scrollbarPadding * 2, verticalScrollbarHeight - scrollbarPadding * 2);
|
|
1325
|
-
}
|
|
1326
|
-
// 绘制水平滚动条
|
|
1327
|
-
if (needHorizontalScrollbar) {
|
|
1328
|
-
var scrollbarX = 0;
|
|
1329
|
-
var scrollbarY = displayHeight - scrollbarSize;
|
|
1330
|
-
var scrollbarWidth = needVerticalScrollbar
|
|
1331
|
-
? displayWidth - scrollbarSize
|
|
1332
|
-
: displayWidth;
|
|
1333
|
-
// 滚动条轨道
|
|
1334
|
-
ctx.fillStyle = COLORS.scrollbarTrack;
|
|
1335
|
-
ctx.fillRect(scrollbarX, scrollbarY, scrollbarWidth, scrollbarSize);
|
|
1336
|
-
// 滚动条滑块
|
|
1337
|
-
ctx.fillStyle = scrollState.isDraggingHorizontal
|
|
1338
|
-
? COLORS.scrollbarThumbActive
|
|
1339
|
-
: COLORS.scrollbarThumb;
|
|
1340
|
-
ctx.fillRect(horizontalScrollbarLeft + scrollbarPadding, scrollbarY + scrollbarPadding, horizontalScrollbarWidth - scrollbarPadding * 2, scrollbarSize - scrollbarPadding * 2);
|
|
1341
|
-
}
|
|
1342
|
-
// 绘制滚动条交叉处
|
|
1343
|
-
if (needVerticalScrollbar && needHorizontalScrollbar) {
|
|
1344
|
-
ctx.fillStyle = COLORS.scrollbarTrack;
|
|
1345
|
-
ctx.fillRect(displayWidth - scrollbarSize, displayHeight - scrollbarSize, scrollbarSize, scrollbarSize);
|
|
1346
|
-
}
|
|
97
|
+
return { startRow: scrollStartRow, endRow: scrollEndRow };
|
|
1347
98
|
});
|
|
1348
99
|
/**
|
|
1349
|
-
*
|
|
1350
|
-
* 遵循绘制顺序:非fixed内容 -> 非fixed边框 -> fixed内容 -> fixed边框
|
|
100
|
+
* 创建渲染上下文
|
|
1351
101
|
*/
|
|
1352
|
-
var
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
102
|
+
var createRenderContext = useMemoizedFn(function (ctx, displayWidth, displayHeight) {
|
|
103
|
+
return {
|
|
104
|
+
ctx: ctx,
|
|
105
|
+
displayWidth: displayWidth,
|
|
106
|
+
displayHeight: displayHeight,
|
|
107
|
+
dpr: dpr,
|
|
108
|
+
bordered: bordered,
|
|
109
|
+
striped: striped,
|
|
110
|
+
headerAlign: headerAlign,
|
|
111
|
+
rowHeight: rowHeight,
|
|
112
|
+
headerHeight: headerHeight,
|
|
113
|
+
baseHeaderHeight: baseHeaderHeight,
|
|
114
|
+
processedDataSource: processedDataSource,
|
|
115
|
+
columnRenderInfos: columnRenderInfos,
|
|
116
|
+
columns: columns,
|
|
117
|
+
state: state,
|
|
118
|
+
scrollState: scrollState,
|
|
119
|
+
resizeState: resizeState,
|
|
120
|
+
getRowKey: getRowKey,
|
|
121
|
+
getColumnWidth: getColumnWidth,
|
|
122
|
+
mergeCellMap: mergeCellMap,
|
|
123
|
+
rowSelection: rowSelection,
|
|
124
|
+
fixedRowsConfig: fixedRowsConfig,
|
|
125
|
+
fixedRowsCount: fixedRowsCount,
|
|
126
|
+
summaryFixed: summaryFixed,
|
|
127
|
+
hasSummaryRow: hasSummaryRow,
|
|
128
|
+
expandable: expandable,
|
|
129
|
+
flattenedData: flattenedData,
|
|
130
|
+
needVerticalScrollbar: needVerticalScrollbar,
|
|
131
|
+
needHorizontalScrollbar: needHorizontalScrollbar,
|
|
132
|
+
verticalScrollbarTop: verticalScrollbarTop,
|
|
133
|
+
verticalScrollbarHeight: verticalScrollbarHeight,
|
|
134
|
+
horizontalScrollbarLeft: horizontalScrollbarLeft,
|
|
135
|
+
horizontalScrollbarWidth: horizontalScrollbarWidth,
|
|
136
|
+
RESIZE_HANDLE_WIDTH: RESIZE_HANDLE_WIDTH,
|
|
137
|
+
};
|
|
1363
138
|
});
|
|
1364
|
-
|
|
139
|
+
/**
|
|
140
|
+
* 绘制表格核心函数
|
|
141
|
+
*/
|
|
1365
142
|
var drawTableCore = useMemoizedFn(function () {
|
|
1366
143
|
try {
|
|
1367
144
|
var canvas = canvasRef.current;
|
|
@@ -1372,16 +149,16 @@ var useTableRender = function (params) {
|
|
|
1372
149
|
return;
|
|
1373
150
|
isDrawingRef.current = true;
|
|
1374
151
|
// 内存优化:复用context对象
|
|
1375
|
-
var
|
|
1376
|
-
if (!
|
|
1377
|
-
|
|
1378
|
-
if (!
|
|
152
|
+
var ctx = canvasContextRef.current;
|
|
153
|
+
if (!ctx || ctx.canvas !== canvas) {
|
|
154
|
+
ctx = canvas.getContext("2d");
|
|
155
|
+
if (!ctx) {
|
|
1379
156
|
isDrawingRef.current = false;
|
|
1380
157
|
return;
|
|
1381
158
|
}
|
|
1382
|
-
canvasContextRef.current =
|
|
159
|
+
canvasContextRef.current = ctx;
|
|
1383
160
|
}
|
|
1384
|
-
//
|
|
161
|
+
// 性能优化:检查是否需要重绘
|
|
1385
162
|
var renderParamsHash = JSON.stringify({
|
|
1386
163
|
width: containerWidth,
|
|
1387
164
|
height: containerHeight,
|
|
@@ -1395,153 +172,86 @@ var useTableRender = function (params) {
|
|
|
1395
172
|
sortOrder: state.sortOrder,
|
|
1396
173
|
sortField: state.sortField,
|
|
1397
174
|
});
|
|
1398
|
-
// 如果关键参数没有变化,跳过重绘
|
|
1399
175
|
if (lastRenderParamsRef.current === renderParamsHash) {
|
|
1400
176
|
isDrawingRef.current = false;
|
|
1401
177
|
return;
|
|
1402
178
|
}
|
|
1403
179
|
lastRenderParamsRef.current = renderParamsHash;
|
|
1404
180
|
// 设置 canvas 尺寸
|
|
1405
|
-
var
|
|
1406
|
-
var
|
|
1407
|
-
canvas.width =
|
|
1408
|
-
canvas.height =
|
|
1409
|
-
canvas.style.width = "".concat(
|
|
1410
|
-
canvas.style.height = "".concat(
|
|
1411
|
-
|
|
1412
|
-
//
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
//
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
181
|
+
var displayWidth = containerWidth;
|
|
182
|
+
var displayHeight = containerHeight;
|
|
183
|
+
canvas.width = displayWidth * dpr;
|
|
184
|
+
canvas.height = displayHeight * dpr;
|
|
185
|
+
canvas.style.width = "".concat(displayWidth, "px");
|
|
186
|
+
canvas.style.height = "".concat(displayHeight, "px");
|
|
187
|
+
ctx.scale(dpr, dpr);
|
|
188
|
+
// 优化文字渲染质量
|
|
189
|
+
ctx.imageSmoothingEnabled = true;
|
|
190
|
+
ctx.imageSmoothingQuality = "high";
|
|
191
|
+
// 清空画布并绘制背景
|
|
192
|
+
ctx.clearRect(0, 0, displayWidth, displayHeight);
|
|
193
|
+
ctx.fillStyle = "#ffffff";
|
|
194
|
+
ctx.fillRect(0, 0, displayWidth, displayHeight);
|
|
195
|
+
// 创建渲染上下文并设置到所有渲染器
|
|
196
|
+
var renderContext_1 = createRenderContext(ctx, displayWidth, displayHeight);
|
|
197
|
+
Object.values(renderers).forEach(function (renderer) {
|
|
198
|
+
renderer.setContext(renderContext_1);
|
|
199
|
+
});
|
|
200
|
+
// 计算可视区域
|
|
201
|
+
var _a = calculateVisibleRows(displayHeight), startRow_1 = _a.startRow, endRow_1 = _a.endRow;
|
|
202
|
+
// 计算固定行的数量
|
|
1421
203
|
var fixedTopRowsCount_1 = fixedRowsCount || (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.topCount) || 0;
|
|
1422
204
|
var fixedBottomRowsCount_1 = (fixedRowsConfig === null || fixedRowsConfig === void 0 ? void 0 : fixedRowsConfig.bottomCount) || 0;
|
|
1423
|
-
// 计算固定行占用的高度
|
|
1424
|
-
var fixedTopRowsHeight = fixedTopRowsCount_1 * rowHeight;
|
|
1425
|
-
var fixedBottomRowsHeight = fixedBottomRowsCount_1 * rowHeight;
|
|
1426
|
-
var fixedSummaryRowHeight = summaryFixed && hasSummaryRow ? rowHeight : 0;
|
|
1427
|
-
// 计算可视区域的数据区域高度(不包括表头、固定行和固定合计行)
|
|
1428
|
-
var dataAreaHeight = displayHeight_1 -
|
|
1429
|
-
headerHeight -
|
|
1430
|
-
fixedTopRowsHeight -
|
|
1431
|
-
fixedBottomRowsHeight -
|
|
1432
|
-
(needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
|
|
1433
|
-
fixedSummaryRowHeight;
|
|
1434
|
-
// 计算可视区域(从固定行之后开始)
|
|
1435
|
-
var scrollStartRow_1 = fixedTopRowsCount_1 + Math.floor(scrollState.scrollTop / rowHeight);
|
|
1436
|
-
var scrollEndRow_1 = Math.min(fixedTopRowsCount_1 +
|
|
1437
|
-
Math.ceil((scrollState.scrollTop + dataAreaHeight) / rowHeight), processedDataSource.length -
|
|
1438
|
-
fixedBottomRowsCount_1 -
|
|
1439
|
-
(summaryFixed && hasSummaryRow ? 1 : 0) // 如果合计行固定,需要排除它
|
|
1440
|
-
);
|
|
1441
|
-
// 关键修复:向前扩展渲染范围,检查是否有合并单元格延伸到可视区域
|
|
1442
|
-
// 策略:对每一列独立检查,找到该列最靠前的需要渲染的主合并单元格
|
|
1443
|
-
if (mergeCellMap && scrollStartRow_1 > fixedTopRowsCount_1) {
|
|
1444
|
-
var originalStartRow = scrollStartRow_1;
|
|
1445
|
-
var minStartRow = scrollStartRow_1;
|
|
1446
|
-
// 对每一列独立检查
|
|
1447
|
-
for (var colIndex = 0; colIndex < columnRenderInfos.length; colIndex++) {
|
|
1448
|
-
// 向前查找这一列的主合并单元格,最多向前查找20行
|
|
1449
|
-
for (var checkRow = originalStartRow - 1; checkRow >= fixedTopRowsCount_1 && checkRow >= originalStartRow - 20; checkRow--) {
|
|
1450
|
-
var mergeCellKey = "".concat(checkRow, "-").concat(colIndex);
|
|
1451
|
-
var mergeInfo = mergeCellMap.get(mergeCellKey);
|
|
1452
|
-
// 如果找到主单元格(不是被合并的单元格)
|
|
1453
|
-
if (mergeInfo && !mergeInfo.skip && mergeInfo.rowSpan > 1) {
|
|
1454
|
-
// 计算合并单元格的底部行索引
|
|
1455
|
-
var mergeEndRow = checkRow + mergeInfo.rowSpan - 1;
|
|
1456
|
-
// 如果这个合并单元格延伸到可视区域
|
|
1457
|
-
if (mergeEndRow >= originalStartRow) {
|
|
1458
|
-
// 更新最小起始行
|
|
1459
|
-
minStartRow = Math.min(minStartRow, checkRow);
|
|
1460
|
-
}
|
|
1461
|
-
// 找到这一列的主单元格后,停止向前查找该列
|
|
1462
|
-
break;
|
|
1463
|
-
}
|
|
1464
|
-
// 如果是skip的单元格或没有合并信息,继续向前查找
|
|
1465
|
-
// 因为主单元格可能在更前面
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
|
-
scrollStartRow_1 = minStartRow;
|
|
1469
|
-
}
|
|
1470
205
|
// 1. 绘制可滚动区域的数据行(包括边框)
|
|
1471
|
-
renderWithFixedLayer(
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
for (var i = scrollStartRow_1; i < scrollEndRow_1; i++) {
|
|
1475
|
-
drawRow(ctx_1, i, displayWidth_1, displayHeight_1, onlyFixed);
|
|
206
|
+
renderWithFixedLayer(function (onlyFixed) {
|
|
207
|
+
for (var i = startRow_1; i < endRow_1; i++) {
|
|
208
|
+
renderers.dataRow.renderRow(i, onlyFixed);
|
|
1476
209
|
}
|
|
1477
|
-
},
|
|
1478
|
-
|
|
1479
|
-
function (onlyFixed) {
|
|
1480
|
-
drawBorders(ctx_1, scrollStartRow_1, scrollEndRow_1, displayWidth_1, displayHeight_1, onlyFixed);
|
|
210
|
+
}, function (onlyFixed) {
|
|
211
|
+
renderers.border.renderDataBorders(startRow_1, endRow_1, onlyFixed);
|
|
1481
212
|
});
|
|
1482
213
|
// 2. 绘制固定的顶部行(如果有)
|
|
1483
214
|
if (fixedTopRowsCount_1 > 0) {
|
|
1484
|
-
|
|
1485
|
-
renderWithFixedLayer(
|
|
1486
|
-
|
|
1487
|
-
function (onlyFixed) {
|
|
1488
|
-
|
|
1489
|
-
drawFixedTopRow(ctx_1, i, displayWidth_1, displayHeight_1, onlyFixed);
|
|
1490
|
-
}
|
|
1491
|
-
},
|
|
1492
|
-
// 渲染边框
|
|
1493
|
-
function (onlyFixed) {
|
|
1494
|
-
drawFixedTopRowsBorder(ctx_1, fixedTopRowsCount_1, displayWidth_1, displayHeight_1, onlyFixed);
|
|
215
|
+
ctx.save();
|
|
216
|
+
renderWithFixedLayer(function (onlyFixed) {
|
|
217
|
+
renderers.fixedRow.renderFixedTopRows(onlyFixed);
|
|
218
|
+
}, function (onlyFixed) {
|
|
219
|
+
renderers.border.renderFixedTopBorders(fixedTopRowsCount_1, onlyFixed);
|
|
1495
220
|
});
|
|
1496
|
-
|
|
221
|
+
ctx.restore();
|
|
1497
222
|
}
|
|
1498
|
-
// 3.
|
|
223
|
+
// 3. 绘制固定的底部行(如果有)
|
|
1499
224
|
if (fixedBottomRowsCount_1 > 0) {
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
// 渲染行内容
|
|
1506
|
-
function (onlyFixed) {
|
|
1507
|
-
for (var i = 0; i < fixedBottomRowsCount_1; i++) {
|
|
1508
|
-
var rowIndex = bottomRowsStartIndex_1 + i;
|
|
1509
|
-
drawFixedBottomRow(ctx_1, rowIndex, i, displayWidth_1, displayHeight_1, onlyFixed);
|
|
1510
|
-
}
|
|
1511
|
-
},
|
|
1512
|
-
// 渲染边框
|
|
1513
|
-
function (onlyFixed) {
|
|
1514
|
-
drawFixedBottomRowsBorder(ctx_1, fixedBottomRowsCount_1, displayWidth_1, displayHeight_1, onlyFixed);
|
|
225
|
+
ctx.save();
|
|
226
|
+
renderWithFixedLayer(function (onlyFixed) {
|
|
227
|
+
renderers.fixedRow.renderFixedBottomRows(onlyFixed);
|
|
228
|
+
}, function (onlyFixed) {
|
|
229
|
+
renderers.border.renderFixedBottomBorders(fixedBottomRowsCount_1, onlyFixed);
|
|
1515
230
|
});
|
|
1516
|
-
|
|
231
|
+
ctx.restore();
|
|
1517
232
|
}
|
|
1518
|
-
//
|
|
1519
|
-
|
|
1520
|
-
//
|
|
233
|
+
// 4. 绘制固定列阴影和边界
|
|
234
|
+
renderers.fixedColumn.render();
|
|
235
|
+
// 5. 绘制固定合计行(如果启用)
|
|
1521
236
|
if (summaryFixed && hasSummaryRow) {
|
|
1522
|
-
var
|
|
1523
|
-
var summaryRowY_1 = displayHeight_1 -
|
|
237
|
+
var summaryRowY_1 = displayHeight -
|
|
1524
238
|
(needHorizontalScrollbar ? SCROLLBAR_SIZE : 0) -
|
|
1525
239
|
rowHeight;
|
|
1526
|
-
|
|
1527
|
-
renderWithFixedLayer(
|
|
1528
|
-
|
|
1529
|
-
function (onlyFixed) {
|
|
1530
|
-
|
|
1531
|
-
},
|
|
1532
|
-
// 渲染边框
|
|
1533
|
-
function (onlyFixed) {
|
|
1534
|
-
drawFixedSummaryRowBorder(ctx_1, summaryRowIndex_1, summaryRowY_1, displayWidth_1, displayHeight_1, onlyFixed);
|
|
240
|
+
ctx.save();
|
|
241
|
+
renderWithFixedLayer(function (onlyFixed) {
|
|
242
|
+
renderers.fixedRow.renderFixedSummaryRow(onlyFixed);
|
|
243
|
+
}, function (onlyFixed) {
|
|
244
|
+
renderers.border.renderFixedSummaryBorder(summaryRowY_1, onlyFixed);
|
|
1535
245
|
});
|
|
1536
|
-
|
|
246
|
+
ctx.restore();
|
|
1537
247
|
}
|
|
1538
|
-
// 6.
|
|
1539
|
-
|
|
1540
|
-
//
|
|
1541
|
-
|
|
1542
|
-
// 绘制框选区域
|
|
248
|
+
// 6. 绘制表头(最后绘制,确保始终在最上层)
|
|
249
|
+
renderers.header.render();
|
|
250
|
+
// 7. 绘制滚动条
|
|
251
|
+
renderers.scrollbar.render();
|
|
252
|
+
// 8. 绘制框选区域
|
|
1543
253
|
if (state.cellSelection) {
|
|
1544
|
-
|
|
254
|
+
renderers.selection.render();
|
|
1545
255
|
}
|
|
1546
256
|
// 重置绘制标志
|
|
1547
257
|
isDrawingRef.current = false;
|
|
@@ -1557,11 +267,9 @@ var useTableRender = function (params) {
|
|
|
1557
267
|
if (canvas) {
|
|
1558
268
|
var ctx = canvas.getContext("2d");
|
|
1559
269
|
if (ctx) {
|
|
1560
|
-
// 清空画布,显示错误状态
|
|
1561
270
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1562
271
|
ctx.fillStyle = "#f5f5f5";
|
|
1563
272
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
1564
|
-
// 显示错误提示
|
|
1565
273
|
ctx.fillStyle = "#ff4d4f";
|
|
1566
274
|
ctx.font = "14px Arial";
|
|
1567
275
|
ctx.textAlign = "center";
|
|
@@ -1572,11 +280,11 @@ var useTableRender = function (params) {
|
|
|
1572
280
|
catch (fallbackError) {
|
|
1573
281
|
console.error("CanvasTable: 错误恢复失败:", fallbackError);
|
|
1574
282
|
}
|
|
1575
|
-
// 可以触发错误回调,通知外部组件
|
|
1576
|
-
// props.onRenderError?.(error);
|
|
1577
283
|
}
|
|
1578
284
|
});
|
|
1579
|
-
|
|
285
|
+
/**
|
|
286
|
+
* 节流的绘制函数,使用 requestAnimationFrame
|
|
287
|
+
*/
|
|
1580
288
|
var drawTable = useMemoizedFn(function () {
|
|
1581
289
|
// 如果已经有pending的绘制请求,取消之前的
|
|
1582
290
|
if (rafIdRef.current !== null) {
|