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.
- package/README.md +194 -194
- package/lib/{Dropdown-D_9cB0rD.js → Dropdown-BAANgg_5.js} +2 -2
- package/lib/{StkTable-CVcmlZi9.js → StkTable-CGcxwpdb.js} +22 -6
- package/lib/src/StkTable/StkTable.vue.d.ts +18 -4
- package/lib/src/StkTable/types/index.d.ts +4 -1
- package/lib/src/StkTable/useVirtualScroll.d.ts +6 -1
- package/lib/stk-table-vue.js +3 -3
- package/lib/style.css +1 -1
- package/package.json +93 -93
- package/src/StkTable/StkTable.vue +2362 -2338
- package/src/StkTable/components/DragHandle.vue +9 -9
- package/src/StkTable/components/SortIcon.vue +6 -6
- package/src/StkTable/components/TreeNodeCell.vue +19 -19
- package/src/StkTable/components/TriangleIcon.vue +3 -3
- package/src/StkTable/const.ts +50 -50
- package/src/StkTable/custom-cells/ChangeCell/ChangeCell.less +34 -34
- package/src/StkTable/custom-cells/ChangeCell/createChangeCell.ts +90 -90
- package/src/StkTable/custom-cells/ChangeCell/index.ts +4 -4
- package/src/StkTable/custom-cells/CheckboxCell/CheckboxCell.less +14 -14
- package/src/StkTable/custom-cells/CheckboxCell/CheckboxCell.vue +56 -56
- package/src/StkTable/custom-cells/CheckboxCell/createCheckboxCell.ts +146 -146
- package/src/StkTable/custom-cells/CheckboxCell/index.ts +5 -5
- package/src/StkTable/custom-cells/FilterCell/Dropdown/index.ts +16 -16
- package/src/StkTable/custom-cells/FilterCell/Dropdown/index.vue +186 -186
- package/src/StkTable/custom-cells/FilterCell/Filter.less +118 -118
- package/src/StkTable/custom-cells/FilterCell/Filter.vue +66 -66
- package/src/StkTable/custom-cells/FilterCell/createFilterCell.ts +120 -120
- package/src/StkTable/custom-cells/FilterCell/index.ts +6 -6
- package/src/StkTable/custom-cells/FilterCell/types.ts +43 -43
- package/src/StkTable/custom-cells/NumberCell/createNumberCell.ts +43 -43
- package/src/StkTable/custom-cells/NumberCell/index.ts +2 -2
- package/src/StkTable/custom-cells/utils/formatNumber.ts +137 -137
- package/src/StkTable/features/const.ts +1 -1
- package/src/StkTable/features/index.ts +1 -1
- package/src/StkTable/features/useAreaSelection.ts +1240 -1239
- package/src/StkTable/index.ts +20 -20
- package/src/StkTable/mergeCellsCache.ts +69 -69
- package/src/StkTable/registerFeature.ts +40 -40
- package/src/StkTable/style.less +791 -791
- package/src/StkTable/types/highlightDimOptions.ts +28 -28
- package/src/StkTable/types/index.ts +503 -500
- package/src/StkTable/useAutoResize.ts +87 -87
- package/src/StkTable/useColResize.ts +202 -202
- package/src/StkTable/useFixedCol.ts +168 -168
- package/src/StkTable/useFixedStyle.ts +75 -75
- package/src/StkTable/useGetFixedColPosition.ts +63 -63
- package/src/StkTable/useHighlight.ts +270 -270
- package/src/StkTable/useIndexResolver.ts +29 -29
- package/src/StkTable/useKeyboardArrowScroll.ts +114 -114
- package/src/StkTable/useMaxRowSpan.ts +60 -60
- package/src/StkTable/useMergeCells.ts +541 -541
- package/src/StkTable/useRowExpand.ts +81 -81
- package/src/StkTable/useScrollRowByRow.ts +96 -96
- package/src/StkTable/useScrollbar.ts +177 -177
- package/src/StkTable/useSorter.ts +267 -267
- package/src/StkTable/useTableColumns.ts +142 -142
- package/src/StkTable/useThDrag.ts +94 -94
- package/src/StkTable/useTrDrag.ts +100 -100
- package/src/StkTable/useTree.ts +253 -253
- package/src/StkTable/useVirtualScroll.ts +1032 -1025
- package/src/StkTable/useWheeling.ts +23 -23
- package/src/StkTable/utils/constRefUtils.ts +29 -29
- package/src/StkTable/utils/index.ts +331 -331
- package/src/StkTable/utils/rowHeightFenwickTree.ts +74 -74
- package/src/StkTable/utils/useTriggerRef.ts +33 -33
- package/src/VirtualTree.vue +622 -622
- package/src/VirtualTreeSelect.vue +367 -367
- 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
|
+
}
|