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/README.md +6 -1
- package/dist/array/index.d.ts +0 -68
- package/dist/browser/index.d.ts +0 -54
- package/dist/control/index.d.ts +0 -16
- package/dist/cookie/index.d.ts +0 -15
- package/dist/copy/index.d.ts +0 -20
- package/dist/format/index.d.ts +0 -117
- package/dist/index.cjs +1 -1000
- package/dist/index.js +1 -917
- package/dist/is/index.d.ts +0 -138
- package/dist/storage/index.d.ts +0 -17
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -1,1000 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,
|
|
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;
|