mazey 3.2.0 → 3.3.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/src/index.ts CHANGED
@@ -1,1426 +1,1595 @@
1
- /* mazey */
2
-
3
- /**
4
- * @method calLongestCommonSubstring
5
- * @description Computes the longest common substring of two strings.
6
- * @param {string} aStr String
7
- * @param {string} bStr String
8
- * @returns {number} Length
9
- */
10
- export function calLongestCommonSubstring(aStr = '', bStr = ''): number {
11
- const aLen = aStr.length;
12
- const bLen = bStr.length;
13
- // 创建二维数组并且深拷贝
14
- const arr = deepCopyObject(new Array(aLen).fill(new Array(bLen).fill(0)));
15
- for (let i = 0; i < aLen; ++i) {
16
- for (let j = 0; j < bLen; ++j) {
17
- if (aStr[i] === bStr[j]) {
18
- let baseNum = 0;
19
- if (i > 0 && j > 0) {
20
- baseNum = arr[i - 1][j - 1];
21
- }
22
- arr[i][j] = baseNum + 1;
23
- }
24
- }
25
- }
26
- // 二维数组转一维数组
27
- const arr1 = Array.prototype.concat.apply([], arr);
28
- // 获取最长公共子串
29
- const maxLong = Math.max(...arr1);
30
- return maxLong;
31
- }
32
-
33
- /**
34
- * @method calLongestCommonSubsequence
35
- * @description 计算两个字符串的最长公共子序列
36
- * @param {string} aStr 字符串
37
- * @param {string} bStr 字符串
38
- * @returns {number} 长度
39
- */
40
- export function calLongestCommonSubsequence(aStr = '', bStr = ''): number {
41
- const aLen = aStr.length;
42
- const bLen = bStr.length;
43
- // 创建二维数组并且深拷贝
44
- const arr = deepCopyObject(new Array(aLen).fill(new Array(bLen).fill(0)));
45
- for (let i = 0; i < aLen; ++i) {
46
- for (let j = 0; j < bLen; ++j) {
47
- if (aStr[i] === bStr[j]) {
48
- let baseNum = 0;
49
- if (i > 0 && j > 0) {
50
- baseNum = arr[i - 1][j - 1];
51
- }
52
- arr[i][j] = baseNum + 1;
53
- } else {
54
- let [leftValue, topValue] = [0, 0];
55
- if (j > 0) {
56
- leftValue = arr[i][j - 1];
57
- }
58
- if (i > 0) {
59
- topValue = arr[i - 1][j];
60
- }
61
- arr[i][j] = Math.max(leftValue, topValue);
62
- }
63
- }
64
- }
65
- // 二维数组转一维数组
66
- const arr1 = Array.prototype.concat.apply([], arr);
67
- // 获取最长公共子串
68
- const maxLong = Math.max(...arr1);
69
- return maxLong;
70
- }
71
-
72
- /**
73
- * @method getQueryParam
74
- * @description Get the query param's value of the current Web URL(`location.search`).
75
- * @param {string} param Query param.
76
- * @returns {string} value
77
- */
78
- export function getQueryParam(param = ''): string {
79
- const reg = new RegExp('(^|&)' + param + '=([^&]*)(&|$)');
80
- const r = location.search.substr(1).match(reg);
81
- if (r !== null) {
82
- // return decodeURIComponent(unescape(r[2]));
83
- return decodeURIComponent(r[2]);
84
- }
85
- return '';
86
- }
87
-
88
- /**
89
- * @method getUrlParam
90
- * @description Get the query param's value of the input URL.
91
- * @param {string} url URL string.
92
- * @param {string} param Query param.
93
- * @returns {string} value
94
- */
95
- export function getUrlParam(url = '', param = ''): string {
96
- const result: any = {};
97
- // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
98
- url.replace(/\??(\w+)=([^&]*)&?/g, function (a, k, v): any {
99
- if (result[k] !== undefined) {
100
- const t = result[k];
101
- result[k] = [].concat(t, v);
102
- } else {
103
- result[k] = v;
104
- }
105
- });
106
- // if (param === undefined) {
107
- // return result;
108
- // } else {
109
- // return result[param] || '';
110
- // }
111
- return result[param] || '';
112
- }
113
-
114
- /**
115
- * @method updateQueryParam
116
- * @description Update the query param's value of the input URL.
117
- * @param {string} url URL string.
118
- * @param {string} param Query param.
119
- * @param {string} value Param's value.
120
- * @returns {string} URL.
121
- */
122
- export function updateQueryParam(url = '', param = '', value = ''): string {
123
- const re = new RegExp('([?&])' + param + '=.*?(&|$)', 'i');
124
- const separator = url.indexOf('?') !== -1 ? '&' : '?';
125
- if (url.match(re)) {
126
- return url.replace(re, '$1' + param + '=' + value + '$2');
127
- } else {
128
- return url + separator + param + '=' + value;
129
- }
130
- }
131
-
132
- /**
133
- * @method getHashQueryParam
134
- * @description Get the hash query param's value of the current Web URL(`location.hash`).
135
- * @param {string} param Query param.
136
- * @returns {string} value
137
- */
138
- export function getHashQueryParam(param = ''): string {
139
- const hashs = location.hash.split('?');
140
- if (hashs.length === 1) {
141
- return '';
142
- }
143
- const reg = new RegExp(`(^|&)${param}=([^&]*)(&|$)`);
144
- const ret = hashs[1].match(reg);
145
- return ret ? ret[2] : '';
146
- }
147
-
148
- /**
149
- * @method getDomain
150
- * @description Get the domain of URL, and other params.
151
- * @param {string} url
152
- * @param {array} rules Object.keys(location), ['href', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash'], ['hostname', 'pathname'] = 'km.mazey.net/plugins/servlet/mobile'
153
- */
154
- export function getDomain(url = '', rules = ['hostname']): string {
155
- const aEl: any = document.createElement('a');
156
- aEl.href = url;
157
- return rules.reduce((ret, v) => {
158
- ret += aEl[v];
159
- return ret;
160
- }, '');
161
- }
162
-
163
- /**
164
- * @method camelCaseToKebabCase
165
- * @description Transfer CamelCase to KebabCase.
166
- * @param {string} camelCase 'aBC' or 'ABC'
167
- * @returns {string} 'a-b-c'
168
- */
169
- export function camelCaseToKebabCase(camelCase = ''): string {
170
- const kebabCase = camelCase.replace(/([A-Z])/g, '-$1').toLowerCase();
171
- return kebabCase[0] === '-' ? kebabCase.substr(1) : kebabCase;
172
- }
173
-
174
- /**
175
- * @method camelCase2Underscore
176
- * @description Transfer CamelCase to Underscore.
177
- * @param {string} camelCase 'aBC' or 'ABC'
178
- * @returns {string} 'a_b_c'
179
- */
180
- export function camelCase2Underscore(camelCase = ''): string {
181
- const kebabCase = camelCase.replace(/([A-Z])/g, '_$1').toLowerCase();
182
- return kebabCase[0] === '_' ? kebabCase.substr(1) : kebabCase;
183
- }
184
-
185
- /**
186
- * @method mTrim
187
- * @description Remove leading and trailing whitespace or specified characters from string.
188
- * @param {string} str The string to trim.
189
- * @returns {string} Trimmed string.
190
- */
191
- export function mTrim(str = ''): string {
192
- str = str.replace(/^\s+/, ''); // 去除头部空格
193
- let end = str.length - 1;
194
- const ws = /\s/;
195
- while (ws.test(str.charAt(end))) {
196
- end--; // 最后一个非空格字符的索引
197
- }
198
- return str.slice(0, end + 1);
199
- }
200
-
201
- /**
202
- * @method newLine
203
- * @description Make a newline of HTML.
204
- * @param {string} str The string to make a newline.
205
- * @returns {string} A newline with `br`.
206
- */
207
- export function newLine(str = ''): string {
208
- if (!str) {
209
- return '';
210
- }
211
- const reg = new RegExp('\\n', 'g');
212
- return str.replace(reg, '<br />');
213
- }
214
-
215
- /**
216
- * @method deepCopyObject
217
- * @description Clone Object deeply.
218
- * @param {object} obj The value to clone.
219
- * @returns {object} Returns the deep cloned value.
220
- */
221
- export function deepCopyObject(obj: any): any {
222
- return JSON.parse(JSON.stringify(obj));
223
- }
224
-
225
- /**
226
- * @method isJsonString
227
- * @description Check whether it is a valid JSON string.
228
- * @param {string} str The string to check.
229
- * @returns {boolean} Return the result of checking.
230
- */
231
- export function isJsonString(str = ''): boolean {
232
- try {
233
- if (typeof JSON.parse(str) === 'object') {
234
- return true;
235
- }
236
- } catch (e) {
237
- /* pass */
238
- }
239
- return false;
240
- }
241
-
242
- /**
243
- * @method generateRndNum
244
- * @description Produce a random string of number, `generateRndNum(7)` => '7658495'.
245
- * @param {number} n Length
246
- * @returns {string} Return the random string.
247
- */
248
- export function generateRndNum(n = 5): string {
249
- let ret = '';
250
- while (n--) {
251
- ret += Math.floor(Math.random() * 10);
252
- }
253
- return ret;
254
- }
255
-
256
- /**
257
- * @method generateUniqueNum
258
- * @description 根据时间生成唯一标志的数字 mGenerateUniqueNum() => 1538324722364123。
259
- * @param {number} n 随机数的长度
260
- */
261
- export function generateUniqueNum(n = 3): string {
262
- const [now, rnd] = [mNow(), generateRndNum(n || 3)];
263
- return now + rnd;
264
- }
265
-
266
- /**
267
- * @method floatToPercent
268
- * @description 浮点数转为百分比 0.2 => 20%。
269
- * @param {number} num 浮点数
270
- * @param {number} fixSize 保留几位浮点数
271
- */
272
- export function floatToPercent(num = 0, fixSize = 0): string {
273
- let ret = '';
274
- if (fixSize) {
275
- ret = (num * 100).toFixed(fixSize);
276
- } else {
277
- ret = String(Math.floor(num * 100));
278
- }
279
- return `${ret}%`;
280
- }
281
-
282
- /**
283
- * @method floatFixed
284
- * @description 浮点数保留指定位。
285
- */
286
- export function floatFixed(num: string, size = 0): string {
287
- return parseFloat(num).toFixed(size);
288
- }
289
-
290
- /**
291
- * @method cancelBubble
292
- * @description 阻止冒泡。
293
- */
294
- export function cancelBubble(e: any): void {
295
- const ev = e || window.event;
296
- if (ev.stopPropagation) {
297
- // W3C
298
- ev.stopPropagation();
299
- } else {
300
- // IE
301
- ev.cancelBubble = true;
302
- }
303
- }
304
-
305
- /**
306
- * @method hasClass
307
- */
308
- export function hasClass(obj: any, cls: string): boolean {
309
- const oriCls = obj.className; // 获取对象的class值
310
- const oriClsArr = oriCls.split(/\s+/); // 分隔空格转换成数组
311
- for (let i = 0; i < oriClsArr.length; i++) {
312
- if (oriClsArr[i] === cls) {
313
- return true; // 若匹配到class则返回True
314
- }
315
- }
316
- return false; // 否则返回False
317
- }
318
-
319
- /**
320
- * @method addClass
321
- */
322
- export function addClass(obj: any, cls: string): void {
323
- const oriCls = obj.className;
324
- let space = '';
325
- let newCls = ''; // 获取对象的class值
326
- if (oriCls !== '') {
327
- space = ' '; // 若原来的class不为空,跟一个空格
328
- }
329
- newCls = oriCls + space + cls; // 将新的class加进去
330
- obj.className = newCls; // 替换新class
331
- }
332
-
333
- /**
334
- * @method removeClass
335
- */
336
- export function removeClass(obj: any, cls: string): void {
337
- const oriCls = obj.className;
338
- let newCls; // 获取对象的class值
339
- newCls = ' ' + oriCls + ' '; // 前后加空格
340
- newCls = newCls.replace(/(\s+)/gi, ' '); // 将多余的空格替换成一个空格
341
- newCls = newCls.replace(' ' + cls + ' ', ' '); // 将加了前后空格的cls替换成空格' '
342
- newCls = newCls.replace(/(^\s+)|(\s+$)/g, ''); // 去掉前后空格
343
- obj.className = newCls;
344
- }
345
-
346
- /**
347
- * @method throttle
348
- * @description 节流。
349
- */
350
- export function throttle(func: any, wait: number, options: any): any {
351
- // timeout: setTimeout Handle
352
- // previous: 上次时间戳
353
- let context: any = null;
354
- let args: any = null;
355
- let timeout: any = null;
356
- let [result, previous] = [null, 0];
357
- if (!options) {
358
- options = {};
359
- }
360
- const later = function () {
361
- previous = options.leading === false ? 0 : mNow();
362
- timeout = null;
363
- result = func.apply(context, args);
364
- if (!timeout) {
365
- context = args = null;
366
- }
367
- };
368
- return function (...argRest: Array<any>) {
369
- const now = mNow();
370
- if (!previous && options.leading === false) {
371
- previous = now;
372
- }
373
- const remaining = wait - (now - previous);
374
- context = this;
375
- args = argRest;
376
- if (remaining <= 0 || remaining > wait) {
377
- if (timeout) {
378
- clearTimeout(timeout);
379
- timeout = null;
380
- }
381
- previous = now;
382
- result = func.apply(context, args);
383
- if (!timeout) {
384
- context = args = null;
385
- }
386
- } else if (!timeout && options.trailing !== false) {
387
- timeout = setTimeout(later, remaining);
388
- }
389
- return result;
390
- };
391
- }
392
-
393
- /**
394
- * @method debounce
395
- * @description 去抖。
396
- */
397
- export function debounce(func: any, wait: number, immediate: any): any {
398
- let context: any = null;
399
- let timeout: any = null;
400
- let timestamp: any = null;
401
- let args: any = null;
402
- let [result] = [null];
403
- const later = function () {
404
- const last = mNow() - timestamp;
405
- if (last < wait && last >= 0) {
406
- timeout = setTimeout(later, wait - last);
407
- } else {
408
- timeout = null;
409
- if (!immediate) {
410
- result = func.apply(context, args);
411
- if (!timeout) {
412
- context = args = null;
413
- }
414
- }
415
- }
416
- };
417
- return function (...argRest: Array<any>) {
418
- context = this;
419
- args = argRest;
420
- timestamp = mNow();
421
- const callNow = immediate && !timeout;
422
- if (!timeout) {
423
- timeout = setTimeout(later, wait);
424
- }
425
- if (callNow) {
426
- result = func.apply(context, args);
427
- context = args = null;
428
- }
429
- return result;
430
- };
431
- }
432
-
433
- /**
434
- * @method friendlyInterval
435
- * @description 获取间隔时间。
436
- * @param {number/Date} start 开始时间戳 1585325367122
437
- * @param {number/Date} end 结束时间戳 1585325367122
438
- * @type {string} type 返回类型 d: 2(天) text: 2 天 4 时...
439
- * @returns {String/number} 取决于 type
440
- */
441
- export function friendlyInterval(start = 0, end = 0, { type = 'd' } = {}): number | string {
442
- if (!isNumber(start)) start = new Date(start).getTime();
443
- if (!isNumber(end)) end = new Date(end).getTime();
444
- const t = end - start;
445
- let ret = null;
446
- let [d, h, m, s] = new Array(4).fill(0);
447
- if (t >= 0) {
448
- d = Math.floor(t / 1000 / 60 / 60 / 24);
449
- h = Math.floor(t / 1000 / 60 / 60);
450
- m = Math.floor(t / 1000 / 60);
451
- s = Math.floor(t / 1000);
452
- switch (type) {
453
- case 'd':
454
- ret = d;
455
- break;
456
- case 'text':
457
- d = Math.floor(t / 1000 / 60 / 60 / 24);
458
- h = Math.floor((t / 1000 / 60 / 60) % 24);
459
- m = Math.floor((t / 1000 / 60) % 60);
460
- s = Math.floor((t / 1000) % 60);
461
- ret = d + ' 天 ' + h + ' 时 ' + m + ' 分 ' + s + ' 秒';
462
- break;
463
- default:
464
- ret = s;
465
- }
466
- }
467
- return ret;
468
- }
469
-
470
- /**
471
- * @method isNumber
472
- * @description 判断是否有效数字
473
- * @param {*} num 被判断的值
474
- * @param {boolean} isNaNAsNumber 是否 NaN 算数字(默认不算)
475
- * @param {boolean} isUnFiniteAsNumber 是否 无限 算数字(默认不算)
476
- * @returns {boolean} true 是数字
477
- */
478
- export function isNumber(num: any, { isNaNAsNumber = false, isUnFiniteAsNumber = false } = {}): boolean {
479
- let ret = true;
480
- // 数字类型
481
- if (typeof num !== 'number') {
482
- ret = false;
483
- }
484
- // 无限值
485
- if (isUnFiniteAsNumber === false && !isFinite(num)) {
486
- // console.log('1333');
487
- ret = false;
488
- }
489
- // NaN
490
- if (isNaNAsNumber === false && isNaN(num)) {
491
- // console.log('2333');
492
- ret = false;
493
- }
494
- return ret;
495
- }
496
-
497
- /**
498
- * @method doFn
499
- * @description 执行有效函数
500
- * @param {function} fn 等待被执行的未知是否有效的函数
501
- */
502
- export function doFn(fn: any, ...params: any[]): any {
503
- let ret = null;
504
- if (fn && typeof fn === 'function') {
505
- ret = fn(...params);
506
- }
507
- return ret;
508
- }
509
-
510
- /**
511
- * @method setSessionStorage
512
- * @description 存储数据到 sessionStorage
513
- * @param {string} key 键
514
- * @param {string} value 值
515
- */
516
- export function setSessionStorage(key: string, value: any = null): void {
517
- if (key) {
518
- sessionStorage.setItem(key, JSON.stringify(value));
519
- }
520
- }
521
-
522
- /**
523
- * @method getSessionStorage
524
- * @description 存储数据到 sessionStorage
525
- * @param {string} key 键
526
- * @returns {*} 返回值
527
- */
528
- export function getSessionStorage(key: string): any {
529
- let ret = null;
530
- if (key) {
531
- const value = sessionStorage.getItem(key);
532
- if (value) {
533
- ret = JSON.parse(value);
534
- }
535
- }
536
- return ret;
537
- }
538
-
539
- /**
540
- * @method setLocalStorage
541
- * @description 存储数据到 localStorage
542
- * @param {string} key 键
543
- * @param {string} value 值
544
- */
545
- export function setLocalStorage(key: string, value: any = null): void {
546
- if (key) {
547
- localStorage.setItem(key, JSON.stringify(value));
548
- }
549
- }
550
-
551
- /**
552
- * @method getLocalStorage
553
- * @description 存储数据到 localStorage
554
- * @param {string} key 键
555
- * @returns {*} 返回值
556
- */
557
- export function getLocalStorage(key: string): any {
558
- let ret = null;
559
- if (key) {
560
- const value = localStorage.getItem(key);
561
- if (value) {
562
- ret = JSON.parse(value);
563
- }
564
- }
565
- return ret;
566
- }
567
-
568
- /**
569
- * @method loadCSS
570
- * @description 动态加载css文件
571
- * @param {string} url -- css资源路径
572
- * @param {string} id -- link标签id
573
- * @returns {Promise<boolean>} true -- 加载成功
574
- */
575
- export function loadCSS(url = '', { id = '' } = {}): Promise<any> {
576
- let success: any = null;
577
- let fail: any = null;
578
- const status = new Promise((resolve, reject) => {
579
- ([success, fail] = [resolve, reject]);
580
- });
581
- // const tempCB = (typeof callback === 'function' ? callback : function () { });
582
- const callback = function () {
583
- doFn(success, true);
584
- };
585
- let node: any = document.createElement('link');
586
- const supportOnload = 'onload' in node;
587
- const isOldWebKit = +navigator.userAgent.replace(/.*(?:AppleWebKit|AndroidWebKit)\/?(\d+).*/i, '$1') < 536; // webkit旧内核做特殊处理
588
- const protectNum = 300000; // 阈值10分钟,一秒钟执行pollCss 500次
589
-
590
- node.rel = 'stylesheet';
591
- node.type = 'text/css';
592
- node.href = url;
593
- if (typeof id !== 'undefined') {
594
- node.id = id;
595
- }
596
- document.getElementsByTagName('head')[0].appendChild(node);
597
-
598
- // for Old WebKit and Old Firefox
599
- if (isOldWebKit || !supportOnload) {
600
- // Begin after node insertion
601
- setTimeout(function () {
602
- pollCss(node, callback, 0);
603
- }, 1);
604
- return status;
605
- }
606
-
607
- if (supportOnload) {
608
- node.onload = onload;
609
- node.onerror = function () {
610
- // 加载失败(404)
611
- onload();
612
- };
613
- } else {
614
- // todo: 和 !supportOnload 重复
615
- node.onreadystatechange = function () {
616
- if (/loaded|complete/.test(node.readyState)) {
617
- onload();
618
- }
619
- };
620
- }
621
-
622
- function onload() {
623
- // 确保只跑一次下载操作
624
- node.onload = node.onerror = node.onreadystatechange = null;
625
-
626
- // 清空node引用,在低版本IE,不清除会造成内存泄露
627
- node = null;
628
-
629
- callback();
630
- }
631
-
632
- // 循环判断css是否已加载成功
633
- /*
634
- * @param node -- link节点
635
- * @param callback -- 回调函数
636
- * @param step -- 计步器,避免无限循环
637
- */
638
- function pollCss(node: any, callback: any, step: number) {
639
- const sheet = node.sheet;
640
- let isLoaded: any;
641
-
642
- step += 1;
643
-
644
- // 保护,大于10分钟,则不再轮询
645
- if (step > protectNum) {
646
- isLoaded = true;
647
-
648
- // 清空node引用
649
- node = null;
650
-
651
- callback();
652
- return;
653
- }
654
-
655
- if (isOldWebKit) {
656
- // for WebKit < 536
657
- if (sheet) {
658
- isLoaded = true;
659
- }
660
- } else if (sheet) {
661
- // for Firefox < 9.0
662
- try {
663
- if (sheet.cssRules) {
664
- isLoaded = true;
665
- }
666
- } catch (ex) {
667
- // 火狐特殊版本,通过特定值获知是否下载成功
668
- // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"
669
- // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0
670
- // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"
671
- if ((ex as any).name === 'NS_ERROR_DOM_SECURITY_ERR') {
672
- isLoaded = true;
673
- }
674
- }
675
- }
676
-
677
- setTimeout(function () {
678
- if (isLoaded) {
679
- // 延迟20ms是为了给下载的样式留够渲染的时间
680
- callback();
681
- } else {
682
- pollCss(node, callback, step);
683
- }
684
- }, 20);
685
- }
686
- return status;
687
- }
688
-
689
- /**
690
- * @method loadScript
691
- * @description 动态加载js文件
692
- * @param {string} url -- js资源路径
693
- * @param {string} id -- DOM ID
694
- * @param {function} callback -- 加载后回调函数
695
- * @param {number} timeout -- 超时时长
696
- * @param {boolean} isDefer -- 是否添加 defer 标签
697
- * @returns {Promise<boolean>} -- true 成功
698
- */
699
- export function loadScript(url = '', { id = '', callback = function () { /* pass */ }, timeout = 5000, isDefer = false } = {}): Promise<any> {
700
- let success: any = null;
701
- let fail: any = null;
702
- const script: any = document.createElement('script');
703
- // 如果没有 script 标签,那么代码就不会运行。可以利用这一事实,在页面的第一个 script 标签上使用 insertBefore()。
704
- const firstScript: any = document.getElementsByTagName('script')[0];
705
- script.type = 'text/javascript';
706
- if (isDefer) {
707
- script.defer = 'defer';
708
- }
709
- if (id) {
710
- script.id = id;
711
- }
712
- if (script.readyState) {
713
- // IE
714
- script.onreadystatechange = function () {
715
- if (script.readyState === 'loaded' || script.readyState === 'complete') {
716
- script.onreadystatechange = null;
717
- doFn(callback);
718
- doFn(success, true);
719
- }
720
- };
721
- } else {
722
- // Others
723
- script.onload = function () {
724
- doFn(callback);
725
- doFn(success, true);
726
- };
727
- }
728
- script.src = url;
729
- firstScript && firstScript.parentNode.insertBefore(script, firstScript);
730
- return new Promise((resolve, reject) => {
731
- ([success, fail] = [resolve, reject]);
732
- if (timeout) {
733
- setTimeout(fail.bind(null, 'timeout'), timeout);
734
- }
735
- });
736
- }
737
-
738
- /**
739
- * @method mNow
740
- * @description 获取时间戳
741
- */
742
- export function mNow(): number {
743
- let ret = 0;
744
- if (Date.now) {
745
- ret = Date.now();
746
- } else {
747
- ret = new Date().getTime();
748
- }
749
- return ret;
750
- }
751
-
752
- /**
753
- * @method setCookie
754
- * @description 设置 Cookie
755
- */
756
- export function setCookie(name: string, value: string, days: number, domain: string): void {
757
- let domainParts, expires;
758
- let date: any;
759
- if (days) {
760
- date = new Date();
761
- date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
762
- expires = "; expires=" + date.toGMTString();
763
- } else {
764
- expires = "";
765
- }
766
- const host = location.host;
767
- if (host.split('.').length === 1) {
768
- // no "." in a domain - it's localhost or something similar
769
- document.cookie = name + "=" + value + expires + "; path=/";
770
- } else {
771
- // Remember the cookie on all subdomains.
772
- //
773
- // Start with trying to set cookie to the top domain.
774
- // (example: if user is on foo.com, try to set
775
- // cookie to domain ".com")
776
- //
777
- // If the cookie will not be set, it means ".com"
778
- // is a top level domain and we need to
779
- // set the cookie to ".foo.com"
780
- domainParts = host.split('.');
781
- domainParts.shift();
782
- domain = domain || ('.' + domainParts.join('.'));
783
- document.cookie = name + "=" + value + expires + "; path=/; domain=" + domain;
784
- // check if cookie was successfuly set to the given domain
785
- // (otherwise it was a Top-Level Domain)
786
- if (getCookie(name) === null || getCookie(name) !== value) {
787
- // append "." to current domain
788
- domain = domain || ('.' + host);
789
- document.cookie = name + "=" + value + expires + "; path=/; domain=" + domain;
790
- }
791
- }
792
- }
793
-
794
- /**
795
- * @method getCookie
796
- * @description 获取 Cookie
797
- */
798
- export function getCookie(name: string): string {
799
- const nameEQ = name + "=";
800
- const ca = document.cookie.split(';');
801
- for (let i = 0; i < ca.length; i++) {
802
- let c = ca[i];
803
- while (c.charAt(0) == ' ') {
804
- c = c.substring(1, c.length);
805
- }
806
- if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
807
- }
808
- return '';
809
- }
810
-
811
- /**
812
- * @method getPerformance
813
- * @description 获取页面加载相关的各项数据
814
- * @param {boolean} camelCase -- true(默认) 以驼峰形式返回数据 false 以下划线形式返回数据
815
- * @returns {Promise<object>} 加载数据
816
- */
817
- export function getPerformance(camelCase = true): Promise<any> {
818
- let success: any;
819
- let fail: any;
820
- const status = new Promise((resolve, reject) => {
821
- ([success, fail] = [resolve, reject]);
822
- });
823
- const timing = window.performance.timing;
824
- const startTime = timing.navigationStart || timing.fetchStart;
825
- let firstPaintTime: any;
826
- let firstContentfulPaintTime: any;
827
- // 是否已经形成数据(页面加载完成之后)
828
- if (window.performance && window.performance.timing && window.performance.timing.loadEventEnd > 0) {
829
- // console.log('created')
830
- getTiming();
831
- } else {
832
- // console.log('creating')
833
- window.addEventListener("load", function() {
834
- //不能影响最后的时间计算
835
- window.setTimeout(function() {
836
- getTiming();
837
- }, 0);
838
- });
839
- }
840
- // performance
841
- function getTiming() {
842
- // 获取首次渲染时间
843
- try {
844
- if (window.performance && Boolean(window.performance.getEntriesByType)) {
845
- const performance = window.performance;
846
- const performanceEntries = performance.getEntriesByType('paint');
847
- performanceEntries.forEach((performanceEntry, i, entries) => {
848
- const startTime = Math.round(performanceEntry.startTime);
849
- if (performanceEntry.name === 'first-paint')
850
- firstPaintTime = startTime;
851
- else if (performanceEntry.name === 'first-contentful-paint')
852
- firstContentfulPaintTime = startTime;
853
- });
854
- } else {
855
- console.error('paint');
856
- }
857
- } catch (e) {
858
- console.error((e as any).message);
859
- }
860
- // 获取加载时间
861
- if (
862
- window.performance &&
863
- typeof window.performance.getEntries === 'function'
864
- ) {
865
- const performanceNavigationTiming: any =
866
- (window.performance.getEntries() || [])[0] || {};
867
- const data: any = {
868
- // url: encodeURI(location.href),
869
- // ua: navigator.userAgent,
870
- os: getOS(),
871
- osVersion: getOSVersion(),
872
- deviceType: getDeviceType(),
873
- network: getNetWork(),
874
- screenDirection: getOrientationStatu(),
875
- unloadTime: timing.unloadEventEnd - timing.unloadEventStart, //上个文档的卸载时间
876
- redirectTime: timing.redirectEnd - timing.redirectStart, //*重定向时间
877
- dnsTime: timing.domainLookupEnd - timing.domainLookupStart, //*DNS查询时间
878
- tcpTime: timing.connectEnd - timing.connectStart, //*服务器连接时间
879
- sslTime: getSSLTime(timing.connectEnd, timing.secureConnectionStart), //*ssl连接时间
880
- responseTime: timing.responseStart - timing.requestStart, //*服务器响应时间
881
- downloadTime: timing.responseEnd - timing.responseStart, //*网页下载时间
882
- firstPaintTime: firstPaintTime, //*首次渲染时间
883
- firstContentfulPaintTime: firstContentfulPaintTime, //*首次渲染内容时间
884
- domreadyTime: timing.domContentLoadedEventStart - startTime || 0, //*dom ready时间(总和)
885
- onloadTime: timing.loadEventStart - startTime || 0, //*onload时间(总和)
886
- whiteTime: timing.responseStart - startTime, //*白屏时间
887
- renderTime: timing.loadEventEnd - startTime || 0, //整个过程的时间之和
888
- decodedBodySize: performanceNavigationTiming.decodedBodySize || '', //页面压缩前大小
889
- encodedBodySize: performanceNavigationTiming.encodedBodySize || '', //页面压缩后大小
890
- };
891
- // 过滤异常数据
892
- Object.keys(data).forEach(k => {
893
- // 过滤掉 <0 的数据
894
- if (isNumber(data[k]) && data[k] < 0) {
895
- data[k] = 0;
896
- }
897
- });
898
- // 过滤掉白屏时间 > onload 的数据
899
- if (isNumber(data.whiteTime) && data.whiteTime > data.onloadTime) {
900
- data.whiteTime = 0;
901
- }
902
- if (startTime > 0) {
903
- let Underscore: any;
904
- if (!camelCase) {
905
- Object.keys(data).forEach(k => {
906
- if (!Underscore) Underscore = {};
907
- // console.log('camelCase2Underscore', k, data, )
908
- Underscore[camelCase2Underscore(k)] = data[k];
909
- });
910
- }
911
- success(Underscore || data);
912
- } else {
913
- fail('startTime');
914
- }
915
- } else {
916
- fail('getEntries');
917
- }
918
- }
919
- //获取当前操作系统
920
- function getOS() {
921
- let os;
922
- if (navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Linux') > -1) {
923
- os = 'android';
924
- } else if (navigator.userAgent.indexOf('iPhone') > -1) {
925
- os = 'ios';
926
- } else if (navigator.userAgent.indexOf('Windows Phone') > -1) {
927
- os = 'wp';
928
- } else {
929
- os = 'others';
930
- }
931
- return os;
932
- }
933
- // 获取操作系统版本
934
- function getOSVersion() {
935
- let OSVision: any;
936
- const u = navigator.userAgent;
937
- const isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //Android
938
- const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
939
- if (isAndroid) {
940
- OSVision = (navigator.userAgent.split(';')[1].match(/\d+\.\d+/g) || [])[0];
941
- }
942
- if (isIOS) {
943
- OSVision = (navigator.userAgent.split(';')[1].match(/(\d+)_(\d+)_?(\d+)?/) || [])[0];
944
- }
945
- return OSVision;
946
- }
947
- //获取设备类型
948
- function getDeviceType() {
949
- let deviceType;
950
- const sUserAgent = navigator.userAgent.toLowerCase();
951
- const bIsIpad = sUserAgent.match(/(ipad)/i) && 'ipad';
952
- const bIsIphoneOs = sUserAgent.match(/iphone os/i) && 'iphone os';
953
- const bIsMidp = sUserAgent.match(/midp/i) && 'midp';
954
- const bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) && 'rv:1.2.3.4';
955
- const bIsUc = sUserAgent.match(/ucweb/i) && 'ucweb';
956
- const bIsAndroid = sUserAgent.match(/android/i) && 'android';
957
- const bIsCE = sUserAgent.match(/windows ce/i) && 'windows ce';
958
- const bIsWM = sUserAgent.match(/windows mobile/i) && 'windows mobile';
959
- if (!(bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM)) {
960
- deviceType = 'pc'; //pc
961
- } else if (bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
962
- deviceType = 'phone'; //phone
963
- } else if (bIsIpad) {
964
- deviceType = 'ipad'; //ipad
965
- } else {
966
- deviceType = undefined;
967
- }
968
- return deviceType;
969
- }
970
- // 获取网络状态
971
- function getNetWork() {
972
- let netWork: any;
973
- if ((navigator as any).connection && (navigator as any).connection.effectiveType) {
974
- switch ((navigator as any).connection.effectiveType) {
975
- case 'wifi':
976
- netWork = 'wifi'; // wifi
977
- break;
978
- case '4g':
979
- netWork = '4g'; // 4g
980
- break;
981
- case '2g':
982
- netWork = '2g'; // 2g
983
- break;
984
- case '3g':
985
- netWork = '3g'; // 3g
986
- break;
987
- case 'ethernet':
988
- netWork = 'ethernet'; // ethernet
989
- break;
990
- case 'default':
991
- netWork = undefined; // 未知
992
- break;
993
- }
994
- }
995
- return netWork;
996
- }
997
- // 获取横竖屏状态
998
- function getOrientationStatu() {
999
- let orientationStatu: any;
1000
- if (window.screen && window.screen.orientation && window.screen.orientation.angle) {
1001
- if (window.screen.orientation.angle === 180 || window.screen.orientation.angle === 0) { // 竖屏
1002
- orientationStatu = '|';
1003
- }
1004
- if (window.screen.orientation.angle === 90 || window.screen.orientation.angle === -90) { // 横屏
1005
- orientationStatu = '-';
1006
- }
1007
- }
1008
- return orientationStatu;
1009
- }
1010
- // 获取ssl连接时间
1011
- function getSSLTime(connectEnd: any, secureConnectionStart: any) {
1012
- let ssl_time: any;
1013
- if (secureConnectionStart) {
1014
- ssl_time = connectEnd - secureConnectionStart;
1015
- }
1016
- return ssl_time;
1017
- }
1018
- return status;
1019
- }
1020
-
1021
- /**
1022
- * @method inRate
1023
- * @description 百分位概率
1024
- * @param {number} rate -- 0.1 ~ 1 => 1% ~ 100%
1025
- * @returns {boolean} true 命中
1026
- */
1027
- export function inRate(rate: number): boolean {
1028
- if(Math.random() < rate) {
1029
- return true;
1030
- }
1031
- return false;
1032
- }
1033
-
1034
- /**
1035
- * @method isSafePWAEnv
1036
- * @description 判断是否是安全的 PWA 环境
1037
- * @returns {boolean} true 是
1038
- */
1039
- export function isSafePWAEnv(): boolean {
1040
- // 判断是否支持 async await
1041
- function isSupportAsyncAwait() {
1042
- let isSupportAsyncAwaitFunc;
1043
- try {
1044
- // eval("func = async function(){};");
1045
- const fn = new Function("return async function(){};");
1046
- isSupportAsyncAwaitFunc = fn();
1047
- // console.log('isSupportAsyncAwaitFunc', isSupportAsyncAwaitFunc);
1048
- // 由于async函数的构造器不是全局对象,所以我们需要由下面代码来获取async函数的构造器
1049
- // 具体可以查看以下MDN上有关于AsyncFunction的说明,
1050
- // 地址:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
1051
- return Object.getPrototypeOf(isSupportAsyncAwaitFunc).constructor != null;
1052
- } catch (e) {
1053
- return false;
1054
- }
1055
- }
1056
- // 判断是否支持 Promise
1057
- function isSupportPromise() {
1058
- if(typeof Promise !== "undefined" && Promise.toString().indexOf("[native code]") !== -1){
1059
- return true;
1060
- }
1061
- return false;
1062
- }
1063
- // 浏览器信息
1064
- const BrowserType = getBrowserInfo();
1065
- if (
1066
- ('serviceWorker' in navigator) &&
1067
- isSupportAsyncAwait() &&
1068
- isSupportPromise() &&
1069
- Boolean(window.fetch) &&
1070
- Boolean(window.indexedDB) &&
1071
- Boolean(window.caches) &&
1072
- !BrowserType['shell']
1073
- ) {
1074
- return true;
1075
- }
1076
- return false;
1077
- }
1078
-
1079
- /**
1080
- * @method getBrowserInfo
1081
- * @description 返回浏览器信息 https://github.com/JowayYoung/juejin-code/blob/master/browser-type.js
1082
- * @returns {object} 浏览器信息
1083
- */
1084
- export function getBrowserInfo(): any {
1085
- try {
1086
- // 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
1087
- const ua: any = navigator.userAgent.toLowerCase();
1088
- const testUa: (regexp: any) => any = regexp => regexp.test(ua);
1089
- const testVs: (regexp: any) => any = regexp => {
1090
- const matchRes = ua.match(regexp);
1091
- let ret = '';
1092
- if (matchRes) {
1093
- ret = matchRes
1094
- .toString()
1095
- .replace(/[^0-9|_.]/g, "")
1096
- .replace(/_/g, ".");
1097
- }
1098
- return ret;
1099
- };
1100
- // 系统
1101
- let system = '';
1102
- // Apple device type.
1103
- let appleType = '';
1104
- if (testUa(/windows|win32|win64|wow32|wow64/g)) {
1105
- system = "windows"; // windows系统
1106
- } else if (testUa(/macintosh|macintel/g)) {
1107
- system = "macos"; // macos系统
1108
- } else if (testUa(/x11/g)) {
1109
- system = "linux"; // linux系统
1110
- } else if (testUa(/android|adr/g)) {
1111
- system = "android"; // android系统
1112
- } else if (testUa(/ios|iphone|ipad|ipod|iwatch/g)) {
1113
- system = "ios"; // ios系统
1114
- if (testUa(/iphone/g)) {
1115
- appleType = 'iphone';
1116
- } else if (testUa(/ipad/g)) {
1117
- appleType = 'ipad';
1118
- } else if (testUa(/iwatch/g)) {
1119
- appleType = 'iwatch';
1120
- } else if (testUa(/ipod/g)) {
1121
- appleType = 'ipod';
1122
- }
1123
- }
1124
- // 系统版本
1125
- let systemVs = '';
1126
- if (system === "windows") {
1127
- if (testUa(/windows nt 5.0|windows 2000/g)) {
1128
- systemVs = "2000";
1129
- } else if (testUa(/windows nt 5.1|windows xp/g)) {
1130
- systemVs = "xp";
1131
- } else if (testUa(/windows nt 5.2|windows 2003/g)) {
1132
- systemVs = "2003";
1133
- } else if (testUa(/windows nt 6.0|windows vista/g)) {
1134
- systemVs = "vista";
1135
- } else if (testUa(/windows nt 6.1|windows 7/g)) {
1136
- systemVs = "7";
1137
- } else if (testUa(/windows nt 6.2|windows 8/g)) {
1138
- systemVs = "8";
1139
- } else if (testUa(/windows nt 6.3|windows 8.1/g)) {
1140
- systemVs = "8.1";
1141
- } else if (testUa(/windows nt 10.0|windows 10/g)) {
1142
- systemVs = "10";
1143
- }
1144
- } else if (system === "macos") {
1145
- systemVs = testVs(/os x [\d._]+/g);
1146
- } else if (system === "android") {
1147
- systemVs = testVs(/android [\d._]+/g);
1148
- } else if (system === "ios") {
1149
- systemVs = testVs(/os [\d._]+/g);
1150
- }
1151
- // 平台
1152
- let platform = '';
1153
- if (system === "windows" || system === "macos" || system === "linux") {
1154
- platform = "desktop"; // 桌面端
1155
- } else if (system === "android" || system === "ios" || testUa(/mobile/g)) {
1156
- platform = "mobile"; // 移动端
1157
- }
1158
- // 内核和载体
1159
- let engine = '';
1160
- let supporter = '';
1161
- if (testUa(/applewebkit/g)) {
1162
- engine = "webkit"; // webkit内核
1163
- if (testUa(/edge/g)) {
1164
- supporter = "edge"; // edge浏览器
1165
- } else if (testUa(/opr/g)) {
1166
- supporter = "opera"; // opera浏览器
1167
- } else if (testUa(/chrome/g)) {
1168
- supporter = "chrome"; // chrome浏览器
1169
- } else if (testUa(/safari/g)) {
1170
- supporter = "safari"; // safari浏览器
1171
- }
1172
- } else if (testUa(/gecko/g) && testUa(/firefox/g)) {
1173
- engine = "gecko"; // gecko内核
1174
- supporter = "firefox"; // firefox浏览器
1175
- } else if (testUa(/presto/g)) {
1176
- engine = "presto"; // presto内核
1177
- supporter = "opera"; // opera浏览器
1178
- } else if (testUa(/trident|compatible|msie/g)) {
1179
- engine = "trident"; // trident内核
1180
- supporter = "iexplore"; // iexplore浏览器
1181
- }
1182
- // 内核版本
1183
- let engineVs = '';
1184
- if (engine === "webkit") {
1185
- engineVs = testVs(/applewebkit\/[\d._]+/g);
1186
- } else if (engine === "gecko") {
1187
- engineVs = testVs(/gecko\/[\d._]+/g);
1188
- } else if (engine === "presto") {
1189
- engineVs = testVs(/presto\/[\d._]+/g);
1190
- } else if (engine === "trident") {
1191
- engineVs = testVs(/trident\/[\d._]+/g);
1192
- }
1193
- // 载体版本
1194
- let supporterVs = '';
1195
- if (supporter === "chrome") {
1196
- supporterVs = testVs(/chrome\/[\d._]+/g);
1197
- } else if (supporter === "safari") {
1198
- supporterVs = testVs(/version\/[\d._]+/g);
1199
- } else if (supporter === "firefox") {
1200
- supporterVs = testVs(/firefox\/[\d._]+/g);
1201
- } else if (supporter === "opera") {
1202
- supporterVs = testVs(/opr\/[\d._]+/g);
1203
- } else if (supporter === "iexplore") {
1204
- supporterVs = testVs(/(msie [\d._]+)|(rv:[\d._]+)/g);
1205
- } else if (supporter === "edge") {
1206
- supporterVs = testVs(/edge\/[\d._]+/g);
1207
- }
1208
- // 外壳和外壳版本
1209
- let shell = '';
1210
- let shellVs = '';
1211
- if (testUa(/micromessenger/g)) {
1212
- shell = "wechat"; // 微信浏览器
1213
- shellVs = testVs(/micromessenger\/[\d._]+/g);
1214
- } else if (testUa(/qqbrowser/g)) {
1215
- shell = "qq_browser"; // QQ Browser
1216
- shellVs = testVs(/qqbrowser\/[\d._]+/g);
1217
- } else if (testUa(/\sqq/g)) {
1218
- shell = "qq_app"; // QQ APP
1219
- } else if (testUa(/ucbrowser/g)) {
1220
- shell = "uc"; // UC浏览器
1221
- shellVs = testVs(/ucbrowser\/[\d._]+/g);
1222
- } else if (testUa(/qihu 360se/g)) {
1223
- shell = "360"; // 360浏览器(无版本)
1224
- } else if (testUa(/2345explorer/g)) {
1225
- shell = "2345"; // 2345浏览器
1226
- shellVs = testVs(/2345explorer\/[\d._]+/g);
1227
- } else if (testUa(/metasr/g)) {
1228
- shell = "sougou"; // 搜狗浏览器(无版本)
1229
- } else if (testUa(/lbbrowser/g)) {
1230
- shell = "liebao"; // 猎豹浏览器(无版本)
1231
- } else if (testUa(/maxthon/g)) {
1232
- shell = "maxthon"; // 遨游浏览器
1233
- shellVs = testVs(/maxthon\/[\d._]+/g);
1234
- } else if (testUa(/biliapp/g)) {
1235
- shell = "bilibili"; // 哔哩哔哩
1236
- }
1237
- return Object.assign({
1238
- engine, // webkit gecko presto trident
1239
- engineVs,
1240
- platform, // desktop mobile
1241
- supporter, // chrome safari firefox opera iexplore edge
1242
- supporterVs,
1243
- system, // windows macos linux android ios
1244
- systemVs,
1245
- }, {
1246
- shell, // wechat qq uc 360 2345 sougou liebao maxthon
1247
- shellVs,
1248
- appleType,
1249
- });
1250
- } catch (err) {
1251
- console.warn('mazey:', err);
1252
- return {};
1253
- }
1254
- }
1255
-
1256
- /**
1257
- * @method clearHtml
1258
- * @description 去除HTML标签
1259
- * @param {string} string 带html标签的字符串
1260
- * @returns {string} 字符串
1261
- */
1262
- export function clearHtml (string = ''): string {
1263
- return string.replace(/<\/?.+?>/g, '').replace(/[\r\n]/g, '');
1264
- }
1265
-
1266
- /**
1267
- * @method cutCHSString
1268
- * @description 截取字符串,中文算2个字节
1269
- * @param {string} str 要截取的字符串
1270
- * @param {number} len
1271
- * @param {boolean} hasDot
1272
- * @returnss {string} 返回截取后的字符串
1273
- */
1274
- export function cutCHSString(str = '', len = str.length, hasDot = false): string {
1275
- if (str == '' || !str) {
1276
- return '';
1277
- } else {
1278
- let newLength = 0;
1279
- let newStr = '';
1280
- // eslint-disable-next-line no-control-regex
1281
- const chineseRegex = /[^\x00-\xff]/g;
1282
- let singleChar = '';
1283
- const strLength = str.replace(chineseRegex, '**').length;
1284
- for (let i = 0; i < strLength; i++) {
1285
- singleChar = str.charAt(i).toString();
1286
- if (singleChar.match(chineseRegex) != null) {
1287
- newLength += 2;
1288
- } else {
1289
- newLength++;
1290
- }
1291
- if (newLength > len) {
1292
- break;
1293
- }
1294
- newStr += singleChar;
1295
- }
1296
-
1297
- if (hasDot && strLength > len) {
1298
- newStr += '...';
1299
- }
1300
- return newStr;
1301
- }
1302
- }
1303
-
1304
- /**
1305
- * @method windowLoaded
1306
- * @description 页面加载完成
1307
- * @param {number} timeout 超时时间 / 单位:秒
1308
- * @returns {Promise<string>} document is loaded? 'complete' 'load' / 'timeout'
1309
- */
1310
- export function windowLoaded(timeout = 90): Promise<string> {
1311
- let loaded: (value: string) => void = () => undefined;
1312
- let loadFail: (value: string) => void = () => undefined;
1313
- const status = new Promise((resolve: (value: string) => void, reject: (value: string) => void) => {
1314
- loaded = resolve;
1315
- loadFail = reject;
1316
- });
1317
- if (document.readyState === 'complete') {
1318
- loaded('complete');
1319
- } else {
1320
- window.addEventListener('load', () => loaded('load'));
1321
- }
1322
- // 超过 timeout 秒后加载失败
1323
- setTimeout(() => loadFail('timeout'), timeout * 1000);
1324
- return status;
1325
- }
1326
-
1327
- /**
1328
- * EN: Add `<style>` in `<head>`.
1329
- *
1330
- * ZH: 添加样式标签; style: 样式标签内的字符串; id: `<style>` 标签的 `id`; 返回: 添加成功/失败.
1331
- *
1332
- * Case 1: Add the `<style>` with `id`, and repeated invoking will update the content instead of adding a new one.
1333
- *
1334
- * ```
1335
- * addStyle(
1336
- * `
1337
- * body {
1338
- * background-color: #333;
1339
- * }
1340
- * `,
1341
- * {
1342
- * id: 'test',
1343
- * }
1344
- * );
1345
- * // <style id="test">
1346
- * // body {
1347
- * // background-color: #333;
1348
- * // }
1349
- * // </style>
1350
- * ```
1351
- *
1352
- * Case 2: Add the `<style>` without `id`, and repeated invoking will adding a new one.
1353
- *
1354
- * ```
1355
- * addStyle(
1356
- * `
1357
- * body {
1358
- * background-color: #333;
1359
- * }
1360
- * `
1361
- * );
1362
- * // <style>
1363
- * // body {
1364
- * // background-color: #333;
1365
- * // }
1366
- * // </style>
1367
- * ```
1368
- *
1369
- * @param style Style string.
1370
- * @param options.id `id` in `<style>`
1371
- * @returns Success/Fail
1372
- */
1373
- export function addStyle(style = '', options: { id: string; } = { id: '' }): boolean {
1374
- // console.log('_ style', style);
1375
- // console.log('_ options', options);
1376
- if (!style) {
1377
- return false;
1378
- }
1379
- // 创建 style 文档碎片
1380
- const styleFrag = document.createDocumentFragment();
1381
- let idDom = null;
1382
- let domId = '';
1383
- // Custom Style
1384
- const customStyle = document.createElement('style');
1385
- // 如果需要 ID
1386
- if (options.id) {
1387
- domId = `${options.id}`;
1388
- idDom = document.getElementById(domId);
1389
- // 如果 Dom 不存在,插入 style
1390
- if (!idDom) {
1391
- customStyle.setAttribute('id', options.id);
1392
- customStyle.innerHTML = style;
1393
- styleFrag.appendChild(customStyle);
1394
- document.head.appendChild(styleFrag);
1395
- } else { // 如果 Dom 存在,直接更新
1396
- idDom.innerHTML = style;
1397
- }
1398
- } else { // 不需要 ID,直接添加新标签
1399
- customStyle.innerHTML = style;
1400
- styleFrag.appendChild(customStyle);
1401
- document.head.appendChild(styleFrag);
1402
- }
1403
- return true;
1404
- }
1405
-
1406
- /**
1407
- * @method genCustomConsole
1408
- * @description 生成自定义控制台打印
1409
- * @param {string} prefix 前缀
1410
- * @param {function} allowFn 允许打印的判断函数
1411
- * @returns {object} 新实例
1412
- */
1413
- export function genCustomConsole(prefix = ''): any {
1414
- const methods = ['log', 'info', 'warn', 'error'];
1415
- const newConsole = Object.create(null);
1416
- methods.forEach(method => {
1417
- newConsole[method] = function (...argu: any) {
1418
- if (prefix) {
1419
- (console as any)[method](prefix, ...argu);
1420
- } else {
1421
- (console as any)[method](...argu);
1422
- }
1423
- };
1424
- });
1425
- return newConsole;
1426
- }
1
+ /* mazey */
2
+
3
+ /**
4
+ * @method calLongestCommonSubstring
5
+ * @description Computes the longest common substring of two strings.
6
+ * @param {string} aStr String
7
+ * @param {string} bStr String
8
+ * @returns {number} Length
9
+ */
10
+ export function calLongestCommonSubstring(aStr: string, bStr: string): number {
11
+ const aLen = aStr.length;
12
+ const bLen = bStr.length;
13
+ // 创建二维数组并且深拷贝
14
+ const arr = deepCopyObject(new Array(aLen).fill(new Array(bLen).fill(0)));
15
+ for (let i = 0; i < aLen; ++i) {
16
+ for (let j = 0; j < bLen; ++j) {
17
+ if (aStr[i] === bStr[j]) {
18
+ let baseNum = 0;
19
+ if (i > 0 && j > 0) {
20
+ baseNum = arr[i - 1][j - 1];
21
+ }
22
+ arr[i][j] = baseNum + 1;
23
+ }
24
+ }
25
+ }
26
+ // 二维数组转一维数组
27
+ const arr1 = Array.prototype.concat.apply([], arr);
28
+ // 获取最长公共子串
29
+ const maxLong = Math.max(...arr1);
30
+ return maxLong;
31
+ }
32
+
33
+ /**
34
+ * @method calLongestCommonSubsequence
35
+ * @description 计算两个字符串的最长公共子序列
36
+ * @param {string} aStr 字符串
37
+ * @param {string} bStr 字符串
38
+ * @returns {number} 长度
39
+ */
40
+ export function calLongestCommonSubsequence(aStr: string, bStr: string): number {
41
+ const aLen = aStr.length;
42
+ const bLen = bStr.length;
43
+ // 创建二维数组并且深拷贝
44
+ const arr = deepCopyObject(new Array(aLen).fill(new Array(bLen).fill(0)));
45
+ for (let i = 0; i < aLen; ++i) {
46
+ for (let j = 0; j < bLen; ++j) {
47
+ if (aStr[i] === bStr[j]) {
48
+ let baseNum = 0;
49
+ if (i > 0 && j > 0) {
50
+ baseNum = arr[i - 1][j - 1];
51
+ }
52
+ arr[i][j] = baseNum + 1;
53
+ } else {
54
+ let [leftValue, topValue] = [0, 0];
55
+ if (j > 0) {
56
+ leftValue = arr[i][j - 1];
57
+ }
58
+ if (i > 0) {
59
+ topValue = arr[i - 1][j];
60
+ }
61
+ arr[i][j] = Math.max(leftValue, topValue);
62
+ }
63
+ }
64
+ }
65
+ // 二维数组转一维数组
66
+ const arr1 = Array.prototype.concat.apply([], arr);
67
+ // 获取最长公共子串
68
+ const maxLong = Math.max(...arr1);
69
+ return maxLong;
70
+ }
71
+
72
+ /**
73
+ * @method getQueryParam
74
+ * @description Get the query param's value of the current Web URL(`location.search`).
75
+ * @param {string} param Query param.
76
+ * @returns {string} value
77
+ */
78
+ export function getQueryParam(param: string): string {
79
+ const reg = new RegExp('(^|&)' + param + '=([^&]*)(&|$)');
80
+ const r = location.search.substr(1).match(reg);
81
+ if (r !== null) {
82
+ // return decodeURIComponent(unescape(r[2]));
83
+ return decodeURIComponent(r[2]);
84
+ }
85
+ return '';
86
+ }
87
+
88
+ /**
89
+ * @method getUrlParam
90
+ * @description Get the query param's value of the input URL.
91
+ * @param {string} url URL string.
92
+ * @param {string} param Query param.
93
+ * @returns {string} value
94
+ */
95
+ export function getUrlParam(url: string, param: string): string | string[] {
96
+ const result: {
97
+ [key: string]: string | string[] | any;
98
+ } = {};
99
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/replace
100
+ url.replace(/\??(\w+)=([^&]*)&?/g, function (a, k, v): any {
101
+ if (result[k] !== undefined) {
102
+ const t = result[k];
103
+ result[k] = [].concat(t, v);
104
+ } else {
105
+ result[k] = v;
106
+ }
107
+ });
108
+ // if (param === undefined) {
109
+ // return result;
110
+ // } else {
111
+ // return result[param] || '';
112
+ // }
113
+ return result[param] || '';
114
+ }
115
+
116
+ /**
117
+ * @method updateQueryParam
118
+ * @description Update the query param's value of the input URL.
119
+ * @param {string} url URL string.
120
+ * @param {string} param Query param.
121
+ * @param {string} value Param's value.
122
+ * @returns {string} URL.
123
+ */
124
+ export function updateQueryParam(url: string, param: string, value: string): string {
125
+ const re = new RegExp('([?&])' + param + '=.*?(&|$)', 'i');
126
+ const separator = url.indexOf('?') !== -1 ? '&' : '?';
127
+ if (url.match(re)) {
128
+ return url.replace(re, '$1' + param + '=' + value + '$2');
129
+ } else {
130
+ return url + separator + param + '=' + value;
131
+ }
132
+ }
133
+
134
+ /**
135
+ * @method getHashQueryParam
136
+ * @description Get the hash query param's value of the current Web URL(`location.hash`).
137
+ * @param {string} param Query param.
138
+ * @returns {string} value
139
+ */
140
+ export function getHashQueryParam(param: string): string {
141
+ const hashs = location.hash.split('?');
142
+ if (hashs.length === 1) {
143
+ return '';
144
+ }
145
+ const reg = new RegExp(`(^|&)${param}=([^&]*)(&|$)`);
146
+ const ret = hashs[1].match(reg);
147
+ return ret ? ret[2] : '';
148
+ }
149
+
150
+ /**
151
+ * @method getDomain
152
+ * @description Get the domain of URL, and other params.
153
+ * @param {string} url
154
+ * @param {array} rules Object.keys(location), ['href', 'protocol', 'host', 'hostname', 'port', 'pathname', 'search', 'hash'], ['hostname', 'pathname'] = 'km.mazey.net/plugins/servlet/mobile'
155
+ */
156
+ export function getDomain(url: string, rules = ['hostname']): string {
157
+ const aEl: any = document.createElement('a');
158
+ aEl.href = url;
159
+ return rules.reduce((ret, v) => {
160
+ ret += aEl[v];
161
+ return ret;
162
+ }, '');
163
+ }
164
+
165
+ /**
166
+ * @method camelCaseToKebabCase
167
+ * @description Transfer CamelCase to KebabCase.
168
+ * @param {string} camelCase 'aBC' or 'ABC'
169
+ * @returns {string} 'a-b-c'
170
+ */
171
+ export function camelCaseToKebabCase(camelCase: string): string {
172
+ const kebabCase = camelCase.replace(/([A-Z])/g, '-$1').toLowerCase();
173
+ return kebabCase[0] === '-' ? kebabCase.substr(1) : kebabCase;
174
+ }
175
+
176
+ /**
177
+ * @method camelCase2Underscore
178
+ * @description Transfer CamelCase to Underscore.
179
+ * @param {string} camelCase 'aBC' or 'ABC'
180
+ * @returns {string} 'a_b_c'
181
+ */
182
+ export function camelCase2Underscore(camelCase: string): string {
183
+ const kebabCase = camelCase.replace(/([A-Z])/g, '_$1').toLowerCase();
184
+ return kebabCase[0] === '_' ? kebabCase.substr(1) : kebabCase;
185
+ }
186
+
187
+ /**
188
+ * @method mTrim
189
+ * @description Remove leading and trailing whitespace or specified characters from string.
190
+ * @param {string} str The string to trim.
191
+ * @returns {string} Trimmed string.
192
+ */
193
+ export function mTrim(str: string): string {
194
+ str = str.replace(/^\s+/, ''); // 去除头部空格
195
+ let end = str.length - 1;
196
+ const ws = /\s/;
197
+ while (ws.test(str.charAt(end))) {
198
+ end--; // 最后一个非空格字符的索引
199
+ }
200
+ return str.slice(0, end + 1);
201
+ }
202
+
203
+ /**
204
+ * @method newLine
205
+ * @description Make a newline of HTML.
206
+ * @param {string} str The string to make a newline.
207
+ * @returns {string} A newline with `br`.
208
+ */
209
+ export function newLine(str: string): string {
210
+ if (!str) {
211
+ return '';
212
+ }
213
+ const reg = new RegExp('\\n', 'g');
214
+ return str.replace(reg, '<br />');
215
+ }
216
+
217
+ /**
218
+ * @method deepCopyObject
219
+ * @description Clone Object deeply.
220
+ * @param {object} obj The value to clone.
221
+ * @returns {object} Returns the deep cloned value.
222
+ */
223
+ export function deepCopyObject(obj: any): any {
224
+ return JSON.parse(JSON.stringify(obj));
225
+ }
226
+
227
+ /**
228
+ * @method isJsonString
229
+ * @description Check whether it is a valid JSON string.
230
+ * @param {string} str The string to check.
231
+ * @returns {boolean} Return the result of checking.
232
+ */
233
+ export function isJsonString(str: string): boolean {
234
+ try {
235
+ if (typeof JSON.parse(str) === 'object') {
236
+ return true;
237
+ }
238
+ } catch (e) {
239
+ /* pass */
240
+ }
241
+ return false;
242
+ }
243
+
244
+ /**
245
+ * @method generateRndNum
246
+ * @description Produce a random string of number, `generateRndNum(7)` => '7658495'.
247
+ * @param {number} n Length
248
+ * @returns {string} Return the random string.
249
+ */
250
+ export function generateRndNum(n = 5): string {
251
+ let ret = '';
252
+ while (n--) {
253
+ ret += Math.floor(Math.random() * 10);
254
+ }
255
+ return ret;
256
+ }
257
+
258
+ /**
259
+ * @method generateUniqueNum
260
+ * @description 根据时间生成唯一标志的数字 mGenerateUniqueNum() => 1538324722364123。
261
+ * @param {number} n 随机数的长度
262
+ */
263
+ export function generateUniqueNum(n = 3): string {
264
+ const [now, rnd] = [mNow(), generateRndNum(n || 3)];
265
+ return now + rnd;
266
+ }
267
+
268
+ /**
269
+ * @method floatToPercent
270
+ * @description 浮点数转为百分比 0.2 => 20%。
271
+ * @param {number} num 浮点数
272
+ * @param {number} fixSize 保留几位浮点数
273
+ */
274
+ export function floatToPercent(num: number, fixSize = 0): string {
275
+ let ret = '';
276
+ if (fixSize) {
277
+ ret = (num * 100).toFixed(fixSize);
278
+ } else {
279
+ ret = String(Math.floor(num * 100));
280
+ }
281
+ return `${ret}%`;
282
+ }
283
+
284
+ /**
285
+ * @method floatFixed
286
+ * @description 浮点数保留指定位。
287
+ */
288
+ export function floatFixed(num: string, size = 0): string {
289
+ return parseFloat(num).toFixed(size);
290
+ }
291
+
292
+ /**
293
+ * @method cancelBubble
294
+ * @description 阻止冒泡。
295
+ */
296
+ export function cancelBubble(e: any): void {
297
+ const ev = e || window.event;
298
+ if (ev.stopPropagation) {
299
+ // W3C
300
+ ev.stopPropagation();
301
+ } else {
302
+ // IE
303
+ ev.cancelBubble = true;
304
+ }
305
+ }
306
+
307
+ /**
308
+ * @method hasClass
309
+ */
310
+ export function hasClass(obj: any, cls: string): boolean {
311
+ const oriCls = obj.className; // 获取对象的class值
312
+ const oriClsArr = oriCls.split(/\s+/); // 分隔空格转换成数组
313
+ for (let i = 0; i < oriClsArr.length; i++) {
314
+ if (oriClsArr[i] === cls) {
315
+ return true; // 若匹配到class则返回True
316
+ }
317
+ }
318
+ return false; // 否则返回False
319
+ }
320
+
321
+ /**
322
+ * @method addClass
323
+ */
324
+ export function addClass(obj: any, cls: string): void {
325
+ const oriCls = obj.className;
326
+ let space = '';
327
+ let newCls = ''; // 获取对象的class值
328
+ if (oriCls !== '') {
329
+ space = ' '; // 若原来的class不为空,跟一个空格
330
+ }
331
+ newCls = oriCls + space + cls; // 将新的class加进去
332
+ obj.className = newCls; // 替换新class
333
+ }
334
+
335
+ /**
336
+ * @method removeClass
337
+ */
338
+ export function removeClass(obj: any, cls: string): void {
339
+ const oriCls = obj.className;
340
+ let newCls; // 获取对象的class值
341
+ newCls = ' ' + oriCls + ' '; // 前后加空格
342
+ newCls = newCls.replace(/(\s+)/gi, ' '); // 将多余的空格替换成一个空格
343
+ newCls = newCls.replace(' ' + cls + ' ', ' '); // 将加了前后空格的cls替换成空格' '
344
+ newCls = newCls.replace(/(^\s+)|(\s+$)/g, ''); // 去掉前后空格
345
+ obj.className = newCls;
346
+ }
347
+
348
+ /**
349
+ * EN: Throttle
350
+ * ZH: 节流
351
+ *
352
+ * @see https://lodash.com/docs/4.17.15#throttle
353
+ */
354
+ export function throttle(func: any, wait: number, options: { leading?: boolean; trailing?: boolean; } = {}): any {
355
+ options = Object.assign(
356
+ {},
357
+ options,
358
+ );
359
+ // timeout: setTimeout Handle
360
+ // previous: 上次时间戳
361
+ let context: any = null;
362
+ let args: any = null;
363
+ let timeout: any = null;
364
+ let [result, previous] = [null, 0];
365
+ // if (!options) {
366
+ // options = {};
367
+ // }
368
+ const later = function () {
369
+ previous = options.leading === false ? 0 : mNow();
370
+ timeout = null;
371
+ result = func.apply(context, args);
372
+ if (!timeout) {
373
+ context = args = null;
374
+ }
375
+ };
376
+ return function (...argRest: Array<any>) {
377
+ const now = mNow();
378
+ if (!previous && options.leading === false) {
379
+ previous = now;
380
+ }
381
+ const remaining = wait - (now - previous);
382
+ context = this;
383
+ args = argRest;
384
+ if (remaining <= 0 || remaining > wait) {
385
+ if (timeout) {
386
+ clearTimeout(timeout);
387
+ timeout = null;
388
+ }
389
+ previous = now;
390
+ result = func.apply(context, args);
391
+ if (!timeout) {
392
+ context = args = null;
393
+ }
394
+ } else if (!timeout && options.trailing !== false) {
395
+ timeout = setTimeout(later, remaining);
396
+ }
397
+ return result;
398
+ };
399
+ }
400
+
401
+ /**
402
+ * EN: Debounce
403
+ *
404
+ * ZH: 去抖
405
+ */
406
+ export function debounce(func: any, wait: number, immediate?: any): any {
407
+ let context: any = null;
408
+ let timeout: any = null;
409
+ let timestamp: any = null;
410
+ let args: any = null;
411
+ let [result] = [null];
412
+ const later = function () {
413
+ const last = mNow() - timestamp;
414
+ if (last < wait && last >= 0) {
415
+ timeout = setTimeout(later, wait - last);
416
+ } else {
417
+ timeout = null;
418
+ if (!immediate) {
419
+ result = func.apply(context, args);
420
+ if (!timeout) {
421
+ context = args = null;
422
+ }
423
+ }
424
+ }
425
+ };
426
+ return function (...argRest: Array<any>) {
427
+ context = this;
428
+ args = argRest;
429
+ timestamp = mNow();
430
+ const callNow = immediate && !timeout;
431
+ if (!timeout) {
432
+ timeout = setTimeout(later, wait);
433
+ }
434
+ if (callNow) {
435
+ result = func.apply(context, args);
436
+ context = args = null;
437
+ }
438
+ return result;
439
+ };
440
+ }
441
+
442
+ /**
443
+ * @method friendlyInterval
444
+ * @description 获取间隔时间。
445
+ * @param {number/Date} start 开始时间戳 1585325367122
446
+ * @param {number/Date} end 结束时间戳 1585325367122
447
+ * @param {string} options.type 返回类型 d: 2(天) text: 2 天 4 时...
448
+ * @returns {String/number} 取决于 type
449
+ */
450
+ export function friendlyInterval(start = 0, end = 0, options: { type?: string; } = { type: 'd' }): number | string {
451
+ const { type } = options;
452
+ if (!isNumber(start)) start = new Date(start).getTime();
453
+ if (!isNumber(end)) end = new Date(end).getTime();
454
+ const t = end - start;
455
+ let ret = '';
456
+ let [d, h, m, s] = new Array(4).fill(0);
457
+ if (t >= 0) {
458
+ d = Math.floor(t / 1000 / 60 / 60 / 24);
459
+ h = Math.floor(t / 1000 / 60 / 60);
460
+ m = Math.floor(t / 1000 / 60);
461
+ s = Math.floor(t / 1000);
462
+ switch (type) {
463
+ case 'd':
464
+ ret = d;
465
+ break;
466
+ case 'text':
467
+ d = Math.floor(t / 1000 / 60 / 60 / 24);
468
+ h = Math.floor((t / 1000 / 60 / 60) % 24);
469
+ m = Math.floor((t / 1000 / 60) % 60);
470
+ s = Math.floor((t / 1000) % 60);
471
+ ret = d + ' 天 ' + h + ' 时 ' + m + ' 分 ' + s + ' 秒';
472
+ break;
473
+ default:
474
+ ret = s;
475
+ }
476
+ }
477
+ return ret;
478
+ }
479
+
480
+ /**
481
+ * @method isNumber
482
+ * @description 判断是否有效数字
483
+ * @param {*} num 被判断的值
484
+ * @param {boolean} options.isNaNAsNumber 是否 NaN 算数字(默认不算)
485
+ * @param {boolean} options.isUnFiniteAsNumber 是否 无限 算数字(默认不算)
486
+ * @returns {boolean} true 是数字
487
+ */
488
+ export function isNumber(num: any, options: { isNaNAsNumber?: boolean; isUnFiniteAsNumber?: boolean; } = { isNaNAsNumber: false, isUnFiniteAsNumber: false }): boolean {
489
+ const { isNaNAsNumber, isUnFiniteAsNumber } = Object.assign(
490
+ { isNaNAsNumber: false, isUnFiniteAsNumber: false },
491
+ options,
492
+ );
493
+ let ret = true;
494
+ // 数字类型
495
+ if (typeof num !== 'number') {
496
+ ret = false;
497
+ }
498
+ // 无限值
499
+ if (isUnFiniteAsNumber === false && !isFinite(num)) {
500
+ // console.log('1333');
501
+ ret = false;
502
+ }
503
+ // NaN
504
+ if (isNaNAsNumber === false && isNaN(num)) {
505
+ // console.log('2333');
506
+ ret = false;
507
+ }
508
+ return ret;
509
+ }
510
+
511
+ /**
512
+ * @method doFn
513
+ * @description 执行有效函数
514
+ * @param {function} fn 等待被执行的未知是否有效的函数
515
+ */
516
+ export function doFn(fn: any, ...params: any[]): any {
517
+ let ret = null;
518
+ if (fn && typeof fn === 'function') {
519
+ ret = fn(...params);
520
+ }
521
+ return ret;
522
+ }
523
+
524
+ /**
525
+ * @method setSessionStorage
526
+ * @description 存储数据到 sessionStorage
527
+ * @param {string} key 键
528
+ * @param {string} value 值
529
+ */
530
+ export function setSessionStorage(key: string, value: any = null): void {
531
+ if (key) {
532
+ sessionStorage.setItem(key, JSON.stringify(value));
533
+ }
534
+ }
535
+
536
+ /**
537
+ * @method getSessionStorage
538
+ * @description 存储数据到 sessionStorage
539
+ * @param {string} key 键
540
+ * @returns {*} 返回值
541
+ */
542
+ export function getSessionStorage(key: string): any {
543
+ let ret = null;
544
+ if (key) {
545
+ const value = sessionStorage.getItem(key);
546
+ if (value) {
547
+ ret = JSON.parse(value);
548
+ }
549
+ }
550
+ return ret;
551
+ }
552
+
553
+ /**
554
+ * @method setLocalStorage
555
+ * @description 存储数据到 localStorage
556
+ * @param {string} key 键
557
+ * @param {string} value 值
558
+ */
559
+ export function setLocalStorage(key: string, value: any = null): void {
560
+ if (key) {
561
+ localStorage.setItem(key, JSON.stringify(value));
562
+ }
563
+ }
564
+
565
+ /**
566
+ * @method getLocalStorage
567
+ * @description 存储数据到 localStorage
568
+ * @param {string} key 键
569
+ * @returns {*} 返回值
570
+ */
571
+ export function getLocalStorage(key: string): any {
572
+ let ret = null;
573
+ if (key) {
574
+ const value = localStorage.getItem(key);
575
+ if (value) {
576
+ ret = JSON.parse(value);
577
+ }
578
+ }
579
+ return ret;
580
+ }
581
+
582
+ /**
583
+ * @method loadCSS
584
+ * @description 动态加载css文件
585
+ * @param {string} url -- css资源路径
586
+ * @param {string} options.id -- link标签id
587
+ * @returns {Promise<boolean>} true -- 加载成功
588
+ */
589
+ export function loadCSS(url: string, options: { id?: string } = { id: '' }): Promise<boolean | Error | any> {
590
+ const { id } = options;
591
+ let success: (v: boolean) => void;
592
+ let fail: (v: Error) => void;
593
+ const status = new Promise((resolve, reject) => {
594
+ ([success, fail] = [resolve, reject]);
595
+ });
596
+ // const tempCB = (typeof callback === 'function' ? callback : function () { });
597
+ const callback = function () {
598
+ // doFn(success, true);
599
+ success(true);
600
+ };
601
+ let node: any = document.createElement('link');
602
+ const supportOnload = 'onload' in node;
603
+ const isOldWebKit = +navigator.userAgent.replace(/.*(?:AppleWebKit|AndroidWebKit)\/?(\d+).*/i, '$1') < 536; // webkit旧内核做特殊处理
604
+ const protectNum = 300000; // 阈值10分钟,一秒钟执行pollCss 500次
605
+
606
+ node.rel = 'stylesheet';
607
+ node.type = 'text/css';
608
+ node.href = url;
609
+ if (typeof id !== 'undefined') {
610
+ node.id = id;
611
+ }
612
+ document.getElementsByTagName('head')[0].appendChild(node);
613
+
614
+ // for Old WebKit and Old Firefox
615
+ if (isOldWebKit || !supportOnload) {
616
+ // Begin after node insertion
617
+ setTimeout(function () {
618
+ pollCss(node, callback, 0);
619
+ }, 1);
620
+ return status;
621
+ }
622
+
623
+ if (supportOnload) {
624
+ node.onload = onload;
625
+ node.onerror = function () {
626
+ // 加载失败(404)
627
+ onload();
628
+ };
629
+ } else {
630
+ // todo: 和 !supportOnload 重复
631
+ node.onreadystatechange = function () {
632
+ if (/loaded|complete/.test(node.readyState)) {
633
+ onload();
634
+ }
635
+ };
636
+ }
637
+
638
+ function onload() {
639
+ // 确保只跑一次下载操作
640
+ node.onload = node.onerror = node.onreadystatechange = null;
641
+
642
+ // 清空node引用,在低版本IE,不清除会造成内存泄露
643
+ node = null;
644
+
645
+ callback();
646
+ }
647
+
648
+ // 循环判断css是否已加载成功
649
+ /*
650
+ * @param node -- link节点
651
+ * @param callback -- 回调函数
652
+ * @param step -- 计步器,避免无限循环
653
+ */
654
+ function pollCss(node: any, callback: any, step: number) {
655
+ const sheet = node.sheet;
656
+ let isLoaded: any;
657
+
658
+ step += 1;
659
+
660
+ // 保护,大于10分钟,则不再轮询
661
+ if (step > protectNum) {
662
+ isLoaded = true;
663
+
664
+ // 清空node引用
665
+ node = null;
666
+
667
+ callback();
668
+ return;
669
+ }
670
+
671
+ if (isOldWebKit) {
672
+ // for WebKit < 536
673
+ if (sheet) {
674
+ isLoaded = true;
675
+ }
676
+ } else if (sheet) {
677
+ // for Firefox < 9.0
678
+ try {
679
+ if (sheet.cssRules) {
680
+ isLoaded = true;
681
+ }
682
+ } catch (ex) {
683
+ // 火狐特殊版本,通过特定值获知是否下载成功
684
+ // The value of `ex.name` is changed from "NS_ERROR_DOM_SECURITY_ERR"
685
+ // to "SecurityError" since Firefox 13.0. But Firefox is less than 9.0
686
+ // in here, So it is ok to just rely on "NS_ERROR_DOM_SECURITY_ERR"
687
+ if ((ex as any).name === 'NS_ERROR_DOM_SECURITY_ERR') {
688
+ isLoaded = true;
689
+ }
690
+ }
691
+ }
692
+
693
+ setTimeout(function () {
694
+ if (isLoaded) {
695
+ // 延迟20ms是为了给下载的样式留够渲染的时间
696
+ callback();
697
+ } else {
698
+ pollCss(node, callback, step);
699
+ }
700
+ }, 20);
701
+ }
702
+ return status;
703
+ }
704
+
705
+ /**
706
+ * @method loadScript
707
+ * @description 动态加载js文件
708
+ * @param {string} url -- js资源路径
709
+ * @param {string} options.id -- DOM ID
710
+ * @param {function} options.callback -- 加载后回调函数
711
+ * @param {number} options.timeout -- 超时时长
712
+ * @param {boolean} options.isDefer -- 是否添加 defer 标签
713
+ * @returns {Promise<boolean>} -- true 成功
714
+ */
715
+ export function loadScript(url: string, options: {
716
+ id: string;
717
+ callback: (...params: any[]) => any;
718
+ timeout: number;
719
+ isDefer: boolean;
720
+ } = { id: '', callback: function () { /* pass */ }, timeout: 5000, isDefer: false }): Promise<boolean | string | Error> {
721
+ const { id, callback, timeout, isDefer } = Object.assign(
722
+ { id: '', callback: function () { /* pass */ }, timeout: 5000, isDefer: false },
723
+ options,
724
+ );
725
+ let success: any = null;
726
+ let fail: any = null;
727
+ const script: any = document.createElement('script');
728
+ // 如果没有 script 标签,那么代码就不会运行。可以利用这一事实,在页面的第一个 script 标签上使用 insertBefore()。
729
+ const firstScript: any = document.getElementsByTagName('script')[0];
730
+ script.type = 'text/javascript';
731
+ if (isDefer) {
732
+ script.defer = 'defer';
733
+ }
734
+ if (id) {
735
+ script.id = id;
736
+ }
737
+ if (script.readyState) {
738
+ // IE
739
+ script.onreadystatechange = function () {
740
+ if (script.readyState === 'loaded' || script.readyState === 'complete') {
741
+ script.onreadystatechange = null;
742
+ doFn(callback);
743
+ doFn(success, true);
744
+ }
745
+ };
746
+ } else {
747
+ // Others
748
+ script.onload = function () {
749
+ doFn(callback);
750
+ doFn(success, true);
751
+ };
752
+ }
753
+ script.src = url;
754
+ firstScript && firstScript.parentNode.insertBefore(script, firstScript);
755
+ return new Promise((resolve, reject) => {
756
+ ([success, fail] = [resolve, reject]);
757
+ if (timeout) {
758
+ setTimeout(fail.bind(null, Error('timeout')), timeout);
759
+ }
760
+ });
761
+ }
762
+
763
+ /**
764
+ * @method mNow
765
+ * @description 获取时间戳
766
+ */
767
+ export function mNow(): number {
768
+ let ret = 0;
769
+ if (Date.now) {
770
+ ret = Date.now();
771
+ } else {
772
+ ret = new Date().getTime();
773
+ }
774
+ return ret;
775
+ }
776
+
777
+ /**
778
+ * @method setCookie
779
+ * @description 设置 Cookie
780
+ */
781
+ export function setCookie(name: string, value: string, days: number, domain: string): void {
782
+ let domainParts, expires;
783
+ let date: any;
784
+ if (days) {
785
+ date = new Date();
786
+ date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
787
+ expires = "; expires=" + date.toGMTString();
788
+ } else {
789
+ expires = "";
790
+ }
791
+ const host = location.host;
792
+ if (host.split('.').length === 1) {
793
+ // no "." in a domain - it's localhost or something similar
794
+ document.cookie = name + "=" + value + expires + "; path=/";
795
+ } else {
796
+ // Remember the cookie on all subdomains.
797
+ //
798
+ // Start with trying to set cookie to the top domain.
799
+ // (example: if user is on foo.com, try to set
800
+ // cookie to domain ".com")
801
+ //
802
+ // If the cookie will not be set, it means ".com"
803
+ // is a top level domain and we need to
804
+ // set the cookie to ".foo.com"
805
+ domainParts = host.split('.');
806
+ domainParts.shift();
807
+ domain = domain || ('.' + domainParts.join('.'));
808
+ document.cookie = name + "=" + value + expires + "; path=/; domain=" + domain;
809
+ // check if cookie was successfuly set to the given domain
810
+ // (otherwise it was a Top-Level Domain)
811
+ if (getCookie(name) === null || getCookie(name) !== value) {
812
+ // append "." to current domain
813
+ domain = domain || ('.' + host);
814
+ document.cookie = name + "=" + value + expires + "; path=/; domain=" + domain;
815
+ }
816
+ }
817
+ }
818
+
819
+ /**
820
+ * @method getCookie
821
+ * @description 获取 Cookie
822
+ */
823
+ export function getCookie(name: string): string {
824
+ const nameEQ = name + "=";
825
+ const ca = document.cookie.split(';');
826
+ for (let i = 0; i < ca.length; i++) {
827
+ let c = ca[i];
828
+ while (c.charAt(0) == ' ') {
829
+ c = c.substring(1, c.length);
830
+ }
831
+ if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
832
+ }
833
+ return '';
834
+ }
835
+
836
+ interface WebPerformance {
837
+ [key: string]: string | number;
838
+ }
839
+
840
+ /**
841
+ * @method getPerformance
842
+ * @description 获取页面加载相关的各项数据
843
+ * @param {boolean} camelCase -- true(默认) 以驼峰形式返回数据 false 以下划线形式返回数据
844
+ * @returns {Promise<object>} 加载数据
845
+ */
846
+ export function getPerformance(camelCase = true): Promise<WebPerformance | Error> {
847
+ let success: (v: WebPerformance) => void;
848
+ let fail: (v: Error) => void;
849
+ const status: Promise<WebPerformance> = new Promise((resolve, reject) => {
850
+ ([success, fail] = [resolve, reject]);
851
+ });
852
+ const timing = window.performance.timing;
853
+ const startTime = timing.navigationStart || timing.fetchStart;
854
+ let firstPaintTime: any;
855
+ let firstContentfulPaintTime: any;
856
+ // 是否已经形成数据(页面加载完成之后)
857
+ if (window.performance && window.performance.timing && window.performance.timing.loadEventEnd > 0) {
858
+ // console.log('created')
859
+ getTiming();
860
+ } else {
861
+ // console.log('creating')
862
+ window.addEventListener("load", function() {
863
+ //不能影响最后的时间计算
864
+ window.setTimeout(function() {
865
+ getTiming();
866
+ }, 0);
867
+ });
868
+ }
869
+ // performance
870
+ function getTiming() {
871
+ // 获取首次渲染时间
872
+ try {
873
+ if (window.performance && Boolean(window.performance.getEntriesByType)) {
874
+ const performance = window.performance;
875
+ const performanceEntries = performance.getEntriesByType('paint');
876
+ performanceEntries.forEach((performanceEntry, i, entries) => {
877
+ const startTime = Math.round(performanceEntry.startTime);
878
+ if (performanceEntry.name === 'first-paint')
879
+ firstPaintTime = startTime;
880
+ else if (performanceEntry.name === 'first-contentful-paint')
881
+ firstContentfulPaintTime = startTime;
882
+ });
883
+ } else {
884
+ console.error('paint');
885
+ }
886
+ } catch (e) {
887
+ console.error((e as any).message);
888
+ }
889
+ // 获取加载时间
890
+ if (
891
+ window.performance &&
892
+ typeof window.performance.getEntries === 'function'
893
+ ) {
894
+ const performanceNavigationTiming: any =
895
+ (window.performance.getEntries() || [])[0] || {};
896
+ const data: any = {
897
+ // url: encodeURI(location.href),
898
+ // ua: navigator.userAgent,
899
+ os: getOS(),
900
+ osVersion: getOSVersion(),
901
+ deviceType: getDeviceType(),
902
+ network: getNetWork(),
903
+ screenDirection: getOrientationStatu(),
904
+ unloadTime: timing.unloadEventEnd - timing.unloadEventStart, //上个文档的卸载时间
905
+ redirectTime: timing.redirectEnd - timing.redirectStart, //*重定向时间
906
+ dnsTime: timing.domainLookupEnd - timing.domainLookupStart, //*DNS查询时间
907
+ tcpTime: timing.connectEnd - timing.connectStart, //*服务器连接时间
908
+ sslTime: getSSLTime(timing.connectEnd, timing.secureConnectionStart), //*ssl连接时间
909
+ responseTime: timing.responseStart - timing.requestStart, //*服务器响应时间
910
+ downloadTime: timing.responseEnd - timing.responseStart, //*网页下载时间
911
+ firstPaintTime: firstPaintTime, //*首次渲染时间
912
+ firstContentfulPaintTime: firstContentfulPaintTime, //*首次渲染内容时间
913
+ domreadyTime: timing.domContentLoadedEventStart - startTime || 0, //*dom ready时间(总和)
914
+ onloadTime: timing.loadEventStart - startTime || 0, //*onload时间(总和)
915
+ whiteTime: timing.responseStart - startTime, //*白屏时间
916
+ renderTime: timing.loadEventEnd - startTime || 0, //整个过程的时间之和
917
+ decodedBodySize: performanceNavigationTiming.decodedBodySize || '', //页面压缩前大小
918
+ encodedBodySize: performanceNavigationTiming.encodedBodySize || '', //页面压缩后大小
919
+ };
920
+ // 过滤异常数据
921
+ Object.keys(data).forEach(k => {
922
+ // 过滤掉 <0 的数据
923
+ if (isNumber(data[k]) && data[k] < 0) {
924
+ data[k] = 0;
925
+ }
926
+ });
927
+ // 过滤掉白屏时间 > onload 的数据
928
+ if (isNumber(data.whiteTime) && data.whiteTime > data.onloadTime) {
929
+ data.whiteTime = 0;
930
+ }
931
+ if (startTime > 0) {
932
+ let Underscore: any;
933
+ if (!camelCase) {
934
+ Object.keys(data).forEach(k => {
935
+ if (!Underscore) Underscore = {};
936
+ // console.log('camelCase2Underscore', k, data, )
937
+ Underscore[camelCase2Underscore(k)] = data[k];
938
+ });
939
+ }
940
+ success(Underscore || data);
941
+ } else {
942
+ fail(Error('startTime'));
943
+ }
944
+ } else {
945
+ fail(Error('getEntries'));
946
+ }
947
+ }
948
+ //获取当前操作系统
949
+ function getOS() {
950
+ let os;
951
+ if (navigator.userAgent.indexOf('Android') > -1 || navigator.userAgent.indexOf('Linux') > -1) {
952
+ os = 'android';
953
+ } else if (navigator.userAgent.indexOf('iPhone') > -1) {
954
+ os = 'ios';
955
+ } else if (navigator.userAgent.indexOf('Windows Phone') > -1) {
956
+ os = 'wp';
957
+ } else {
958
+ os = 'others';
959
+ }
960
+ return os;
961
+ }
962
+ // 获取操作系统版本
963
+ function getOSVersion() {
964
+ let OSVision: any;
965
+ const u = navigator.userAgent;
966
+ const isAndroid = u.indexOf('Android') > -1 || u.indexOf('Linux') > -1; //Android
967
+ const isIOS = !!u.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios终端
968
+ if (isAndroid) {
969
+ OSVision = (navigator.userAgent.split(';')[1].match(/\d+\.\d+/g) || [])[0];
970
+ }
971
+ if (isIOS) {
972
+ OSVision = (navigator.userAgent.split(';')[1].match(/(\d+)_(\d+)_?(\d+)?/) || [])[0];
973
+ }
974
+ return OSVision;
975
+ }
976
+ //获取设备类型
977
+ function getDeviceType() {
978
+ let deviceType;
979
+ const sUserAgent = navigator.userAgent.toLowerCase();
980
+ const bIsIpad = sUserAgent.match(/(ipad)/i) && 'ipad';
981
+ const bIsIphoneOs = sUserAgent.match(/iphone os/i) && 'iphone os';
982
+ const bIsMidp = sUserAgent.match(/midp/i) && 'midp';
983
+ const bIsUc7 = sUserAgent.match(/rv:1.2.3.4/i) && 'rv:1.2.3.4';
984
+ const bIsUc = sUserAgent.match(/ucweb/i) && 'ucweb';
985
+ const bIsAndroid = sUserAgent.match(/android/i) && 'android';
986
+ const bIsCE = sUserAgent.match(/windows ce/i) && 'windows ce';
987
+ const bIsWM = sUserAgent.match(/windows mobile/i) && 'windows mobile';
988
+ if (!(bIsIpad || bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM)) {
989
+ deviceType = 'pc'; //pc
990
+ } else if (bIsIphoneOs || bIsMidp || bIsUc7 || bIsUc || bIsAndroid || bIsCE || bIsWM) {
991
+ deviceType = 'phone'; //phone
992
+ } else if (bIsIpad) {
993
+ deviceType = 'ipad'; //ipad
994
+ } else {
995
+ deviceType = undefined;
996
+ }
997
+ return deviceType;
998
+ }
999
+ // 获取网络状态
1000
+ function getNetWork() {
1001
+ let netWork: any;
1002
+ if ((navigator as any).connection && (navigator as any).connection.effectiveType) {
1003
+ switch ((navigator as any).connection.effectiveType) {
1004
+ case 'wifi':
1005
+ netWork = 'wifi'; // wifi
1006
+ break;
1007
+ case '4g':
1008
+ netWork = '4g'; // 4g
1009
+ break;
1010
+ case '2g':
1011
+ netWork = '2g'; // 2g
1012
+ break;
1013
+ case '3g':
1014
+ netWork = '3g'; // 3g
1015
+ break;
1016
+ case 'ethernet':
1017
+ netWork = 'ethernet'; // ethernet
1018
+ break;
1019
+ case 'default':
1020
+ netWork = undefined; // 未知
1021
+ break;
1022
+ }
1023
+ }
1024
+ return netWork;
1025
+ }
1026
+ // 获取横竖屏状态
1027
+ function getOrientationStatu() {
1028
+ let orientationStatu: any;
1029
+ if (window.screen && window.screen.orientation && window.screen.orientation.angle) {
1030
+ if (window.screen.orientation.angle === 180 || window.screen.orientation.angle === 0) { // 竖屏
1031
+ orientationStatu = '|';
1032
+ }
1033
+ if (window.screen.orientation.angle === 90 || window.screen.orientation.angle === -90) { // 横屏
1034
+ orientationStatu = '-';
1035
+ }
1036
+ }
1037
+ return orientationStatu;
1038
+ }
1039
+ // 获取ssl连接时间
1040
+ function getSSLTime(connectEnd: any, secureConnectionStart: any) {
1041
+ let ssl_time: any;
1042
+ if (secureConnectionStart) {
1043
+ ssl_time = connectEnd - secureConnectionStart;
1044
+ }
1045
+ return ssl_time;
1046
+ }
1047
+ return status;
1048
+ }
1049
+
1050
+ /**
1051
+ * @method inRate
1052
+ * @description 百分位概率
1053
+ * @param {number} rate -- 0.1 ~ 1 => 1% ~ 100%
1054
+ * @returns {boolean} true 命中
1055
+ */
1056
+ export function inRate(rate: number): boolean {
1057
+ if(Math.random() < rate) {
1058
+ return true;
1059
+ }
1060
+ return false;
1061
+ }
1062
+
1063
+ /**
1064
+ * @method isSafePWAEnv
1065
+ * @description 判断是否是安全的 PWA 环境
1066
+ * @returns {boolean} true 是
1067
+ */
1068
+ export function isSafePWAEnv(): boolean {
1069
+ // 判断是否支持 async await
1070
+ function isSupportAsyncAwait() {
1071
+ let isSupportAsyncAwaitFunc;
1072
+ try {
1073
+ // eval("func = async function(){};");
1074
+ const fn = new Function("return async function(){};");
1075
+ isSupportAsyncAwaitFunc = fn();
1076
+ // console.log('isSupportAsyncAwaitFunc', isSupportAsyncAwaitFunc);
1077
+ // 由于async函数的构造器不是全局对象,所以我们需要由下面代码来获取async函数的构造器
1078
+ // 具体可以查看以下MDN上有关于AsyncFunction的说明,
1079
+ // 地址:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
1080
+ return Object.getPrototypeOf(isSupportAsyncAwaitFunc).constructor != null;
1081
+ } catch (e) {
1082
+ return false;
1083
+ }
1084
+ }
1085
+ // 判断是否支持 Promise
1086
+ function isSupportPromise() {
1087
+ if(typeof Promise !== "undefined" && Promise.toString().indexOf("[native code]") !== -1){
1088
+ return true;
1089
+ }
1090
+ return false;
1091
+ }
1092
+ // 浏览器信息
1093
+ const BrowserType = getBrowserInfo();
1094
+ if (
1095
+ ('serviceWorker' in navigator) &&
1096
+ isSupportAsyncAwait() &&
1097
+ isSupportPromise() &&
1098
+ Boolean(window.fetch) &&
1099
+ Boolean(window.indexedDB) &&
1100
+ Boolean(window.caches) &&
1101
+ !BrowserType['shell']
1102
+ ) {
1103
+ return true;
1104
+ }
1105
+ return false;
1106
+ }
1107
+
1108
+ /**
1109
+ * EN: Browser Information
1110
+ *
1111
+ * ZH: 返回浏览器信息 https://github.com/JowayYoung/juejin-code/blob/master/browser-type.js
1112
+ *
1113
+ * ```
1114
+ * getBrowserInfo(); // {"engine":"webkit","engineVs":"537.36","platform":"desktop","supporter":"chrome","supporterVs":"85.0.4183.121","system":"windows","systemVs":"10"}
1115
+ * ```
1116
+ *
1117
+ * | Index | Field | Description |
1118
+ * | --- | --- | --- |
1119
+ * | System | system | android, ios, windows, macos, linux |
1120
+ * | System version | systemVs | windows: 2000, xp, 2003, vista, 7, 8, 8.1, 10 <br />macos: ... |
1121
+ * | Platform | platform | desktop, mobile |
1122
+ * | Engine | engine | webkit, gecko, presto, trident |
1123
+ * | Engine version | engineVs | - |
1124
+ * | Supporter | supporter | edge, opera, chrome, safari, firefox, iexplore |
1125
+ * | Supporter version | supporterVs | - |
1126
+ * | Shell | shell | (Optional) wechat, qq_browser, qq_app, uc, 360, 2345, sougou, liebao, maxthon, bilibili |
1127
+ * | Shell version | shellVs | (Optional) |
1128
+ * | Apple device type | appleType | (Optional) iphone, ipad, ipod, iwatch |
1129
+ *
1130
+ * Example: Determine the environment of the mobile QQ.
1131
+ *
1132
+ * ```
1133
+ * const { system, shell } = getBrowserInfo();
1134
+ * const isMobileQQ = ['android', 'ios'].includes(system) && ['qq_browser', 'qq_app'].includes(shell);
1135
+ * ```
1136
+ *
1137
+ * @returns 浏览器信息
1138
+ */
1139
+ export function getBrowserInfo(): {
1140
+ engine: string; // webkit gecko presto trident
1141
+ engineVs: string;
1142
+ platform: string; // desktop mobile
1143
+ supporter: string; // chrome safari firefox opera iexplore edge
1144
+ supporterVs: string;
1145
+ system: string; // windows macos linux android ios
1146
+ systemVs: string;
1147
+ shell?: string; // wechat qq uc 360 2345 sougou liebao maxthon
1148
+ shellVs?: string;
1149
+ appleType?: string;
1150
+ } {
1151
+ try {
1152
+ // 权重:系统 + 系统版本 > 平台 > 内核 + 载体 + 内核版本 + 载体版本 > 外壳 + 外壳版本
1153
+ const ua: string = navigator.userAgent.toLowerCase();
1154
+ const testUa: (regexp: RegExp) => boolean = regexp => regexp.test(ua);
1155
+ const testVs: (regexp: RegExp) => string = regexp => {
1156
+ const matchRes = ua.match(regexp);
1157
+ let ret = '';
1158
+ if (matchRes) {
1159
+ ret = matchRes
1160
+ .toString()
1161
+ .replace(/[^0-9|_.]/g, "")
1162
+ .replace(/_/g, ".");
1163
+ }
1164
+ return ret;
1165
+ };
1166
+ // 系统
1167
+ let system = '';
1168
+ // Apple device type.
1169
+ let appleType = '';
1170
+ if (testUa(/windows|win32|win64|wow32|wow64/g)) {
1171
+ system = "windows"; // windows系统
1172
+ } else if (testUa(/macintosh|macintel/g)) {
1173
+ system = "macos"; // macos系统
1174
+ } else if (testUa(/x11/g)) {
1175
+ system = "linux"; // linux系统
1176
+ } else if (testUa(/android|adr/g)) {
1177
+ system = "android"; // android系统
1178
+ } else if (testUa(/ios|iphone|ipad|ipod|iwatch/g)) {
1179
+ system = "ios"; // ios系统
1180
+ if (testUa(/iphone/g)) {
1181
+ appleType = 'iphone';
1182
+ } else if (testUa(/ipad/g)) {
1183
+ appleType = 'ipad';
1184
+ } else if (testUa(/iwatch/g)) {
1185
+ appleType = 'iwatch';
1186
+ } else if (testUa(/ipod/g)) {
1187
+ appleType = 'ipod';
1188
+ }
1189
+ }
1190
+ // 系统版本
1191
+ let systemVs = '';
1192
+ if (system === "windows") {
1193
+ if (testUa(/windows nt 5.0|windows 2000/g)) {
1194
+ systemVs = "2000";
1195
+ } else if (testUa(/windows nt 5.1|windows xp/g)) {
1196
+ systemVs = "xp";
1197
+ } else if (testUa(/windows nt 5.2|windows 2003/g)) {
1198
+ systemVs = "2003";
1199
+ } else if (testUa(/windows nt 6.0|windows vista/g)) {
1200
+ systemVs = "vista";
1201
+ } else if (testUa(/windows nt 6.1|windows 7/g)) {
1202
+ systemVs = "7";
1203
+ } else if (testUa(/windows nt 6.2|windows 8/g)) {
1204
+ systemVs = "8";
1205
+ } else if (testUa(/windows nt 6.3|windows 8.1/g)) {
1206
+ systemVs = "8.1";
1207
+ } else if (testUa(/windows nt 10.0|windows 10/g)) {
1208
+ systemVs = "10";
1209
+ }
1210
+ } else if (system === "macos") {
1211
+ systemVs = testVs(/os x [\d._]+/g);
1212
+ } else if (system === "android") {
1213
+ systemVs = testVs(/android [\d._]+/g);
1214
+ } else if (system === "ios") {
1215
+ systemVs = testVs(/os [\d._]+/g);
1216
+ }
1217
+ // 平台
1218
+ let platform = '';
1219
+ if (system === "windows" || system === "macos" || system === "linux") {
1220
+ platform = "desktop"; // 桌面端
1221
+ } else if (system === "android" || system === "ios" || testUa(/mobile/g)) {
1222
+ platform = "mobile"; // 移动端
1223
+ }
1224
+ // 内核和载体
1225
+ let engine = '';
1226
+ let supporter = '';
1227
+ if (testUa(/applewebkit/g)) {
1228
+ engine = "webkit"; // webkit内核
1229
+ if (testUa(/edge/g)) {
1230
+ supporter = "edge"; // edge浏览器
1231
+ } else if (testUa(/opr/g)) {
1232
+ supporter = "opera"; // opera浏览器
1233
+ } else if (testUa(/chrome/g)) {
1234
+ supporter = "chrome"; // chrome浏览器
1235
+ } else if (testUa(/safari/g)) {
1236
+ supporter = "safari"; // safari浏览器
1237
+ }
1238
+ } else if (testUa(/gecko/g) && testUa(/firefox/g)) {
1239
+ engine = "gecko"; // gecko内核
1240
+ supporter = "firefox"; // firefox浏览器
1241
+ } else if (testUa(/presto/g)) {
1242
+ engine = "presto"; // presto内核
1243
+ supporter = "opera"; // opera浏览器
1244
+ } else if (testUa(/trident|compatible|msie/g)) {
1245
+ engine = "trident"; // trident内核
1246
+ supporter = "iexplore"; // iexplore浏览器
1247
+ }
1248
+ // 内核版本
1249
+ let engineVs = '';
1250
+ if (engine === "webkit") {
1251
+ engineVs = testVs(/applewebkit\/[\d._]+/g);
1252
+ } else if (engine === "gecko") {
1253
+ engineVs = testVs(/gecko\/[\d._]+/g);
1254
+ } else if (engine === "presto") {
1255
+ engineVs = testVs(/presto\/[\d._]+/g);
1256
+ } else if (engine === "trident") {
1257
+ engineVs = testVs(/trident\/[\d._]+/g);
1258
+ }
1259
+ // 载体版本
1260
+ let supporterVs = '';
1261
+ if (supporter === "chrome") {
1262
+ supporterVs = testVs(/chrome\/[\d._]+/g);
1263
+ } else if (supporter === "safari") {
1264
+ supporterVs = testVs(/version\/[\d._]+/g);
1265
+ } else if (supporter === "firefox") {
1266
+ supporterVs = testVs(/firefox\/[\d._]+/g);
1267
+ } else if (supporter === "opera") {
1268
+ supporterVs = testVs(/opr\/[\d._]+/g);
1269
+ } else if (supporter === "iexplore") {
1270
+ supporterVs = testVs(/(msie [\d._]+)|(rv:[\d._]+)/g);
1271
+ } else if (supporter === "edge") {
1272
+ supporterVs = testVs(/edge\/[\d._]+/g);
1273
+ }
1274
+ // 外壳和外壳版本
1275
+ let shell = '';
1276
+ let shellVs = '';
1277
+ if (testUa(/micromessenger/g)) {
1278
+ shell = "wechat"; // 微信浏览器
1279
+ shellVs = testVs(/micromessenger\/[\d._]+/g);
1280
+ } else if (testUa(/qqbrowser/g)) {
1281
+ shell = "qq_browser"; // QQ Browser
1282
+ shellVs = testVs(/qqbrowser\/[\d._]+/g);
1283
+ } else if (testUa(/\sqq/g)) {
1284
+ shell = "qq_app"; // QQ APP
1285
+ } else if (testUa(/ucbrowser/g)) {
1286
+ shell = "uc"; // UC浏览器
1287
+ shellVs = testVs(/ucbrowser\/[\d._]+/g);
1288
+ } else if (testUa(/qihu 360se/g)) {
1289
+ shell = "360"; // 360浏览器(无版本)
1290
+ } else if (testUa(/2345explorer/g)) {
1291
+ shell = "2345"; // 2345浏览器
1292
+ shellVs = testVs(/2345explorer\/[\d._]+/g);
1293
+ } else if (testUa(/metasr/g)) {
1294
+ shell = "sougou"; // 搜狗浏览器(无版本)
1295
+ } else if (testUa(/lbbrowser/g)) {
1296
+ shell = "liebao"; // 猎豹浏览器(无版本)
1297
+ } else if (testUa(/maxthon/g)) {
1298
+ shell = "maxthon"; // 遨游浏览器
1299
+ shellVs = testVs(/maxthon\/[\d._]+/g);
1300
+ } else if (testUa(/biliapp/g)) {
1301
+ shell = "bilibili"; // 哔哩哔哩
1302
+ }
1303
+ return Object.assign({
1304
+ engine, // webkit gecko presto trident
1305
+ engineVs,
1306
+ platform, // desktop mobile
1307
+ supporter, // chrome safari firefox opera iexplore edge
1308
+ supporterVs,
1309
+ system, // windows macos linux android ios
1310
+ systemVs,
1311
+ }, {
1312
+ shell, // wechat qq uc 360 2345 sougou liebao maxthon
1313
+ shellVs,
1314
+ appleType,
1315
+ });
1316
+ } catch (err) {
1317
+ console.warn('mazey:', err);
1318
+ return {
1319
+ engine: '', // webkit gecko presto trident
1320
+ engineVs: '',
1321
+ platform: '', // desktop mobile
1322
+ supporter: '', // chrome safari firefox opera iexplore edge
1323
+ supporterVs: '',
1324
+ system: '', // windows macos linux android ios
1325
+ systemVs: '',
1326
+ };
1327
+ }
1328
+ }
1329
+
1330
+ /**
1331
+ * @method clearHtml
1332
+ * @description 去除HTML标签
1333
+ * @param {string} str 带html标签的字符串
1334
+ * @returns {string} 字符串
1335
+ */
1336
+ export function clearHtml (str: string): string {
1337
+ return str.replace(/<\/?.+?>/g, '').replace(/[\r\n]/g, '');
1338
+ }
1339
+
1340
+ /**
1341
+ * @method cutCHSString
1342
+ * @description 截取字符串,中文算2个字节
1343
+ * @param {string} str 要截取的字符串
1344
+ * @param {number} len
1345
+ * @param {boolean} hasDot
1346
+ * @returnss {string} 返回截取后的字符串
1347
+ */
1348
+ export function cutCHSString(str: string, len: number, hasDot = false): string {
1349
+ if (str == '' || !str) {
1350
+ return '';
1351
+ } else {
1352
+ let newLength = 0;
1353
+ let newStr = '';
1354
+ // eslint-disable-next-line no-control-regex
1355
+ const chineseRegex = /[^\x00-\xff]/g;
1356
+ let singleChar = '';
1357
+ const strLength = str.replace(chineseRegex, '**').length;
1358
+ for (let i = 0; i < strLength; i++) {
1359
+ singleChar = str.charAt(i).toString();
1360
+ if (singleChar.match(chineseRegex) != null) {
1361
+ newLength += 2;
1362
+ } else {
1363
+ newLength++;
1364
+ }
1365
+ if (newLength > len) {
1366
+ break;
1367
+ }
1368
+ newStr += singleChar;
1369
+ }
1370
+
1371
+ if (hasDot && strLength > len) {
1372
+ newStr += '...';
1373
+ }
1374
+ return newStr;
1375
+ }
1376
+ }
1377
+
1378
+ /**
1379
+ * @method windowLoaded
1380
+ * @description 页面加载完成
1381
+ * @param {number} timeout 超时时间 / 单位:秒
1382
+ * @returns {Promise<string>} document is loaded? 'complete' 'load' / 'timeout'
1383
+ */
1384
+ export function windowLoaded(timeout = 90): Promise<string | Error> {
1385
+ let loaded: (value: string) => void = () => undefined;
1386
+ let loadFail: (value: Error) => void;
1387
+ const status = new Promise((resolve: (value: string) => void, reject: (value: Error) => void) => {
1388
+ loaded = resolve;
1389
+ loadFail = reject;
1390
+ });
1391
+ if (document.readyState === 'complete') {
1392
+ loaded('complete');
1393
+ } else {
1394
+ window.addEventListener('load', () => loaded('load'));
1395
+ }
1396
+ // 超过 timeout 秒后加载失败
1397
+ setTimeout(() => loadFail(Error('timeout')), timeout * 1000);
1398
+ return status;
1399
+ }
1400
+
1401
+ /**
1402
+ * EN: Add `<style>` in `<head>`.
1403
+ *
1404
+ * ZH: 添加样式标签; style: 样式标签内的字符串; id: `<style>` 标签的 `id`; 返回: 添加成功/失败.
1405
+ *
1406
+ * Example 1: Add the `<style>` with `id`, and repeated invoking will update the content instead of adding a new one.
1407
+ *
1408
+ * ```
1409
+ * addStyle(
1410
+ * `
1411
+ * body {
1412
+ * background-color: #333;
1413
+ * }
1414
+ * `,
1415
+ * {
1416
+ * id: 'test',
1417
+ * }
1418
+ * );
1419
+ * // <style id="test">
1420
+ * // body {
1421
+ * // background-color: #333;
1422
+ * // }
1423
+ * // </style>
1424
+ * ```
1425
+ *
1426
+ * Example 2: Add the `<style>` without `id`, and repeated invoking will adding a new one.
1427
+ *
1428
+ * ```
1429
+ * addStyle(
1430
+ * `
1431
+ * body {
1432
+ * background-color: #333;
1433
+ * }
1434
+ * `
1435
+ * );
1436
+ * // <style>
1437
+ * // body {
1438
+ * // background-color: #333;
1439
+ * // }
1440
+ * // </style>
1441
+ * ```
1442
+ *
1443
+ * @param style Style string.
1444
+ * @param options.id `id` in `<style>`
1445
+ * @returns Success/Fail
1446
+ */
1447
+ export function addStyle(style: string, options: { id?: string; } = { id: '' }): boolean {
1448
+ // console.log('_ style', style);
1449
+ // console.log('_ options', options);
1450
+ if (!style) {
1451
+ return false;
1452
+ }
1453
+ // 创建 style 文档碎片
1454
+ const styleFrag = document.createDocumentFragment();
1455
+ let idDom: HTMLElement | null = null;
1456
+ let domId = '';
1457
+ // Custom Style
1458
+ const customStyle = document.createElement('style');
1459
+ // 如果需要 ID
1460
+ if (options.id) {
1461
+ domId = `${options.id}`;
1462
+ idDom = document.getElementById(domId);
1463
+ // 如果 Dom 不存在,插入 style
1464
+ if (!idDom) {
1465
+ customStyle.setAttribute('id', options.id);
1466
+ customStyle.innerHTML = style;
1467
+ styleFrag.appendChild(customStyle);
1468
+ document.head.appendChild(styleFrag);
1469
+ } else { // 如果 Dom 存在,直接更新
1470
+ idDom.innerHTML = style;
1471
+ }
1472
+ } else { // 不需要 ID,直接添加新标签
1473
+ customStyle.innerHTML = style;
1474
+ styleFrag.appendChild(customStyle);
1475
+ document.head.appendChild(styleFrag);
1476
+ }
1477
+ return true;
1478
+ }
1479
+
1480
+ /**
1481
+ * @method genCustomConsole
1482
+ * @description 生成自定义控制台打印
1483
+ * @param {string} prefix 前缀
1484
+ * @param {function} allowFn 允许打印的判断函数
1485
+ * @returns {object} 新实例
1486
+ */
1487
+ export function genCustomConsole(prefix = ''): Console {
1488
+ const methods = ['log', 'info', 'warn', 'error'];
1489
+ const newConsole = Object.create(null);
1490
+ methods.forEach(method => {
1491
+ newConsole[method] = function (...argu: any) {
1492
+ if (prefix) {
1493
+ (console as any)[method](prefix, ...argu);
1494
+ } else {
1495
+ (console as any)[method](...argu);
1496
+ }
1497
+ };
1498
+ });
1499
+ return newConsole;
1500
+ }
1501
+
1502
+ /**
1503
+ * Verify the validity of axios response.
1504
+ */
1505
+ export function zAxiosIsValidRes(res: any, options: {
1506
+ validStatus: number[];
1507
+ validCode: number[];
1508
+ } = {
1509
+ validStatus: [200],
1510
+ validCode: [0],
1511
+ }): boolean {
1512
+ const { validStatus, validCode } = Object.assign(
1513
+ {
1514
+ validStatus: [200],
1515
+ validCode: [0],
1516
+ },
1517
+ options,
1518
+ );
1519
+ let ret = false;
1520
+ if (res && res.status && validStatus.includes(res.status)) {
1521
+ ret = true;
1522
+ const resData = res.data;
1523
+ if (resData && validCode.includes(resData.code)) {
1524
+ ret = true;
1525
+ }
1526
+ }
1527
+ return ret;
1528
+ }
1529
+
1530
+ /**
1531
+ * Verify the validity of a non-empty array.
1532
+ */
1533
+ export function isNonEmptyArray(arr: any[]): boolean {
1534
+ let ret = false;
1535
+ if (Array.isArray(arr) && arr.length) {
1536
+ ret = true;
1537
+ }
1538
+ return ret;
1539
+ }
1540
+
1541
+ /**
1542
+ * 语义化文件大小, 把字节转换成正常文件大小.
1543
+ */
1544
+ export function getFileSize(size: number): string {
1545
+ const toCeilStr: (v: number) => string = n => String(Math.ceil(n));
1546
+ if (!size) return '';
1547
+ const num = 1024.0; // byte
1548
+ if (size < num) {
1549
+ return size + ' B';
1550
+ }
1551
+ if (size < Math.pow(num, 2)) {
1552
+ return toCeilStr(size / num) + ' KB';
1553
+ } // kb
1554
+ if (size < Math.pow(num, 3)) {
1555
+ return toCeilStr(size / Math.pow(num, 2)) + ' MB';
1556
+ } // M
1557
+ if (size < Math.pow(num, 4)) {
1558
+ return toCeilStr(size / Math.pow(num, 3)) + ' G';
1559
+ } // G
1560
+ return toCeilStr(size / Math.pow(num, 4)) + ' T';
1561
+ }
1562
+
1563
+ /**
1564
+ * Detect webp support.
1565
+ *
1566
+ * @see https://davidwalsh.name/detect-webp
1567
+ */
1568
+ export function isSupportWebp(): Promise<boolean> {
1569
+ const fn = (resolve: (v: boolean) => void) => {
1570
+ const img = new Image();
1571
+ img.onload = () => {
1572
+ resolve(img.width > 0 && img.height > 0);
1573
+ };
1574
+ img.onerror = () => {
1575
+ resolve(false);
1576
+ };
1577
+ img.src = "data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=";
1578
+ };
1579
+ return new Promise(fn);
1580
+ }
1581
+
1582
+ /**
1583
+ * Generate a Hash Code from a string.
1584
+ *
1585
+ * @see https://stackoverflow.com/questions/7616461/generate-a-hash-from-string-in-javascript-jquery
1586
+ */
1587
+ export function genHashCode(str: string): number {
1588
+ let hash = 0, i, chr;
1589
+ for (i = 0; i < str.length; i++) {
1590
+ chr = str.charCodeAt(i);
1591
+ hash = ((hash << 5) - hash) + chr;
1592
+ hash |= 0;
1593
+ }
1594
+ return hash;
1595
+ }