@zcrkey/js-utils 0.0.4 → 0.0.5
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/.dumi/global.less +1396 -0
- package/.dumirc.ts +36 -0
- package/.fatherrc.ts +14 -0
- package/.husky/commit-msg +4 -0
- package/.husky/pre-commit +4 -0
- package/.prettierignore +2 -0
- package/.stylelintrc +10 -0
- package/README.md +60 -56
- package/dist/index.d.ts +5 -1
- package/dist/index.js +2 -0
- package/dist/index.umd.js +1 -1
- package/dist/objUtil.d.ts +12 -0
- package/dist/objUtil.js +28 -0
- package/dist/treeUtil.d.ts +48 -0
- package/dist/treeUtil.js +185 -0
- package/dist/util.d.ts +41 -6
- package/dist/util.js +117 -6
- package/docs/api/eventCenter/index.md +34 -0
- package/docs/api/index.md +5 -0
- package/docs/api/storage/index.md +9 -0
- package/docs/api/storage/local.tsx +91 -0
- package/docs/api/storage/session.tsx +85 -0
- package/docs/api/treeUtil/index.md +5 -0
- package/docs/api/treeUtil/index.tsx +266 -0
- package/docs/api/util/index.md +6 -0
- package/docs/api/util/index.tsx +405 -0
- package/docs/api/util/is.tsx +196 -0
- package/docs/guide.md +24 -0
- package/package.json +3 -5
- package/src/eventCenter.ts +112 -0
- package/src/index.ts +8 -0
- package/src/objUtil.ts +20 -0
- package/src/storage.ts +101 -0
- package/src/treeUtil.ts +164 -0
- package/src/util.ts +656 -0
- package/tsconfig.json +18 -0
package/src/util.ts
ADDED
|
@@ -0,0 +1,656 @@
|
|
|
1
|
+
import { cloneDeep as _cloneDeep } from 'lodash';
|
|
2
|
+
import Qs from 'qs';
|
|
3
|
+
|
|
4
|
+
export default class CrUtil {
|
|
5
|
+
/**
|
|
6
|
+
* 判断是否为数组
|
|
7
|
+
* @param value
|
|
8
|
+
* @returns boolean
|
|
9
|
+
*/
|
|
10
|
+
static isArray<T = any>(value: any): value is T[] {
|
|
11
|
+
return Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 判断是否为对象
|
|
16
|
+
* @param value
|
|
17
|
+
* @returns boolean
|
|
18
|
+
*/
|
|
19
|
+
static isObject<T = any>(
|
|
20
|
+
value: T,
|
|
21
|
+
): value is T extends object ? (T extends any[] ? never : T) : never {
|
|
22
|
+
return (
|
|
23
|
+
typeof value === 'object' && value !== null && !CrUtil.isArray(value)
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 判断是否为空对象
|
|
29
|
+
* @param value
|
|
30
|
+
* @returns boolean
|
|
31
|
+
*/
|
|
32
|
+
static isEmptyObject(value: object): value is Record<string | number, never> {
|
|
33
|
+
return Object.keys(value).length === 0 && value.constructor === Object;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* 判断是否对象的属性是否全部为空
|
|
38
|
+
* @param value
|
|
39
|
+
* @returns boolean
|
|
40
|
+
*/
|
|
41
|
+
static isObjectPropertiesAllEmpty(
|
|
42
|
+
value: Record<string | number, any>,
|
|
43
|
+
): boolean {
|
|
44
|
+
return Object.keys(value).every((key) => {
|
|
45
|
+
const _value = value[key];
|
|
46
|
+
return (
|
|
47
|
+
_value === undefined ||
|
|
48
|
+
_value === null ||
|
|
49
|
+
_value === '' ||
|
|
50
|
+
(_value === 0 && typeof _value === 'number') ||
|
|
51
|
+
isNaN(_value)
|
|
52
|
+
);
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 判断是否为日期
|
|
58
|
+
* @param value
|
|
59
|
+
* @returns boolean
|
|
60
|
+
*/
|
|
61
|
+
static isDate(value: unknown) {
|
|
62
|
+
return Object.prototype.toString.call(value) === '[object Date]';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 判断是否为字符串
|
|
67
|
+
* @param value
|
|
68
|
+
* @returns boolean
|
|
69
|
+
*/
|
|
70
|
+
static isString(value: unknown): value is string {
|
|
71
|
+
return Object.prototype.toString.call(value) === '[object String]';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* 判断是否为数字
|
|
76
|
+
* @param value
|
|
77
|
+
* @returns boolean
|
|
78
|
+
*/
|
|
79
|
+
static isNumber(value: unknown): value is number {
|
|
80
|
+
return typeof value === 'number' && !isNaN(value);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 判断是否为文件 File
|
|
85
|
+
* @param value
|
|
86
|
+
* @returns boolean
|
|
87
|
+
*/
|
|
88
|
+
static isFile(value: unknown): value is File {
|
|
89
|
+
return Object.prototype.toString.call(value) === '[object File]';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* 判断是否为 Boolean
|
|
94
|
+
* @param value
|
|
95
|
+
* @returns boolean
|
|
96
|
+
*/
|
|
97
|
+
static isBoolean(value: unknown): value is boolean {
|
|
98
|
+
return Object.prototype.toString.call(value) === '[object Boolean]';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 判断是否为 Function
|
|
103
|
+
* @param value
|
|
104
|
+
* @returns boolean
|
|
105
|
+
*/
|
|
106
|
+
static isFunction(value: unknown): value is (...args: any[]) => any {
|
|
107
|
+
return typeof value === 'function';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 去掉字符串前后所有空格
|
|
112
|
+
* @param str
|
|
113
|
+
* @returns string
|
|
114
|
+
*/
|
|
115
|
+
static trim(str: string) {
|
|
116
|
+
return (str + '').replace(/(^[\s\n\t]+|[\s\n\t]+$)/g, '');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* 获取数组为几维数组
|
|
121
|
+
* @param arr
|
|
122
|
+
* @returns number
|
|
123
|
+
*/
|
|
124
|
+
static getArrayDimension(arr: any[]): number {
|
|
125
|
+
// 如果不是一个数组,返回 0
|
|
126
|
+
if (!CrUtil.isArray(arr)) return 0;
|
|
127
|
+
|
|
128
|
+
// 找到数组中的最大维度
|
|
129
|
+
let maxDimension = 0;
|
|
130
|
+
for (let i = 0; i < arr.length; i++) {
|
|
131
|
+
const dimension = CrUtil.getArrayDimension(arr[i]);
|
|
132
|
+
maxDimension = Math.max(maxDimension, dimension);
|
|
133
|
+
}
|
|
134
|
+
// 返回最大维度加1,代表当前层数
|
|
135
|
+
return maxDimension + 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 深拷贝
|
|
140
|
+
* @param value
|
|
141
|
+
* @returns
|
|
142
|
+
*/
|
|
143
|
+
static cloneDeep<T>(value: T): T {
|
|
144
|
+
return _cloneDeep(value);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* 深拷贝
|
|
149
|
+
* @param target
|
|
150
|
+
* @deprecated 即将移除,使用 {@link CrUtil.cloneDeep} 替代
|
|
151
|
+
* @returns
|
|
152
|
+
*/
|
|
153
|
+
static deepCopy<T = any>(target: T): T {
|
|
154
|
+
let _target: any;
|
|
155
|
+
if (CrUtil.isArray(target)) {
|
|
156
|
+
_target = [];
|
|
157
|
+
for (let i = 0, len = target.length; i < len; i++) {
|
|
158
|
+
if (CrUtil.isArray(target[i]) || CrUtil.isObject(target[i])) {
|
|
159
|
+
_target.push(CrUtil.deepCopy(target[i]));
|
|
160
|
+
} else {
|
|
161
|
+
_target.push(target[i]);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
} else if (CrUtil.isObject(target)) {
|
|
165
|
+
_target = {};
|
|
166
|
+
for (let i in target) {
|
|
167
|
+
if (CrUtil.isArray(target[i]) || CrUtil.isObject(target[i])) {
|
|
168
|
+
_target[i] = CrUtil.deepCopy(target[i]);
|
|
169
|
+
} else {
|
|
170
|
+
_target[i] = target[i];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
_target = target;
|
|
175
|
+
}
|
|
176
|
+
return _target;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 列表数据转树型数据
|
|
181
|
+
* @param listData
|
|
182
|
+
* @param settings
|
|
183
|
+
* @param settings.idField 默认值 id
|
|
184
|
+
* @param settings.pidField 默认值 parentId
|
|
185
|
+
* @param settings.childrenField 默认值 children
|
|
186
|
+
* @deprecated 即将移除,使用 {@link CrObjUtil.listToTree} 替代
|
|
187
|
+
* @returns
|
|
188
|
+
*/
|
|
189
|
+
static listToTreeData(
|
|
190
|
+
listData: any[],
|
|
191
|
+
settings?: {
|
|
192
|
+
idField?: string;
|
|
193
|
+
pidField?: string;
|
|
194
|
+
childrenField?: string;
|
|
195
|
+
isDeepCopy?: boolean;
|
|
196
|
+
getData?: (item: any) => any;
|
|
197
|
+
},
|
|
198
|
+
) {
|
|
199
|
+
const options = Object.assign(
|
|
200
|
+
{
|
|
201
|
+
idField: 'id',
|
|
202
|
+
pidField: 'parentId',
|
|
203
|
+
childrenField: 'children',
|
|
204
|
+
isDeepCopy: false,
|
|
205
|
+
getData: (item: any) => {
|
|
206
|
+
return item;
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
settings,
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
const { idField, pidField, childrenField, isDeepCopy, getData } = options;
|
|
213
|
+
|
|
214
|
+
if (isDeepCopy) {
|
|
215
|
+
listData = CrUtil.deepCopy(listData);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// 构建索引
|
|
219
|
+
const pidMap = new Map<any, any[]>();
|
|
220
|
+
const idMap = new Map<any, any>();
|
|
221
|
+
for (const item of listData) {
|
|
222
|
+
const pid = item[pidField];
|
|
223
|
+
const id = item[idField];
|
|
224
|
+
if (!pidMap.has(pid)) {
|
|
225
|
+
pidMap.set(pid, []);
|
|
226
|
+
}
|
|
227
|
+
pidMap.get(pid)!.push(item);
|
|
228
|
+
idMap.set(id, item);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// 递归构建树 + 字段过滤
|
|
232
|
+
const buildTree = (pid: any): any[] => {
|
|
233
|
+
const children = pidMap.get(pid) || [];
|
|
234
|
+
return children.map((item) => {
|
|
235
|
+
const rawChildren = buildTree(item[idField]);
|
|
236
|
+
const node = {
|
|
237
|
+
...getData(item),
|
|
238
|
+
};
|
|
239
|
+
if (rawChildren.length > 0) {
|
|
240
|
+
node[childrenField] = rawChildren;
|
|
241
|
+
}
|
|
242
|
+
return node;
|
|
243
|
+
});
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
// 收集所有根节点 pid,去重
|
|
247
|
+
const rootPids = new Set<any>();
|
|
248
|
+
for (const pid of pidMap.keys()) {
|
|
249
|
+
if (pid === undefined || pid === null || pid === 0 || !idMap.has(pid)) {
|
|
250
|
+
rootPids.add(pid);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// 构建最终树
|
|
255
|
+
const tree: any[] = [];
|
|
256
|
+
for (const pid of rootPids) {
|
|
257
|
+
tree.push(...buildTree(pid));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// 只构建根节点(pid 为 null / 0 / undefined / 不存在)
|
|
261
|
+
// const tree = buildTree(undefined)
|
|
262
|
+
// .concat(buildTree(null))
|
|
263
|
+
// .concat(buildTree(0))
|
|
264
|
+
// .concat(
|
|
265
|
+
// Array.from(pidMap.keys())
|
|
266
|
+
// .filter((pid) => !idMap.has(pid))
|
|
267
|
+
// .flatMap((pid) => buildTree(pid)),
|
|
268
|
+
// );
|
|
269
|
+
|
|
270
|
+
return tree;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* 树型数据转列表数据
|
|
275
|
+
* @param treeData
|
|
276
|
+
* @param settings
|
|
277
|
+
* @param settings.idField 默认值 id
|
|
278
|
+
* @param settings.pidField 默认值 parentId
|
|
279
|
+
* @param settings.childrenField 默认值 children
|
|
280
|
+
* @deprecated 即将移除,使用 {@link CrObjUtil.treeToList} 替代
|
|
281
|
+
* @returns
|
|
282
|
+
*/
|
|
283
|
+
static treeDataToListData(
|
|
284
|
+
treeData: any[],
|
|
285
|
+
pid: string | number = 0,
|
|
286
|
+
settings?: {
|
|
287
|
+
childrenField?: string;
|
|
288
|
+
idField?: string;
|
|
289
|
+
pidField?: string;
|
|
290
|
+
isDeepCopy?: boolean;
|
|
291
|
+
},
|
|
292
|
+
) {
|
|
293
|
+
let options = Object.assign(
|
|
294
|
+
{
|
|
295
|
+
idField: 'id',
|
|
296
|
+
pidField: 'parentId',
|
|
297
|
+
childrenField: 'children',
|
|
298
|
+
isDeepCopy: false,
|
|
299
|
+
},
|
|
300
|
+
settings,
|
|
301
|
+
);
|
|
302
|
+
let listData: any[] = [];
|
|
303
|
+
for (let i = 0; i < treeData.length; i++) {
|
|
304
|
+
const node = treeData[i];
|
|
305
|
+
const item: any = { ...node };
|
|
306
|
+
item[options.pidField] = pid;
|
|
307
|
+
delete item[options.childrenField];
|
|
308
|
+
listData.push(item);
|
|
309
|
+
if (node[options.childrenField]) {
|
|
310
|
+
const childrenList = this.treeDataToListData(
|
|
311
|
+
node[options.childrenField],
|
|
312
|
+
item[options.idField],
|
|
313
|
+
settings,
|
|
314
|
+
);
|
|
315
|
+
listData.push(...childrenList);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (options.isDeepCopy) {
|
|
319
|
+
listData = CrUtil.deepCopy(listData);
|
|
320
|
+
}
|
|
321
|
+
return listData;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 获取所有父级数据(包含自身)
|
|
326
|
+
* @param listData
|
|
327
|
+
* @param value
|
|
328
|
+
* @param settings
|
|
329
|
+
* @param settings.valueField 默认值 value
|
|
330
|
+
* @param settings.idField 默认值 id
|
|
331
|
+
* @param settings.pidField 默认值 parentId
|
|
332
|
+
* @deprecated 即将移除,使用 {@link CrObjUtil.getFlatParentDatas} 替代
|
|
333
|
+
* @returns
|
|
334
|
+
*/
|
|
335
|
+
static getParentNodes<T, R extends Record<string, any>>(
|
|
336
|
+
listData: R[],
|
|
337
|
+
value: number | string,
|
|
338
|
+
settings?: {
|
|
339
|
+
valueField?: string;
|
|
340
|
+
idField?: string;
|
|
341
|
+
pidField?: string;
|
|
342
|
+
getData?: (item: R) => T;
|
|
343
|
+
},
|
|
344
|
+
): T[] {
|
|
345
|
+
const options = Object.assign(
|
|
346
|
+
{
|
|
347
|
+
valueField: 'value',
|
|
348
|
+
idField: 'id',
|
|
349
|
+
pidField: 'parentId',
|
|
350
|
+
getData: (item: R): T => {
|
|
351
|
+
return item as unknown as T;
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
settings,
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
if (!(listData && listData.length > 0) || !value) {
|
|
358
|
+
return [];
|
|
359
|
+
}
|
|
360
|
+
const idField = options.idField;
|
|
361
|
+
const pidField = options.pidField;
|
|
362
|
+
const valueField = options.valueField;
|
|
363
|
+
const nodes: T[] = [];
|
|
364
|
+
let node: R | undefined = listData.find(
|
|
365
|
+
(item: R) => item[valueField] == value,
|
|
366
|
+
);
|
|
367
|
+
if (node) {
|
|
368
|
+
nodes.push(options.getData(node));
|
|
369
|
+
while (node && node[pidField]) {
|
|
370
|
+
const parentOption: R | undefined = listData.find(
|
|
371
|
+
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
|
372
|
+
(item: R) => item[idField] == (node as R)[pidField],
|
|
373
|
+
);
|
|
374
|
+
if (parentOption) {
|
|
375
|
+
nodes.push(options.getData(parentOption));
|
|
376
|
+
}
|
|
377
|
+
node = parentOption;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
nodes.reverse();
|
|
381
|
+
return nodes;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* 参数序列化
|
|
386
|
+
* @param params {a:'1',b:{},c:[]}
|
|
387
|
+
* @returns
|
|
388
|
+
*/
|
|
389
|
+
static paramsSerializer(
|
|
390
|
+
params: Record<string, any>,
|
|
391
|
+
settings?: { isFilterNonNull: boolean },
|
|
392
|
+
) {
|
|
393
|
+
const options = Object.assign(
|
|
394
|
+
{
|
|
395
|
+
isFilterNonNull: false,
|
|
396
|
+
},
|
|
397
|
+
settings,
|
|
398
|
+
);
|
|
399
|
+
if (options.isFilterNonNull) {
|
|
400
|
+
Object.keys(params).forEach((key) => {
|
|
401
|
+
if (
|
|
402
|
+
params[key] === null ||
|
|
403
|
+
params[key] === undefined ||
|
|
404
|
+
params[key] === ''
|
|
405
|
+
) {
|
|
406
|
+
delete params[key];
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
return Qs.stringify(params);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* 参数解析
|
|
415
|
+
* @param {*} str
|
|
416
|
+
* @param {*} settings
|
|
417
|
+
* @returns
|
|
418
|
+
*/
|
|
419
|
+
static paramsParse(str: string, settings?: { ignoreQueryPrefix: boolean }) {
|
|
420
|
+
let obj = {};
|
|
421
|
+
let option = Object.assign(
|
|
422
|
+
{
|
|
423
|
+
ignoreQueryPrefix: true,
|
|
424
|
+
},
|
|
425
|
+
settings,
|
|
426
|
+
);
|
|
427
|
+
if (str) {
|
|
428
|
+
obj = Qs.parse(str, option);
|
|
429
|
+
}
|
|
430
|
+
return obj;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* 获取 URL 参数
|
|
435
|
+
* @param {*} url
|
|
436
|
+
* @returns
|
|
437
|
+
* @example
|
|
438
|
+
* "https://example.com?foo=bar&baz=qux" => {"query":{"foo":"bar","baz":"qux"},"hash":{},"all":{"foo":"bar","baz":"qux"}}
|
|
439
|
+
* "https://example.com/page?foo=1#/?a=1&b=2" => {"query":{"foo":"1"},"hash":{"/":"","a":"1","b":"2"},"all":{"foo":"1","/":"","a":"1","b":"2"}},
|
|
440
|
+
*/
|
|
441
|
+
static getQueryParams(url: string): {
|
|
442
|
+
query: Record<string, any>;
|
|
443
|
+
hash: Record<string, any>;
|
|
444
|
+
all: Record<string, any>;
|
|
445
|
+
} {
|
|
446
|
+
const query: Record<string, any> = {};
|
|
447
|
+
const hash: Record<string, any> = {};
|
|
448
|
+
const all: Record<string, any> = {};
|
|
449
|
+
const parse = (queryString: string) => {
|
|
450
|
+
if (!queryString) {
|
|
451
|
+
return {};
|
|
452
|
+
}
|
|
453
|
+
try {
|
|
454
|
+
return Qs.parse(queryString);
|
|
455
|
+
} catch {
|
|
456
|
+
const result: Record<string, any> = {};
|
|
457
|
+
if (!queryString) return result;
|
|
458
|
+
queryString.split('&').forEach((pair) => {
|
|
459
|
+
if (!pair) return;
|
|
460
|
+
const [key, val] = pair.split('=');
|
|
461
|
+
if (key)
|
|
462
|
+
result[decodeURIComponent(key)] = decodeURIComponent(val || '');
|
|
463
|
+
});
|
|
464
|
+
return result;
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
try {
|
|
468
|
+
const [base, hashPart] = url.split('#');
|
|
469
|
+
const queryPart = base.split('?')[1];
|
|
470
|
+
Object.assign(query, parse(queryPart));
|
|
471
|
+
if (hashPart) {
|
|
472
|
+
for (const part of hashPart.split('?')) {
|
|
473
|
+
Object.assign(hash, parse(part));
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
Object.assign(all, query, hash);
|
|
477
|
+
} catch (error) {
|
|
478
|
+
console.error('getQueryParams:', error);
|
|
479
|
+
}
|
|
480
|
+
return { query, hash, all };
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
/**
|
|
484
|
+
* 版本号比较
|
|
485
|
+
* @param v1
|
|
486
|
+
* @param v2
|
|
487
|
+
* @returns number
|
|
488
|
+
* @description v1大于v2(1)、v1小于v2(-1)、v1等于v2(0)
|
|
489
|
+
*/
|
|
490
|
+
static compareVersion(v1: string, v2: string): number {
|
|
491
|
+
let v1Arr: Array<string> = CrUtil.trim(v1).split('.');
|
|
492
|
+
let v2Arr: Array<string> = CrUtil.trim(v2).split('.');
|
|
493
|
+
const len = Math.max(v1.length, v2.length);
|
|
494
|
+
while (v1Arr.length < len) {
|
|
495
|
+
v1Arr.push('0');
|
|
496
|
+
}
|
|
497
|
+
while (v2Arr.length < len) {
|
|
498
|
+
v2Arr.push('0');
|
|
499
|
+
}
|
|
500
|
+
for (let i = 0; i < len; i++) {
|
|
501
|
+
const num1 = parseInt(v1Arr[i]);
|
|
502
|
+
const num2 = parseInt(v2Arr[i]);
|
|
503
|
+
if (num1 > num2) {
|
|
504
|
+
return 1;
|
|
505
|
+
} else if (num1 < num2) {
|
|
506
|
+
return -1;
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
return 0;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
/**
|
|
513
|
+
* 根据 ids 获取相对应的数据名称
|
|
514
|
+
* @param data
|
|
515
|
+
* @param ids
|
|
516
|
+
* @param settings
|
|
517
|
+
* @returns string
|
|
518
|
+
*/
|
|
519
|
+
static getDataNameByIds<T extends Record<string, any>>(
|
|
520
|
+
data: T[],
|
|
521
|
+
ids: (string | number)[],
|
|
522
|
+
settings?: { sep?: string; nameField?: string; idField?: string },
|
|
523
|
+
): string {
|
|
524
|
+
const options = Object.assign(
|
|
525
|
+
{
|
|
526
|
+
sep: '、',
|
|
527
|
+
idField: 'id',
|
|
528
|
+
nameField: 'name',
|
|
529
|
+
},
|
|
530
|
+
settings,
|
|
531
|
+
);
|
|
532
|
+
let str = '';
|
|
533
|
+
if (ids && ids.length > 0) {
|
|
534
|
+
str = ids
|
|
535
|
+
.map((id) => {
|
|
536
|
+
let item = data.find((item) => item[options.idField] == id);
|
|
537
|
+
if (item) {
|
|
538
|
+
return item[options.nameField];
|
|
539
|
+
} else {
|
|
540
|
+
return '';
|
|
541
|
+
}
|
|
542
|
+
})
|
|
543
|
+
.join(options.sep);
|
|
544
|
+
}
|
|
545
|
+
return str;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
/**
|
|
549
|
+
* 追加html标签属性值
|
|
550
|
+
* @param htmlStr
|
|
551
|
+
* @param tagName
|
|
552
|
+
* @param attrName
|
|
553
|
+
* @param newAttrValue
|
|
554
|
+
* @returns string
|
|
555
|
+
*/
|
|
556
|
+
static appendHtmlTagAttr(
|
|
557
|
+
htmlStr: string,
|
|
558
|
+
tagName: string,
|
|
559
|
+
attrName: string,
|
|
560
|
+
newAttrValue: string,
|
|
561
|
+
) {
|
|
562
|
+
let regex = new RegExp(`(<${tagName}\\s+)([^>]*?)([^>]*>)`, 'g');
|
|
563
|
+
let replacedHtml = htmlStr.replace(regex, function (match, p1, p2, p3) {
|
|
564
|
+
let _regex = new RegExp(`${attrName}="(.*?)"`, 'g');
|
|
565
|
+
if (_regex.exec(match)) {
|
|
566
|
+
let __regex = new RegExp(
|
|
567
|
+
`(<${tagName}\\s+)([^>]*?)${attrName}="(.*)"([^>]*>)`,
|
|
568
|
+
'g',
|
|
569
|
+
);
|
|
570
|
+
return match.replace(__regex, function (_match, _p1, _p2, _p3, _p4) {
|
|
571
|
+
return _p1 + _p2 + `${attrName}="${_p3} ${newAttrValue}"` + _p4;
|
|
572
|
+
});
|
|
573
|
+
} else {
|
|
574
|
+
return p1 + `${attrName}="${newAttrValue}" ` + p3;
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
replacedHtml = replacedHtml.replace(
|
|
578
|
+
new RegExp(`<${tagName}>`, 'g'),
|
|
579
|
+
`<${tagName} ${attrName}="${newAttrValue}">`,
|
|
580
|
+
);
|
|
581
|
+
return replacedHtml;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/**
|
|
585
|
+
* 比较数据差异
|
|
586
|
+
* @param data1
|
|
587
|
+
* @param data2
|
|
588
|
+
* @returns
|
|
589
|
+
*/
|
|
590
|
+
static compareDataDiff(data1: any, data2: any) {
|
|
591
|
+
const diffFieldSet: Record<string, 'added' | 'removed' | 'modified'> = {};
|
|
592
|
+
const compare = (path: string, o1: any, o2: any) => {
|
|
593
|
+
if (CrUtil.isArray(o1) && CrUtil.isArray(o2)) {
|
|
594
|
+
// 如果两者都是数组,比较它们的长度和内容
|
|
595
|
+
const lengthDiff = o1.length - o2.length;
|
|
596
|
+
if (lengthDiff !== 0) {
|
|
597
|
+
// 数组长度不同
|
|
598
|
+
diffFieldSet[path] = 'modified';
|
|
599
|
+
if (lengthDiff > 0) {
|
|
600
|
+
// o1比o2长,所以o1中有多余的元素
|
|
601
|
+
for (let i = o2.length; i < o1.length; i++) {
|
|
602
|
+
diffFieldSet[`${path}[${i}]`] = 'removed'; // 只在o1中存在
|
|
603
|
+
}
|
|
604
|
+
} else {
|
|
605
|
+
// o2比o1长,所以o2中有新增的元素
|
|
606
|
+
for (let i = o1.length; i < o2.length; i++) {
|
|
607
|
+
diffFieldSet[`${path}[${i}]`] = 'added'; // 只在o2中存在
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
for (let i = 0; i < o1.length; i++) {
|
|
612
|
+
compare(`${path}[${i}]`, o1[i], o2[i]);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
} else if (CrUtil.isObject(o1) && CrUtil.isObject(o2)) {
|
|
616
|
+
// 如果两者都是对象,遍历它们的属性
|
|
617
|
+
// eslint-disable-next-line guard-for-in
|
|
618
|
+
for (let key in o1) {
|
|
619
|
+
const _path = path ? `${path}.${key}` : key;
|
|
620
|
+
if (Object.prototype.hasOwnProperty.call(o1, key) && !(key in o2)) {
|
|
621
|
+
diffFieldSet[_path] = 'removed'; // 只在o1中存在
|
|
622
|
+
} else if (Object.prototype.hasOwnProperty.call(o2, key)) {
|
|
623
|
+
compare(_path, o1[key], o2[key]);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// 检查o2中是否存在o1中没有的属性
|
|
627
|
+
// eslint-disable-next-line guard-for-in
|
|
628
|
+
for (let key in o2) {
|
|
629
|
+
const _path = path ? `${path}.${key}` : key;
|
|
630
|
+
if (Object.prototype.hasOwnProperty.call(o2, key) && !(key in o1)) {
|
|
631
|
+
diffFieldSet[_path] = 'added'; // 只在o2中存在
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
} else if (o1 !== o2) {
|
|
635
|
+
// 既不是对象也不是数组,直接比较值
|
|
636
|
+
diffFieldSet[path] = 'modified';
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
compare('', data1, data2);
|
|
640
|
+
return diffFieldSet;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* 格式化为千分位
|
|
645
|
+
* @param value
|
|
646
|
+
* @returns 12345.6789 => 12,345.6789
|
|
647
|
+
*/
|
|
648
|
+
static fmtThousands(value: number | string | undefined): string {
|
|
649
|
+
if (value == undefined || value == null || isNaN(Number(value))) {
|
|
650
|
+
return String(value);
|
|
651
|
+
}
|
|
652
|
+
const [intPart, decimalPart] = String(value).split('.');
|
|
653
|
+
const formattedInt = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
654
|
+
return decimalPart ? `${formattedInt}.${decimalPart}` : formattedInt;
|
|
655
|
+
}
|
|
656
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "es2015",
|
|
4
|
+
"downlevelIteration": true,
|
|
5
|
+
"strict": true,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"jsx": "react",
|
|
10
|
+
"baseUrl": "./",
|
|
11
|
+
"paths": {
|
|
12
|
+
"@@/*": [".dumi/tmp/*"],
|
|
13
|
+
"@zcrkey/js-utils": ["src"],
|
|
14
|
+
"@zcrkey/js-utils/*": ["src/*", "*"]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"include": [".dumirc.ts", "src/**/*", "docs/**/*"]
|
|
18
|
+
}
|