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