sculp-js 1.8.3 → 1.9.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/lib/cjs/array.js +1 -1
- package/lib/cjs/async.js +1 -1
- package/lib/cjs/base64.js +1 -1
- package/lib/cjs/clipboard.js +7 -11
- package/lib/cjs/cloneDeep.js +1 -1
- package/lib/cjs/cookie.js +1 -1
- package/lib/cjs/date.js +1 -1
- package/lib/cjs/dom.js +1 -1
- package/lib/cjs/download.js +29 -5
- package/lib/cjs/easing.js +1 -1
- package/lib/cjs/file.js +1 -1
- package/lib/cjs/func.js +1 -1
- package/lib/cjs/index.js +3 -1
- package/lib/cjs/isEqual.js +1 -1
- package/lib/cjs/math.js +1 -1
- package/lib/cjs/number.js +49 -12
- package/lib/cjs/object.js +1 -1
- package/lib/cjs/path.js +1 -1
- package/lib/cjs/qs.js +1 -1
- package/lib/cjs/random.js +1 -1
- package/lib/cjs/string.js +1 -1
- package/lib/cjs/tooltip.js +1 -1
- package/lib/cjs/tree.js +2 -2
- package/lib/cjs/type.js +1 -1
- package/lib/cjs/unique.js +1 -1
- package/lib/cjs/url.js +15 -5
- package/lib/cjs/validator.js +1 -1
- package/lib/cjs/variable.js +3 -2
- package/lib/cjs/watermark.js +1 -1
- package/lib/cjs/we-decode.js +1 -1
- package/lib/es/array.js +1 -1
- package/lib/es/async.js +1 -1
- package/lib/es/base64.js +1 -1
- package/lib/es/clipboard.js +7 -11
- package/lib/es/cloneDeep.js +1 -1
- package/lib/es/cookie.js +1 -1
- package/lib/es/date.js +1 -1
- package/lib/es/dom.js +1 -1
- package/lib/es/download.js +30 -6
- package/lib/es/easing.js +1 -1
- package/lib/es/file.js +1 -1
- package/lib/es/func.js +1 -1
- package/lib/es/index.js +2 -2
- package/lib/es/isEqual.js +1 -1
- package/lib/es/math.js +1 -1
- package/lib/es/number.js +48 -13
- package/lib/es/object.js +1 -1
- package/lib/es/path.js +1 -1
- package/lib/es/qs.js +1 -1
- package/lib/es/random.js +1 -1
- package/lib/es/string.js +1 -1
- package/lib/es/tooltip.js +1 -1
- package/lib/es/tree.js +2 -2
- package/lib/es/type.js +1 -1
- package/lib/es/unique.js +1 -1
- package/lib/es/url.js +15 -5
- package/lib/es/validator.js +1 -1
- package/lib/es/variable.js +4 -3
- package/lib/es/watermark.js +1 -1
- package/lib/es/we-decode.js +1 -1
- package/lib/index.d.ts +43 -11
- package/lib/umd/index.js +603 -535
- package/package.json +1 -1
package/lib/umd/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* sculp-js v1.
|
|
2
|
+
* sculp-js v1.9.0
|
|
3
3
|
* (c) 2023-present chandq
|
|
4
4
|
* Released under the MIT License.
|
|
5
5
|
*/
|
|
@@ -97,6 +97,29 @@
|
|
|
97
97
|
return array;
|
|
98
98
|
}
|
|
99
99
|
|
|
100
|
+
/**
|
|
101
|
+
* 复制文本
|
|
102
|
+
* @param {string} text
|
|
103
|
+
*/
|
|
104
|
+
function copyText(text) {
|
|
105
|
+
const textEl = document.createElement('textarea');
|
|
106
|
+
textEl.style.position = 'absolute';
|
|
107
|
+
textEl.style.top = '-9999px';
|
|
108
|
+
textEl.style.left = '-9999px';
|
|
109
|
+
textEl.value = text;
|
|
110
|
+
document.body.appendChild(textEl);
|
|
111
|
+
textEl.focus({ preventScroll: true });
|
|
112
|
+
textEl.select();
|
|
113
|
+
try {
|
|
114
|
+
document.execCommand('copy');
|
|
115
|
+
textEl.blur();
|
|
116
|
+
document.body.removeChild(textEl);
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
// ignore
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
100
123
|
// 常用类型定义
|
|
101
124
|
/**
|
|
102
125
|
* 判断对象内是否有该静态属性
|
|
@@ -213,123 +236,373 @@
|
|
|
213
236
|
return !Object.keys(value).length;
|
|
214
237
|
}
|
|
215
238
|
|
|
216
|
-
// @ref https://cubic-bezier.com/
|
|
217
|
-
const easingDefines = {
|
|
218
|
-
linear: [0, 0, 1, 1],
|
|
219
|
-
ease: [0.25, 0.1, 0.25, 1],
|
|
220
|
-
'ease-in': [0.42, 0, 1, 1],
|
|
221
|
-
'ease-out': [0, 0, 0.58, 1],
|
|
222
|
-
'ease-in-out': [0.42, 0, 0.58, 1]
|
|
223
|
-
};
|
|
224
239
|
/**
|
|
225
|
-
*
|
|
226
|
-
* @param {
|
|
227
|
-
* @returns {
|
|
240
|
+
* 获取cookie
|
|
241
|
+
* @param {string} name
|
|
242
|
+
* @returns {string}
|
|
228
243
|
*/
|
|
229
|
-
function
|
|
230
|
-
|
|
231
|
-
if (
|
|
232
|
-
|
|
244
|
+
function cookieGet(name) {
|
|
245
|
+
const { cookie } = document;
|
|
246
|
+
if (!cookie)
|
|
247
|
+
return '';
|
|
248
|
+
const result = cookie.split(';');
|
|
249
|
+
for (let i = 0; i < result.length; i++) {
|
|
250
|
+
const item = result[i];
|
|
251
|
+
const [key, val = ''] = item.split('=');
|
|
252
|
+
if (key === name)
|
|
253
|
+
return decodeURIComponent(val);
|
|
233
254
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
255
|
+
return '';
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* 设置 cookie
|
|
259
|
+
* @param {string} name
|
|
260
|
+
* @param {string} value
|
|
261
|
+
* @param {number | Date} [maxAge]
|
|
262
|
+
*/
|
|
263
|
+
function cookieSet(name, value, maxAge) {
|
|
264
|
+
const metas = [];
|
|
265
|
+
const EXPIRES = 'expires';
|
|
266
|
+
metas.push([name, encodeURIComponent(value)]);
|
|
267
|
+
if (isNumber(maxAge)) {
|
|
268
|
+
const d = new Date();
|
|
269
|
+
d.setTime(d.getTime() + maxAge);
|
|
270
|
+
metas.push([EXPIRES, d.toUTCString()]);
|
|
240
271
|
}
|
|
241
|
-
|
|
272
|
+
else if (isDate(maxAge)) {
|
|
273
|
+
metas.push([EXPIRES, maxAge.toUTCString()]);
|
|
274
|
+
}
|
|
275
|
+
metas.push(['path', '/']);
|
|
276
|
+
document.cookie = metas
|
|
277
|
+
.map(item => {
|
|
278
|
+
const [key, val] = item;
|
|
279
|
+
return `${key}=${val}`;
|
|
280
|
+
})
|
|
281
|
+
.join(';');
|
|
242
282
|
}
|
|
243
|
-
|
|
244
283
|
/**
|
|
245
|
-
*
|
|
246
|
-
* @param
|
|
247
|
-
* @returns {boolean}
|
|
284
|
+
* 删除单个 cookie
|
|
285
|
+
* @param name cookie 名称
|
|
248
286
|
*/
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
if (!
|
|
255
|
-
return
|
|
256
|
-
|
|
257
|
-
return
|
|
287
|
+
const cookieDel = (name) => cookieSet(name, '', -1);
|
|
288
|
+
|
|
289
|
+
const isValidDate = (any) => isDate(any) && !isNaN(any.getTime());
|
|
290
|
+
/* istanbul ignore next */
|
|
291
|
+
const guessDateSeparator = (value) => {
|
|
292
|
+
if (!isString(value))
|
|
293
|
+
return;
|
|
294
|
+
const value2 = value.replace(/-/g, '/');
|
|
295
|
+
return new Date(value2);
|
|
296
|
+
};
|
|
297
|
+
/* istanbul ignore next */
|
|
298
|
+
const guessDateTimezone = (value) => {
|
|
299
|
+
if (!isString(value))
|
|
300
|
+
return;
|
|
301
|
+
const re = /([+-])(\d\d)(\d\d)$/;
|
|
302
|
+
const matches = re.exec(value);
|
|
303
|
+
if (!matches)
|
|
304
|
+
return;
|
|
305
|
+
const value2 = value.replace(re, 'Z');
|
|
306
|
+
const d = new Date(value2);
|
|
307
|
+
if (!isValidDate(d))
|
|
308
|
+
return;
|
|
309
|
+
const [, flag, hours, minutes] = matches;
|
|
310
|
+
const hours2 = parseInt(hours, 10);
|
|
311
|
+
const minutes2 = parseInt(minutes, 10);
|
|
312
|
+
const offset = (a, b) => (flag === '+' ? a - b : a + b);
|
|
313
|
+
d.setHours(offset(d.getHours(), hours2));
|
|
314
|
+
d.setMinutes(offset(d.getMinutes(), minutes2));
|
|
315
|
+
return d;
|
|
258
316
|
};
|
|
259
317
|
/**
|
|
260
|
-
*
|
|
261
|
-
* @param {
|
|
262
|
-
* @
|
|
318
|
+
* 解析为Date对象
|
|
319
|
+
* @param {DateValue} value - 可以是数值、字符串或 Date 对象
|
|
320
|
+
* @returns {Date} - 转换后的目标Date
|
|
263
321
|
*/
|
|
264
|
-
function
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
322
|
+
function dateParse(value) {
|
|
323
|
+
const d1 = new Date(value);
|
|
324
|
+
if (isValidDate(d1))
|
|
325
|
+
return d1;
|
|
326
|
+
// safari 浏览器的日期解析有问题
|
|
327
|
+
// new Date('2020-06-26 18:06:15') 返回值是一个非法日期对象
|
|
328
|
+
/* istanbul ignore next */
|
|
329
|
+
const d2 = guessDateSeparator(value);
|
|
330
|
+
/* istanbul ignore next */
|
|
331
|
+
if (isValidDate(d2))
|
|
332
|
+
return d2;
|
|
333
|
+
// safari 浏览器的日期解析有问题
|
|
334
|
+
// new Date('2020-06-26T18:06:15.000+0800') 返回值是一个非法日期对象
|
|
335
|
+
/* istanbul ignore next */
|
|
336
|
+
const d3 = guessDateTimezone(value);
|
|
337
|
+
/* istanbul ignore next */
|
|
338
|
+
if (isValidDate(d3))
|
|
339
|
+
return d3;
|
|
340
|
+
throw new SyntaxError(`${value.toString()} 不是一个合法的日期描述`);
|
|
271
341
|
}
|
|
272
342
|
/**
|
|
273
|
-
*
|
|
274
|
-
* @param {
|
|
275
|
-
* @param {
|
|
343
|
+
* 格式化为日期对象(带自定义格式化模板)
|
|
344
|
+
* @param {DateValue} value 可以是数值、字符串或 Date 对象
|
|
345
|
+
* @param {string} [format] 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
|
|
346
|
+
* - YYYY:年
|
|
347
|
+
* - yyyy: 年
|
|
348
|
+
* - MM:月
|
|
349
|
+
* - DD:日
|
|
350
|
+
* - dd: 日
|
|
351
|
+
* - HH:时(24 小时制)
|
|
352
|
+
* - hh:时(12 小时制)
|
|
353
|
+
* - mm:分
|
|
354
|
+
* - ss:秒
|
|
355
|
+
* - SSS:毫秒
|
|
356
|
+
* @returns {string}
|
|
276
357
|
*/
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
358
|
+
// export const dateStringify = (value: DateValue, format = 'YYYY-MM-DD HH:mm:ss'): string => {
|
|
359
|
+
// const date = dateParse(value);
|
|
360
|
+
// let fmt = format;
|
|
361
|
+
// let ret;
|
|
362
|
+
// const opt: DateObj = {
|
|
363
|
+
// 'Y+': `${date.getFullYear()}`, // 年
|
|
364
|
+
// 'y+': `${date.getFullYear()}`, // 年
|
|
365
|
+
// 'M+': `${date.getMonth() + 1}`, // 月
|
|
366
|
+
// 'D+': `${date.getDate()}`, // 日
|
|
367
|
+
// 'd+': `${date.getDate()}`, // 日
|
|
368
|
+
// 'H+': `${date.getHours()}`, // 时
|
|
369
|
+
// 'm+': `${date.getMinutes()}`, // 分
|
|
370
|
+
// 's+': `${date.getSeconds()}`, // 秒
|
|
371
|
+
// 'S+': `${date.getMilliseconds()}` // 豪秒
|
|
372
|
+
// };
|
|
373
|
+
// for (const k in opt) {
|
|
374
|
+
// ret = new RegExp(`(${k})`).exec(fmt);
|
|
375
|
+
// if (ret) {
|
|
376
|
+
// fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
|
|
377
|
+
// }
|
|
378
|
+
// }
|
|
379
|
+
// return fmt;
|
|
380
|
+
// };
|
|
285
381
|
/**
|
|
286
|
-
*
|
|
287
|
-
* @param {
|
|
288
|
-
* @
|
|
289
|
-
* @returns {Record<Extract<keyof O, string>, T>}
|
|
382
|
+
* 将日期转换为一天的开始时间,即0点0分0秒0毫秒
|
|
383
|
+
* @param {DateValue} value
|
|
384
|
+
* @returns {Date}
|
|
290
385
|
*/
|
|
291
|
-
function
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
if (!objectHas(obj, key))
|
|
295
|
-
continue;
|
|
296
|
-
obj2[key] = iterator(obj[key], key);
|
|
297
|
-
}
|
|
298
|
-
return obj2;
|
|
386
|
+
function dateToStart(value) {
|
|
387
|
+
const d = dateParse(value);
|
|
388
|
+
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
|
|
299
389
|
}
|
|
300
390
|
/**
|
|
301
|
-
*
|
|
302
|
-
* @param {
|
|
303
|
-
* @
|
|
304
|
-
* @returns {Pick<O, ArrayElements<K>>}
|
|
391
|
+
* 将日期转换为一天的结束时间,即23点59分59秒999毫秒
|
|
392
|
+
* @param {DateValue} value
|
|
393
|
+
* @returns {Date}
|
|
305
394
|
*/
|
|
306
|
-
function
|
|
307
|
-
const
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
// @ts-ignore
|
|
311
|
-
obj2[k] = v;
|
|
312
|
-
}
|
|
313
|
-
});
|
|
314
|
-
return obj2;
|
|
395
|
+
function dateToEnd(value) {
|
|
396
|
+
const d = dateToStart(value);
|
|
397
|
+
d.setDate(d.getDate() + 1);
|
|
398
|
+
return dateParse(d.getTime() - 1);
|
|
315
399
|
}
|
|
316
400
|
/**
|
|
317
|
-
*
|
|
318
|
-
* @param {
|
|
319
|
-
* @param {
|
|
320
|
-
*
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
401
|
+
* 格式化为日期对象(带自定义格式化模板)
|
|
402
|
+
* @param {Date} value - 可以是数值、字符串或 Date 对象
|
|
403
|
+
* @param {string} [format] - 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
|
|
404
|
+
* - YYYY:年
|
|
405
|
+
* - yyyy: 年
|
|
406
|
+
* - MM:月
|
|
407
|
+
* - DD:日
|
|
408
|
+
* - dd: 日
|
|
409
|
+
* - HH:时(24 小时制)
|
|
410
|
+
* - mm:分
|
|
411
|
+
* - ss:秒
|
|
412
|
+
* - SSS:毫秒
|
|
413
|
+
* - ww: 周
|
|
414
|
+
* @returns {string} 格式化后的日期字符串
|
|
415
|
+
*/
|
|
416
|
+
function formatDate(value, format = 'YYYY-MM-DD HH:mm:ss') {
|
|
417
|
+
const date = dateParse(value);
|
|
418
|
+
let fmt = format;
|
|
419
|
+
let ret;
|
|
420
|
+
const opt = {
|
|
421
|
+
'Y+': `${date.getFullYear()}`,
|
|
422
|
+
'y+': `${date.getFullYear()}`,
|
|
423
|
+
'M+': `${date.getMonth() + 1}`,
|
|
424
|
+
'D+': `${date.getDate()}`,
|
|
425
|
+
'd+': `${date.getDate()}`,
|
|
426
|
+
'H+': `${date.getHours()}`,
|
|
427
|
+
'm+': `${date.getMinutes()}`,
|
|
428
|
+
's+': `${date.getSeconds()}`,
|
|
429
|
+
'S+': `${date.getMilliseconds()}`,
|
|
430
|
+
'w+': ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()] // 周
|
|
431
|
+
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
|
432
|
+
};
|
|
433
|
+
for (const k in opt) {
|
|
434
|
+
ret = new RegExp('(' + k + ')').exec(fmt);
|
|
435
|
+
if (ret) {
|
|
436
|
+
fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return fmt;
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* 计算向前或向后N天的具体日期
|
|
443
|
+
* @param {DateValue} originDate - 参考日期
|
|
444
|
+
* @param {number} n - 正数:向后推算;负数:向前推算
|
|
445
|
+
* @param {string} sep - 日期格式的分隔符
|
|
446
|
+
* @returns {string} 计算后的目标日期
|
|
447
|
+
*/
|
|
448
|
+
function calculateDate(originDate, n, sep = '-') {
|
|
449
|
+
//originDate 为字符串日期 如:'2019-01-01' n为你要传入的参数,当前为0,前一天为-1,后一天为1
|
|
450
|
+
const date = new Date(originDate); //这边给定一个特定时间
|
|
451
|
+
const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
452
|
+
const millisecondGap = newDate.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
|
|
453
|
+
const targetDate = new Date(millisecondGap);
|
|
454
|
+
const finalNewDate = targetDate.getFullYear() +
|
|
455
|
+
sep +
|
|
456
|
+
String(targetDate.getMonth() + 1).padStart(2, '0') +
|
|
457
|
+
'-' +
|
|
458
|
+
String(targetDate.getDate()).padStart(2, '0');
|
|
459
|
+
return finalNewDate;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* 计算向前或向后N天的具体日期时间
|
|
463
|
+
* @param {DateValue} originDateTime - 参考日期时间
|
|
464
|
+
* @param {number} n - 正数:向后推算;负数:向前推算
|
|
465
|
+
* @param {string} dateSep - 日期分隔符
|
|
466
|
+
* @param {string} timeSep - 时间分隔符
|
|
467
|
+
* @returns {string} 转换后的目标日期时间
|
|
468
|
+
*/
|
|
469
|
+
function calculateDateTime(originDateTime, n, dateSep = '-', timeSep = ':') {
|
|
470
|
+
const date = new Date(originDateTime);
|
|
471
|
+
const separator1 = dateSep;
|
|
472
|
+
const separator2 = timeSep;
|
|
473
|
+
const dateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
|
|
474
|
+
const millisecondGap = dateTime.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
|
|
475
|
+
const targetDateTime = new Date(millisecondGap);
|
|
476
|
+
return (targetDateTime.getFullYear() +
|
|
477
|
+
separator1 +
|
|
478
|
+
String(targetDateTime.getMonth() + 1).padStart(2, '0') +
|
|
479
|
+
separator1 +
|
|
480
|
+
String(targetDateTime.getDate()).padStart(2, '0') +
|
|
481
|
+
' ' +
|
|
482
|
+
String(targetDateTime.getHours()).padStart(2, '0') +
|
|
483
|
+
separator2 +
|
|
484
|
+
String(targetDateTime.getMinutes()).padStart(2, '0') +
|
|
485
|
+
separator2 +
|
|
486
|
+
String(targetDateTime.getSeconds()).padStart(2, '0'));
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// @ref https://cubic-bezier.com/
|
|
490
|
+
const easingDefines = {
|
|
491
|
+
linear: [0, 0, 1, 1],
|
|
492
|
+
ease: [0.25, 0.1, 0.25, 1],
|
|
493
|
+
'ease-in': [0.42, 0, 1, 1],
|
|
494
|
+
'ease-out': [0, 0, 0.58, 1],
|
|
495
|
+
'ease-in-out': [0.42, 0, 0.58, 1]
|
|
496
|
+
};
|
|
497
|
+
/**
|
|
498
|
+
* 缓冲函数化,用于 js 计算缓冲进度
|
|
499
|
+
* @param {EasingNameOrDefine} [name=linear]
|
|
500
|
+
* @returns {EasingFunction}
|
|
501
|
+
*/
|
|
502
|
+
function easingFunctional(name) {
|
|
503
|
+
let fn;
|
|
504
|
+
if (isArray(name)) {
|
|
505
|
+
fn = bezier(...name);
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
const define = easingDefines[name];
|
|
509
|
+
if (!define) {
|
|
510
|
+
throw new Error(`${name} 缓冲函数未定义`);
|
|
511
|
+
}
|
|
512
|
+
fn = bezier(...define);
|
|
513
|
+
}
|
|
514
|
+
return (input) => fn(Math.max(0, Math.min(input, 1)));
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/**
|
|
518
|
+
* 判断对象是否为纯对象
|
|
519
|
+
* @param {object} obj
|
|
520
|
+
* @returns {boolean}
|
|
521
|
+
*/
|
|
522
|
+
const isPlainObject = (obj) => {
|
|
523
|
+
if (!isObject(obj))
|
|
524
|
+
return false;
|
|
525
|
+
const proto = Object.getPrototypeOf(obj);
|
|
526
|
+
// 对象无原型
|
|
527
|
+
if (!proto)
|
|
528
|
+
return true;
|
|
529
|
+
// 是否对象直接实例
|
|
530
|
+
return proto === Object.prototype;
|
|
531
|
+
};
|
|
532
|
+
/**
|
|
533
|
+
* 遍历对象,返回 false 中断遍历
|
|
534
|
+
* @param {O} obj
|
|
535
|
+
* @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
|
|
536
|
+
*/
|
|
537
|
+
function objectEach(obj, iterator) {
|
|
538
|
+
for (const key in obj) {
|
|
539
|
+
if (!objectHas(obj, key))
|
|
540
|
+
continue;
|
|
541
|
+
if (iterator(obj[key], key) === false)
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* 异步遍历对象,返回 false 中断遍历
|
|
547
|
+
* @param {O} obj
|
|
548
|
+
* @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
|
|
549
|
+
*/
|
|
550
|
+
async function objectEachAsync(obj, iterator) {
|
|
551
|
+
for (const key in obj) {
|
|
552
|
+
if (!objectHas(obj, key))
|
|
553
|
+
continue;
|
|
554
|
+
if ((await iterator(obj[key], key)) === false)
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* 对象映射
|
|
560
|
+
* @param {O} obj
|
|
561
|
+
* @param {(val: O[keyof O], key: Extract<keyof O, string>) => any} iterator
|
|
562
|
+
* @returns {Record<Extract<keyof O, string>, T>}
|
|
563
|
+
*/
|
|
564
|
+
function objectMap(obj, iterator) {
|
|
565
|
+
const obj2 = {};
|
|
566
|
+
for (const key in obj) {
|
|
567
|
+
if (!objectHas(obj, key))
|
|
568
|
+
continue;
|
|
569
|
+
obj2[key] = iterator(obj[key], key);
|
|
570
|
+
}
|
|
571
|
+
return obj2;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* 对象提取
|
|
575
|
+
* @param {O} obj
|
|
576
|
+
* @param {K} keys
|
|
577
|
+
* @returns {Pick<O, ArrayElements<K>>}
|
|
578
|
+
*/
|
|
579
|
+
function objectPick(obj, keys) {
|
|
580
|
+
const obj2 = {};
|
|
581
|
+
objectEach(obj, (v, k) => {
|
|
582
|
+
if (keys.includes(k)) {
|
|
583
|
+
// @ts-ignore
|
|
584
|
+
obj2[k] = v;
|
|
585
|
+
}
|
|
586
|
+
});
|
|
587
|
+
return obj2;
|
|
588
|
+
}
|
|
589
|
+
/**
|
|
590
|
+
* 对象去除
|
|
591
|
+
* @param {O} obj
|
|
592
|
+
* @param {K} keys
|
|
593
|
+
* @returns {Pick<O, ArrayElements<K>>}
|
|
594
|
+
*/
|
|
595
|
+
function objectOmit(obj, keys) {
|
|
596
|
+
const obj2 = {};
|
|
597
|
+
objectEach(obj, (v, k) => {
|
|
598
|
+
if (!keys.includes(k)) {
|
|
599
|
+
// @ts-ignore
|
|
600
|
+
obj2[k] = v;
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
return obj2;
|
|
604
|
+
}
|
|
605
|
+
const merge = (map, source, target) => {
|
|
333
606
|
if (isUndefined(target))
|
|
334
607
|
return source;
|
|
335
608
|
const sourceType = typeIs(source);
|
|
@@ -560,437 +833,162 @@
|
|
|
560
833
|
if (!queryObj[item[1]]) {
|
|
561
834
|
queryObj[item[1]] = item[2];
|
|
562
835
|
}
|
|
563
|
-
else if (typeof queryObj[item[1]] === 'string') {
|
|
564
|
-
queryObj[item[1]] = [queryObj[item[1]], item[2]];
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
queryObj[item[1]].push(item[2]);
|
|
568
|
-
}
|
|
569
|
-
});
|
|
570
|
-
return queryObj;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
/**
|
|
574
|
-
* 判断元素是否包含某个样式名
|
|
575
|
-
* @param {HTMLElement} el
|
|
576
|
-
* @param {string} className
|
|
577
|
-
* @returns {boolean}
|
|
578
|
-
*/
|
|
579
|
-
function hasClass(el, className) {
|
|
580
|
-
if (className.indexOf(' ') !== -1)
|
|
581
|
-
throw new Error('className should not contain space.');
|
|
582
|
-
return el.classList.contains(className);
|
|
583
|
-
}
|
|
584
|
-
const eachClassName = (classNames, func) => {
|
|
585
|
-
const classNameList = classNames.split(/\s+/g);
|
|
586
|
-
classNameList.forEach(func);
|
|
587
|
-
};
|
|
588
|
-
/**
|
|
589
|
-
* 给元素增加样式名
|
|
590
|
-
* @param {HTMLElement} el
|
|
591
|
-
* @param {string} classNames
|
|
592
|
-
*/
|
|
593
|
-
function addClass(el, classNames) {
|
|
594
|
-
eachClassName(classNames, className => el.classList.add(className));
|
|
595
|
-
}
|
|
596
|
-
/**
|
|
597
|
-
* 给元素移除样式名
|
|
598
|
-
* @param {HTMLElement} el
|
|
599
|
-
* @param {string} classNames
|
|
600
|
-
*/
|
|
601
|
-
function removeClass(el, classNames) {
|
|
602
|
-
eachClassName(classNames, className => el.classList.remove(className));
|
|
603
|
-
}
|
|
604
|
-
/**
|
|
605
|
-
* 设置元素样式
|
|
606
|
-
* @param {HTMLElement} el
|
|
607
|
-
* @param {string | Style} key
|
|
608
|
-
* @param {string} val
|
|
609
|
-
*/
|
|
610
|
-
const setStyle = (el, key, val) => {
|
|
611
|
-
if (isObject(key)) {
|
|
612
|
-
objectEach(key, (val1, key1) => {
|
|
613
|
-
setStyle(el, key1, val1);
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
else {
|
|
617
|
-
el.style.setProperty(stringKebabCase(key), val);
|
|
618
|
-
}
|
|
619
|
-
};
|
|
620
|
-
/**
|
|
621
|
-
* 获取元素样式
|
|
622
|
-
* @param {HTMLElement} el 元素
|
|
623
|
-
* @param {string} key
|
|
624
|
-
* @returns {string}
|
|
625
|
-
*/
|
|
626
|
-
function getStyle(el, key) {
|
|
627
|
-
return getComputedStyle(el).getPropertyValue(key);
|
|
628
|
-
}
|
|
629
|
-
function smoothScroll(options) {
|
|
630
|
-
return new Promise(resolve => {
|
|
631
|
-
const defaults = {
|
|
632
|
-
el: document,
|
|
633
|
-
to: 0,
|
|
634
|
-
duration: 567,
|
|
635
|
-
easing: 'ease'
|
|
636
|
-
};
|
|
637
|
-
const { el, to, duration, easing } = objectAssign(defaults, options);
|
|
638
|
-
const htmlEl = document.documentElement;
|
|
639
|
-
const bodyEl = document.body;
|
|
640
|
-
const globalMode = el === window || el === document || el === htmlEl || el === bodyEl;
|
|
641
|
-
const els = globalMode ? [htmlEl, bodyEl] : [el];
|
|
642
|
-
const query = () => {
|
|
643
|
-
let value = 0;
|
|
644
|
-
arrayEach(els, el => {
|
|
645
|
-
if ('scrollTop' in el) {
|
|
646
|
-
value = el.scrollTop;
|
|
647
|
-
return false;
|
|
648
|
-
}
|
|
649
|
-
});
|
|
650
|
-
return value;
|
|
651
|
-
};
|
|
652
|
-
const update = (val) => {
|
|
653
|
-
els.forEach(el => {
|
|
654
|
-
if ('scrollTop' in el) {
|
|
655
|
-
el.scrollTop = val;
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
};
|
|
659
|
-
let startTime;
|
|
660
|
-
const startValue = query();
|
|
661
|
-
const length = to - startValue;
|
|
662
|
-
const easingFn = easingFunctional(easing);
|
|
663
|
-
const render = () => {
|
|
664
|
-
const now = performance.now();
|
|
665
|
-
const passingTime = startTime ? now - startTime : 0;
|
|
666
|
-
const t = passingTime / duration;
|
|
667
|
-
const p = easingFn(t);
|
|
668
|
-
if (!startTime)
|
|
669
|
-
startTime = now;
|
|
670
|
-
update(startValue + length * p);
|
|
671
|
-
if (t >= 1)
|
|
672
|
-
resolve();
|
|
673
|
-
else
|
|
674
|
-
requestAnimationFrame(render);
|
|
675
|
-
};
|
|
676
|
-
render();
|
|
677
|
-
});
|
|
678
|
-
}
|
|
679
|
-
/**
|
|
680
|
-
* 获取元素样式属性的计算值
|
|
681
|
-
* @param {HTMLElement} el
|
|
682
|
-
* @param {string} property
|
|
683
|
-
* @param {boolean} reNumber
|
|
684
|
-
* @returns {string|number}
|
|
685
|
-
*/
|
|
686
|
-
function getComputedCssVal(el, property, reNumber = true) {
|
|
687
|
-
const originVal = getComputedStyle(el).getPropertyValue(property) ?? '';
|
|
688
|
-
return reNumber ? Number(originVal.replace(/([0-9]*)(.*)/g, '$1')) : originVal;
|
|
689
|
-
}
|
|
690
|
-
/**
|
|
691
|
-
* 字符串的像素宽度
|
|
692
|
-
* @param {string} str 目标字符串
|
|
693
|
-
* @param {number} fontSize 字符串字体大小
|
|
694
|
-
* @param {boolean} isRemoveDom 计算后是否移除中间dom元素
|
|
695
|
-
* @returns {*}
|
|
696
|
-
*/
|
|
697
|
-
function getStrWidthPx(str, fontSize = 14, isRemoveDom = false) {
|
|
698
|
-
let strWidth = 0;
|
|
699
|
-
console.assert(isString(str), `${str} 不是有效的字符串`);
|
|
700
|
-
if (isString(str) && str.length > 0) {
|
|
701
|
-
let getEle = document.querySelector('#getStrWidth1494304949567');
|
|
702
|
-
if (!getEle) {
|
|
703
|
-
const _ele = document.createElement('span');
|
|
704
|
-
_ele.id = 'getStrWidth1494304949567';
|
|
705
|
-
_ele.style.fontSize = fontSize + 'px';
|
|
706
|
-
_ele.style.whiteSpace = 'nowrap';
|
|
707
|
-
_ele.style.visibility = 'hidden';
|
|
708
|
-
_ele.textContent = str;
|
|
709
|
-
document.body.appendChild(_ele);
|
|
710
|
-
getEle = _ele;
|
|
711
|
-
}
|
|
712
|
-
getEle.textContent = str;
|
|
713
|
-
strWidth = getEle.offsetWidth;
|
|
714
|
-
if (isRemoveDom) {
|
|
715
|
-
document.body.appendChild(getEle);
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
return strWidth;
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
const textEl = document.createElement('textarea');
|
|
722
|
-
setStyle(textEl, {
|
|
723
|
-
position: 'absolute',
|
|
724
|
-
top: '-9999px',
|
|
725
|
-
left: '-9999px',
|
|
726
|
-
opacity: 0
|
|
727
|
-
});
|
|
728
|
-
document.body.appendChild(textEl);
|
|
729
|
-
/**
|
|
730
|
-
* 复制文本
|
|
731
|
-
* @param {string} text
|
|
732
|
-
*/
|
|
733
|
-
function copyText(text) {
|
|
734
|
-
textEl.value = text;
|
|
735
|
-
textEl.focus({ preventScroll: true });
|
|
736
|
-
textEl.select();
|
|
737
|
-
try {
|
|
738
|
-
document.execCommand('copy');
|
|
739
|
-
textEl.blur();
|
|
740
|
-
}
|
|
741
|
-
catch (err) {
|
|
742
|
-
// ignore
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
/**
|
|
747
|
-
* 获取cookie
|
|
748
|
-
* @param {string} name
|
|
749
|
-
* @returns {string}
|
|
750
|
-
*/
|
|
751
|
-
function cookieGet(name) {
|
|
752
|
-
const { cookie } = document;
|
|
753
|
-
if (!cookie)
|
|
754
|
-
return '';
|
|
755
|
-
const result = cookie.split(';');
|
|
756
|
-
for (let i = 0; i < result.length; i++) {
|
|
757
|
-
const item = result[i];
|
|
758
|
-
const [key, val = ''] = item.split('=');
|
|
759
|
-
if (key === name)
|
|
760
|
-
return decodeURIComponent(val);
|
|
761
|
-
}
|
|
762
|
-
return '';
|
|
763
|
-
}
|
|
764
|
-
/**
|
|
765
|
-
* 设置 cookie
|
|
766
|
-
* @param {string} name
|
|
767
|
-
* @param {string} value
|
|
768
|
-
* @param {number | Date} [maxAge]
|
|
769
|
-
*/
|
|
770
|
-
function cookieSet(name, value, maxAge) {
|
|
771
|
-
const metas = [];
|
|
772
|
-
const EXPIRES = 'expires';
|
|
773
|
-
metas.push([name, encodeURIComponent(value)]);
|
|
774
|
-
if (isNumber(maxAge)) {
|
|
775
|
-
const d = new Date();
|
|
776
|
-
d.setTime(d.getTime() + maxAge);
|
|
777
|
-
metas.push([EXPIRES, d.toUTCString()]);
|
|
778
|
-
}
|
|
779
|
-
else if (isDate(maxAge)) {
|
|
780
|
-
metas.push([EXPIRES, maxAge.toUTCString()]);
|
|
781
|
-
}
|
|
782
|
-
metas.push(['path', '/']);
|
|
783
|
-
document.cookie = metas
|
|
784
|
-
.map(item => {
|
|
785
|
-
const [key, val] = item;
|
|
786
|
-
return `${key}=${val}`;
|
|
787
|
-
})
|
|
788
|
-
.join(';');
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* 删除单个 cookie
|
|
792
|
-
* @param name cookie 名称
|
|
793
|
-
*/
|
|
794
|
-
const cookieDel = (name) => cookieSet(name, '', -1);
|
|
795
|
-
|
|
796
|
-
const isValidDate = (any) => isDate(any) && !isNaN(any.getTime());
|
|
797
|
-
/* istanbul ignore next */
|
|
798
|
-
const guessDateSeparator = (value) => {
|
|
799
|
-
if (!isString(value))
|
|
800
|
-
return;
|
|
801
|
-
const value2 = value.replace(/-/g, '/');
|
|
802
|
-
return new Date(value2);
|
|
803
|
-
};
|
|
804
|
-
/* istanbul ignore next */
|
|
805
|
-
const guessDateTimezone = (value) => {
|
|
806
|
-
if (!isString(value))
|
|
807
|
-
return;
|
|
808
|
-
const re = /([+-])(\d\d)(\d\d)$/;
|
|
809
|
-
const matches = re.exec(value);
|
|
810
|
-
if (!matches)
|
|
811
|
-
return;
|
|
812
|
-
const value2 = value.replace(re, 'Z');
|
|
813
|
-
const d = new Date(value2);
|
|
814
|
-
if (!isValidDate(d))
|
|
815
|
-
return;
|
|
816
|
-
const [, flag, hours, minutes] = matches;
|
|
817
|
-
const hours2 = parseInt(hours, 10);
|
|
818
|
-
const minutes2 = parseInt(minutes, 10);
|
|
819
|
-
const offset = (a, b) => (flag === '+' ? a - b : a + b);
|
|
820
|
-
d.setHours(offset(d.getHours(), hours2));
|
|
821
|
-
d.setMinutes(offset(d.getMinutes(), minutes2));
|
|
822
|
-
return d;
|
|
823
|
-
};
|
|
824
|
-
/**
|
|
825
|
-
* 解析为Date对象
|
|
826
|
-
* @param {DateValue} value - 可以是数值、字符串或 Date 对象
|
|
827
|
-
* @returns {Date} - 转换后的目标Date
|
|
828
|
-
*/
|
|
829
|
-
function dateParse(value) {
|
|
830
|
-
const d1 = new Date(value);
|
|
831
|
-
if (isValidDate(d1))
|
|
832
|
-
return d1;
|
|
833
|
-
// safari 浏览器的日期解析有问题
|
|
834
|
-
// new Date('2020-06-26 18:06:15') 返回值是一个非法日期对象
|
|
835
|
-
/* istanbul ignore next */
|
|
836
|
-
const d2 = guessDateSeparator(value);
|
|
837
|
-
/* istanbul ignore next */
|
|
838
|
-
if (isValidDate(d2))
|
|
839
|
-
return d2;
|
|
840
|
-
// safari 浏览器的日期解析有问题
|
|
841
|
-
// new Date('2020-06-26T18:06:15.000+0800') 返回值是一个非法日期对象
|
|
842
|
-
/* istanbul ignore next */
|
|
843
|
-
const d3 = guessDateTimezone(value);
|
|
844
|
-
/* istanbul ignore next */
|
|
845
|
-
if (isValidDate(d3))
|
|
846
|
-
return d3;
|
|
847
|
-
throw new SyntaxError(`${value.toString()} 不是一个合法的日期描述`);
|
|
848
|
-
}
|
|
849
|
-
/**
|
|
850
|
-
* 格式化为日期对象(带自定义格式化模板)
|
|
851
|
-
* @param {DateValue} value 可以是数值、字符串或 Date 对象
|
|
852
|
-
* @param {string} [format] 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
|
|
853
|
-
* - YYYY:年
|
|
854
|
-
* - yyyy: 年
|
|
855
|
-
* - MM:月
|
|
856
|
-
* - DD:日
|
|
857
|
-
* - dd: 日
|
|
858
|
-
* - HH:时(24 小时制)
|
|
859
|
-
* - hh:时(12 小时制)
|
|
860
|
-
* - mm:分
|
|
861
|
-
* - ss:秒
|
|
862
|
-
* - SSS:毫秒
|
|
863
|
-
* @returns {string}
|
|
864
|
-
*/
|
|
865
|
-
// export const dateStringify = (value: DateValue, format = 'YYYY-MM-DD HH:mm:ss'): string => {
|
|
866
|
-
// const date = dateParse(value);
|
|
867
|
-
// let fmt = format;
|
|
868
|
-
// let ret;
|
|
869
|
-
// const opt: DateObj = {
|
|
870
|
-
// 'Y+': `${date.getFullYear()}`, // 年
|
|
871
|
-
// 'y+': `${date.getFullYear()}`, // 年
|
|
872
|
-
// 'M+': `${date.getMonth() + 1}`, // 月
|
|
873
|
-
// 'D+': `${date.getDate()}`, // 日
|
|
874
|
-
// 'd+': `${date.getDate()}`, // 日
|
|
875
|
-
// 'H+': `${date.getHours()}`, // 时
|
|
876
|
-
// 'm+': `${date.getMinutes()}`, // 分
|
|
877
|
-
// 's+': `${date.getSeconds()}`, // 秒
|
|
878
|
-
// 'S+': `${date.getMilliseconds()}` // 豪秒
|
|
879
|
-
// };
|
|
880
|
-
// for (const k in opt) {
|
|
881
|
-
// ret = new RegExp(`(${k})`).exec(fmt);
|
|
882
|
-
// if (ret) {
|
|
883
|
-
// fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
|
|
884
|
-
// }
|
|
885
|
-
// }
|
|
886
|
-
// return fmt;
|
|
887
|
-
// };
|
|
836
|
+
else if (typeof queryObj[item[1]] === 'string') {
|
|
837
|
+
queryObj[item[1]] = [queryObj[item[1]], item[2]];
|
|
838
|
+
}
|
|
839
|
+
else {
|
|
840
|
+
queryObj[item[1]].push(item[2]);
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
return queryObj;
|
|
844
|
+
}
|
|
845
|
+
|
|
888
846
|
/**
|
|
889
|
-
*
|
|
890
|
-
* @param {
|
|
891
|
-
* @
|
|
847
|
+
* 判断元素是否包含某个样式名
|
|
848
|
+
* @param {HTMLElement} el
|
|
849
|
+
* @param {string} className
|
|
850
|
+
* @returns {boolean}
|
|
892
851
|
*/
|
|
893
|
-
function
|
|
894
|
-
|
|
895
|
-
|
|
852
|
+
function hasClass(el, className) {
|
|
853
|
+
if (className.indexOf(' ') !== -1)
|
|
854
|
+
throw new Error('className should not contain space.');
|
|
855
|
+
return el.classList.contains(className);
|
|
896
856
|
}
|
|
857
|
+
const eachClassName = (classNames, func) => {
|
|
858
|
+
const classNameList = classNames.split(/\s+/g);
|
|
859
|
+
classNameList.forEach(func);
|
|
860
|
+
};
|
|
897
861
|
/**
|
|
898
|
-
*
|
|
899
|
-
* @param {
|
|
900
|
-
* @
|
|
862
|
+
* 给元素增加样式名
|
|
863
|
+
* @param {HTMLElement} el
|
|
864
|
+
* @param {string} classNames
|
|
901
865
|
*/
|
|
902
|
-
function
|
|
903
|
-
|
|
904
|
-
d.setDate(d.getDate() + 1);
|
|
905
|
-
return dateParse(d.getTime() - 1);
|
|
866
|
+
function addClass(el, classNames) {
|
|
867
|
+
eachClassName(classNames, className => el.classList.add(className));
|
|
906
868
|
}
|
|
907
869
|
/**
|
|
908
|
-
*
|
|
909
|
-
* @param {
|
|
910
|
-
* @param {string}
|
|
911
|
-
* - YYYY:年
|
|
912
|
-
* - yyyy: 年
|
|
913
|
-
* - MM:月
|
|
914
|
-
* - DD:日
|
|
915
|
-
* - dd: 日
|
|
916
|
-
* - HH:时(24 小时制)
|
|
917
|
-
* - mm:分
|
|
918
|
-
* - ss:秒
|
|
919
|
-
* - SSS:毫秒
|
|
920
|
-
* - ww: 周
|
|
921
|
-
* @returns {string} 格式化后的日期字符串
|
|
870
|
+
* 给元素移除样式名
|
|
871
|
+
* @param {HTMLElement} el
|
|
872
|
+
* @param {string} classNames
|
|
922
873
|
*/
|
|
923
|
-
function
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
'w+': ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()] // 周
|
|
938
|
-
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
|
939
|
-
};
|
|
940
|
-
for (const k in opt) {
|
|
941
|
-
ret = new RegExp('(' + k + ')').exec(fmt);
|
|
942
|
-
if (ret) {
|
|
943
|
-
fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
|
|
944
|
-
}
|
|
874
|
+
function removeClass(el, classNames) {
|
|
875
|
+
eachClassName(classNames, className => el.classList.remove(className));
|
|
876
|
+
}
|
|
877
|
+
/**
|
|
878
|
+
* 设置元素样式
|
|
879
|
+
* @param {HTMLElement} el
|
|
880
|
+
* @param {string | Style} key
|
|
881
|
+
* @param {string} val
|
|
882
|
+
*/
|
|
883
|
+
const setStyle = (el, key, val) => {
|
|
884
|
+
if (isObject(key)) {
|
|
885
|
+
objectEach(key, (val1, key1) => {
|
|
886
|
+
setStyle(el, key1, val1);
|
|
887
|
+
});
|
|
945
888
|
}
|
|
946
|
-
|
|
889
|
+
else {
|
|
890
|
+
el.style.setProperty(stringKebabCase(key), val);
|
|
891
|
+
}
|
|
892
|
+
};
|
|
893
|
+
/**
|
|
894
|
+
* 获取元素样式
|
|
895
|
+
* @param {HTMLElement} el 元素
|
|
896
|
+
* @param {string} key
|
|
897
|
+
* @returns {string}
|
|
898
|
+
*/
|
|
899
|
+
function getStyle(el, key) {
|
|
900
|
+
return getComputedStyle(el).getPropertyValue(key);
|
|
901
|
+
}
|
|
902
|
+
function smoothScroll(options) {
|
|
903
|
+
return new Promise(resolve => {
|
|
904
|
+
const defaults = {
|
|
905
|
+
el: document,
|
|
906
|
+
to: 0,
|
|
907
|
+
duration: 567,
|
|
908
|
+
easing: 'ease'
|
|
909
|
+
};
|
|
910
|
+
const { el, to, duration, easing } = objectAssign(defaults, options);
|
|
911
|
+
const htmlEl = document.documentElement;
|
|
912
|
+
const bodyEl = document.body;
|
|
913
|
+
const globalMode = el === window || el === document || el === htmlEl || el === bodyEl;
|
|
914
|
+
const els = globalMode ? [htmlEl, bodyEl] : [el];
|
|
915
|
+
const query = () => {
|
|
916
|
+
let value = 0;
|
|
917
|
+
arrayEach(els, el => {
|
|
918
|
+
if ('scrollTop' in el) {
|
|
919
|
+
value = el.scrollTop;
|
|
920
|
+
return false;
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
return value;
|
|
924
|
+
};
|
|
925
|
+
const update = (val) => {
|
|
926
|
+
els.forEach(el => {
|
|
927
|
+
if ('scrollTop' in el) {
|
|
928
|
+
el.scrollTop = val;
|
|
929
|
+
}
|
|
930
|
+
});
|
|
931
|
+
};
|
|
932
|
+
let startTime;
|
|
933
|
+
const startValue = query();
|
|
934
|
+
const length = to - startValue;
|
|
935
|
+
const easingFn = easingFunctional(easing);
|
|
936
|
+
const render = () => {
|
|
937
|
+
const now = performance.now();
|
|
938
|
+
const passingTime = startTime ? now - startTime : 0;
|
|
939
|
+
const t = passingTime / duration;
|
|
940
|
+
const p = easingFn(t);
|
|
941
|
+
if (!startTime)
|
|
942
|
+
startTime = now;
|
|
943
|
+
update(startValue + length * p);
|
|
944
|
+
if (t >= 1)
|
|
945
|
+
resolve();
|
|
946
|
+
else
|
|
947
|
+
requestAnimationFrame(render);
|
|
948
|
+
};
|
|
949
|
+
render();
|
|
950
|
+
});
|
|
947
951
|
}
|
|
948
952
|
/**
|
|
949
|
-
*
|
|
950
|
-
* @param {
|
|
951
|
-
* @param {
|
|
952
|
-
* @param {
|
|
953
|
-
* @returns {string}
|
|
953
|
+
* 获取元素样式属性的计算值
|
|
954
|
+
* @param {HTMLElement} el
|
|
955
|
+
* @param {string} property
|
|
956
|
+
* @param {boolean} reNumber
|
|
957
|
+
* @returns {string|number}
|
|
954
958
|
*/
|
|
955
|
-
function
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
959
|
-
const millisecondGap = newDate.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
|
|
960
|
-
const targetDate = new Date(millisecondGap);
|
|
961
|
-
const finalNewDate = targetDate.getFullYear() +
|
|
962
|
-
sep +
|
|
963
|
-
String(targetDate.getMonth() + 1).padStart(2, '0') +
|
|
964
|
-
'-' +
|
|
965
|
-
String(targetDate.getDate()).padStart(2, '0');
|
|
966
|
-
return finalNewDate;
|
|
959
|
+
function getComputedCssVal(el, property, reNumber = true) {
|
|
960
|
+
const originVal = getComputedStyle(el).getPropertyValue(property) ?? '';
|
|
961
|
+
return reNumber ? Number(originVal.replace(/([0-9]*)(.*)/g, '$1')) : originVal;
|
|
967
962
|
}
|
|
968
963
|
/**
|
|
969
|
-
*
|
|
970
|
-
* @param {
|
|
971
|
-
* @param {number}
|
|
972
|
-
* @param {
|
|
973
|
-
* @
|
|
974
|
-
* @returns {string} 转换后的目标日期时间
|
|
964
|
+
* 字符串的像素宽度
|
|
965
|
+
* @param {string} str 目标字符串
|
|
966
|
+
* @param {number} fontSize 字符串字体大小
|
|
967
|
+
* @param {boolean} isRemoveDom 计算后是否移除中间dom元素
|
|
968
|
+
* @returns {*}
|
|
975
969
|
*/
|
|
976
|
-
function
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
970
|
+
function getStrWidthPx(str, fontSize = 14, isRemoveDom = false) {
|
|
971
|
+
let strWidth = 0;
|
|
972
|
+
console.assert(isString(str), `${str} 不是有效的字符串`);
|
|
973
|
+
if (isString(str) && str.length > 0) {
|
|
974
|
+
let getEle = document.querySelector('#getStrWidth1494304949567');
|
|
975
|
+
if (!getEle) {
|
|
976
|
+
const _ele = document.createElement('span');
|
|
977
|
+
_ele.id = 'getStrWidth1494304949567';
|
|
978
|
+
_ele.style.fontSize = fontSize + 'px';
|
|
979
|
+
_ele.style.whiteSpace = 'nowrap';
|
|
980
|
+
_ele.style.visibility = 'hidden';
|
|
981
|
+
_ele.textContent = str;
|
|
982
|
+
document.body.appendChild(_ele);
|
|
983
|
+
getEle = _ele;
|
|
984
|
+
}
|
|
985
|
+
getEle.textContent = str;
|
|
986
|
+
strWidth = getEle.offsetWidth;
|
|
987
|
+
if (isRemoveDom) {
|
|
988
|
+
document.body.appendChild(getEle);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
return strWidth;
|
|
994
992
|
}
|
|
995
993
|
|
|
996
994
|
/**
|
|
@@ -1107,21 +1105,30 @@
|
|
|
1107
1105
|
return params.toString();
|
|
1108
1106
|
}
|
|
1109
1107
|
|
|
1110
|
-
const anchorEl = document.createElement('a');
|
|
1111
1108
|
/**
|
|
1112
1109
|
* url 解析
|
|
1113
1110
|
* @param {string} url
|
|
1111
|
+
* @param {boolean} isModernApi 使用现代API:URL, 默认true (对无效url解析会抛错), 否则使用a标签来解析(兼容性更强)
|
|
1114
1112
|
* @returns {Url}
|
|
1115
1113
|
*/
|
|
1116
|
-
const urlParse = (url) => {
|
|
1117
|
-
|
|
1118
|
-
|
|
1114
|
+
const urlParse = (url, isModernApi = true) => {
|
|
1115
|
+
// @ts-ignore
|
|
1116
|
+
let urlObj = null;
|
|
1117
|
+
if (isFunction(URL) && isModernApi) {
|
|
1118
|
+
urlObj = new URL(url);
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
urlObj = document.createElement('a');
|
|
1122
|
+
urlObj.href = url;
|
|
1123
|
+
}
|
|
1124
|
+
const { protocol, username, password, host, port, hostname, hash, search, pathname: _pathname } = urlObj;
|
|
1119
1125
|
// fix: ie 浏览器下,解析出来的 pathname 是没有 / 根的
|
|
1120
1126
|
const pathname = pathJoin('/', _pathname);
|
|
1121
1127
|
const auth = username && password ? `${username}:${password}` : '';
|
|
1122
1128
|
const query = search.replace(/^\?/, '');
|
|
1123
1129
|
const searchParams = qsParse(query);
|
|
1124
1130
|
const path = `${pathname}${search}`;
|
|
1131
|
+
urlObj = null;
|
|
1125
1132
|
return {
|
|
1126
1133
|
protocol,
|
|
1127
1134
|
auth,
|
|
@@ -1222,17 +1229,41 @@
|
|
|
1222
1229
|
}
|
|
1223
1230
|
/**
|
|
1224
1231
|
* 根据URL下载文件(解决跨域a.download不生效问题)
|
|
1232
|
+
*
|
|
1233
|
+
* 可定制下载成功的状态码status(浏览器原生状态码)
|
|
1234
|
+
*
|
|
1235
|
+
* 支持下载操作成功、失败后的回调
|
|
1225
1236
|
* @param {string} url
|
|
1226
1237
|
* @param {string} filename
|
|
1227
|
-
* @param {
|
|
1238
|
+
* @param {CrossOriginDownloadParams} options
|
|
1228
1239
|
*/
|
|
1229
|
-
function crossOriginDownload(url, filename,
|
|
1240
|
+
function crossOriginDownload(url, filename, options) {
|
|
1241
|
+
const { successCode = 200, successCallback, failCallback } = isNullOrUnDef(options) ? { successCode: 200, successCallback: void 0, failCallback: void 0 } : options;
|
|
1230
1242
|
const xhr = new XMLHttpRequest();
|
|
1231
1243
|
xhr.open('GET', url, true);
|
|
1232
1244
|
xhr.responseType = 'blob';
|
|
1233
1245
|
xhr.onload = function () {
|
|
1234
|
-
if (xhr.status ===
|
|
1235
|
-
downloadBlob(xhr.response, filename,
|
|
1246
|
+
if (xhr.status === successCode)
|
|
1247
|
+
downloadBlob(xhr.response, filename, successCallback);
|
|
1248
|
+
else if (isFunction(failCallback)) {
|
|
1249
|
+
const status = xhr.status;
|
|
1250
|
+
const responseType = xhr.getResponseHeader('Content-Type');
|
|
1251
|
+
if (isString(responseType) && responseType.includes('application/json')) {
|
|
1252
|
+
const reader = new FileReader();
|
|
1253
|
+
reader.onload = () => {
|
|
1254
|
+
failCallback({ status, response: reader.result });
|
|
1255
|
+
};
|
|
1256
|
+
reader.readAsText(xhr.response);
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
failCallback(xhr);
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
xhr.onerror = e => {
|
|
1264
|
+
if (isFunction(failCallback)) {
|
|
1265
|
+
failCallback({ status: 0, code: 'ERROR_CONNECTION_REFUSED' });
|
|
1266
|
+
}
|
|
1236
1267
|
};
|
|
1237
1268
|
xhr.send();
|
|
1238
1269
|
}
|
|
@@ -1858,35 +1889,69 @@
|
|
|
1858
1889
|
return ret.join('');
|
|
1859
1890
|
}
|
|
1860
1891
|
/**
|
|
1861
|
-
*
|
|
1892
|
+
* 将数字转换为携带单位的字符串
|
|
1862
1893
|
* @param {number | string} num
|
|
1863
1894
|
* @param {Array<string>} units
|
|
1864
|
-
* @param {
|
|
1865
|
-
* @param {number} exponent
|
|
1895
|
+
* @param {INumberAbbr} options default: { ratio: 1000, decimals: 0, separator: ' ' }
|
|
1866
1896
|
* @returns {string}
|
|
1867
1897
|
*/
|
|
1868
|
-
const numberAbbr = (num, units,
|
|
1898
|
+
const numberAbbr = (num, units, options = { ratio: 1000, decimals: 0, separator: ' ' }) => {
|
|
1899
|
+
const { ratio = 1000, decimals = 0, separator = ' ' } = options;
|
|
1869
1900
|
const { length } = units;
|
|
1870
1901
|
if (length === 0)
|
|
1871
|
-
throw new Error('
|
|
1902
|
+
throw new Error('At least one unit is required');
|
|
1872
1903
|
let num2 = Number(num);
|
|
1873
1904
|
let times = 0;
|
|
1874
1905
|
while (num2 >= ratio && times < length - 1) {
|
|
1875
1906
|
num2 = num2 / ratio;
|
|
1876
1907
|
times++;
|
|
1877
1908
|
}
|
|
1878
|
-
const value = num2.toFixed(
|
|
1909
|
+
const value = num2.toFixed(decimals);
|
|
1879
1910
|
const unit = units[times];
|
|
1880
|
-
return value
|
|
1911
|
+
return String(value) + separator + unit;
|
|
1881
1912
|
};
|
|
1913
|
+
/**
|
|
1914
|
+
* Converting file size in bytes to human-readable string
|
|
1915
|
+
* reference: https://zh.wikipedia.org/wiki/%E5%8D%83%E5%AD%97%E8%8A%82
|
|
1916
|
+
* @param {number | string} num bytes Number in Bytes
|
|
1917
|
+
* @param {IHumanFileSizeOptions} options default: { decimals = 0, si = false, separator = ' ' }
|
|
1918
|
+
* si: True to use metric (SI) units, aka powers of 1000, the units is
|
|
1919
|
+
* ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'].
|
|
1920
|
+
* False to use binary (IEC), aka powers of 1024, the units is
|
|
1921
|
+
* ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
|
|
1922
|
+
* @returns
|
|
1923
|
+
*/
|
|
1924
|
+
function humanFileSize(num, options) {
|
|
1925
|
+
const { decimals = 0, si = false, separator = ' ', maxUnit } = options;
|
|
1926
|
+
const units = si
|
|
1927
|
+
? ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
1928
|
+
: ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
|
1929
|
+
if (!isNullOrUnDef(maxUnit)) {
|
|
1930
|
+
const targetIndex = units.findIndex(el => el === maxUnit);
|
|
1931
|
+
if (targetIndex !== -1) {
|
|
1932
|
+
units.splice(targetIndex + 1);
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
return numberAbbr(num, units, { ratio: si ? 1000 : 1024, decimals, separator });
|
|
1936
|
+
}
|
|
1882
1937
|
/**
|
|
1883
1938
|
* 将数字格式化成千位分隔符显示的字符串
|
|
1884
|
-
* @param {number}
|
|
1885
|
-
* @param {
|
|
1939
|
+
* @param {number|string} num 数字
|
|
1940
|
+
* @param {number} decimals 格式化成指定小数位精度的参数
|
|
1886
1941
|
* @returns {string}
|
|
1887
1942
|
*/
|
|
1888
|
-
function formatNumber(
|
|
1889
|
-
|
|
1943
|
+
function formatNumber(num, decimals) {
|
|
1944
|
+
if (isNullOrUnDef(decimals)) {
|
|
1945
|
+
return parseInt(String(num)).toLocaleString();
|
|
1946
|
+
}
|
|
1947
|
+
let prec = 0;
|
|
1948
|
+
if (!isNumber(decimals)) {
|
|
1949
|
+
throw new Error('Decimals must be a positive number not less than zero');
|
|
1950
|
+
}
|
|
1951
|
+
else if (decimals > 0) {
|
|
1952
|
+
prec = decimals;
|
|
1953
|
+
}
|
|
1954
|
+
return Number(Number(num).toFixed(prec)).toLocaleString('en-US');
|
|
1890
1955
|
}
|
|
1891
1956
|
|
|
1892
1957
|
const padStartWithZero = (str, maxLength = 2) => String(str).padStart(maxLength, '0');
|
|
@@ -2266,7 +2331,7 @@
|
|
|
2266
2331
|
...node,
|
|
2267
2332
|
[childField]: [] // 清空子级
|
|
2268
2333
|
};
|
|
2269
|
-
item
|
|
2334
|
+
objectHas(item, childField) && delete item[childField];
|
|
2270
2335
|
res.push(item);
|
|
2271
2336
|
if (node[childField]) {
|
|
2272
2337
|
const children = node[childField].map(item => ({
|
|
@@ -2629,7 +2694,8 @@
|
|
|
2629
2694
|
* default match symbol {} same as /{\s*([^{}\s]*)\s*}/g
|
|
2630
2695
|
*/
|
|
2631
2696
|
function parseVarFromString(str, leftMatchSymbol = '{', rightMatchSymbol = '}') {
|
|
2632
|
-
|
|
2697
|
+
// @ts-ignore
|
|
2698
|
+
return Array.from(str.matchAll(parseVariableRegExp(leftMatchSymbol, rightMatchSymbol))).map(el => isNullOrUnDef(el) ? void 0 : el[1]);
|
|
2633
2699
|
}
|
|
2634
2700
|
/**
|
|
2635
2701
|
* 替换字符串中的插值变量
|
|
@@ -2967,6 +3033,7 @@
|
|
|
2967
3033
|
exports.flatTree = flatTree;
|
|
2968
3034
|
exports.forEachDeep = forEachDeep;
|
|
2969
3035
|
exports.formatDate = formatDate;
|
|
3036
|
+
exports.formatMoney = formatNumber;
|
|
2970
3037
|
exports.formatNumber = formatNumber;
|
|
2971
3038
|
exports.formatTree = formatTree;
|
|
2972
3039
|
exports.fuzzySearchTree = fuzzySearchTree;
|
|
@@ -2976,6 +3043,7 @@
|
|
|
2976
3043
|
exports.getStrWidthPx = getStrWidthPx;
|
|
2977
3044
|
exports.getStyle = getStyle;
|
|
2978
3045
|
exports.hasClass = hasClass;
|
|
3046
|
+
exports.humanFileSize = humanFileSize;
|
|
2979
3047
|
exports.isArray = isArray;
|
|
2980
3048
|
exports.isBigInt = isBigInt;
|
|
2981
3049
|
exports.isBoolean = isBoolean;
|