stk-table-vue 1.2.4 → 1.2.6

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.
Files changed (68) hide show
  1. package/README.md +194 -194
  2. package/lib/{Dropdown-D_9cB0rD.js → Dropdown-BAANgg_5.js} +2 -2
  3. package/lib/{StkTable-CVcmlZi9.js → StkTable-CGcxwpdb.js} +22 -6
  4. package/lib/src/StkTable/StkTable.vue.d.ts +18 -4
  5. package/lib/src/StkTable/types/index.d.ts +4 -1
  6. package/lib/src/StkTable/useVirtualScroll.d.ts +6 -1
  7. package/lib/stk-table-vue.js +3 -3
  8. package/lib/style.css +1 -1
  9. package/package.json +93 -93
  10. package/src/StkTable/StkTable.vue +2362 -2338
  11. package/src/StkTable/components/DragHandle.vue +9 -9
  12. package/src/StkTable/components/SortIcon.vue +6 -6
  13. package/src/StkTable/components/TreeNodeCell.vue +19 -19
  14. package/src/StkTable/components/TriangleIcon.vue +3 -3
  15. package/src/StkTable/const.ts +50 -50
  16. package/src/StkTable/custom-cells/ChangeCell/ChangeCell.less +34 -34
  17. package/src/StkTable/custom-cells/ChangeCell/createChangeCell.ts +90 -90
  18. package/src/StkTable/custom-cells/ChangeCell/index.ts +4 -4
  19. package/src/StkTable/custom-cells/CheckboxCell/CheckboxCell.less +14 -14
  20. package/src/StkTable/custom-cells/CheckboxCell/CheckboxCell.vue +56 -56
  21. package/src/StkTable/custom-cells/CheckboxCell/createCheckboxCell.ts +146 -146
  22. package/src/StkTable/custom-cells/CheckboxCell/index.ts +5 -5
  23. package/src/StkTable/custom-cells/FilterCell/Dropdown/index.ts +16 -16
  24. package/src/StkTable/custom-cells/FilterCell/Dropdown/index.vue +186 -186
  25. package/src/StkTable/custom-cells/FilterCell/Filter.less +118 -118
  26. package/src/StkTable/custom-cells/FilterCell/Filter.vue +66 -66
  27. package/src/StkTable/custom-cells/FilterCell/createFilterCell.ts +120 -120
  28. package/src/StkTable/custom-cells/FilterCell/index.ts +6 -6
  29. package/src/StkTable/custom-cells/FilterCell/types.ts +43 -43
  30. package/src/StkTable/custom-cells/NumberCell/createNumberCell.ts +43 -43
  31. package/src/StkTable/custom-cells/NumberCell/index.ts +2 -2
  32. package/src/StkTable/custom-cells/utils/formatNumber.ts +137 -137
  33. package/src/StkTable/features/const.ts +1 -1
  34. package/src/StkTable/features/index.ts +1 -1
  35. package/src/StkTable/features/useAreaSelection.ts +1240 -1239
  36. package/src/StkTable/index.ts +20 -20
  37. package/src/StkTable/mergeCellsCache.ts +69 -69
  38. package/src/StkTable/registerFeature.ts +40 -40
  39. package/src/StkTable/style.less +791 -791
  40. package/src/StkTable/types/highlightDimOptions.ts +28 -28
  41. package/src/StkTable/types/index.ts +503 -500
  42. package/src/StkTable/useAutoResize.ts +87 -87
  43. package/src/StkTable/useColResize.ts +202 -202
  44. package/src/StkTable/useFixedCol.ts +168 -168
  45. package/src/StkTable/useFixedStyle.ts +75 -75
  46. package/src/StkTable/useGetFixedColPosition.ts +63 -63
  47. package/src/StkTable/useHighlight.ts +270 -270
  48. package/src/StkTable/useIndexResolver.ts +29 -29
  49. package/src/StkTable/useKeyboardArrowScroll.ts +114 -114
  50. package/src/StkTable/useMaxRowSpan.ts +60 -60
  51. package/src/StkTable/useMergeCells.ts +541 -541
  52. package/src/StkTable/useRowExpand.ts +81 -81
  53. package/src/StkTable/useScrollRowByRow.ts +96 -96
  54. package/src/StkTable/useScrollbar.ts +177 -177
  55. package/src/StkTable/useSorter.ts +267 -267
  56. package/src/StkTable/useTableColumns.ts +142 -142
  57. package/src/StkTable/useThDrag.ts +94 -94
  58. package/src/StkTable/useTrDrag.ts +100 -100
  59. package/src/StkTable/useTree.ts +253 -253
  60. package/src/StkTable/useVirtualScroll.ts +1032 -1025
  61. package/src/StkTable/useWheeling.ts +23 -23
  62. package/src/StkTable/utils/constRefUtils.ts +29 -29
  63. package/src/StkTable/utils/index.ts +331 -331
  64. package/src/StkTable/utils/rowHeightFenwickTree.ts +74 -74
  65. package/src/StkTable/utils/useTriggerRef.ts +33 -33
  66. package/src/VirtualTree.vue +622 -622
  67. package/src/VirtualTreeSelect.vue +367 -367
  68. package/src/vite-env.d.ts +10 -10
@@ -1,331 +1,331 @@
1
- import { CELL_KEY_SEPARATE, DEFAULT_SORT_CONFIG } from '../const';
2
- import { Order, SortConfig, SortOption, SortState, StkTableColumn, UniqKey } from '../types';
3
-
4
- /** 是否空值 */
5
- export function isEmptyValue(val: any, isNumber?: boolean) {
6
- let isEmpty = val === null || val === void 0;
7
- if (isNumber) {
8
- isEmpty = isEmpty || typeof val === 'boolean' || Number.isNaN(+val);
9
- }
10
- return isEmpty;
11
- }
12
-
13
- /**
14
- * 对有序数组插入新数据
15
- *
16
- * 注意:不会改变原数组,返回新数组
17
- * @param sortState
18
- * @param sortState.dataIndex 排序的字段
19
- * @param sortState.order 排序顺序
20
- * @param sortState.sortType 排序方式
21
- * @param newItem 要插入的数据
22
- * @param targetArray 表格数据
23
- * @return targetArray 的浅拷贝
24
- */
25
- export function insertToOrderedArray<T extends object>(
26
- sortState: SortState<T>,
27
- newItem: T,
28
- targetArray: T[],
29
- sortConfig: SortConfig<T> & { customCompare?: (a: T, b: T) => number } = {},
30
- ) {
31
- const { dataIndex, sortField, order } = sortState;
32
- let { sortType } = sortState;
33
- const field = sortField || dataIndex;
34
- if (!sortType) sortType = typeof newItem[field] as 'number' | 'string';
35
- const isNumber = sortType === 'number';
36
- const data = targetArray.slice();
37
-
38
- if (!order || !data.length) {
39
- // 没有排序的情况,插入在最上方
40
- data.unshift(newItem);
41
- return data;
42
- }
43
-
44
- const { emptyToBottom, customCompare, stringLocaleCompare } = { emptyToBottom: false, ...sortConfig };
45
-
46
- const targetVal: any = newItem[field];
47
- if (emptyToBottom && isEmptyValue(targetVal, isNumber)) {
48
- // 空值排在最下方
49
- data.push(newItem);
50
- } else {
51
- const isAsc = order === 'asc';
52
-
53
- const customCompareFn =
54
- customCompare ||
55
- ((a, b) => {
56
- const midVal: any = a[field];
57
- const compareRes = strCompare(midVal, targetVal, isNumber, stringLocaleCompare);
58
- return isAsc ? compareRes : -compareRes;
59
- });
60
- // 二分插入
61
- const sIndex = binarySearch(data, midIndex => customCompareFn(data[midIndex], newItem));
62
- data.splice(sIndex, 0, newItem);
63
- }
64
-
65
- return data;
66
- }
67
-
68
- /**
69
- * 二分查找
70
- * @param searchArray 查找数组
71
- * @param compareCallback 比较函数,返回 -1|0|1
72
- */
73
- export function binarySearch(searchArray: any[], compareCallback: (midIndex: number) => number) {
74
- let sIndex = 0;
75
- let eIndex = searchArray.length - 1;
76
- while (sIndex <= eIndex) {
77
- const midIndex = Math.floor((sIndex + eIndex) / 2);
78
- const compareRes = compareCallback(midIndex);
79
- if (compareRes === 0) {
80
- //midVal == targetVal
81
- sIndex = midIndex;
82
- break;
83
- } else if (compareRes < 0) {
84
- // midVal < targetVal
85
- sIndex = midIndex + 1;
86
- } else {
87
- //midVal > targetVal
88
- eIndex = midIndex - 1;
89
- }
90
- }
91
- return sIndex;
92
- }
93
- /**
94
- * 字符串比较
95
- * @param a
96
- * @param b
97
- * @param type 类型
98
- * @param isNumber 是否是数字类型
99
- * @param localeCompare 是否 使用Array.prototyshpe.localeCompare
100
- * @return {number} <0: a < b, 0: a = b, >0: a > b
101
- */
102
- export function strCompare(a: string, b: string, isNumber: boolean, localeCompare = false): number {
103
- let _a: number | string = a;
104
- let _b: number | string = b;
105
- if (isNumber) {
106
- // 是数字就转数字
107
- _a = +a;
108
- _b = +b;
109
- } else if (localeCompare) {
110
- // 字符串才可以localeCompare
111
- return String(a).localeCompare(b);
112
- }
113
- if (_a > _b) return 1;
114
- else if (_a === _b) return 0;
115
- else return -1;
116
- }
117
-
118
- /**
119
- * 分离出空数据和非空数据成两个数组。NaN视为空数据。
120
- * @param sortOption
121
- * @param targetDataSource
122
- * @param isNumber 是否数字
123
- * @return [值数组,空数组]
124
- */
125
- function separatedData<T extends Record<string, any>>(sortOption: SortOption<T>, targetDataSource: T[], isNumber?: boolean) {
126
- const emptyArr: T[] = [];
127
- const valueArr: T[] = [];
128
- const sortField = sortOption.sortField || sortOption.dataIndex;
129
-
130
- for (let i = 0, len = targetDataSource.length; i < len; i++) {
131
- const row = targetDataSource[i];
132
- const isEmpty = isEmptyValue(row?.[sortField], isNumber); // deal row is null
133
- if (isEmpty) {
134
- emptyArr.push(row);
135
- } else {
136
- valueArr.push(row);
137
- }
138
- }
139
- return [valueArr, emptyArr] as const;
140
- }
141
-
142
- /**
143
- * 表格排序抽离
144
- * 可以在组件外部自己实现表格排序,组件配置remote,使表格不排序。
145
- * 使用者在@sort-change事件中自行更改table props 'dataSource'完成排序。
146
- * TODO: key 唯一值,排序字段相同时,根据唯一值排序。
147
- *
148
- * sortConfig.defaultSort 会在order为null时生效
149
- * @param sortOption 列配置
150
- * @param order 排序方式
151
- * @param dataSource 排序的数组
152
- */
153
- export function tableSort<T extends Record<string, any>>(
154
- sortOption: SortOption<T>,
155
- order: Order,
156
- dataSource: T[],
157
- sortConfig: SortConfig<T> = {},
158
- ): T[] {
159
- if (!dataSource?.length || !sortOption) return dataSource || [];
160
-
161
- sortConfig = { ...DEFAULT_SORT_CONFIG, ...sortConfig };
162
- let targetDataSource = dataSource.slice();
163
- let sortField = sortOption.sortField || sortOption.dataIndex;
164
- const { defaultSort, stringLocaleCompare, emptyToBottom, sortChildren } = sortConfig;
165
-
166
- if (!order && defaultSort) {
167
- // 默认排序
168
- order = defaultSort.order;
169
- sortField = defaultSort.dataIndex;
170
- }
171
-
172
- if (typeof sortOption.sorter === 'function') {
173
- const customSorterData = sortOption.sorter(targetDataSource, { order, column: sortOption });
174
- if (customSorterData) targetDataSource = customSorterData;
175
-
176
- // 如果开启了子节点排序且使用了自定义排序器,递归排序children
177
- if (sortChildren) {
178
- targetDataSource.forEach(item => {
179
- if (!item.children?.length) return;
180
- (item as any).children = tableSort(sortOption, order, item.children, sortConfig);
181
- });
182
- }
183
- } else if (order) {
184
- let { sortType } = sortOption;
185
- if (!sortType) sortType = typeof dataSource[0][sortField] as 'number' | 'string';
186
-
187
- const isNumber = sortType === 'number';
188
- const [valueArr, emptyArr] = separatedData(sortOption, targetDataSource, isNumber);
189
-
190
- if (order === 'asc') {
191
- valueArr.sort((a, b) => strCompare(a[sortField], b[sortField], isNumber, stringLocaleCompare));
192
- } else {
193
- valueArr.sort((a, b) => strCompare(b[sortField], a[sortField], isNumber, stringLocaleCompare));
194
- }
195
-
196
- targetDataSource = order === 'desc' || emptyToBottom ? valueArr.concat(emptyArr) : emptyArr.concat(valueArr);
197
-
198
- // 递归排序子节点
199
- if (sortChildren) {
200
- targetDataSource.forEach(item => {
201
- if (!item.children?.length) return;
202
- (item as any).children = tableSort(sortOption, order, item.children, sortConfig);
203
- });
204
- }
205
- }
206
- return targetDataSource;
207
- }
208
-
209
- /** 多级表头深度 从0开始为一级*/
210
- export function howDeepTheHeader(arr: StkTableColumn<any>[], level = 0) {
211
- const levels = [level];
212
- arr.forEach(item => {
213
- if (item.children?.length) {
214
- levels.push(howDeepTheHeader(item.children, level + 1));
215
- }
216
- });
217
- return Math.max(...levels);
218
- }
219
-
220
- /** number width +px */
221
- export function transformWidthToStr(width?: string | number) {
222
- if (width === void 0) return;
223
- const numberWidth = Number(width);
224
- return width + (!Number.isNaN(numberWidth) ? 'px' : '');
225
- }
226
-
227
- export function getBrowsersVersion(browserName: string) {
228
- try {
229
- const reg = new RegExp(`${browserName}/\\d+`, 'i');
230
- const userAgent = navigator.userAgent.match(reg);
231
- if (userAgent) {
232
- return +userAgent[0].split('/')[1];
233
- }
234
- } catch (e) {
235
- console.error('Cannot get version', e);
236
- }
237
- return 100;
238
- }
239
-
240
- export function pureCellKeyGen(rowKey: UniqKey, colKey: UniqKey) {
241
- return rowKey + CELL_KEY_SEPARATE + colKey;
242
- }
243
-
244
- export function getClosestTr(target: HTMLElement) {
245
- return target?.closest('tr');
246
- }
247
-
248
- export function getClosestTh(target: HTMLElement) {
249
- return target?.closest('th');
250
- }
251
-
252
- export function getClosestTd(target: HTMLElement) {
253
- return target?.closest('td');
254
- }
255
-
256
- export function getClosestTrIndex(target: HTMLElement) {
257
- const tr = getClosestTr(target);
258
- if (!tr) return -1;
259
- return Number(tr.dataset.rowI);
260
- }
261
-
262
- export function getClosestColKey(target: HTMLElement) {
263
- return getClosestTd(target as HTMLElement)?.dataset.colKey;
264
- }
265
-
266
- /**
267
- * 改进的节流函数,确保最后一个调用不会被丢弃
268
- * @param fn 要执行的函数
269
- * @param delay 延迟时间(毫秒)
270
- * @returns 节流处理后的函数
271
- */
272
- export function throttle<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
273
- let timer: number;
274
- let lastArgs: Parameters<T> | null = null;
275
-
276
- const callFn = () => {
277
- if (lastArgs) {
278
- fn(...lastArgs);
279
- lastArgs = null;
280
- }
281
- };
282
-
283
- return function (...args: Parameters<T>) {
284
- lastArgs = args;
285
- if (!timer) {
286
- callFn();
287
- timer = self.setTimeout(() => {
288
- callFn();
289
- timer = 0;
290
- }, delay);
291
- }
292
- };
293
- }
294
-
295
- /**
296
- * Creates a requestAnimationFrame-based throttled function for smooth scrolling performance.
297
- * Multiple calls within a single frame are coalesced - only the last call is executed.
298
- * @param fn The function to throttle
299
- * @returns A throttled function that executes on the next animation frame
300
- */
301
- export function rafThrottle<T extends (...args: any[]) => any>(fn: T): (...args: Parameters<T>) => void {
302
- let rafId: number | null = null;
303
- let lastArgs: Parameters<T> | null = null;
304
-
305
- const callFn = () => {
306
- if (lastArgs) {
307
- fn(...lastArgs);
308
- lastArgs = null;
309
- }
310
- };
311
-
312
- return function (...args: Parameters<T>) {
313
- lastArgs = args;
314
- if (rafId === null) {
315
- rafId = requestAnimationFrame(() => {
316
- callFn();
317
- rafId = null;
318
- });
319
- }
320
- };
321
- }
322
- export function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
323
- let timer: ReturnType<typeof setTimeout> | null = null;
324
- return function (...args: Parameters<T>) {
325
- if (timer) clearTimeout(timer);
326
- timer = setTimeout(() => {
327
- fn(...args);
328
- timer = null;
329
- }, delay);
330
- };
331
- }
1
+ import { CELL_KEY_SEPARATE, DEFAULT_SORT_CONFIG } from '../const';
2
+ import { Order, SortConfig, SortOption, SortState, StkTableColumn, UniqKey } from '../types';
3
+
4
+ /** 是否空值 */
5
+ export function isEmptyValue(val: any, isNumber?: boolean) {
6
+ let isEmpty = val === null || val === void 0;
7
+ if (isNumber) {
8
+ isEmpty = isEmpty || typeof val === 'boolean' || Number.isNaN(+val);
9
+ }
10
+ return isEmpty;
11
+ }
12
+
13
+ /**
14
+ * 对有序数组插入新数据
15
+ *
16
+ * 注意:不会改变原数组,返回新数组
17
+ * @param sortState
18
+ * @param sortState.dataIndex 排序的字段
19
+ * @param sortState.order 排序顺序
20
+ * @param sortState.sortType 排序方式
21
+ * @param newItem 要插入的数据
22
+ * @param targetArray 表格数据
23
+ * @return targetArray 的浅拷贝
24
+ */
25
+ export function insertToOrderedArray<T extends object>(
26
+ sortState: SortState<T>,
27
+ newItem: T,
28
+ targetArray: T[],
29
+ sortConfig: SortConfig<T> & { customCompare?: (a: T, b: T) => number } = {},
30
+ ) {
31
+ const { dataIndex, sortField, order } = sortState;
32
+ let { sortType } = sortState;
33
+ const field = sortField || dataIndex;
34
+ if (!sortType) sortType = typeof newItem[field] as 'number' | 'string';
35
+ const isNumber = sortType === 'number';
36
+ const data = targetArray.slice();
37
+
38
+ if (!order || !data.length) {
39
+ // 没有排序的情况,插入在最上方
40
+ data.unshift(newItem);
41
+ return data;
42
+ }
43
+
44
+ const { emptyToBottom, customCompare, stringLocaleCompare } = { emptyToBottom: false, ...sortConfig };
45
+
46
+ const targetVal: any = newItem[field];
47
+ if (emptyToBottom && isEmptyValue(targetVal, isNumber)) {
48
+ // 空值排在最下方
49
+ data.push(newItem);
50
+ } else {
51
+ const isAsc = order === 'asc';
52
+
53
+ const customCompareFn =
54
+ customCompare ||
55
+ ((a, b) => {
56
+ const midVal: any = a[field];
57
+ const compareRes = strCompare(midVal, targetVal, isNumber, stringLocaleCompare);
58
+ return isAsc ? compareRes : -compareRes;
59
+ });
60
+ // 二分插入
61
+ const sIndex = binarySearch(data, midIndex => customCompareFn(data[midIndex], newItem));
62
+ data.splice(sIndex, 0, newItem);
63
+ }
64
+
65
+ return data;
66
+ }
67
+
68
+ /**
69
+ * 二分查找
70
+ * @param searchArray 查找数组
71
+ * @param compareCallback 比较函数,返回 -1|0|1
72
+ */
73
+ export function binarySearch(searchArray: any[], compareCallback: (midIndex: number) => number) {
74
+ let sIndex = 0;
75
+ let eIndex = searchArray.length - 1;
76
+ while (sIndex <= eIndex) {
77
+ const midIndex = Math.floor((sIndex + eIndex) / 2);
78
+ const compareRes = compareCallback(midIndex);
79
+ if (compareRes === 0) {
80
+ //midVal == targetVal
81
+ sIndex = midIndex;
82
+ break;
83
+ } else if (compareRes < 0) {
84
+ // midVal < targetVal
85
+ sIndex = midIndex + 1;
86
+ } else {
87
+ //midVal > targetVal
88
+ eIndex = midIndex - 1;
89
+ }
90
+ }
91
+ return sIndex;
92
+ }
93
+ /**
94
+ * 字符串比较
95
+ * @param a
96
+ * @param b
97
+ * @param type 类型
98
+ * @param isNumber 是否是数字类型
99
+ * @param localeCompare 是否 使用Array.prototyshpe.localeCompare
100
+ * @return {number} <0: a < b, 0: a = b, >0: a > b
101
+ */
102
+ export function strCompare(a: string, b: string, isNumber: boolean, localeCompare = false): number {
103
+ let _a: number | string = a;
104
+ let _b: number | string = b;
105
+ if (isNumber) {
106
+ // 是数字就转数字
107
+ _a = +a;
108
+ _b = +b;
109
+ } else if (localeCompare) {
110
+ // 字符串才可以localeCompare
111
+ return String(a).localeCompare(b);
112
+ }
113
+ if (_a > _b) return 1;
114
+ else if (_a === _b) return 0;
115
+ else return -1;
116
+ }
117
+
118
+ /**
119
+ * 分离出空数据和非空数据成两个数组。NaN视为空数据。
120
+ * @param sortOption
121
+ * @param targetDataSource
122
+ * @param isNumber 是否数字
123
+ * @return [值数组,空数组]
124
+ */
125
+ function separatedData<T extends Record<string, any>>(sortOption: SortOption<T>, targetDataSource: T[], isNumber?: boolean) {
126
+ const emptyArr: T[] = [];
127
+ const valueArr: T[] = [];
128
+ const sortField = sortOption.sortField || sortOption.dataIndex;
129
+
130
+ for (let i = 0, len = targetDataSource.length; i < len; i++) {
131
+ const row = targetDataSource[i];
132
+ const isEmpty = isEmptyValue(row?.[sortField], isNumber); // deal row is null
133
+ if (isEmpty) {
134
+ emptyArr.push(row);
135
+ } else {
136
+ valueArr.push(row);
137
+ }
138
+ }
139
+ return [valueArr, emptyArr] as const;
140
+ }
141
+
142
+ /**
143
+ * 表格排序抽离
144
+ * 可以在组件外部自己实现表格排序,组件配置remote,使表格不排序。
145
+ * 使用者在@sort-change事件中自行更改table props 'dataSource'完成排序。
146
+ * TODO: key 唯一值,排序字段相同时,根据唯一值排序。
147
+ *
148
+ * sortConfig.defaultSort 会在order为null时生效
149
+ * @param sortOption 列配置
150
+ * @param order 排序方式
151
+ * @param dataSource 排序的数组
152
+ */
153
+ export function tableSort<T extends Record<string, any>>(
154
+ sortOption: SortOption<T>,
155
+ order: Order,
156
+ dataSource: T[],
157
+ sortConfig: SortConfig<T> = {},
158
+ ): T[] {
159
+ if (!dataSource?.length || !sortOption) return dataSource || [];
160
+
161
+ sortConfig = { ...DEFAULT_SORT_CONFIG, ...sortConfig };
162
+ let targetDataSource = dataSource.slice();
163
+ let sortField = sortOption.sortField || sortOption.dataIndex;
164
+ const { defaultSort, stringLocaleCompare, emptyToBottom, sortChildren } = sortConfig;
165
+
166
+ if (!order && defaultSort) {
167
+ // 默认排序
168
+ order = defaultSort.order;
169
+ sortField = defaultSort.dataIndex;
170
+ }
171
+
172
+ if (typeof sortOption.sorter === 'function') {
173
+ const customSorterData = sortOption.sorter(targetDataSource, { order, column: sortOption });
174
+ if (customSorterData) targetDataSource = customSorterData;
175
+
176
+ // 如果开启了子节点排序且使用了自定义排序器,递归排序children
177
+ if (sortChildren) {
178
+ targetDataSource.forEach(item => {
179
+ if (!item.children?.length) return;
180
+ (item as any).children = tableSort(sortOption, order, item.children, sortConfig);
181
+ });
182
+ }
183
+ } else if (order) {
184
+ let { sortType } = sortOption;
185
+ if (!sortType) sortType = typeof dataSource[0][sortField] as 'number' | 'string';
186
+
187
+ const isNumber = sortType === 'number';
188
+ const [valueArr, emptyArr] = separatedData(sortOption, targetDataSource, isNumber);
189
+
190
+ if (order === 'asc') {
191
+ valueArr.sort((a, b) => strCompare(a[sortField], b[sortField], isNumber, stringLocaleCompare));
192
+ } else {
193
+ valueArr.sort((a, b) => strCompare(b[sortField], a[sortField], isNumber, stringLocaleCompare));
194
+ }
195
+
196
+ targetDataSource = order === 'desc' || emptyToBottom ? valueArr.concat(emptyArr) : emptyArr.concat(valueArr);
197
+
198
+ // 递归排序子节点
199
+ if (sortChildren) {
200
+ targetDataSource.forEach(item => {
201
+ if (!item.children?.length) return;
202
+ (item as any).children = tableSort(sortOption, order, item.children, sortConfig);
203
+ });
204
+ }
205
+ }
206
+ return targetDataSource;
207
+ }
208
+
209
+ /** 多级表头深度 从0开始为一级*/
210
+ export function howDeepTheHeader(arr: StkTableColumn<any>[], level = 0) {
211
+ const levels = [level];
212
+ arr.forEach(item => {
213
+ if (item.children?.length) {
214
+ levels.push(howDeepTheHeader(item.children, level + 1));
215
+ }
216
+ });
217
+ return Math.max(...levels);
218
+ }
219
+
220
+ /** number width +px */
221
+ export function transformWidthToStr(width?: string | number) {
222
+ if (width === void 0) return;
223
+ const numberWidth = Number(width);
224
+ return width + (!Number.isNaN(numberWidth) ? 'px' : '');
225
+ }
226
+
227
+ export function getBrowsersVersion(browserName: string) {
228
+ try {
229
+ const reg = new RegExp(`${browserName}/\\d+`, 'i');
230
+ const userAgent = navigator.userAgent.match(reg);
231
+ if (userAgent) {
232
+ return +userAgent[0].split('/')[1];
233
+ }
234
+ } catch (e) {
235
+ console.error('Cannot get version', e);
236
+ }
237
+ return 100;
238
+ }
239
+
240
+ export function pureCellKeyGen(rowKey: UniqKey, colKey: UniqKey) {
241
+ return rowKey + CELL_KEY_SEPARATE + colKey;
242
+ }
243
+
244
+ export function getClosestTr(target: HTMLElement) {
245
+ return target?.closest('tr');
246
+ }
247
+
248
+ export function getClosestTh(target: HTMLElement) {
249
+ return target?.closest('th');
250
+ }
251
+
252
+ export function getClosestTd(target: HTMLElement) {
253
+ return target?.closest('td');
254
+ }
255
+
256
+ export function getClosestTrIndex(target: HTMLElement) {
257
+ const tr = getClosestTr(target);
258
+ if (!tr) return -1;
259
+ return Number(tr.dataset.rowI);
260
+ }
261
+
262
+ export function getClosestColKey(target: HTMLElement) {
263
+ return getClosestTd(target as HTMLElement)?.dataset.colKey;
264
+ }
265
+
266
+ /**
267
+ * 改进的节流函数,确保最后一个调用不会被丢弃
268
+ * @param fn 要执行的函数
269
+ * @param delay 延迟时间(毫秒)
270
+ * @returns 节流处理后的函数
271
+ */
272
+ export function throttle<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
273
+ let timer: number;
274
+ let lastArgs: Parameters<T> | null = null;
275
+
276
+ const callFn = () => {
277
+ if (lastArgs) {
278
+ fn(...lastArgs);
279
+ lastArgs = null;
280
+ }
281
+ };
282
+
283
+ return function (...args: Parameters<T>) {
284
+ lastArgs = args;
285
+ if (!timer) {
286
+ callFn();
287
+ timer = self.setTimeout(() => {
288
+ callFn();
289
+ timer = 0;
290
+ }, delay);
291
+ }
292
+ };
293
+ }
294
+
295
+ /**
296
+ * Creates a requestAnimationFrame-based throttled function for smooth scrolling performance.
297
+ * Multiple calls within a single frame are coalesced - only the last call is executed.
298
+ * @param fn The function to throttle
299
+ * @returns A throttled function that executes on the next animation frame
300
+ */
301
+ export function rafThrottle<T extends (...args: any[]) => any>(fn: T): (...args: Parameters<T>) => void {
302
+ let rafId: number | null = null;
303
+ let lastArgs: Parameters<T> | null = null;
304
+
305
+ const callFn = () => {
306
+ if (lastArgs) {
307
+ fn(...lastArgs);
308
+ lastArgs = null;
309
+ }
310
+ };
311
+
312
+ return function (...args: Parameters<T>) {
313
+ lastArgs = args;
314
+ if (rafId === null) {
315
+ rafId = requestAnimationFrame(() => {
316
+ callFn();
317
+ rafId = null;
318
+ });
319
+ }
320
+ };
321
+ }
322
+ export function debounce<T extends (...args: any[]) => any>(fn: T, delay: number): (...args: Parameters<T>) => void {
323
+ let timer: ReturnType<typeof setTimeout> | null = null;
324
+ return function (...args: Parameters<T>) {
325
+ if (timer) clearTimeout(timer);
326
+ timer = setTimeout(() => {
327
+ fn(...args);
328
+ timer = null;
329
+ }, delay);
330
+ };
331
+ }