@hzab/data-model 2.0.2 → 2.0.3-alpha.1

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/src/ArrayUtils.ts CHANGED
@@ -1,712 +1,712 @@
1
- import { nanoid } from "nanoid";
2
- import { cloneDeep, isEmpty, isObject, isNil, get } from "lodash";
3
- import dayjs from "dayjs";
4
-
5
- /**
6
- * 状态码枚举
7
- */
8
- export const STATE_CODE = {
9
- /** 成功 */
10
- SUC: 200,
11
- /** 未找到 */
12
- NOT_FOUNT: 404,
13
- /** 错误 */
14
- ERR: 500,
15
- /** 缺少 ID 错误 */
16
- ERR_INPUT_ID: 501,
17
- } as const;
18
-
19
- /**
20
- * 状态码类型
21
- */
22
- export type StateCode = (typeof STATE_CODE)[keyof typeof STATE_CODE];
23
-
24
- /**
25
- * 分页结果接口
26
- */
27
- export interface Pagination {
28
- /** 当前页码 */
29
- current?: number;
30
- /** 总条数 */
31
- total: number;
32
- /** 每页条数 */
33
- pageSize?: number;
34
- }
35
-
36
- /**
37
- * 列表查询结果接口
38
- */
39
- export interface FindListResult<T = Record<string, any>> {
40
- /** 列表数据 */
41
- list: T[];
42
- /** 分页信息 */
43
- pagination: Pagination;
44
- }
45
-
46
- /**
47
- * 排序路径配置接口
48
- */
49
- export interface SortPathConfig {
50
- /** 字段路径 */
51
- path: string;
52
- /** 字段类型 */
53
- type?: string;
54
- /** 自定义比较函数 */
55
- customCompare?: (a: any, b: any, opt: any) => number;
56
- /** 自定义值格式化函数 */
57
- customFormat?: (val: any, data: any) => any;
58
- }
59
-
60
- /**
61
- * 查询参数接口
62
- */
63
- export interface ItemQuery {
64
- /** 页码 */
65
- pageNum?: number;
66
- /** 每页条数 */
67
- pageSize?: number;
68
- /** 排序字段 */
69
- sortKey?: string | string[] | SortPathConfig[];
70
- /** 排序方式 */
71
- sortType?: "asc" | "desc" | Record<string, "asc" | "desc">;
72
- /** 兼容旧排序字段参数 */
73
- orderByColumn?: string | string[] | SortPathConfig[];
74
- /** 兼容旧排序方式参数 */
75
- isAsc?: "asc" | "desc" | boolean;
76
- /** 其他筛选条件 */
77
- [key: string]: any;
78
- }
79
-
80
- /**
81
- * 查询选项接口
82
- */
83
- export interface QueryOptions {
84
- /** 字符串字段是否开启模糊匹配 */
85
- fuzzy?: boolean;
86
- /** 模糊匹配时是否忽略大小写 */
87
- ignoreCase?: boolean;
88
- }
89
-
90
- /**
91
- * 范围查询配置接口
92
- */
93
- export interface RangeQuery {
94
- /** 大于 */
95
- $gt?: any;
96
- /** 小于 */
97
- $lt?: any;
98
- /** 大于等于 */
99
- $gte?: any;
100
- /** 小于等于 */
101
- $lte?: any;
102
- }
103
-
104
- /**
105
- * ArrayUtils 构造函数参数接口
106
- */
107
- export interface ArrayUtilsOptions<T = Record<string, any>> {
108
- /** 子项唯一标志字段 */
109
- idKey?: string;
110
- /** 列表数据 */
111
- list?: T[];
112
- }
113
-
114
- /**
115
- * 数组模拟数据库存储
116
- * @template T - 列表项数据类型
117
- */
118
- export class ArrayUtils<T extends Record<string, any> = Record<string, any>> {
119
- /** 子项唯一标志字段 */
120
- protected _idKey: string;
121
- /** 列表数据 */
122
- protected _list: T[];
123
-
124
- constructor(params: ArrayUtilsOptions<T> = {}) {
125
- const { idKey = "id", list } = params;
126
- this._idKey = idKey || "id";
127
- this._list = list ? cloneDeep(list) : [];
128
- }
129
-
130
- /**
131
- * 通过 id 获取子项
132
- * @param id 目标 ID
133
- * @returns 匹配的子项或 null
134
- */
135
- findItemById(id: string | number): T | null {
136
- if (this._isInvalidId(id)) {
137
- return null;
138
- }
139
- const { _idKey } = this;
140
- const item = this._list.find((it) => it[_idKey] === id);
141
- return item ? cloneDeep(item) : null;
142
- }
143
-
144
- /**
145
- * 通过 query 获取子项
146
- * @param query 查询条件
147
- * @param options 查询选项
148
- * @returns 匹配的子项或 null
149
- */
150
- findItem(query: Record<string, any> = {}, options: QueryOptions = {}): T | null {
151
- const { fuzzy = false, ignoreCase = false } = options;
152
- const id = query[this._idKey];
153
-
154
- // 如果有 ID 且 ID 有效,优先通过 ID 查询
155
- if (!this._isInvalidId(id)) {
156
- return this.findItemById(id);
157
- }
158
-
159
- const validQuery = this._filterEmptyQuery(query);
160
- if (isEmpty(validQuery)) {
161
- return null;
162
- }
163
-
164
- const matchItem = this._list.find((item) => {
165
- return this._isItemMatchQuery(item, validQuery, { fuzzy, ignoreCase });
166
- });
167
-
168
- return matchItem ? cloneDeep(matchItem) : null;
169
- }
170
-
171
- /**
172
- * 通过 query 筛选列表(支持多条件筛选、分页、排序)
173
- * @param query - 综合查询参数
174
- * @param query.pageNum - 页码(默认 1)
175
- * @param query.pageSize - 每页条数(默认 10)
176
- * @param query.sortKey - 排序字段(支持嵌套路径,如 'user.age')
177
- * @param query.sortType - 排序方式(desc/asc)
178
- * @param query.orderByColumn - 兼容旧排序字段参数
179
- * @param query.isAsc - 兼容旧排序方式参数
180
- * @param options - 查询选项
181
- * @param options.fuzzy - 字符串字段是否开启模糊匹配
182
- * @param options.ignoreCase - 模糊匹配时是否忽略大小写
183
- * @returns 分页结果
184
- */
185
- findListByQuery(query: ItemQuery = {}, options: QueryOptions = {}): Promise<FindListResult<T>> {
186
- const { pageNum = 1, pageSize = 10, sortKey, sortType, orderByColumn, isAsc, ...filterQuery } = query;
187
-
188
- const { fuzzy = true, ignoreCase = false } = options;
189
-
190
- // 数据筛选(基于 query 条件)
191
- let filteredList = [...this._list];
192
- const validFilterQuery = this._filterEmptyQuery(filterQuery);
193
-
194
- if (!isEmpty(validFilterQuery)) {
195
- filteredList = filteredList.filter((item) => {
196
- return this._isItemMatchQuery(item, validFilterQuery, { fuzzy, ignoreCase });
197
- });
198
- }
199
-
200
- // 数据排序(基于筛选后的数据)
201
- const sortedList = this.getSortList(sortKey || orderByColumn, sortType || isAsc, filteredList);
202
-
203
- // 分页处理(边界校验)
204
- const pagination: Pagination = {
205
- current: Math.max(1, pageNum),
206
- pageSize: Math.max(1, pageSize),
207
- total: filteredList.length,
208
- };
209
-
210
- // 计算分页截取范围
211
- const startIndex = (pagination.current - 1) * pagination.pageSize;
212
- const endIndex = startIndex + pagination.pageSize;
213
- const paginatedList = sortedList.slice(startIndex, endIndex);
214
-
215
- return Promise.resolve({
216
- list: cloneDeep(paginatedList),
217
- pagination: { ...pagination },
218
- });
219
- }
220
-
221
- /**
222
- * 对目标数据进行多维度排序
223
- * @param sortKey - 排序字段配置
224
- * @param sortType - 排序方式(desc/asc)
225
- * @param customList - 自定义排序列表(默认使用内部 _list)
226
- * @returns 排序后的列表
227
- */
228
- getSortList(
229
- sortKey?: string | string[] | SortPathConfig[],
230
- sortType?: "asc" | "desc" | Record<string, "asc" | "desc"> | boolean,
231
- customList?: T[],
232
- ): T[] {
233
- const sourceList = customList || this._list;
234
- const _list = cloneDeep(sourceList);
235
-
236
- // 无排序条件时直接返回原列表
237
- if (!sortKey && sortType === undefined) {
238
- return _list;
239
- }
240
-
241
- // 处理布尔类型的 sortType(兼容 isAsc 参数)
242
- let normalizedSortType: "asc" | "desc" | Record<string, "asc" | "desc"> | undefined;
243
- if (typeof sortType === "boolean") {
244
- normalizedSortType = sortType ? "asc" : "desc";
245
- } else {
246
- normalizedSortType = sortType;
247
- }
248
-
249
- // 统一排序字段格式为数组
250
- let paths: (string | SortPathConfig)[];
251
- if (Array.isArray(sortKey)) {
252
- paths = sortKey;
253
- } else if (sortKey) {
254
- paths = [sortKey];
255
- } else {
256
- paths = [];
257
- }
258
-
259
- if (paths.length === 0 && !normalizedSortType) {
260
- return _list;
261
- }
262
-
263
- _list.sort((a, b) => {
264
- return this.compareData(a, b, { paths, sortType: normalizedSortType, pathIdx: 0 });
265
- });
266
-
267
- return _list;
268
- }
269
-
270
- /**
271
- * 比较两个数据对象的排序顺序(支持多字段优先级排序)
272
- * @param dataA - 比较对象 A
273
- * @param dataB - 比较对象 B
274
- * @param opt - 比较配置
275
- * @returns 排序结果(-1/0/1)
276
- */
277
- compareData(
278
- dataA: T,
279
- dataB: T,
280
- opt: {
281
- paths: (string | SortPathConfig)[];
282
- pathIdx?: number;
283
- sortType?: "asc" | "desc" | Record<string, "asc" | "desc">;
284
- },
285
- ): number {
286
- const { paths, pathIdx = 0, sortType = "desc" } = opt;
287
- const currentPath = paths[pathIdx];
288
-
289
- // 处理自定义比较逻辑
290
- if (
291
- currentPath &&
292
- typeof currentPath === "object" &&
293
- typeof (currentPath as SortPathConfig).customCompare === "function"
294
- ) {
295
- const direction = sortType === "asc" ? -1 : 1;
296
- return direction * (currentPath as SortPathConfig).customCompare!(dataA, dataB, opt);
297
- }
298
-
299
- // 提取实际字段路径(支持配置对象格式)
300
- const path = typeof currentPath === "object" ? (currentPath as SortPathConfig).path : currentPath;
301
- if (!path) {
302
- // 没有更多字段时,继续下一个或返回 0
303
- if (pathIdx + 1 < paths.length) {
304
- return this.compareData(dataA, dataB, { ...opt, pathIdx: pathIdx + 1 });
305
- }
306
- return 0;
307
- }
308
-
309
- // 确定排序方向(支持按字段单独配置排序方式)
310
- let direction = 1;
311
- let currentSortType = sortType;
312
- if (typeof currentSortType === "object") {
313
- currentSortType = (currentSortType as Record<string, "asc" | "desc">)[path] || "desc";
314
- }
315
- if (currentSortType === "asc") {
316
- direction = -1;
317
- }
318
-
319
- // 获取并处理字段值(支持日期转换、自定义格式化)
320
- const valueA = this.handleValByKey(dataA, currentPath);
321
- const valueB = this.handleValByKey(dataB, currentPath);
322
-
323
- // 数值比较
324
- if (valueA < valueB) {
325
- return -1 * direction;
326
- }
327
- if (valueA > valueB) {
328
- return 1 * direction;
329
- }
330
-
331
- // 当前字段值相等时,使用下一个优先级字段排序
332
- if (pathIdx + 1 < paths.length) {
333
- return this.compareData(dataA, dataB, { ...opt, pathIdx: pathIdx + 1 });
334
- }
335
-
336
- // 所有字段都相等时,保持原有顺序
337
- return 0;
338
- }
339
-
340
- /**
341
- * 根据路径获取并处理字段值(支持嵌套路径、类型转换、自定义格式化)
342
- * @param data - 数据源对象
343
- * @param pathConfig - 字段配置
344
- * @returns 处理后的字段值
345
- */
346
- handleValByKey(data: T, pathConfig: string | SortPathConfig | null | undefined): any {
347
- // 处理字符串路径
348
- if (typeof pathConfig === "string") {
349
- return get(data, pathConfig);
350
- }
351
-
352
- // 处理空配置
353
- if (isNil(pathConfig)) {
354
- return undefined;
355
- }
356
-
357
- // 处理对象配置
358
- if (typeof pathConfig === "object") {
359
- let val = get(data, (pathConfig as SortPathConfig).path);
360
-
361
- // 日期类型转换为时间戳(便于数值比较)
362
- if ((pathConfig as SortPathConfig).type === "date" && val) {
363
- const parsed = dayjs(val);
364
- val = parsed.isValid() ? parsed.valueOf() : 0;
365
- }
366
-
367
- // 自定义值格式化
368
- if (typeof (pathConfig as SortPathConfig).customFormat === "function") {
369
- val = (pathConfig as SortPathConfig).customFormat!(val, data);
370
- }
371
-
372
- return val;
373
- }
374
-
375
- return undefined;
376
- }
377
-
378
- /**
379
- * 获取完整列表
380
- * @returns 完整列表的深拷贝
381
- */
382
- findAllList(): T[] {
383
- return cloneDeep(this._list);
384
- }
385
-
386
- /**
387
- * 获取总数
388
- * @returns 列表总数
389
- */
390
- getCount(): number {
391
- return this._list.length;
392
- }
393
-
394
- /**
395
- * push 数据
396
- * @param data 要添加的数据
397
- * @returns 状态码
398
- */
399
- pushItem(data: Partial<T> | Partial<T>[]): StateCode {
400
- const items = Array.isArray(data) ? data : [data];
401
- items.forEach((item) => {
402
- const cloneItem = cloneDeep(item) as T;
403
- this._setId(cloneItem);
404
- this._setCreateTime(cloneItem);
405
- this._list.push(cloneItem);
406
- });
407
- return STATE_CODE.SUC;
408
- }
409
-
410
- /**
411
- * unshift 数据
412
- * @param data 要添加的数据
413
- * @returns 状态码
414
- */
415
- unshiftItem(data: Partial<T>): StateCode {
416
- const item = cloneDeep(data) as T;
417
- this._setId(item);
418
- this._setCreateTime(item);
419
- this._list.unshift(item);
420
- return STATE_CODE.SUC;
421
- }
422
-
423
- /**
424
- * 根据 id 更新子项——直接替换
425
- * @param data 新的数据对象
426
- * @returns 状态码
427
- */
428
- replaceItem(data: T): StateCode {
429
- const { _idKey, _list } = this;
430
- const id = data[_idKey];
431
- if (this._isInvalidId(id)) {
432
- return STATE_CODE.ERR_INPUT_ID;
433
- }
434
- const idx = _list.findIndex((it) => it[_idKey] === id);
435
- if (idx < 0) {
436
- return STATE_CODE.NOT_FOUNT;
437
- }
438
- _list.splice(idx, 1, cloneDeep(data));
439
- this._setUpdateTime(_list[idx]);
440
- return STATE_CODE.SUC;
441
- }
442
-
443
- /**
444
- * 根据 id 更新子项——仅修改传入的数据
445
- * @param data 要更新的数据
446
- * @returns 状态码
447
- */
448
- updateItemValue(data: Partial<T> & { [key: string]: any }): StateCode {
449
- const { _idKey, _list } = this;
450
- const id = data[_idKey];
451
- if (this._isInvalidId(id)) {
452
- return STATE_CODE.ERR_INPUT_ID;
453
- }
454
- const index = _list.findIndex((it) => it[_idKey] === id);
455
- if (index < 0) {
456
- return STATE_CODE.NOT_FOUNT;
457
- }
458
- const item = _list[index];
459
- Object.keys(data).forEach((key) => {
460
- if (key !== _idKey) {
461
- (item as any)[key] = data[key];
462
- }
463
- });
464
- this._setUpdateTime(item);
465
- return STATE_CODE.SUC;
466
- }
467
-
468
- /**
469
- * 删除子项
470
- * @param id 要删除的子项 ID
471
- * @returns 状态码
472
- */
473
- delItem(id: any): StateCode {
474
- const { _idKey, _list } = this;
475
- if (this._isInvalidId(id)) {
476
- return STATE_CODE.ERR_INPUT_ID;
477
- }
478
- const idx = _list.findIndex((it) => it[_idKey] === id);
479
- if (idx < 0) {
480
- return STATE_CODE.NOT_FOUNT;
481
- }
482
- _list.splice(idx, 1);
483
- return STATE_CODE.SUC;
484
- }
485
-
486
- /**
487
- * 清空所有数据
488
- * @returns 状态码
489
- */
490
- clearAll(): StateCode {
491
- this._list = [];
492
- return STATE_CODE.SUC;
493
- }
494
-
495
- /**
496
- * 批量删除
497
- * @param ids 要删除的 ID 数组
498
- * @returns 删除成功的数量
499
- */
500
- deleteItems(ids: any[]): number {
501
- let successCount = 0;
502
- ids.forEach((id) => {
503
- const res = this.delItem(id);
504
- if (res === STATE_CODE.SUC) {
505
- successCount++;
506
- }
507
- });
508
- return successCount;
509
- }
510
-
511
- /**
512
- * 判断列表是否为空
513
- * @returns 是否为空
514
- */
515
- isEmpty(): boolean {
516
- return this._list.length === 0;
517
- }
518
-
519
- /**
520
- * 获取指定范围的列表
521
- * @param start 起始索引
522
- * @param end 结束索引
523
- * @returns 切片后的列表
524
- */
525
- slice(start?: number, end?: number): T[] {
526
- return cloneDeep(this._list.slice(start, end));
527
- }
528
-
529
- /**
530
- * 检查单个 item 是否匹配所有 query 条件
531
- * @param item - 待检查的列表项
532
- * @param query - 查询条件
533
- * @param options - 匹配选项
534
- * @param options.fuzzy - 是否开启模糊匹配
535
- * @param options.ignoreCase - 模糊匹配是否忽略大小写
536
- * @returns 是否匹配所有条件
537
- */
538
- protected _isItemMatchQuery(item: T, query: Record<string, any>, { fuzzy, ignoreCase }: QueryOptions): boolean {
539
- // 遍历所有查询条件,必须全部满足才返回 true
540
- return Object.entries(query).every(([key, targetValue]) => {
541
- // 获取 item 中对应字段的值(支持嵌套路径,如 'user.name')
542
- const itemValue = get(item, key);
543
-
544
- // 处理空值情况(null/undefined 仅匹配 null/undefined)
545
- if (isNil(itemValue)) {
546
- return isNil(targetValue);
547
- }
548
-
549
- // 处理数组类型查询(支持 in 操作,如 { status: [1, 2] })
550
- if (Array.isArray(targetValue)) {
551
- return targetValue.some((val) => this._compareSingleValue(itemValue, val, fuzzy, ignoreCase));
552
- }
553
-
554
- // 处理范围查询(如 { age: { $gt: 18, $lt: 30 } })
555
- if (typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
556
- return this._handleRangeQuery(itemValue, targetValue as RangeQuery);
557
- }
558
-
559
- // 处理普通值匹配(精确/模糊)
560
- return this._compareSingleValue(itemValue, targetValue, fuzzy, ignoreCase);
561
- });
562
- }
563
-
564
- /**
565
- * 比较单个值是否匹配(支持精确/模糊匹配)
566
- * @param itemValue - 列表项字段值
567
- * @param targetValue - 查询目标值
568
- * @param fuzzy - 是否模糊匹配
569
- * @param ignoreCase - 是否忽略大小写
570
- * @returns 是否匹配
571
- */
572
- protected _compareSingleValue(itemValue: any, targetValue: any, fuzzy: boolean, ignoreCase: boolean): boolean {
573
- // 类型不同时直接不匹配(避免隐式类型转换导致的问题)
574
- if (typeof itemValue !== typeof targetValue) {
575
- // 特殊处理:数字和字符串数字的匹配(如 123 和 '123')
576
- if (
577
- (typeof itemValue === "number" && typeof targetValue === "string" && !isNaN(Number(targetValue))) ||
578
- (typeof itemValue === "string" && typeof targetValue === "number" && !isNaN(Number(itemValue)))
579
- ) {
580
- return Number(itemValue) === targetValue;
581
- }
582
- return false;
583
- }
584
-
585
- // 字符串处理(支持模糊匹配和大小写忽略)
586
- if (typeof itemValue === "string" && fuzzy) {
587
- const itemStr = ignoreCase ? itemValue.toLowerCase() : itemValue;
588
- const targetStr = ignoreCase ? String(targetValue).toLowerCase() : String(targetValue);
589
- return itemStr.includes(targetStr);
590
- }
591
-
592
- // 其他类型(数字、布尔等)精确匹配
593
- return itemValue === targetValue;
594
- }
595
-
596
- /**
597
- * 处理范围查询(如 $gt/$lt/$gte/$lte)
598
- * @param itemValue - 列表项字段值
599
- * @param rangeConfig - 范围配置
600
- * @returns 是否在范围内
601
- */
602
- protected _handleRangeQuery(itemValue: any, rangeConfig: RangeQuery): boolean {
603
- const { $gt, $lt, $gte, $lte } = rangeConfig;
604
- let isValid = true;
605
-
606
- // 转换为可比较的类型(优先处理日期)
607
- const compareValue = this._convertToComparableValue(itemValue);
608
- const convertTarget = (val: any) => this._convertToComparableValue(val);
609
-
610
- // 大于(>)
611
- if ($gt !== undefined) {
612
- isValid = isValid && compareValue > convertTarget($gt);
613
- }
614
- // 小于(<)
615
- if ($lt !== undefined) {
616
- isValid = isValid && compareValue < convertTarget($lt);
617
- }
618
- // 大于等于(>=)
619
- if ($gte !== undefined) {
620
- isValid = isValid && compareValue >= convertTarget($gte);
621
- }
622
- // 小于等于(<=)
623
- if ($lte !== undefined) {
624
- isValid = isValid && compareValue <= convertTarget($lte);
625
- }
626
-
627
- return isValid;
628
- }
629
-
630
- /**
631
- * 将值转换为可比较的类型(统一数字、日期格式)
632
- * @param value - 待转换的值
633
- * @returns 可比较的值
634
- */
635
- protected _convertToComparableValue(value: any): any {
636
- // null/undefined 处理
637
- if (isNil(value)) {
638
- return value;
639
- }
640
- // 日期转换为时间戳
641
- if (value instanceof Date || (typeof value === "string" && dayjs(value).isValid())) {
642
- return dayjs(value).valueOf();
643
- }
644
- // 字符串数字转换为数字
645
- if (typeof value === "string" && !isNaN(Number(value))) {
646
- return Number(value);
647
- }
648
- return value;
649
- }
650
-
651
- /**
652
- * 过滤空查询条件(移除 undefined/null/空字符串/空数组/空对象)
653
- * @param query - 原始查询条件
654
- * @returns 过滤后的有效查询条件
655
- */
656
- protected _filterEmptyQuery(query: Record<string, any>): Record<string, any> {
657
- return Object.entries(query).reduce((acc: Record<string, any>, [key, value]) => {
658
- if (
659
- !isNil(value) &&
660
- value !== "" &&
661
- !(Array.isArray(value) && value.length === 0) &&
662
- !(typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0)
663
- ) {
664
- acc[key] = value;
665
- }
666
- return acc;
667
- }, {});
668
- }
669
-
670
- /**
671
- * 检查 ID 是否无效
672
- * @param id 要检查的 ID
673
- * @returns ID 是否无效(为空)
674
- */
675
- protected _isInvalidId(id: any): boolean {
676
- return isNil(id) || id === "";
677
- }
678
-
679
- /**
680
- * 子项没有 id 的时候自动添加
681
- * @param item 要设置 ID 的子项
682
- * @returns 设置 ID 后的子项
683
- */
684
- protected _setId(item: T): T {
685
- if (isObject(item) && this._isInvalidId((item as any)[this._idKey])) {
686
- (item as any)[this._idKey] = nanoid();
687
- }
688
- return item;
689
- }
690
-
691
- /**
692
- * 设置创建时间
693
- * @param item 要设置创建时间的子项
694
- */
695
- protected _setCreateTime(item: T): void {
696
- if (isObject(item) && this._isInvalidId((item as any).createTime)) {
697
- (item as any).createTime = Date.now();
698
- }
699
- }
700
-
701
- /**
702
- * 设置更新时间
703
- * @param item 要设置更新时间的子项
704
- */
705
- protected _setUpdateTime(item: T): void {
706
- if (isObject(item)) {
707
- (item as any).updateTime = Date.now();
708
- }
709
- }
710
- }
711
-
712
- export default ArrayUtils;
1
+ import { nanoid } from "nanoid";
2
+ import { cloneDeep, isEmpty, isObject, isNil, get } from "lodash-es";
3
+ import dayjs from "dayjs";
4
+
5
+ /**
6
+ * 状态码枚举
7
+ */
8
+ export const STATE_CODE = {
9
+ /** 成功 */
10
+ SUC: 200,
11
+ /** 未找到 */
12
+ NOT_FOUNT: 404,
13
+ /** 错误 */
14
+ ERR: 500,
15
+ /** 缺少 ID 错误 */
16
+ ERR_INPUT_ID: 501,
17
+ } as const;
18
+
19
+ /**
20
+ * 状态码类型
21
+ */
22
+ export type StateCode = (typeof STATE_CODE)[keyof typeof STATE_CODE];
23
+
24
+ /**
25
+ * 分页结果接口
26
+ */
27
+ export interface Pagination {
28
+ /** 当前页码 */
29
+ current?: number;
30
+ /** 总条数 */
31
+ total: number;
32
+ /** 每页条数 */
33
+ pageSize?: number;
34
+ }
35
+
36
+ /**
37
+ * 列表查询结果接口
38
+ */
39
+ export interface FindListResult<T = Record<string, any>> {
40
+ /** 列表数据 */
41
+ list: T[];
42
+ /** 分页信息 */
43
+ pagination: Pagination;
44
+ }
45
+
46
+ /**
47
+ * 排序路径配置接口
48
+ */
49
+ export interface SortPathConfig {
50
+ /** 字段路径 */
51
+ path: string;
52
+ /** 字段类型 */
53
+ type?: string;
54
+ /** 自定义比较函数 */
55
+ customCompare?: (a: any, b: any, opt: any) => number;
56
+ /** 自定义值格式化函数 */
57
+ customFormat?: (val: any, data: any) => any;
58
+ }
59
+
60
+ /**
61
+ * 查询参数接口
62
+ */
63
+ export interface ItemQuery {
64
+ /** 页码 */
65
+ pageNum?: number;
66
+ /** 每页条数 */
67
+ pageSize?: number;
68
+ /** 排序字段 */
69
+ sortKey?: string | string[] | SortPathConfig[];
70
+ /** 排序方式 */
71
+ sortType?: "asc" | "desc" | Record<string, "asc" | "desc">;
72
+ /** 兼容旧排序字段参数 */
73
+ orderByColumn?: string | string[] | SortPathConfig[];
74
+ /** 兼容旧排序方式参数 */
75
+ isAsc?: "asc" | "desc" | boolean;
76
+ /** 其他筛选条件 */
77
+ [key: string]: any;
78
+ }
79
+
80
+ /**
81
+ * 查询选项接口
82
+ */
83
+ export interface QueryOptions {
84
+ /** 字符串字段是否开启模糊匹配 */
85
+ fuzzy?: boolean;
86
+ /** 模糊匹配时是否忽略大小写 */
87
+ ignoreCase?: boolean;
88
+ }
89
+
90
+ /**
91
+ * 范围查询配置接口
92
+ */
93
+ export interface RangeQuery {
94
+ /** 大于 */
95
+ $gt?: any;
96
+ /** 小于 */
97
+ $lt?: any;
98
+ /** 大于等于 */
99
+ $gte?: any;
100
+ /** 小于等于 */
101
+ $lte?: any;
102
+ }
103
+
104
+ /**
105
+ * ArrayUtils 构造函数参数接口
106
+ */
107
+ export interface ArrayUtilsOptions<T = Record<string, any>> {
108
+ /** 子项唯一标志字段 */
109
+ idKey?: string;
110
+ /** 列表数据 */
111
+ list?: T[];
112
+ }
113
+
114
+ /**
115
+ * 数组模拟数据库存储
116
+ * @template T - 列表项数据类型
117
+ */
118
+ export class ArrayUtils<T extends Record<string, any> = Record<string, any>> {
119
+ /** 子项唯一标志字段 */
120
+ protected _idKey: string;
121
+ /** 列表数据 */
122
+ protected _list: T[];
123
+
124
+ constructor(params: ArrayUtilsOptions<T> = {}) {
125
+ const { idKey = "id", list } = params;
126
+ this._idKey = idKey || "id";
127
+ this._list = list ? cloneDeep(list) : [];
128
+ }
129
+
130
+ /**
131
+ * 通过 id 获取子项
132
+ * @param id 目标 ID
133
+ * @returns 匹配的子项或 null
134
+ */
135
+ findItemById(id: string | number): T | null {
136
+ if (this._isInvalidId(id)) {
137
+ return null;
138
+ }
139
+ const { _idKey } = this;
140
+ const item = this._list.find((it) => it[_idKey] === id);
141
+ return item ? cloneDeep(item) : null;
142
+ }
143
+
144
+ /**
145
+ * 通过 query 获取子项
146
+ * @param query 查询条件
147
+ * @param options 查询选项
148
+ * @returns 匹配的子项或 null
149
+ */
150
+ findItem(query: Record<string, any> = {}, options: QueryOptions = {}): T | null {
151
+ const { fuzzy = false, ignoreCase = false } = options;
152
+ const id = query[this._idKey];
153
+
154
+ // 如果有 ID 且 ID 有效,优先通过 ID 查询
155
+ if (!this._isInvalidId(id)) {
156
+ return this.findItemById(id);
157
+ }
158
+
159
+ const validQuery = this._filterEmptyQuery(query);
160
+ if (isEmpty(validQuery)) {
161
+ return null;
162
+ }
163
+
164
+ const matchItem = this._list.find((item) => {
165
+ return this._isItemMatchQuery(item, validQuery, { fuzzy, ignoreCase });
166
+ });
167
+
168
+ return matchItem ? cloneDeep(matchItem) : null;
169
+ }
170
+
171
+ /**
172
+ * 通过 query 筛选列表(支持多条件筛选、分页、排序)
173
+ * @param query - 综合查询参数
174
+ * @param query.pageNum - 页码(默认 1)
175
+ * @param query.pageSize - 每页条数(默认 10)
176
+ * @param query.sortKey - 排序字段(支持嵌套路径,如 'user.age')
177
+ * @param query.sortType - 排序方式(desc/asc)
178
+ * @param query.orderByColumn - 兼容旧排序字段参数
179
+ * @param query.isAsc - 兼容旧排序方式参数
180
+ * @param options - 查询选项
181
+ * @param options.fuzzy - 字符串字段是否开启模糊匹配
182
+ * @param options.ignoreCase - 模糊匹配时是否忽略大小写
183
+ * @returns 分页结果
184
+ */
185
+ findListByQuery(query: ItemQuery = {}, options: QueryOptions = {}): Promise<FindListResult<T>> {
186
+ const { pageNum = 1, pageSize = 10, sortKey, sortType, orderByColumn, isAsc, ...filterQuery } = query;
187
+
188
+ const { fuzzy = true, ignoreCase = false } = options;
189
+
190
+ // 数据筛选(基于 query 条件)
191
+ let filteredList = [...this._list];
192
+ const validFilterQuery = this._filterEmptyQuery(filterQuery);
193
+
194
+ if (!isEmpty(validFilterQuery)) {
195
+ filteredList = filteredList.filter((item) => {
196
+ return this._isItemMatchQuery(item, validFilterQuery, { fuzzy, ignoreCase });
197
+ });
198
+ }
199
+
200
+ // 数据排序(基于筛选后的数据)
201
+ const sortedList = this.getSortList(sortKey || orderByColumn, sortType || isAsc, filteredList);
202
+
203
+ // 分页处理(边界校验)
204
+ const pagination: Pagination = {
205
+ current: Math.max(1, pageNum),
206
+ pageSize: Math.max(1, pageSize),
207
+ total: filteredList.length,
208
+ };
209
+
210
+ // 计算分页截取范围
211
+ const startIndex = (pagination.current - 1) * pagination.pageSize;
212
+ const endIndex = startIndex + pagination.pageSize;
213
+ const paginatedList = sortedList.slice(startIndex, endIndex);
214
+
215
+ return Promise.resolve({
216
+ list: cloneDeep(paginatedList),
217
+ pagination: { ...pagination },
218
+ });
219
+ }
220
+
221
+ /**
222
+ * 对目标数据进行多维度排序
223
+ * @param sortKey - 排序字段配置
224
+ * @param sortType - 排序方式(desc/asc)
225
+ * @param customList - 自定义排序列表(默认使用内部 _list)
226
+ * @returns 排序后的列表
227
+ */
228
+ getSortList(
229
+ sortKey?: string | string[] | SortPathConfig[],
230
+ sortType?: "asc" | "desc" | Record<string, "asc" | "desc"> | boolean,
231
+ customList?: T[],
232
+ ): T[] {
233
+ const sourceList = customList || this._list;
234
+ const _list = cloneDeep(sourceList);
235
+
236
+ // 无排序条件时直接返回原列表
237
+ if (!sortKey && sortType === undefined) {
238
+ return _list;
239
+ }
240
+
241
+ // 处理布尔类型的 sortType(兼容 isAsc 参数)
242
+ let normalizedSortType: "asc" | "desc" | Record<string, "asc" | "desc"> | undefined;
243
+ if (typeof sortType === "boolean") {
244
+ normalizedSortType = sortType ? "asc" : "desc";
245
+ } else {
246
+ normalizedSortType = sortType;
247
+ }
248
+
249
+ // 统一排序字段格式为数组
250
+ let paths: (string | SortPathConfig)[];
251
+ if (Array.isArray(sortKey)) {
252
+ paths = sortKey;
253
+ } else if (sortKey) {
254
+ paths = [sortKey];
255
+ } else {
256
+ paths = [];
257
+ }
258
+
259
+ if (paths.length === 0 && !normalizedSortType) {
260
+ return _list;
261
+ }
262
+
263
+ _list.sort((a, b) => {
264
+ return this.compareData(a, b, { paths, sortType: normalizedSortType, pathIdx: 0 });
265
+ });
266
+
267
+ return _list;
268
+ }
269
+
270
+ /**
271
+ * 比较两个数据对象的排序顺序(支持多字段优先级排序)
272
+ * @param dataA - 比较对象 A
273
+ * @param dataB - 比较对象 B
274
+ * @param opt - 比较配置
275
+ * @returns 排序结果(-1/0/1)
276
+ */
277
+ compareData(
278
+ dataA: T,
279
+ dataB: T,
280
+ opt: {
281
+ paths: (string | SortPathConfig)[];
282
+ pathIdx?: number;
283
+ sortType?: "asc" | "desc" | Record<string, "asc" | "desc">;
284
+ },
285
+ ): number {
286
+ const { paths, pathIdx = 0, sortType = "desc" } = opt;
287
+ const currentPath = paths[pathIdx];
288
+
289
+ // 处理自定义比较逻辑
290
+ if (
291
+ currentPath &&
292
+ typeof currentPath === "object" &&
293
+ typeof (currentPath as SortPathConfig).customCompare === "function"
294
+ ) {
295
+ const direction = sortType === "asc" ? -1 : 1;
296
+ return direction * (currentPath as SortPathConfig).customCompare!(dataA, dataB, opt);
297
+ }
298
+
299
+ // 提取实际字段路径(支持配置对象格式)
300
+ const path = typeof currentPath === "object" ? (currentPath as SortPathConfig).path : currentPath;
301
+ if (!path) {
302
+ // 没有更多字段时,继续下一个或返回 0
303
+ if (pathIdx + 1 < paths.length) {
304
+ return this.compareData(dataA, dataB, { ...opt, pathIdx: pathIdx + 1 });
305
+ }
306
+ return 0;
307
+ }
308
+
309
+ // 确定排序方向(支持按字段单独配置排序方式)
310
+ let direction = 1;
311
+ let currentSortType = sortType;
312
+ if (typeof currentSortType === "object") {
313
+ currentSortType = (currentSortType as Record<string, "asc" | "desc">)[path] || "desc";
314
+ }
315
+ if (currentSortType === "asc") {
316
+ direction = -1;
317
+ }
318
+
319
+ // 获取并处理字段值(支持日期转换、自定义格式化)
320
+ const valueA = this.handleValByKey(dataA, currentPath);
321
+ const valueB = this.handleValByKey(dataB, currentPath);
322
+
323
+ // 数值比较
324
+ if (valueA < valueB) {
325
+ return -1 * direction;
326
+ }
327
+ if (valueA > valueB) {
328
+ return 1 * direction;
329
+ }
330
+
331
+ // 当前字段值相等时,使用下一个优先级字段排序
332
+ if (pathIdx + 1 < paths.length) {
333
+ return this.compareData(dataA, dataB, { ...opt, pathIdx: pathIdx + 1 });
334
+ }
335
+
336
+ // 所有字段都相等时,保持原有顺序
337
+ return 0;
338
+ }
339
+
340
+ /**
341
+ * 根据路径获取并处理字段值(支持嵌套路径、类型转换、自定义格式化)
342
+ * @param data - 数据源对象
343
+ * @param pathConfig - 字段配置
344
+ * @returns 处理后的字段值
345
+ */
346
+ handleValByKey(data: T, pathConfig: string | SortPathConfig | null | undefined): any {
347
+ // 处理字符串路径
348
+ if (typeof pathConfig === "string") {
349
+ return get(data, pathConfig);
350
+ }
351
+
352
+ // 处理空配置
353
+ if (isNil(pathConfig)) {
354
+ return undefined;
355
+ }
356
+
357
+ // 处理对象配置
358
+ if (typeof pathConfig === "object") {
359
+ let val = get(data, (pathConfig as SortPathConfig).path);
360
+
361
+ // 日期类型转换为时间戳(便于数值比较)
362
+ if ((pathConfig as SortPathConfig).type === "date" && val) {
363
+ const parsed = dayjs(val);
364
+ val = parsed.isValid() ? parsed.valueOf() : 0;
365
+ }
366
+
367
+ // 自定义值格式化
368
+ if (typeof (pathConfig as SortPathConfig).customFormat === "function") {
369
+ val = (pathConfig as SortPathConfig).customFormat!(val, data);
370
+ }
371
+
372
+ return val;
373
+ }
374
+
375
+ return undefined;
376
+ }
377
+
378
+ /**
379
+ * 获取完整列表
380
+ * @returns 完整列表的深拷贝
381
+ */
382
+ findAllList(): T[] {
383
+ return cloneDeep(this._list);
384
+ }
385
+
386
+ /**
387
+ * 获取总数
388
+ * @returns 列表总数
389
+ */
390
+ getCount(): number {
391
+ return this._list.length;
392
+ }
393
+
394
+ /**
395
+ * push 数据
396
+ * @param data 要添加的数据
397
+ * @returns 状态码
398
+ */
399
+ pushItem(data: Partial<T> | Partial<T>[]): StateCode {
400
+ const items = Array.isArray(data) ? data : [data];
401
+ items.forEach((item) => {
402
+ const cloneItem = cloneDeep(item) as T;
403
+ this._setId(cloneItem);
404
+ this._setCreateTime(cloneItem);
405
+ this._list.push(cloneItem);
406
+ });
407
+ return STATE_CODE.SUC;
408
+ }
409
+
410
+ /**
411
+ * unshift 数据
412
+ * @param data 要添加的数据
413
+ * @returns 状态码
414
+ */
415
+ unshiftItem(data: Partial<T>): StateCode {
416
+ const item = cloneDeep(data) as T;
417
+ this._setId(item);
418
+ this._setCreateTime(item);
419
+ this._list.unshift(item);
420
+ return STATE_CODE.SUC;
421
+ }
422
+
423
+ /**
424
+ * 根据 id 更新子项——直接替换
425
+ * @param data 新的数据对象
426
+ * @returns 状态码
427
+ */
428
+ replaceItem(data: T): StateCode {
429
+ const { _idKey, _list } = this;
430
+ const id = data[_idKey];
431
+ if (this._isInvalidId(id)) {
432
+ return STATE_CODE.ERR_INPUT_ID;
433
+ }
434
+ const idx = _list.findIndex((it) => it[_idKey] === id);
435
+ if (idx < 0) {
436
+ return STATE_CODE.NOT_FOUNT;
437
+ }
438
+ _list.splice(idx, 1, cloneDeep(data));
439
+ this._setUpdateTime(_list[idx]);
440
+ return STATE_CODE.SUC;
441
+ }
442
+
443
+ /**
444
+ * 根据 id 更新子项——仅修改传入的数据
445
+ * @param data 要更新的数据
446
+ * @returns 状态码
447
+ */
448
+ updateItemValue(data: Partial<T> & { [key: string]: any }): StateCode {
449
+ const { _idKey, _list } = this;
450
+ const id = data[_idKey];
451
+ if (this._isInvalidId(id)) {
452
+ return STATE_CODE.ERR_INPUT_ID;
453
+ }
454
+ const index = _list.findIndex((it) => it[_idKey] === id);
455
+ if (index < 0) {
456
+ return STATE_CODE.NOT_FOUNT;
457
+ }
458
+ const item = _list[index];
459
+ Object.keys(data).forEach((key) => {
460
+ if (key !== _idKey) {
461
+ (item as any)[key] = data[key];
462
+ }
463
+ });
464
+ this._setUpdateTime(item);
465
+ return STATE_CODE.SUC;
466
+ }
467
+
468
+ /**
469
+ * 删除子项
470
+ * @param id 要删除的子项 ID
471
+ * @returns 状态码
472
+ */
473
+ delItem(id: any): StateCode {
474
+ const { _idKey, _list } = this;
475
+ if (this._isInvalidId(id)) {
476
+ return STATE_CODE.ERR_INPUT_ID;
477
+ }
478
+ const idx = _list.findIndex((it) => it[_idKey] === id);
479
+ if (idx < 0) {
480
+ return STATE_CODE.NOT_FOUNT;
481
+ }
482
+ _list.splice(idx, 1);
483
+ return STATE_CODE.SUC;
484
+ }
485
+
486
+ /**
487
+ * 清空所有数据
488
+ * @returns 状态码
489
+ */
490
+ clearAll(): StateCode {
491
+ this._list = [];
492
+ return STATE_CODE.SUC;
493
+ }
494
+
495
+ /**
496
+ * 批量删除
497
+ * @param ids 要删除的 ID 数组
498
+ * @returns 删除成功的数量
499
+ */
500
+ deleteItems(ids: any[]): number {
501
+ let successCount = 0;
502
+ ids.forEach((id) => {
503
+ const res = this.delItem(id);
504
+ if (res === STATE_CODE.SUC) {
505
+ successCount++;
506
+ }
507
+ });
508
+ return successCount;
509
+ }
510
+
511
+ /**
512
+ * 判断列表是否为空
513
+ * @returns 是否为空
514
+ */
515
+ isEmpty(): boolean {
516
+ return this._list.length === 0;
517
+ }
518
+
519
+ /**
520
+ * 获取指定范围的列表
521
+ * @param start 起始索引
522
+ * @param end 结束索引
523
+ * @returns 切片后的列表
524
+ */
525
+ slice(start?: number, end?: number): T[] {
526
+ return cloneDeep(this._list.slice(start, end));
527
+ }
528
+
529
+ /**
530
+ * 检查单个 item 是否匹配所有 query 条件
531
+ * @param item - 待检查的列表项
532
+ * @param query - 查询条件
533
+ * @param options - 匹配选项
534
+ * @param options.fuzzy - 是否开启模糊匹配
535
+ * @param options.ignoreCase - 模糊匹配是否忽略大小写
536
+ * @returns 是否匹配所有条件
537
+ */
538
+ protected _isItemMatchQuery(item: T, query: Record<string, any>, { fuzzy, ignoreCase }: QueryOptions): boolean {
539
+ // 遍历所有查询条件,必须全部满足才返回 true
540
+ return Object.entries(query).every(([key, targetValue]) => {
541
+ // 获取 item 中对应字段的值(支持嵌套路径,如 'user.name')
542
+ const itemValue = get(item, key);
543
+
544
+ // 处理空值情况(null/undefined 仅匹配 null/undefined)
545
+ if (isNil(itemValue)) {
546
+ return isNil(targetValue);
547
+ }
548
+
549
+ // 处理数组类型查询(支持 in 操作,如 { status: [1, 2] })
550
+ if (Array.isArray(targetValue)) {
551
+ return targetValue.some((val) => this._compareSingleValue(itemValue, val, fuzzy, ignoreCase));
552
+ }
553
+
554
+ // 处理范围查询(如 { age: { $gt: 18, $lt: 30 } })
555
+ if (typeof targetValue === "object" && targetValue !== null && !Array.isArray(targetValue)) {
556
+ return this._handleRangeQuery(itemValue, targetValue as RangeQuery);
557
+ }
558
+
559
+ // 处理普通值匹配(精确/模糊)
560
+ return this._compareSingleValue(itemValue, targetValue, fuzzy, ignoreCase);
561
+ });
562
+ }
563
+
564
+ /**
565
+ * 比较单个值是否匹配(支持精确/模糊匹配)
566
+ * @param itemValue - 列表项字段值
567
+ * @param targetValue - 查询目标值
568
+ * @param fuzzy - 是否模糊匹配
569
+ * @param ignoreCase - 是否忽略大小写
570
+ * @returns 是否匹配
571
+ */
572
+ protected _compareSingleValue(itemValue: any, targetValue: any, fuzzy: boolean, ignoreCase: boolean): boolean {
573
+ // 类型不同时直接不匹配(避免隐式类型转换导致的问题)
574
+ if (typeof itemValue !== typeof targetValue) {
575
+ // 特殊处理:数字和字符串数字的匹配(如 123 和 '123')
576
+ if (
577
+ (typeof itemValue === "number" && typeof targetValue === "string" && !isNaN(Number(targetValue))) ||
578
+ (typeof itemValue === "string" && typeof targetValue === "number" && !isNaN(Number(itemValue)))
579
+ ) {
580
+ return Number(itemValue) === targetValue;
581
+ }
582
+ return false;
583
+ }
584
+
585
+ // 字符串处理(支持模糊匹配和大小写忽略)
586
+ if (typeof itemValue === "string" && fuzzy) {
587
+ const itemStr = ignoreCase ? itemValue.toLowerCase() : itemValue;
588
+ const targetStr = ignoreCase ? String(targetValue).toLowerCase() : String(targetValue);
589
+ return itemStr.includes(targetStr);
590
+ }
591
+
592
+ // 其他类型(数字、布尔等)精确匹配
593
+ return itemValue === targetValue;
594
+ }
595
+
596
+ /**
597
+ * 处理范围查询(如 $gt/$lt/$gte/$lte)
598
+ * @param itemValue - 列表项字段值
599
+ * @param rangeConfig - 范围配置
600
+ * @returns 是否在范围内
601
+ */
602
+ protected _handleRangeQuery(itemValue: any, rangeConfig: RangeQuery): boolean {
603
+ const { $gt, $lt, $gte, $lte } = rangeConfig;
604
+ let isValid = true;
605
+
606
+ // 转换为可比较的类型(优先处理日期)
607
+ const compareValue = this._convertToComparableValue(itemValue);
608
+ const convertTarget = (val: any) => this._convertToComparableValue(val);
609
+
610
+ // 大于(>)
611
+ if ($gt !== undefined) {
612
+ isValid = isValid && compareValue > convertTarget($gt);
613
+ }
614
+ // 小于(<)
615
+ if ($lt !== undefined) {
616
+ isValid = isValid && compareValue < convertTarget($lt);
617
+ }
618
+ // 大于等于(>=)
619
+ if ($gte !== undefined) {
620
+ isValid = isValid && compareValue >= convertTarget($gte);
621
+ }
622
+ // 小于等于(<=)
623
+ if ($lte !== undefined) {
624
+ isValid = isValid && compareValue <= convertTarget($lte);
625
+ }
626
+
627
+ return isValid;
628
+ }
629
+
630
+ /**
631
+ * 将值转换为可比较的类型(统一数字、日期格式)
632
+ * @param value - 待转换的值
633
+ * @returns 可比较的值
634
+ */
635
+ protected _convertToComparableValue(value: any): any {
636
+ // null/undefined 处理
637
+ if (isNil(value)) {
638
+ return value;
639
+ }
640
+ // 日期转换为时间戳
641
+ if (value instanceof Date || (typeof value === "string" && dayjs(value).isValid())) {
642
+ return dayjs(value).valueOf();
643
+ }
644
+ // 字符串数字转换为数字
645
+ if (typeof value === "string" && !isNaN(Number(value))) {
646
+ return Number(value);
647
+ }
648
+ return value;
649
+ }
650
+
651
+ /**
652
+ * 过滤空查询条件(移除 undefined/null/空字符串/空数组/空对象)
653
+ * @param query - 原始查询条件
654
+ * @returns 过滤后的有效查询条件
655
+ */
656
+ protected _filterEmptyQuery(query: Record<string, any>): Record<string, any> {
657
+ return Object.entries(query).reduce((acc: Record<string, any>, [key, value]) => {
658
+ if (
659
+ !isNil(value) &&
660
+ value !== "" &&
661
+ !(Array.isArray(value) && value.length === 0) &&
662
+ !(typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 0)
663
+ ) {
664
+ acc[key] = value;
665
+ }
666
+ return acc;
667
+ }, {});
668
+ }
669
+
670
+ /**
671
+ * 检查 ID 是否无效
672
+ * @param id 要检查的 ID
673
+ * @returns ID 是否无效(为空)
674
+ */
675
+ protected _isInvalidId(id: any): boolean {
676
+ return isNil(id) || id === "";
677
+ }
678
+
679
+ /**
680
+ * 子项没有 id 的时候自动添加
681
+ * @param item 要设置 ID 的子项
682
+ * @returns 设置 ID 后的子项
683
+ */
684
+ protected _setId(item: T): T {
685
+ if (isObject(item) && this._isInvalidId((item as any)[this._idKey])) {
686
+ (item as any)[this._idKey] = nanoid();
687
+ }
688
+ return item;
689
+ }
690
+
691
+ /**
692
+ * 设置创建时间
693
+ * @param item 要设置创建时间的子项
694
+ */
695
+ protected _setCreateTime(item: T): void {
696
+ if (isObject(item) && this._isInvalidId((item as any).createTime)) {
697
+ (item as any).createTime = Date.now();
698
+ }
699
+ }
700
+
701
+ /**
702
+ * 设置更新时间
703
+ * @param item 要设置更新时间的子项
704
+ */
705
+ protected _setUpdateTime(item: T): void {
706
+ if (isObject(item)) {
707
+ (item as any).updateTime = Date.now();
708
+ }
709
+ }
710
+ }
711
+
712
+ export default ArrayUtils;