pen-it 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1,1000 +1 @@
1
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- //#region src/is/index.ts
3
- /**
4
- * 判断一个值是否为数组
5
- * @param value - 要判断的值
6
- * @returns 如果是数组返回 true,否则返回 false
7
- */
8
- const isArray = (value) => {
9
- return Array.isArray(value);
10
- };
11
- /**
12
- * 判断一个值是否为对象(但不是数组)
13
- * @param value - 要判断的值
14
- * @returns 如果是对象且不是数组返回 true,否则返回 false
15
- */
16
- const isObject = (value) => {
17
- return value !== null && typeof value === "object" && !Array.isArray(value);
18
- };
19
- /**
20
- * 判断数据类型(内部工具函数)
21
- * @param val - 要判断的数据
22
- * @param type - 数据类型字符串,如 "Array"、"Function"
23
- * @returns 如果数据是指定类型返回 true
24
- */
25
- const is = (val, type) => {
26
- return Object.prototype.toString.call(val) === `[object ${type}]`;
27
- };
28
- /**
29
- * 判断一个值是否为函数
30
- * @param val - 要判断的数据
31
- * @returns 如果是函数返回 true
32
- */
33
- const isFunction = (val) => {
34
- return is(val, "Function") || !!(val && val.constructor && val.call && val.apply);
35
- };
36
- /**
37
- * 判断一个值是否为 Date 对象
38
- * @param val - 要判断的数据
39
- * @returns 如果是 Date 对象返回 true
40
- */
41
- const isDate = (val) => {
42
- return is(val, "Date") || !!val && val.constructor === Date;
43
- };
44
- /**
45
- * 判断一个值是否为数字(NaN 和 Infinity 也视为数字)
46
- * @param val - 要判断的数据
47
- * @returns 如果是数字返回 true
48
- */
49
- const isNumber = (val) => {
50
- return Number(val) === val;
51
- };
52
- /**
53
- * 判断一个值是否为整数
54
- * @param val - 要判断的数据
55
- * @returns 如果是整数返回 true
56
- */
57
- const isInt = (val) => {
58
- return isNumber(val) && Number.isFinite(val) && val % 1 === 0;
59
- };
60
- /**
61
- * 判断一个值是否为浮点数(有限小数)
62
- * @param val - 要判断的数据
63
- * @returns 如果是浮点数返回 true
64
- */
65
- const isFloat = (val) => {
66
- return isNumber(val) && Number.isFinite(val) && val % 1 !== 0;
67
- };
68
- /**
69
- * 判断一个值是否为异步函数
70
- * @param val - 要判断的数据
71
- * @returns 如果是异步函数返回 true
72
- */
73
- const isAsyncFunction = (val) => {
74
- return is(val, "AsyncFunction");
75
- };
76
- /**
77
- * 判断一个值是否为 Promise
78
- * @param value - 要判断的数据
79
- * @returns 如果是 Promise 返回 true
80
- */
81
- const isPromise = (value) => {
82
- if (!value) return false;
83
- if (!value.then) return false;
84
- if (!isFunction(value.then)) return false;
85
- return true;
86
- };
87
- /**
88
- * 判断一个值是否为字符串
89
- * @param val - 要判断的数据
90
- * @returns 如果是字符串返回 true
91
- */
92
- const isString = (val) => {
93
- return is(val, "String") || typeof val === "string" || val instanceof String;
94
- };
95
- /**
96
- * 判断一个值是否为布尔值
97
- * @param val - 要判断的数据
98
- * @returns 如果是布尔值返回 true
99
- */
100
- const isBoolean = (val) => {
101
- return typeof val === "boolean" || is(val, "Boolean");
102
- };
103
- /**
104
- * 判断是否为 PC 端(浏览器环境)
105
- * @returns 如果在浏览器环境中返回 true
106
- */
107
- const isPC = () => {
108
- return typeof window !== "undefined";
109
- };
110
- /**
111
- * 判断一个值是否为 window 对象
112
- * @param val - 要判断的数据
113
- * @returns 如果是 window 对象返回 true
114
- */
115
- const isWindow = (val) => {
116
- return typeof window !== "undefined" && is(val, "Window");
117
- };
118
- /**
119
- * 判断一个值是否为 DOM 元素
120
- * @param val - 要判断的数据
121
- * @returns 如果是 DOM 元素返回 true
122
- */
123
- const isElement = (val) => {
124
- return isObject(val) && !!val.tagName;
125
- };
126
- /**
127
- * 判断一个值是否为 null
128
- * @param val - 要判断的数据
129
- * @returns 如果是 null 返回 true
130
- */
131
- const isNull = (val) => {
132
- return val === null;
133
- };
134
- /**
135
- * 判断一个值是否为十六进制颜色
136
- * @param str - 要判断的字符串
137
- * @returns 如果是有效的十六进制颜色值返回 true
138
- */
139
- const isHexColor = (str) => {
140
- return /^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(str);
141
- };
142
- /**
143
- * 判断设备是否为 iOS
144
- * @returns 如果是 iOS 设备返回 true
145
- */
146
- const isIOS = () => {
147
- return /iPad|iPhone|iPod/.test(navigator.userAgent);
148
- };
149
- /**
150
- * 判断一个值是否为有效的电子邮件
151
- * @param email - 要判断的字符串
152
- * @returns 如果是有效的电子邮件地址返回 true
153
- */
154
- const isValidEmail = (email) => {
155
- return /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(email);
156
- };
157
- /**
158
- * 判断一个值是否已定义(不是 undefined)
159
- * @param val - 要判断的数据
160
- * @returns 如果不是 undefined 返回 true
161
- */
162
- const isDef = (val) => {
163
- return typeof val !== "undefined";
164
- };
165
- /**
166
- * 判断一个值是否为 undefined
167
- * @param val - 要判断的数据
168
- * @returns 如果是 undefined 返回 true
169
- */
170
- const isUnDef = (val) => {
171
- return typeof val === "undefined";
172
- };
173
- /**
174
- * 判断一个值是否为 null 或 undefined
175
- * @param val - 要判断的数据
176
- * @returns 如果是 null 或 undefined 返回 true
177
- */
178
- const isNullOrUnDef = (val) => {
179
- return isUnDef(val) || isNull(val);
180
- };
181
- /**
182
- * 判断一个值是否为 symbol
183
- * @param value - 要判断的数据
184
- * @returns 如果是 symbol 返回 true
185
- */
186
- const isSymbol = (value) => {
187
- return !!value && value.constructor === Symbol;
188
- };
189
- /**
190
- * 判断一个值是否为原始类型
191
- * 原始类型包括:number、string、boolean、symbol、bigint、undefined、null
192
- * @param value - 要判断的值
193
- * @returns 如果是原始类型返回 true
194
- */
195
- const isPrimitive = (value) => {
196
- return value === void 0 || value === null || typeof value !== "object" && typeof value !== "function";
197
- };
198
- /**
199
- * 判断一个值是否为空
200
- * 空值包括:null、undefined、空字符串、空数组、空对象、空 Map/Set、无效 Date
201
- * @param value - 要判断的数据
202
- * @returns 如果为空返回 true
203
- */
204
- const isEmpty = (value) => {
205
- if (value === null || value === void 0) return true;
206
- if (isDate(value)) return isNaN(value.getTime());
207
- if (isFunction(value) || isSymbol(value)) return false;
208
- if (isNumber(value)) return false;
209
- const length = value.length;
210
- if (isNumber(length)) return length === 0;
211
- const size = value.size;
212
- if (isNumber(size)) return size === 0;
213
- return Object.keys(value).length === 0;
214
- };
215
- /**
216
- * 判断一个值是否为空(简化版)
217
- * 支持:字符串、数组、对象、Map、Set、类型化数组
218
- * @param value - 要检查的值
219
- * @returns 如果为空返回 true
220
- */
221
- function isEmptySv(value) {
222
- if (value === null || value === void 0) return true;
223
- if (typeof value === "string" || Array.isArray(value)) return value.length === 0;
224
- if (value instanceof Map || value instanceof Set) return value.size === 0;
225
- if (ArrayBuffer.isView(value)) return value.byteLength === 0;
226
- if (typeof value === "object") return Object.keys(value).length === 0;
227
- return false;
228
- }
229
- /**
230
- * 判断两个值是否深度相等
231
- * @param x - 要比较的第一个值
232
- * @param y - 要比较的第二个值
233
- * @returns 如果值深度相等返回 true
234
- */
235
- const isEqual = (x, y) => {
236
- if (Object.is(x, y)) return true;
237
- if (x instanceof Date && y instanceof Date) return x.getTime() === y.getTime();
238
- if (x instanceof RegExp && y instanceof RegExp) return x.toString() === y.toString();
239
- if (typeof x !== "object" || x === null || typeof y !== "object" || y === null) return false;
240
- const keysX = Reflect.ownKeys(x);
241
- const keysY = Reflect.ownKeys(y);
242
- if (keysX.length !== keysY.length) return false;
243
- for (let i = 0; i < keysX.length; i++) {
244
- if (!Reflect.has(y, keysX[i])) return false;
245
- if (!isEqual(x[keysX[i]], y[keysX[i]])) return false;
246
- }
247
- return true;
248
- };
249
- //#endregion
250
- //#region src/format/index.ts
251
- /**
252
- * 完整日期时间(年/月/日 时分秒)_ 斜杠
253
- * @param date - Date 对象
254
- * @returns 格式化后的完整日期时间字符串,如 "2026/06/03 14:30:45"
255
- */
256
- const formatFull = (date) => {
257
- return new Intl.DateTimeFormat("zh-CN", {
258
- year: "numeric",
259
- month: "2-digit",
260
- day: "2-digit",
261
- hour: "2-digit",
262
- minute: "2-digit",
263
- second: "2-digit",
264
- hour12: false
265
- }).format(date);
266
- };
267
- /**
268
- * 完整日期时间(年-月-日 时分秒)_ 短横线
269
- * @param date - Date 对象
270
- * @returns 格式化后的完整日期时间字符串,如 "2026-06-03 14:30:45"
271
- */
272
- const formatFullReplace = (date) => {
273
- return formatFull(date).replace(/\//g, "-");
274
- };
275
- /**
276
- * 中文年月日
277
- * @param date - Date 对象
278
- * @returns 格式化后的中文日期字符串,如 "2026年6月3日"
279
- */
280
- const formatYMD = (date) => {
281
- return new Intl.DateTimeFormat("zh-CN", {
282
- year: "numeric",
283
- month: "long",
284
- day: "numeric"
285
- }).format(date);
286
- };
287
- /**
288
- * 星期几
289
- * @param date - Date 对象
290
- * @returns 中文星期字符串,如 "星期三"
291
- */
292
- const formatWeek = (date) => {
293
- return new Intl.DateTimeFormat("zh-CN", { weekday: "long" }).format(date);
294
- };
295
- /**
296
- * 货币格式化
297
- * @param value - 数值
298
- * @param options - 格式化选项
299
- * @returns 格式化后的货币字符串
300
- */
301
- const formatRmb = (value, options) => {
302
- return new Intl.NumberFormat(options.type, {
303
- style: "currency",
304
- currency: options.currency
305
- }).format(value);
306
- };
307
- /**
308
- * 千位分隔符(数字格式化),中文(逗号分隔)
309
- * @param value - 数值
310
- * @returns 格式化后的千位分隔数字字符串
311
- */
312
- const formatNum = (value) => {
313
- return new Intl.NumberFormat("zh-CN").format(value);
314
- };
315
- /**
316
- * 百分比格式化
317
- * @param value - 百分比数值
318
- * @param digit - 保留的小数位数,默认为 0
319
- * @returns 格式化后的百分比字符串
320
- */
321
- const percentCN = (value, digit = 0) => {
322
- return new Intl.NumberFormat("zh-CN", {
323
- style: "percent",
324
- minimumFractionDigits: digit
325
- }).format(value);
326
- };
327
- /**
328
- * 紧凑计数法(大数简化)_ 英文缩写
329
- * @param value - 数值
330
- * @returns 格式化后的紧凑数字字符串(英文缩写)
331
- */
332
- const compactEN = (value) => {
333
- return new Intl.NumberFormat("en-US", {
334
- notation: "compact",
335
- compactDisplay: "short"
336
- }).format(value);
337
- };
338
- /**
339
- * 紧凑计数法(大数简化)_ 中文缩写
340
- * @param value - 数值
341
- * @returns 格式化后的紧凑数字字符串(中文缩写)
342
- */
343
- const compactCN = (value) => {
344
- return new Intl.NumberFormat("zh-CN", {
345
- notation: "compact",
346
- compactDisplay: "short"
347
- }).format(value);
348
- };
349
- /**
350
- * 带符号的正负数显示
351
- * @param value - 数值
352
- * @param digit - 保留的小数位数,默认为 0
353
- * @returns 格式化后的带正负号的数值显示
354
- */
355
- const signed = (value, digit = 0) => {
356
- return new Intl.NumberFormat("en-US", {
357
- signDisplay: "always",
358
- minimumFractionDigits: digit
359
- }).format(value);
360
- };
361
- /**
362
- * 手机号格式化脱敏 _ 隐藏中间 4 位数
363
- * @param phone - 手机号码
364
- * @returns 手机号格式化脱敏后的手机号
365
- */
366
- const maskPhone = (phone) => {
367
- return phone.replace(/(\d{3})\d{4}(\d{4})/, "$1****$2");
368
- };
369
- /**
370
- * 手机号格式化 _ 空格分隔
371
- * @param phone - 手机号
372
- * @returns 手机号空格格式化的手机号
373
- */
374
- const spacePhone = (phone) => {
375
- return phone.replace(/(\d{3})(\d{4})(\d{4})/, "$1 $2 $3");
376
- };
377
- /**
378
- * 每个单词首字母大写
379
- * @param str - 字符串
380
- * @returns 每个单词首字母大写后的字符串
381
- */
382
- const capitalize = (str) => {
383
- return str.replace(/\b\w/g, (char) => char.toUpperCase());
384
- };
385
- /**
386
- * 短横线连接转小驼峰(kebab-case → camelCase)
387
- * @param str - 字符串
388
- * @returns 转换后的小驼峰字符串
389
- */
390
- const kebabToCamel = (str) => {
391
- return str.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
392
- };
393
- /**
394
- * 大驼峰或小驼峰 _ 转为 ‘-’ 短横线连接
395
- * @param str - 字符串
396
- * @returns 大驼峰或小驼峰命名格式化的字符串
397
- */
398
- const camelToKebab = (str) => {
399
- return str.replace(/([A-Z])/g, "-$1").toLowerCase();
400
- };
401
- /**
402
- * 超长文本截断
403
- * @param str - 内容
404
- * @param maxLength - 截断位置
405
- * @param suffix - 自定义的替换截断后的内容
406
- * @returns 超长文本截断后内容
407
- */
408
- const truncate = (str, maxLength, suffix = "...") => {
409
- if (str.length <= maxLength) return str;
410
- return str.substring(0, maxLength - suffix.length) + suffix;
411
- };
412
- /**
413
- * 按字数截断(中文友好)
414
- * @param str - 内容
415
- * @param maxWords - 截断位置
416
- * @param suffix - 自定义的替换截断后的内容
417
- * @returns 超长文本截断后内容
418
- */
419
- const truncateByWords = (str, maxWords, suffix = "...") => {
420
- const chars = str.split("");
421
- if (chars.length <= maxWords) return str;
422
- return chars.slice(0, maxWords).join("") + suffix;
423
- };
424
- /**
425
- * 去除所有空格 _ 前后中间
426
- * @param str - 内容
427
- * @returns 去除空格后的内容
428
- */
429
- const trimAll = (str) => str.replace(/\s+/g, "");
430
- /**
431
- * 下划线转驼峰(snake_case → camelCase)
432
- * @param str - 字符串
433
- * @returns 转换后的驼峰字符串
434
- */
435
- const toCamel = (str) => {
436
- return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
437
- };
438
- /**
439
- * 首字母大写
440
- * @param str - 字符串
441
- * @returns 首字母大写后的字符串
442
- */
443
- const firstUpper = (str) => {
444
- return str.charAt(0).toUpperCase() + str.slice(1);
445
- };
446
- /**
447
- * 首字母小写
448
- * @param str - 字符串
449
- * @returns 首字母小写后的字符串
450
- */
451
- const firstLower = (str) => {
452
- return str.charAt(0).toLowerCase() + str.slice(1);
453
- };
454
- /**
455
- * 反转字符串
456
- * @param str - 字符串
457
- * @returns 反转后的字符串
458
- */
459
- const reverse = (str) => {
460
- return str.split("").reverse().join("");
461
- };
462
- //#endregion
463
- //#region src/storage/index.ts
464
- /**
465
- * 获取 localStorage
466
- * @param key - 存储键名
467
- * @returns 解析后的值,解析失败则返回原始字符串,键不存在返回 null
468
- */
469
- const localGet = (key) => {
470
- const value = localStorage.getItem(key);
471
- if (value === null) return null;
472
- try {
473
- return JSON.parse(value);
474
- } catch {
475
- return value;
476
- }
477
- };
478
- /**
479
- * 设置 localStorage
480
- * @param key - 存储键名
481
- * @param value - 存储值(会自动 JSON 序列化)
482
- */
483
- const localSet = (key, value) => {
484
- localStorage.setItem(key, JSON.stringify(value));
485
- };
486
- /**
487
- * 移除指定 localStorage
488
- * @param key - 存储键名
489
- */
490
- const localRm = (key) => {
491
- localStorage.removeItem(key);
492
- };
493
- /**
494
- * 清除所有 localStorage
495
- */
496
- const localClear = () => {
497
- localStorage.clear();
498
- };
499
- //#endregion
500
- //#region src/copy/index.ts
501
- /**
502
- * 递归深拷贝
503
- * 支持 Date、RegExp、Map、Set、Array、Object 以及循环引用
504
- * @param obj - 要拷贝的值
505
- * @param hash - 内部 WeakMap 用于处理循环引用,调用方无需传入
506
- * @returns 深拷贝后的值
507
- */
508
- function deepClone(obj, hash = /* @__PURE__ */ new WeakMap()) {
509
- if (obj === null || typeof obj !== "object") return obj;
510
- if (hash.has(obj)) return hash.get(obj);
511
- if (obj instanceof Date) return new Date(obj.getTime());
512
- if (obj instanceof RegExp) return new RegExp(obj.source, obj.flags);
513
- if (obj instanceof Map) {
514
- const clone = /* @__PURE__ */ new Map();
515
- hash.set(obj, clone);
516
- obj.forEach((value, key) => {
517
- clone.set(key, deepClone(value, hash));
518
- });
519
- return clone;
520
- }
521
- if (obj instanceof Set) {
522
- const clone = /* @__PURE__ */ new Set();
523
- hash.set(obj, clone);
524
- obj.forEach((value) => {
525
- clone.add(deepClone(value, hash));
526
- });
527
- return clone;
528
- }
529
- if (Array.isArray(obj)) {
530
- const clone = [];
531
- hash.set(obj, clone);
532
- obj.forEach((item, index) => {
533
- clone[index] = deepClone(item, hash);
534
- });
535
- return clone;
536
- }
537
- const clone = {};
538
- hash.set(obj, clone);
539
- Object.keys(obj).forEach((key) => {
540
- clone[key] = deepClone(obj[key], hash);
541
- });
542
- return clone;
543
- }
544
- /**
545
- * JSON 深拷贝
546
- * 使用 JSON 序列化与反序列化实现,仅支持 JSON 安全的数据类型
547
- * (不支持函数、undefined、Date、RegExp、Map、Set 等)
548
- * @param obj - 要拷贝的 JSON 安全值
549
- * @returns 深拷贝后的值
550
- */
551
- function deepCloneWithJSON(obj) {
552
- return JSON.parse(JSON.stringify(obj));
553
- }
554
- /**
555
- * 浅拷贝
556
- * 仅拷贝第一层属性,嵌套对象仍共享引用
557
- * @param obj - 要拷贝的对象或数组
558
- * @returns 浅拷贝后的值
559
- */
560
- function shallowClone(obj) {
561
- if (obj === null || typeof obj !== "object") return obj;
562
- if (Array.isArray(obj)) return [...obj];
563
- return { ...obj };
564
- }
565
- //#endregion
566
- //#region src/array/index.ts
567
- /**
568
- * Set 去重
569
- * @param arr - 数组
570
- * @returns 去重后的数组
571
- */
572
- const unique = (arr) => {
573
- return [...new Set(arr)];
574
- };
575
- /**
576
- * 对象数组按 key 去重
577
- * @param arr - 对象数组
578
- * @param key - 用于去重判定的键名
579
- * @returns 去重后的数组
580
- */
581
- const uniqueByKey = (arr, key) => {
582
- return [...new Map(arr.map((item) => [item[key], item])).values()];
583
- };
584
- /**
585
- * 数值升序
586
- * @param arr - 数值数组
587
- * @returns 升序排序后的新数组
588
- */
589
- const sortNumAsc = (arr) => {
590
- return [...arr].sort((a, b) => a - b);
591
- };
592
- /**
593
- * 数值降序
594
- * @param arr - 数值数组
595
- * @returns 降序排序后的新数组
596
- */
597
- const sortNumDesc = (arr) => {
598
- return [...arr].sort((a, b) => b - a);
599
- };
600
- /**
601
- * 按对象属性排序
602
- * @param arr - 对象数组
603
- * @param key - 用于排序的属性名
604
- * @param order - 排序方向,"asc" 升序 / "desc" 降序,默认 "asc"
605
- * @returns 排序后的新数组
606
- */
607
- const sortByKey = (arr, key, order = "asc") => {
608
- return [...arr].sort((a, b) => order === "asc" ? a[key] - b[key] : b[key] - a[key]);
609
- };
610
- /**
611
- * 类数组转数组
612
- * @param arrayLike - 类数组对象(如 NodeList、arguments)
613
- * @returns 转换后的数组
614
- */
615
- const toArray = (arrayLike) => {
616
- return Array.from(arrayLike);
617
- };
618
- /**
619
- * 合并多个数组
620
- * @param arrays - 一个或多个数组
621
- * @returns 合并后的新数组
622
- */
623
- const mergeArrays = (...arrays) => {
624
- return [].concat(...arrays);
625
- };
626
- /**
627
- * 将多维数组扁平化到指定层级
628
- * @param arr - 多维数组
629
- * @param depth - 扁平化深度,默认 1(仅展开一层)。传 Infinity 可完全扁平化为一维
630
- * @returns 扁平化后的数组
631
- */
632
- const flatten = (arr, depth = 1) => {
633
- return arr.flat(depth);
634
- };
635
- /**
636
- * 在对象数组中按 key-value 查找第一个匹配项
637
- * @param arr - 对象数组
638
- * @param key - 要匹配的键名
639
- * @param value - 要匹配的键值
640
- * @returns 第一个匹配的对象,无匹配则返回 undefined
641
- */
642
- const arrFind = (arr, key, value) => {
643
- return arr.find((item) => item[key] === value);
644
- };
645
- /**
646
- * 将对象数组按指定属性分组
647
- * @param arr - 对象数组
648
- * @param key - 用于分组的属性名
649
- * @returns 以属性值为键、对应数组为值的对象
650
- */
651
- const groupBy = (arr, key) => {
652
- return arr.reduce((acc, item) => {
653
- const groupKey = String(item[key]);
654
- if (!acc[groupKey]) acc[groupKey] = [];
655
- acc[groupKey].push(item);
656
- return acc;
657
- }, {});
658
- };
659
- /**
660
- * 移除对象中值为空(null / undefined / 空字符串)的属性
661
- * @param obj - 待过滤的对象
662
- * @returns 过滤后的新对象
663
- */
664
- const filterEmptyValues = (obj) => {
665
- return Object.fromEntries(Object.entries(obj).filter(([, v]) => v !== null && v !== void 0 && v !== ""));
666
- };
667
- /**
668
- * 根据长度和映射函数快速生成数组
669
- * @param length - 数组长度
670
- * @param mapFn - 映射函数,接收索引返回元素值,默认为返回索引
671
- * @returns 生成的数组
672
- */
673
- const createRange = (length, mapFn) => {
674
- return Array.from({ length }, (_, i) => mapFn ? mapFn(i) : i);
675
- };
676
- //#endregion
677
- //#region src/cookie/index.ts
678
- /**
679
- * 设置 Cookie
680
- * @param name - Cookie 名称
681
- * @param value - Cookie 值
682
- * @param days - 有效天数,默认 7 天
683
- */
684
- const setCookie = (name, value, days = 0) => {
685
- const expires = /* @__PURE__ */ new Date();
686
- expires.setDate(expires.getDate() + days);
687
- document.cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}; expires=${expires.toUTCString()}; path=/`;
688
- };
689
- /**
690
- * 获取 Cookie
691
- * @param name - Cookie 名称
692
- * @returns Cookie 值,不存在则返回 null
693
- */
694
- const getCookie = (name) => {
695
- const match = document.cookie.match(new RegExp("(^| )" + encodeURIComponent(name) + "=([^;]+)"));
696
- return match ? decodeURIComponent(match[2]) : null;
697
- };
698
- /**
699
- * 删除 Cookie
700
- * @param name - Cookie 名称
701
- */
702
- const delCookie = (name) => {
703
- setCookie(name, "", -1);
704
- };
705
- //#endregion
706
- //#region src/browser/index.ts
707
- /**
708
- * 获取 URL 参数对象
709
- * @param url - URL 字符串,默认使用当前页面地址
710
- * @returns 参数键值对对象
711
- */
712
- const getUrlParams = (url) => {
713
- const params = {};
714
- new URL(url || window.location.href).searchParams.forEach((val, key) => {
715
- params[key] = val;
716
- });
717
- return params;
718
- };
719
- /**
720
- * 获取单个 URL 参数
721
- * @param key - 参数名
722
- * @param url - URL 字符串,默认使用当前页面地址
723
- * @returns 参数值,不存在则返回 null
724
- */
725
- const getUrlParam = (key, url) => {
726
- return new URL(url || window.location.href).searchParams.get(key);
727
- };
728
- /**
729
- * 对象转 URL 参数字符串
730
- * @param params - 参数键值对对象,值为 null/undefined 的项会被过滤
731
- * @returns URL 参数字符串(不含 ? 前缀)
732
- */
733
- const toQueryString = (params) => {
734
- return Object.entries(params).filter(([, v]) => v !== void 0 && v !== null).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
735
- };
736
- /**
737
- * 复制文本到剪贴板
738
- * 优先使用 Clipboard API,失败时降级为 execCommand
739
- * @param text - 要复制的文本
740
- * @returns 复制成功返回 true,失败返回 false
741
- */
742
- const copyToClipboard = async (text) => {
743
- if (navigator.clipboard) try {
744
- await navigator.clipboard.writeText(text);
745
- return true;
746
- } catch {
747
- return false;
748
- }
749
- const textarea = document.createElement("textarea");
750
- textarea.value = text;
751
- textarea.style.position = "fixed";
752
- textarea.style.opacity = "0";
753
- document.body.appendChild(textarea);
754
- textarea.select();
755
- const result = document.execCommand("copy");
756
- document.body.removeChild(textarea);
757
- return result;
758
- };
759
- /**
760
- * 下载文件(Blob)
761
- * @param content - 文件内容
762
- * @param filename - 文件名
763
- * @param mimeType - MIME 类型,默认 "text/plain"
764
- */
765
- const downloadFile = (content, filename, mimeType = "text/plain") => {
766
- const blob = new Blob([content], { type: mimeType });
767
- const url = URL.createObjectURL(blob);
768
- const a = document.createElement("a");
769
- a.href = url;
770
- a.download = filename;
771
- a.click();
772
- URL.revokeObjectURL(url);
773
- };
774
- /**
775
- * 导出 JSON 为文件
776
- * @param data - 要导出的数据
777
- * @param filename - 文件名,默认 "data.json"
778
- */
779
- const exportJSON = (data, filename = "data.json") => {
780
- downloadFile(JSON.stringify(data, null, 2), filename, "application/json");
781
- };
782
- /**
783
- * 滚动到顶部
784
- * @param behavior - 滚动行为,"smooth" 平滑 / "auto" 瞬间,默认 "smooth"
785
- */
786
- const scrollToTop = (behavior = "smooth") => {
787
- window.scrollTo({
788
- top: 0,
789
- behavior
790
- });
791
- };
792
- /**
793
- * 滚动到底部
794
- * @param behavior - 滚动行为,"smooth" 平滑 / "auto" 瞬间,默认 "smooth"
795
- */
796
- const scrollToBottom = (behavior = "smooth") => {
797
- window.scrollTo({
798
- top: document.documentElement.scrollHeight,
799
- behavior
800
- });
801
- };
802
- /**
803
- * 监听页面滚动(requestAnimationFrame 节流)
804
- * @param callback - 滚动回调,接收当前 scrollY 值
805
- * @returns 清理函数,调用后移除事件监听
806
- */
807
- const onScroll = (callback) => {
808
- let ticking = false;
809
- const handler = () => {
810
- if (!ticking) {
811
- requestAnimationFrame(() => {
812
- callback(window.scrollY);
813
- ticking = false;
814
- });
815
- ticking = true;
816
- }
817
- };
818
- window.addEventListener("scroll", handler);
819
- return () => window.removeEventListener("scroll", handler);
820
- };
821
- /**
822
- * 监听目标元素是否进入/离开可视区域
823
- * @param target - 目标 DOM 元素
824
- * @param onEnter - 元素进入可视区域时的回调
825
- * @param onLeave - 元素离开可视区域时的回调(可选)
826
- * @param options - IntersectionObserver 配置项
827
- * @returns 清理函数,调用后停止观察
828
- */
829
- const observeIntersection = (target, onEnter, onLeave, options) => {
830
- const observer = new IntersectionObserver((entries) => {
831
- entries.forEach((entry) => {
832
- if (entry.isIntersecting) onEnter(entry);
833
- else if (onLeave) onLeave(entry);
834
- });
835
- }, options);
836
- observer.observe(target);
837
- return () => observer.disconnect();
838
- };
839
- //#endregion
840
- //#region src/control/index.ts
841
- /**
842
- * 防抖 —— 在事件被触发 n 秒后再执行,如果 n 秒内再次触发则重新计时
843
- * @param fn - 需要防抖的函数
844
- * @param delay - 延迟时间(毫秒),默认 300
845
- * @param options.leading - 首次调用是否立即执行,默认 false
846
- * @param options.trailing - 停止调用后是否延迟执行,默认 true
847
- * @returns 防抖后的函数,附带 cancel 方法
848
- */
849
- const debounce = (fn, delay = 300, { leading = false, trailing = true } = {}) => {
850
- let timer = null;
851
- let lastArgs = null;
852
- let shouldTrail = trailing;
853
- const debounced = (...args) => {
854
- lastArgs = args;
855
- if (timer !== null) {
856
- clearTimeout(timer);
857
- shouldTrail = trailing;
858
- } else if (leading) {
859
- fn(...args);
860
- shouldTrail = false;
861
- }
862
- timer = setTimeout(() => {
863
- if (shouldTrail && lastArgs) fn(...lastArgs);
864
- timer = lastArgs = null;
865
- shouldTrail = trailing;
866
- }, delay);
867
- };
868
- debounced.cancel = () => {
869
- if (timer) {
870
- clearTimeout(timer);
871
- timer = lastArgs = null;
872
- }
873
- shouldTrail = trailing;
874
- };
875
- return debounced;
876
- };
877
- /**
878
- * 节流 —— 固定间隔内最多执行一次,超出频率的调用被忽略
879
- * @param fn - 需要节流的函数
880
- * @param interval - 间隔时间(毫秒),默认 300
881
- * @param options.leading - 首次调用是否立即执行,默认 true
882
- * @param options.trailing - 最后一次调用后是否尾部执行,默认 true
883
- * @returns 节流后的函数,附带 cancel 方法
884
- */
885
- const throttle = (fn, interval = 300, { leading = true, trailing = true } = {}) => {
886
- let timer = null;
887
- let lastTime = 0;
888
- let pendingArgs = null;
889
- const throttled = (...args) => {
890
- const now = Date.now();
891
- if (!lastTime && !leading) lastTime = now;
892
- const remaining = interval - (now - lastTime);
893
- pendingArgs = args;
894
- if (remaining <= 0) {
895
- if (timer) {
896
- clearTimeout(timer);
897
- timer = null;
898
- }
899
- fn(...args);
900
- lastTime = now;
901
- pendingArgs = null;
902
- } else if (trailing && !timer) timer = setTimeout(() => {
903
- timer = null;
904
- lastTime = leading ? Date.now() : 0;
905
- if (pendingArgs) fn(...pendingArgs);
906
- pendingArgs = null;
907
- }, remaining);
908
- };
909
- throttled.cancel = () => {
910
- if (timer) {
911
- clearTimeout(timer);
912
- timer = pendingArgs = null;
913
- }
914
- };
915
- return throttled;
916
- };
917
- //#endregion
918
- exports.arrFind = arrFind;
919
- exports.camelToKebab = camelToKebab;
920
- exports.capitalize = capitalize;
921
- exports.compactCN = compactCN;
922
- exports.compactEN = compactEN;
923
- exports.copyToClipboard = copyToClipboard;
924
- exports.createRange = createRange;
925
- exports.debounce = debounce;
926
- exports.deepClone = deepClone;
927
- exports.deepCloneWithJSON = deepCloneWithJSON;
928
- exports.delCookie = delCookie;
929
- exports.downloadFile = downloadFile;
930
- exports.exportJSON = exportJSON;
931
- exports.filterEmptyValues = filterEmptyValues;
932
- exports.firstLower = firstLower;
933
- exports.firstUpper = firstUpper;
934
- exports.flatten = flatten;
935
- exports.formatFull = formatFull;
936
- exports.formatFullReplace = formatFullReplace;
937
- exports.formatNum = formatNum;
938
- exports.formatRmb = formatRmb;
939
- exports.formatWeek = formatWeek;
940
- exports.formatYMD = formatYMD;
941
- exports.getCookie = getCookie;
942
- exports.getUrlParam = getUrlParam;
943
- exports.getUrlParams = getUrlParams;
944
- exports.groupBy = groupBy;
945
- exports.is = is;
946
- exports.isArray = isArray;
947
- exports.isAsyncFunction = isAsyncFunction;
948
- exports.isBoolean = isBoolean;
949
- exports.isDate = isDate;
950
- exports.isDef = isDef;
951
- exports.isElement = isElement;
952
- exports.isEmpty = isEmpty;
953
- exports.isEmptySv = isEmptySv;
954
- exports.isEqual = isEqual;
955
- exports.isFloat = isFloat;
956
- exports.isFunction = isFunction;
957
- exports.isHexColor = isHexColor;
958
- exports.isIOS = isIOS;
959
- exports.isInt = isInt;
960
- exports.isNull = isNull;
961
- exports.isNullOrUnDef = isNullOrUnDef;
962
- exports.isNumber = isNumber;
963
- exports.isObject = isObject;
964
- exports.isPC = isPC;
965
- exports.isPrimitive = isPrimitive;
966
- exports.isPromise = isPromise;
967
- exports.isString = isString;
968
- exports.isSymbol = isSymbol;
969
- exports.isUnDef = isUnDef;
970
- exports.isValidEmail = isValidEmail;
971
- exports.isWindow = isWindow;
972
- exports.kebabToCamel = kebabToCamel;
973
- exports.localClear = localClear;
974
- exports.localGet = localGet;
975
- exports.localRm = localRm;
976
- exports.localSet = localSet;
977
- exports.maskPhone = maskPhone;
978
- exports.mergeArrays = mergeArrays;
979
- exports.observeIntersection = observeIntersection;
980
- exports.onScroll = onScroll;
981
- exports.percentCN = percentCN;
982
- exports.reverse = reverse;
983
- exports.scrollToBottom = scrollToBottom;
984
- exports.scrollToTop = scrollToTop;
985
- exports.setCookie = setCookie;
986
- exports.shallowClone = shallowClone;
987
- exports.signed = signed;
988
- exports.sortByKey = sortByKey;
989
- exports.sortNumAsc = sortNumAsc;
990
- exports.sortNumDesc = sortNumDesc;
991
- exports.spacePhone = spacePhone;
992
- exports.throttle = throttle;
993
- exports.toArray = toArray;
994
- exports.toCamel = toCamel;
995
- exports.toQueryString = toQueryString;
996
- exports.trimAll = trimAll;
997
- exports.truncate = truncate;
998
- exports.truncateByWords = truncateByWords;
999
- exports.unique = unique;
1000
- exports.uniqueByKey = uniqueByKey;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=e=>Array.isArray(e),t=e=>typeof e==`object`&&!!e&&!Array.isArray(e),n=(e,t)=>Object.prototype.toString.call(e)===`[object ${t}]`,r=e=>n(e,`Function`)||!!(e&&e.constructor&&e.call&&e.apply),i=e=>n(e,`Date`)||!!e&&e.constructor===Date,a=e=>Number(e)===e,o=e=>a(e)&&Number.isFinite(e)&&e%1==0,s=e=>a(e)&&Number.isFinite(e)&&e%1!=0,c=e=>n(e,`AsyncFunction`),l=e=>!(!e||!e.then||!r(e.then)),u=e=>n(e,`String`)||typeof e==`string`||e instanceof String,d=e=>typeof e==`boolean`||n(e,`Boolean`),f=()=>typeof window<`u`,p=e=>typeof window<`u`&&n(e,`Window`),m=e=>t(e)&&!!e.tagName,h=e=>e===null,ee=e=>/^#?([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(e),te=()=>/iPad|iPhone|iPod/.test(navigator.userAgent),ne=e=>/^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/.test(e),re=e=>e!==void 0,g=e=>e===void 0,_=e=>g(e)||h(e),v=e=>!!e&&e.constructor===Symbol,y=e=>e==null||typeof e!=`object`&&typeof e!=`function`,b=e=>{if(e==null)return!0;if(i(e))return isNaN(e.getTime());if(r(e)||v(e)||a(e))return!1;let t=e.length;if(a(t))return t===0;let n=e.size;return a(n)?n===0:Object.keys(e).length===0};function x(e){return e==null?!0:typeof e==`string`||Array.isArray(e)?e.length===0:e instanceof Map||e instanceof Set?e.size===0:ArrayBuffer.isView(e)?e.byteLength===0:typeof e==`object`?Object.keys(e).length===0:!1}const S=(e,t)=>{if(Object.is(e,t))return!0;if(e instanceof Date&&t instanceof Date)return e.getTime()===t.getTime();if(e instanceof RegExp&&t instanceof RegExp)return e.toString()===t.toString();if(typeof e!=`object`||!e||typeof t!=`object`||!t)return!1;let n=Reflect.ownKeys(e),r=Reflect.ownKeys(t);if(n.length!==r.length)return!1;for(let r=0;r<n.length;r++)if(!Reflect.has(t,n[r])||!S(e[n[r]],t[n[r]]))return!1;return!0},C=e=>new Intl.DateTimeFormat(`zh-CN`,{year:`numeric`,month:`2-digit`,day:`2-digit`,hour:`2-digit`,minute:`2-digit`,second:`2-digit`,hour12:!1}).format(e),w=e=>C(e).replace(/\//g,`-`),T=e=>new Intl.DateTimeFormat(`zh-CN`,{year:`numeric`,month:`long`,day:`numeric`}).format(e),E=e=>new Intl.DateTimeFormat(`zh-CN`,{weekday:`long`}).format(e),D=(e,t)=>new Intl.NumberFormat(t.type,{style:`currency`,currency:t.currency}).format(e),O=e=>new Intl.NumberFormat(`zh-CN`).format(e),k=(e,t=0)=>new Intl.NumberFormat(`zh-CN`,{style:`percent`,minimumFractionDigits:t}).format(e),A=e=>new Intl.NumberFormat(`en-US`,{notation:`compact`,compactDisplay:`short`}).format(e),j=e=>new Intl.NumberFormat(`zh-CN`,{notation:`compact`,compactDisplay:`short`}).format(e),M=(e,t=0)=>new Intl.NumberFormat(`en-US`,{signDisplay:`always`,minimumFractionDigits:t}).format(e),N=e=>e.replace(/(\d{3})\d{4}(\d{4})/,`$1****$2`),P=e=>e.replace(/(\d{3})(\d{4})(\d{4})/,`$1 $2 $3`),F=e=>e.replace(/\b\w/g,e=>e.toUpperCase()),I=e=>e.replace(/-([a-z])/g,(e,t)=>t.toUpperCase()),L=e=>e.replace(/([A-Z])/g,`-$1`).toLowerCase(),R=(e,t,n=`...`)=>e.length<=t?e:e.substring(0,t-n.length)+n,z=(e,t,n=`...`)=>{let r=e.split(``);return r.length<=t?e:r.slice(0,t).join(``)+n},B=e=>e.replace(/\s+/g,``),V=e=>e.replace(/_([a-z])/g,(e,t)=>t.toUpperCase()),H=e=>e.charAt(0).toUpperCase()+e.slice(1),U=e=>e.charAt(0).toLowerCase()+e.slice(1),W=e=>e.split(``).reverse().join(``),G=e=>{let t=localStorage.getItem(e);if(t===null)return null;try{return JSON.parse(t)}catch{return t}},K=(e,t)=>{localStorage.setItem(e,JSON.stringify(t))},q=e=>{localStorage.removeItem(e)},J=()=>{localStorage.clear()};function Y(e,t=new WeakMap){if(typeof e!=`object`||!e)return e;if(t.has(e))return t.get(e);if(e instanceof Date)return new Date(e.getTime());if(e instanceof RegExp)return new RegExp(e.source,e.flags);if(e instanceof Map){let n=new Map;return t.set(e,n),e.forEach((e,r)=>{n.set(r,Y(e,t))}),n}if(e instanceof Set){let n=new Set;return t.set(e,n),e.forEach(e=>{n.add(Y(e,t))}),n}if(Array.isArray(e)){let n=[];return t.set(e,n),e.forEach((e,r)=>{n[r]=Y(e,t)}),n}let n={};return t.set(e,n),Object.keys(e).forEach(r=>{n[r]=Y(e[r],t)}),n}function X(e){return JSON.parse(JSON.stringify(e))}function ie(e){return typeof e!=`object`||!e?e:Array.isArray(e)?[...e]:{...e}}const ae=e=>[...new Set(e)],oe=(e,t)=>[...new Map(e.map(e=>[e[t],e])).values()],se=e=>[...e].sort((e,t)=>e-t),ce=e=>[...e].sort((e,t)=>t-e),le=(e,t,n=`asc`)=>[...e].sort((e,r)=>n===`asc`?e[t]-r[t]:r[t]-e[t]),ue=e=>Array.from(e),de=(...e)=>[].concat(...e),fe=(e,t=1)=>e.flat(t),pe=(e,t,n)=>e.find(e=>e[t]===n),me=(e,t)=>e.reduce((e,n)=>{let r=String(n[t]);return e[r]||(e[r]=[]),e[r].push(n),e},{}),he=e=>Object.fromEntries(Object.entries(e).filter(([,e])=>e!=null&&e!==``)),ge=(e,t)=>Array.from({length:e},(e,n)=>t?t(n):n),Z=(e,t,n=0)=>{let r=new Date;r.setDate(r.getDate()+n),document.cookie=`${encodeURIComponent(e)}=${encodeURIComponent(t)}; expires=${r.toUTCString()}; path=/`},_e=e=>{let t=document.cookie.match(RegExp(`(^| )`+encodeURIComponent(e)+`=([^;]+)`));return t?decodeURIComponent(t[2]):null},ve=e=>{Z(e,``,-1)},ye=e=>{let t={};return new URL(e||window.location.href).searchParams.forEach((e,n)=>{t[n]=e}),t},be=(e,t)=>new URL(t||window.location.href).searchParams.get(e),xe=e=>Object.entries(e).filter(([,e])=>e!=null).map(([e,t])=>`${encodeURIComponent(e)}=${encodeURIComponent(t)}`).join(`&`),Q=async e=>{if(navigator.clipboard)try{return await navigator.clipboard.writeText(e),!0}catch{return!1}let t=document.createElement(`textarea`);t.value=e,t.style.position=`fixed`,t.style.opacity=`0`,document.body.appendChild(t),t.select();let n=document.execCommand(`copy`);return document.body.removeChild(t),n},$=(e,t,n=`text/plain`)=>{let r=new Blob([e],{type:n}),i=URL.createObjectURL(r),a=document.createElement(`a`);a.href=i,a.download=t,a.click(),URL.revokeObjectURL(i)},Se=(e,t=`data.json`)=>{$(JSON.stringify(e,null,2),t,`application/json`)},Ce=(e=`smooth`)=>{window.scrollTo({top:0,behavior:e})},we=(e=`smooth`)=>{window.scrollTo({top:document.documentElement.scrollHeight,behavior:e})},Te=e=>{let t=!1,n=()=>{t||=(requestAnimationFrame(()=>{e(window.scrollY),t=!1}),!0)};return window.addEventListener(`scroll`,n),()=>window.removeEventListener(`scroll`,n)},Ee=(e,t,n,r)=>{let i=new IntersectionObserver(e=>{e.forEach(e=>{e.isIntersecting?t(e):n&&n(e)})},r);return i.observe(e),()=>i.disconnect()},De=(e,t=300,{leading:n=!1,trailing:r=!0}={})=>{let i=null,a=null,o=r,s=(...s)=>{a=s,i===null?n&&(e(...s),o=!1):(clearTimeout(i),o=r),i=setTimeout(()=>{o&&a&&e(...a),i=a=null,o=r},t)};return s.cancel=()=>{i&&=(clearTimeout(i),a=null),o=r},s},Oe=(e,t=300,{leading:n=!0,trailing:r=!0}={})=>{let i=null,a=0,o=null,s=(...s)=>{let c=Date.now();!a&&!n&&(a=c);let l=t-(c-a);o=s,l<=0?(i&&=(clearTimeout(i),null),e(...s),a=c,o=null):r&&!i&&(i=setTimeout(()=>{i=null,a=n?Date.now():0,o&&e(...o),o=null},l))};return s.cancel=()=>{i&&=(clearTimeout(i),o=null)},s};exports.arrFind=pe,exports.camelToKebab=L,exports.capitalize=F,exports.compactCN=j,exports.compactEN=A,exports.copyToClipboard=Q,exports.createRange=ge,exports.debounce=De,exports.deepClone=Y,exports.deepCloneWithJSON=X,exports.delCookie=ve,exports.downloadFile=$,exports.exportJSON=Se,exports.filterEmptyValues=he,exports.firstLower=U,exports.firstUpper=H,exports.flatten=fe,exports.formatFull=C,exports.formatFullReplace=w,exports.formatNum=O,exports.formatRmb=D,exports.formatWeek=E,exports.formatYMD=T,exports.getCookie=_e,exports.getUrlParam=be,exports.getUrlParams=ye,exports.groupBy=me,exports.is=n,exports.isArray=e,exports.isAsyncFunction=c,exports.isBoolean=d,exports.isDate=i,exports.isDef=re,exports.isElement=m,exports.isEmpty=b,exports.isEmptySv=x,exports.isEqual=S,exports.isFloat=s,exports.isFunction=r,exports.isHexColor=ee,exports.isIOS=te,exports.isInt=o,exports.isNull=h,exports.isNullOrUnDef=_,exports.isNumber=a,exports.isObject=t,exports.isPC=f,exports.isPrimitive=y,exports.isPromise=l,exports.isString=u,exports.isSymbol=v,exports.isUnDef=g,exports.isValidEmail=ne,exports.isWindow=p,exports.kebabToCamel=I,exports.localClear=J,exports.localGet=G,exports.localRm=q,exports.localSet=K,exports.maskPhone=N,exports.mergeArrays=de,exports.observeIntersection=Ee,exports.onScroll=Te,exports.percentCN=k,exports.reverse=W,exports.scrollToBottom=we,exports.scrollToTop=Ce,exports.setCookie=Z,exports.shallowClone=ie,exports.signed=M,exports.sortByKey=le,exports.sortNumAsc=se,exports.sortNumDesc=ce,exports.spacePhone=P,exports.throttle=Oe,exports.toArray=ue,exports.toCamel=V,exports.toQueryString=xe,exports.trimAll=B,exports.truncate=R,exports.truncateByWords=z,exports.unique=ae,exports.uniqueByKey=oe;