sculp-js 1.13.3-beta.0 → 1.13.6

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.
Files changed (119) hide show
  1. package/dist/cjs/_virtual/_commonjsHelpers.js +3 -3
  2. package/dist/cjs/array.js +36 -46
  3. package/dist/cjs/async.js +41 -43
  4. package/dist/cjs/base64.js +77 -66
  5. package/dist/cjs/clipboard.js +54 -53
  6. package/dist/cjs/cloneDeep.js +90 -91
  7. package/dist/cjs/cookie.js +27 -30
  8. package/dist/cjs/date.js +101 -98
  9. package/dist/cjs/dom.js +114 -121
  10. package/dist/cjs/download.js +77 -78
  11. package/dist/cjs/easing.js +29 -32
  12. package/dist/cjs/file.js +147 -152
  13. package/dist/cjs/func.js +64 -78
  14. package/dist/cjs/index.js +1 -3
  15. package/dist/cjs/math.js +32 -38
  16. package/dist/cjs/node_modules/bezier-easing/src/index.js +35 -23
  17. package/dist/cjs/number.js +58 -61
  18. package/dist/cjs/object.js +112 -126
  19. package/dist/cjs/path.js +39 -43
  20. package/dist/cjs/qs.js +34 -41
  21. package/dist/cjs/random.js +31 -34
  22. package/dist/cjs/string.js +62 -63
  23. package/dist/cjs/tooltip.js +66 -69
  24. package/dist/cjs/tree.js +265 -263
  25. package/dist/cjs/type.js +42 -46
  26. package/dist/cjs/unique.js +39 -42
  27. package/dist/cjs/url.js +47 -48
  28. package/dist/cjs/validator.js +38 -38
  29. package/dist/cjs/variable.js +27 -21
  30. package/dist/cjs/watermark.js +78 -67
  31. package/dist/esm/array.js +36 -46
  32. package/dist/esm/async.js +41 -43
  33. package/dist/esm/base64.js +77 -66
  34. package/dist/esm/clipboard.js +55 -54
  35. package/dist/esm/cloneDeep.js +90 -91
  36. package/dist/esm/cookie.js +27 -30
  37. package/dist/esm/date.js +101 -98
  38. package/dist/esm/dom.js +114 -121
  39. package/dist/esm/download.js +78 -79
  40. package/dist/esm/easing.js +29 -32
  41. package/dist/esm/file.js +147 -152
  42. package/dist/esm/func.js +64 -78
  43. package/dist/esm/index.js +96 -8
  44. package/dist/esm/math.js +32 -38
  45. package/dist/esm/number.js +58 -61
  46. package/dist/esm/object.js +124 -127
  47. package/dist/esm/path.js +39 -43
  48. package/dist/esm/qs.js +34 -41
  49. package/dist/esm/random.js +31 -34
  50. package/dist/esm/string.js +74 -64
  51. package/dist/esm/tooltip.js +66 -69
  52. package/dist/esm/tree.js +265 -263
  53. package/dist/esm/type.js +68 -47
  54. package/dist/esm/unique.js +39 -42
  55. package/dist/esm/url.js +47 -48
  56. package/dist/esm/validator.js +56 -39
  57. package/dist/esm/variable.js +27 -21
  58. package/dist/esm/watermark.js +79 -68
  59. package/dist/types/array.d.ts +0 -1
  60. package/dist/types/async.d.ts +0 -1
  61. package/dist/types/base64.d.ts +0 -1
  62. package/dist/types/clipboard.d.ts +0 -1
  63. package/dist/types/cloneDeep.d.ts +0 -1
  64. package/dist/types/cookie.d.ts +0 -1
  65. package/dist/types/date.d.ts +0 -1
  66. package/dist/types/dom.d.ts +0 -1
  67. package/dist/types/download.d.ts +0 -1
  68. package/dist/types/easing.d.ts +0 -1
  69. package/dist/types/file.d.ts +0 -1
  70. package/dist/types/func.d.ts +0 -1
  71. package/dist/types/index.d.ts +0 -1
  72. package/dist/types/math.d.ts +0 -1
  73. package/dist/types/number.d.ts +0 -1
  74. package/dist/types/object.d.ts +0 -1
  75. package/dist/types/path.d.ts +0 -1
  76. package/dist/types/qs.d.ts +0 -1
  77. package/dist/types/random.d.ts +0 -1
  78. package/dist/types/string.d.ts +0 -1
  79. package/dist/types/tooltip.d.ts +0 -1
  80. package/dist/types/tree.d.ts +2 -2
  81. package/dist/types/type.d.ts +0 -1
  82. package/dist/types/unique.d.ts +0 -1
  83. package/dist/types/url.d.ts +0 -1
  84. package/dist/types/validator.d.ts +0 -1
  85. package/dist/types/variable.d.ts +0 -1
  86. package/dist/types/watermark.d.ts +0 -1
  87. package/dist/umd/index.min.js +2 -2
  88. package/package.json +12 -6
  89. package/dist/types/array.d.ts.map +0 -1
  90. package/dist/types/async.d.ts.map +0 -1
  91. package/dist/types/base64.d.ts.map +0 -1
  92. package/dist/types/clipboard.d.ts.map +0 -1
  93. package/dist/types/cloneDeep.d.ts.map +0 -1
  94. package/dist/types/cookie.d.ts.map +0 -1
  95. package/dist/types/core-index.d.ts +0 -17
  96. package/dist/types/core-index.d.ts.map +0 -1
  97. package/dist/types/date.d.ts.map +0 -1
  98. package/dist/types/dom.d.ts.map +0 -1
  99. package/dist/types/download.d.ts.map +0 -1
  100. package/dist/types/easing.d.ts.map +0 -1
  101. package/dist/types/file.d.ts.map +0 -1
  102. package/dist/types/func.d.ts.map +0 -1
  103. package/dist/types/index.d.ts.map +0 -1
  104. package/dist/types/index.js +0 -3278
  105. package/dist/types/math.d.ts.map +0 -1
  106. package/dist/types/number.d.ts.map +0 -1
  107. package/dist/types/object.d.ts.map +0 -1
  108. package/dist/types/path.d.ts.map +0 -1
  109. package/dist/types/qs.d.ts.map +0 -1
  110. package/dist/types/random.d.ts.map +0 -1
  111. package/dist/types/string.d.ts.map +0 -1
  112. package/dist/types/tooltip.d.ts.map +0 -1
  113. package/dist/types/tree.d.ts.map +0 -1
  114. package/dist/types/type.d.ts.map +0 -1
  115. package/dist/types/unique.d.ts.map +0 -1
  116. package/dist/types/url.d.ts.map +0 -1
  117. package/dist/types/validator.d.ts.map +0 -1
  118. package/dist/types/variable.d.ts.map +0 -1
  119. package/dist/types/watermark.d.ts.map +0 -1
@@ -1,3278 +0,0 @@
1
- /**
2
- * 遍历数组,返回 false 中断遍历(支持continue和break操作)
3
- *
4
- * @param {ArrayLike<V>} array
5
- * @param {(val: V, idx: number) => any} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
6
- * @param {boolean} reverse 是否倒序
7
- * @returns {*}
8
- */
9
- function arrayEach(array, iterator, reverse = false) {
10
- if (reverse) {
11
- for (let idx = array.length - 1; idx >= 0; idx--) {
12
- const re = iterator(array[idx], idx, array);
13
- if (re === false)
14
- break;
15
- else if (re === true)
16
- continue;
17
- }
18
- }
19
- else {
20
- for (let idx = 0, len = array.length; idx < len; idx++) {
21
- const re = iterator(array[idx], idx, array);
22
- if (re === false)
23
- break;
24
- else if (re === true)
25
- continue;
26
- }
27
- }
28
- // @ts-ignore
29
- array = null;
30
- }
31
- /**
32
- * 异步遍历数组,返回 false 中断遍历
33
- * @param {ArrayLike<V>} array 数组
34
- * @param {(val: V, idx: number) => Promise<any>} iterator 支持Promise类型的回调函数
35
- * @param {boolean} reverse 是否反向遍历
36
- * @example
37
- * 使用范例如下:
38
- * const start = async () => {
39
- * await arrayEachAsync(result, async (item) => {
40
- * await request(item);
41
- * count++;
42
- * })
43
- * console.log('发送次数', count);
44
- * }
45
-
46
- * for await...of 使用范例如下
47
- * const loadImages = async (images) => {
48
- * for await (const item of images) {
49
- * await request(item);
50
- * count++;
51
- * }
52
- * }
53
- */
54
- async function arrayEachAsync(array, iterator, reverse = false) {
55
- if (reverse) {
56
- for (let idx = array.length - 1; idx >= 0; idx--) {
57
- if ((await iterator(array[idx], idx)) === false)
58
- break;
59
- }
60
- }
61
- else {
62
- for (let idx = 0, len = array.length; idx < len; idx++) {
63
- if ((await iterator(array[idx], idx)) === false)
64
- break;
65
- }
66
- }
67
- }
68
- /**
69
- * 插入到目标位置之前
70
- * @param {AnyArray} array
71
- * @param {number} start
72
- * @param {number} to
73
- * @returns {*}
74
- */
75
- function arrayInsertBefore(array, start, to) {
76
- if (start === to || start + 1 === to)
77
- return;
78
- const [source] = array.splice(start, 1);
79
- const insertIndex = to < start ? to : to - 1;
80
- array.splice(insertIndex, 0, source);
81
- }
82
- /**
83
- * 数组删除指定项目
84
- * @param {V[]} array
85
- * @param {(val: V, idx: number) => boolean} expect
86
- * @returns {V[]}
87
- */
88
- function arrayRemove(array, expect) {
89
- const indexes = [];
90
- // 这里重命名一下:是为了杜绝 jest 里的 expect 与之产生检查错误
91
- // eslint 的 jest 语法检查是遇到 expect 函数就以为是单元测试
92
- const _expect = expect;
93
- arrayEach(array, (val, idx) => {
94
- if (_expect(val, idx))
95
- indexes.push(idx);
96
- });
97
- indexes.forEach((val, idx) => {
98
- array.splice(val - idx, 1);
99
- });
100
- return array;
101
- }
102
-
103
- function getDefaultExportFromCjs (x) {
104
- return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
105
- }
106
-
107
- /**
108
- * https://github.com/gre/bezier-easing
109
- * BezierEasing - use bezier curve for transition easing function
110
- * by Gaëtan Renaudeau 2014 - 2015 – MIT License
111
- */
112
-
113
- // These values are established by empiricism with tests (tradeoff: performance VS precision)
114
- var NEWTON_ITERATIONS = 4;
115
- var NEWTON_MIN_SLOPE = 0.001;
116
- var SUBDIVISION_PRECISION = 0.0000001;
117
- var SUBDIVISION_MAX_ITERATIONS = 10;
118
-
119
- var kSplineTableSize = 11;
120
- var kSampleStepSize = 1.0 / (kSplineTableSize - 1.0);
121
-
122
- var float32ArraySupported = typeof Float32Array === 'function';
123
-
124
- function A (aA1, aA2) { return 1.0 - 3.0 * aA2 + 3.0 * aA1; }
125
- function B (aA1, aA2) { return 3.0 * aA2 - 6.0 * aA1; }
126
- function C (aA1) { return 3.0 * aA1; }
127
-
128
- // Returns x(t) given t, x1, and x2, or y(t) given t, y1, and y2.
129
- function calcBezier (aT, aA1, aA2) { return ((A(aA1, aA2) * aT + B(aA1, aA2)) * aT + C(aA1)) * aT; }
130
-
131
- // Returns dx/dt given t, x1, and x2, or dy/dt given t, y1, and y2.
132
- function getSlope (aT, aA1, aA2) { return 3.0 * A(aA1, aA2) * aT * aT + 2.0 * B(aA1, aA2) * aT + C(aA1); }
133
-
134
- function binarySubdivide (aX, aA, aB, mX1, mX2) {
135
- var currentX, currentT, i = 0;
136
- do {
137
- currentT = aA + (aB - aA) / 2.0;
138
- currentX = calcBezier(currentT, mX1, mX2) - aX;
139
- if (currentX > 0.0) {
140
- aB = currentT;
141
- } else {
142
- aA = currentT;
143
- }
144
- } while (Math.abs(currentX) > SUBDIVISION_PRECISION && ++i < SUBDIVISION_MAX_ITERATIONS);
145
- return currentT;
146
- }
147
-
148
- function newtonRaphsonIterate (aX, aGuessT, mX1, mX2) {
149
- for (var i = 0; i < NEWTON_ITERATIONS; ++i) {
150
- var currentSlope = getSlope(aGuessT, mX1, mX2);
151
- if (currentSlope === 0.0) {
152
- return aGuessT;
153
- }
154
- var currentX = calcBezier(aGuessT, mX1, mX2) - aX;
155
- aGuessT -= currentX / currentSlope;
156
- }
157
- return aGuessT;
158
- }
159
-
160
- function LinearEasing (x) {
161
- return x;
162
- }
163
-
164
- var src = function bezier (mX1, mY1, mX2, mY2) {
165
- if (!(0 <= mX1 && mX1 <= 1 && 0 <= mX2 && mX2 <= 1)) {
166
- throw new Error('bezier x values must be in [0, 1] range');
167
- }
168
-
169
- if (mX1 === mY1 && mX2 === mY2) {
170
- return LinearEasing;
171
- }
172
-
173
- // Precompute samples table
174
- var sampleValues = float32ArraySupported ? new Float32Array(kSplineTableSize) : new Array(kSplineTableSize);
175
- for (var i = 0; i < kSplineTableSize; ++i) {
176
- sampleValues[i] = calcBezier(i * kSampleStepSize, mX1, mX2);
177
- }
178
-
179
- function getTForX (aX) {
180
- var intervalStart = 0.0;
181
- var currentSample = 1;
182
- var lastSample = kSplineTableSize - 1;
183
-
184
- for (; currentSample !== lastSample && sampleValues[currentSample] <= aX; ++currentSample) {
185
- intervalStart += kSampleStepSize;
186
- }
187
- --currentSample;
188
-
189
- // Interpolate to provide an initial guess for t
190
- var dist = (aX - sampleValues[currentSample]) / (sampleValues[currentSample + 1] - sampleValues[currentSample]);
191
- var guessForT = intervalStart + dist * kSampleStepSize;
192
-
193
- var initialSlope = getSlope(guessForT, mX1, mX2);
194
- if (initialSlope >= NEWTON_MIN_SLOPE) {
195
- return newtonRaphsonIterate(aX, guessForT, mX1, mX2);
196
- } else if (initialSlope === 0.0) {
197
- return guessForT;
198
- } else {
199
- return binarySubdivide(aX, intervalStart, intervalStart + kSampleStepSize, mX1, mX2);
200
- }
201
- }
202
-
203
- return function BezierEasing (x) {
204
- // Because JavaScript number are imprecise, we should guarantee the extremes are right.
205
- if (x === 0) {
206
- return 0;
207
- }
208
- if (x === 1) {
209
- return 1;
210
- }
211
- return calcBezier(getTForX(x), mY1, mY2);
212
- };
213
- };
214
-
215
- var bezier = /*@__PURE__*/getDefaultExportFromCjs(src);
216
-
217
- // 常用类型定义
218
- const { toString, hasOwnProperty, propertyIsEnumerable } = Object.prototype;
219
- /**
220
- * 判断对象内是否有该静态属性
221
- * @param {object} obj
222
- * @param {string} key
223
- * @returns {boolean}
224
- */
225
- function objectHas(obj, key) {
226
- return hasOwnProperty.call(obj, key);
227
- }
228
- /**
229
- * 判断一个对象是否为类数组
230
- *
231
- * @param any
232
- * @returns {boolean}
233
- */
234
- function arrayLike(any) {
235
- if (isArray(any))
236
- return true;
237
- if (isString(any))
238
- return true;
239
- if (!isObject(any))
240
- return false;
241
- return objectHas(any, 'length');
242
- }
243
- /**
244
- * 判断任意值的数据类型,检查非对象时不如typeof、instanceof的性能高
245
- *
246
- * 当检查类对象时是不可靠的,对象可以通过定义 Symbol.toStringTag 属性来更改检查结果
247
- *
248
- * 详见:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/toString
249
- * @param {unknown} any
250
- * @returns
251
- */
252
- function typeIs(any) {
253
- return toString.call(any).slice(8, -1);
254
- }
255
- // 基本数据类型判断
256
- const isString = (any) => typeof any === 'string';
257
- const isBoolean = (any) => typeof any === 'boolean';
258
- const isSymbol = (any) => typeof any === 'symbol';
259
- const isBigInt = (any) => typeof any === 'bigint';
260
- const isNumber = (any) => typeof any === 'number' && !Number.isNaN(any);
261
- const isUndefined = (any) => typeof any === 'undefined';
262
- const isNull = (any) => any === null;
263
- const isPrimitive = (any) => any === null || typeof any !== 'object';
264
- function isNullOrUnDef(val) {
265
- return isUndefined(val) || isNull(val);
266
- }
267
- // 复合数据类型判断
268
- const isObject = (any) => typeIs(any) === 'Object';
269
- const isArray = (any) => Array.isArray(any);
270
- /**
271
- * 判断是否为函数
272
- * @param {unknown} any
273
- * @returns {boolean}
274
- */
275
- const isFunction = (any) => typeof any === 'function';
276
- // 对象类型判断
277
- const isNaN = (any) => Number.isNaN(any);
278
- const isDate = (any) => typeIs(any) === 'Date';
279
- const isError = (any) => typeIs(any) === 'Error';
280
- const isRegExp = (any) => typeIs(any) === 'RegExp';
281
- /**
282
- * 判断一个字符串是否为有效的 JSON, 若有效则返回有效的JSON对象,否则false
283
- * @param {string} str
284
- * @returns {Object | boolean}
285
- */
286
- function isJsonString(str) {
287
- try {
288
- const parsed = JSON.parse(str);
289
- return typeof parsed === 'object' && parsed !== null ? parsed : false;
290
- }
291
- catch (e) {
292
- return false;
293
- }
294
- }
295
- /**
296
- * Checks if `value` is an empty object, collection, map, or set.
297
- *
298
- * Objects are considered empty if they have no own enumerable string keyed
299
- * properties.
300
- *
301
- * Array-like values such as `arguments` objects, arrays, buffers, strings, or
302
- * jQuery-like collections are considered empty if they have a `length` of `0`.
303
- * Similarly, maps and sets are considered empty if they have a `size` of `0`.
304
- *
305
- * @param {*} value The value to check.
306
- * @returns {boolean} Returns `true` if `value` is empty, else `false`.
307
- * @example
308
- *
309
- * isEmpty(null);
310
- * // => true
311
- *
312
- * isEmpty(true);
313
- * // => true
314
- *
315
- * isEmpty(1);
316
- * // => true
317
- *
318
- * isEmpty([1, 2, 3]);
319
- * // => false
320
- *
321
- * isEmpty({ 'a': 1 });
322
- * // => false
323
- */
324
- function isEmpty(value) {
325
- if (isNullOrUnDef(value) || Number.isNaN(value)) {
326
- return true;
327
- }
328
- const tag = typeIs(value);
329
- if (arrayLike(value) || 'Arguments' === tag) {
330
- return !value.length;
331
- }
332
- if ('Set' === tag || 'Map' === tag) {
333
- return !value.size;
334
- }
335
- return !Object.keys(value).length;
336
- }
337
- /**
338
- * Checks if `value` is an NodeList object
339
- */
340
- function isNodeList(value) {
341
- return isUndefined(NodeList) ? false : NodeList.prototype.isPrototypeOf(value);
342
- }
343
-
344
- // @ref https://cubic-bezier.com/
345
- const easingDefines = {
346
- linear: [0, 0, 1, 1],
347
- ease: [0.25, 0.1, 0.25, 1],
348
- 'ease-in': [0.42, 0, 1, 1],
349
- 'ease-out': [0, 0, 0.58, 1],
350
- 'ease-in-out': [0.42, 0, 0.58, 1]
351
- };
352
- /**
353
- * 设置缓存定义
354
- * @param {string} name
355
- * @param {EasingDefine} define
356
- */
357
- function setEasing(name, define) {
358
- easingDefines[name] = define;
359
- }
360
- function getEasing(name) {
361
- if (name)
362
- return easingDefines[name];
363
- return easingDefines;
364
- }
365
- /**
366
- * 缓冲函数化,用于 js 计算缓冲进度
367
- * @param {EasingNameOrDefine} [name=linear]
368
- * @returns {EasingFunction}
369
- */
370
- function easingFunctional(name) {
371
- let fn;
372
- if (isArray(name)) {
373
- fn = bezier(...name);
374
- }
375
- else {
376
- const define = easingDefines[name];
377
- if (!define) {
378
- throw new Error(`${name} 缓冲函数未定义`);
379
- }
380
- fn = bezier(...define);
381
- }
382
- return (input) => fn(Math.max(0, Math.min(input, 1)));
383
- }
384
- /**
385
- * 缓冲字符化,用于 css 设置缓冲属性
386
- * @param {EasingNameOrDefine} name
387
- * @returns {string}
388
- */
389
- function easingStringify(name) {
390
- let bezierDefine;
391
- if (isArray(name)) {
392
- bezierDefine = name;
393
- }
394
- else {
395
- const define = easingDefines[name];
396
- if (!define) {
397
- throw new Error(`${name} 缓冲函数未定义`);
398
- }
399
- bezierDefine = define;
400
- }
401
- return `cubic-bezier(${bezierDefine.join(',')})`;
402
- }
403
-
404
- /**
405
- * 判断对象是否为纯对象
406
- * @param {object} obj
407
- * @returns {boolean}
408
- */
409
- const isPlainObject = (obj) => {
410
- if (!isObject(obj))
411
- return false;
412
- const proto = Object.getPrototypeOf(obj);
413
- // 对象无原型
414
- if (!proto)
415
- return true;
416
- // 是否对象直接实例
417
- return proto === Object.prototype;
418
- };
419
- /**
420
- * 遍历对象,返回 false 中断遍历
421
- * @param {O} obj
422
- * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
423
- */
424
- function objectEach(obj, iterator) {
425
- for (const key in obj) {
426
- if (!objectHas(obj, key))
427
- continue;
428
- if (iterator(obj[key], key) === false)
429
- break;
430
- }
431
- // @ts-ignore
432
- obj = null;
433
- }
434
- /**
435
- * 异步遍历对象,返回 false 中断遍历
436
- * @param {O} obj
437
- * @param {(val: O[keyof O], key: keyof O) => (boolean | void)} iterator
438
- */
439
- async function objectEachAsync(obj, iterator) {
440
- for (const key in obj) {
441
- if (!objectHas(obj, key))
442
- continue;
443
- if ((await iterator(obj[key], key)) === false)
444
- break;
445
- }
446
- }
447
- /**
448
- * 对象映射
449
- * @param {O} obj
450
- * @param {(val: O[keyof O], key: Extract<keyof O, string>) => any} iterator
451
- * @returns {Record<Extract<keyof O, string>, T>}
452
- */
453
- function objectMap(obj, iterator) {
454
- const obj2 = {};
455
- for (const key in obj) {
456
- if (!objectHas(obj, key))
457
- continue;
458
- obj2[key] = iterator(obj[key], key);
459
- }
460
- // @ts-ignore
461
- obj = null;
462
- return obj2;
463
- }
464
- /**
465
- * 对象提取
466
- * @param {O} obj
467
- * @param {K} keys
468
- * @returns {Pick<O, ArrayElements<K>>}
469
- */
470
- function objectPick(obj, keys) {
471
- const obj2 = {};
472
- objectEach(obj, (v, k) => {
473
- if (keys.includes(k)) {
474
- // @ts-ignore
475
- obj2[k] = v;
476
- }
477
- });
478
- // @ts-ignore
479
- obj = null;
480
- return obj2;
481
- }
482
- /**
483
- * 对象去除
484
- * @param {O} obj
485
- * @param {K} keys
486
- * @returns {Pick<O, ArrayElements<K>>}
487
- */
488
- function objectOmit(obj, keys) {
489
- const obj2 = {};
490
- objectEach(obj, (v, k) => {
491
- if (!keys.includes(k)) {
492
- // @ts-ignore
493
- obj2[k] = v;
494
- }
495
- });
496
- // @ts-ignore
497
- obj = null;
498
- return obj2;
499
- }
500
- const merge = (map, source, target) => {
501
- if (isUndefined(target))
502
- return source;
503
- const sourceType = typeIs(source);
504
- const targetType = typeIs(target);
505
- if (sourceType !== targetType) {
506
- if (isArray(target))
507
- return merge(map, [], target);
508
- if (isObject(target))
509
- return merge(map, {}, target);
510
- return target;
511
- }
512
- // 朴素对象
513
- if (isPlainObject(target)) {
514
- const exist = map.get(target);
515
- if (exist)
516
- return exist;
517
- map.set(target, source);
518
- objectEach(target, (val, key) => {
519
- source[key] = merge(map, source[key], val);
520
- });
521
- return source;
522
- }
523
- // 数组
524
- else if (isArray(target)) {
525
- const exist = map.get(target);
526
- if (exist)
527
- return exist;
528
- map.set(target, source);
529
- target.forEach((val, index) => {
530
- source[index] = merge(map, source[index], val);
531
- });
532
- return source;
533
- }
534
- return target;
535
- };
536
- /**
537
- * 对象合并,返回原始对象
538
- * @param {ObjectAssignItem} source
539
- * @param {ObjectAssignItem | undefined} targets
540
- * @returns {R}
541
- */
542
- function objectAssign(source, ...targets) {
543
- const map = new Map();
544
- for (let i = 0, len = targets.length; i < len; i++) {
545
- const target = targets[i];
546
- // @ts-ignore
547
- source = merge(map, source, target);
548
- }
549
- map.clear();
550
- return source;
551
- }
552
- /**
553
- * 对象填充
554
- * @param {Partial<R>} source
555
- * @param {Partial<R>} target
556
- * @param {(s: Partial<R>, t: Partial<R>, key: keyof R) => boolean} fillable
557
- * @returns {R}
558
- */
559
- function objectFill(source, target, fillable) {
560
- const _fillable = fillable || ((source, target, key) => source[key] === undefined);
561
- objectEach(target, (val, key) => {
562
- if (_fillable(source, target, key)) {
563
- source[key] = val;
564
- }
565
- });
566
- return source;
567
- }
568
- /**
569
- * 获取对象/数组指定层级下的属性值(现在可用ES6+的可选链?.来替代)
570
- * @param {AnyObject} obj
571
- * @param {string} path
572
- * @param {boolean} strict
573
- * @returns
574
- */
575
- function objectGet(obj, path, strict = false) {
576
- path = path.replace(/\[(\w+)\]/g, '.$1');
577
- path = path.replace(/^\./, '');
578
- const keyArr = path.split('.');
579
- let tempObj = obj;
580
- let i = 0;
581
- for (let len = keyArr.length; i < len - 1; ++i) {
582
- const key = keyArr[i];
583
- if (isNumber(Number(key)) && Array.isArray(tempObj)) {
584
- tempObj = tempObj[key];
585
- }
586
- else if (isObject(tempObj) && objectHas(tempObj, key)) {
587
- tempObj = tempObj[key];
588
- }
589
- else {
590
- tempObj = undefined;
591
- if (strict) {
592
- throw new Error('[Object] objectGet path 路径不正确');
593
- }
594
- break;
595
- }
596
- }
597
- return {
598
- p: tempObj,
599
- k: tempObj ? keyArr[i] : undefined,
600
- v: tempObj ? tempObj[keyArr[i]] : undefined
601
- };
602
- }
603
-
604
- /**
605
- * 将字符串转换为驼峰格式
606
- * @param {string} string
607
- * @param {boolean} [bigger] 是否大写第一个字母
608
- * @returns {string}
609
- */
610
- function stringCamelCase(string, bigger) {
611
- let string2 = string;
612
- if (bigger) {
613
- string2 = string.replace(/^./, origin => origin.toUpperCase());
614
- }
615
- const HUMP_RE = /[\s_-](.)/g;
616
- return string2.replace(HUMP_RE, (orign, char) => char.toUpperCase());
617
- }
618
- /**
619
- * 将字符串转换为连字格式
620
- * @param {string} string
621
- * @param {string} [separator] 分隔符,默认是"-"(短横线)
622
- * @returns {string}
623
- */
624
- function stringKebabCase(string, separator = '-') {
625
- const string2 = string.replace(/^./, origin => origin.toLowerCase());
626
- return string2.replace(/[A-Z]/g, origin => `${separator}${origin.toLowerCase()}`);
627
- }
628
- const STRING_ARABIC_NUMERALS = '0123456789';
629
- const STRING_LOWERCASE_ALPHA = 'abcdefghijklmnopqrstuvwxyz';
630
- const STRING_UPPERCASE_ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
631
- const placeholderRE = /%[%sdo]/g;
632
- /**
633
- * 字符串格式化
634
- * @example
635
- * ```js
636
- * stringFormat("My name is %s.", "zhangsan")
637
- * // => "My name is zhangsan."
638
- * ```
639
- * @param {string} string 字符串模板,使用 %s 表示字符串,%d 表示数值,%o 表示对象,%% 表示百分号,参考 console.log
640
- * @param args
641
- * @returns {string}
642
- */
643
- function stringFormat(string, ...args) {
644
- let index = 0;
645
- const result = string.replace(placeholderRE, (origin) => {
646
- const arg = args[index++];
647
- switch (origin) {
648
- case '%%':
649
- index--;
650
- return '%';
651
- default:
652
- case '%s':
653
- return String(arg);
654
- case '%d':
655
- return String(Number(arg));
656
- case '%o':
657
- return JSON.stringify(arg);
658
- }
659
- });
660
- return [result, ...args.splice(index).map(String)].join(' ');
661
- }
662
- const ev = (expression, data) => {
663
- try {
664
- // eslint-disable-next-line @typescript-eslint/no-implied-eval,@typescript-eslint/no-unsafe-return
665
- return new Function('with(arguments[0]){' +
666
- /****/ `if(arguments[0].${expression} === undefined)throw "";` +
667
- /****/
668
- /****/ `return String(arguments[0].${expression})` +
669
- '}')(data);
670
- }
671
- catch (err) {
672
- throw new SyntaxError(`无法执行表达式:${expression}`);
673
- }
674
- };
675
- const templateRE = /\${(.*?)}/g;
676
- /**
677
- * 字符串赋值
678
- * @example
679
- * ```js
680
- * stringAssign('My name is ${user}.', { user: 'zhangsan' });
681
- * // => "My name is zhangsan."
682
- * ```
683
- * @param {string} template
684
- * @param {AnyObject} data
685
- * @returns {string}
686
- */
687
- const stringAssign = (template, data) => {
688
- return template.replace(templateRE, (origin, expression) => ev(expression, data));
689
- };
690
- /**
691
- * 字符串编码 HTML
692
- * @example
693
- * ```js
694
- * stringEscapeHtml('<b>You & Me speak "xixi"</b>')
695
- * // => "&lt;b&gt;You &amp; Me speak &quot;xixi&quot;&lt;/b&gt;"
696
- * ```
697
- * @param {string} html
698
- * @returns {string}
699
- */
700
- const stringEscapeHtml = (html) => {
701
- const htmlCharRE = /[&<>"]/g;
702
- const htmlCharReplacements = {
703
- '&': '&amp;',
704
- '<': '&lt;',
705
- '>': '&gt;',
706
- '"': '&quot;'
707
- };
708
- // eslint-disable-next-line @typescript-eslint/ban-ts-comment
709
- // @ts-ignore
710
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
711
- return html.replace(htmlCharRE, $0 => htmlCharReplacements[$0]);
712
- };
713
- /**
714
- * 字符串填充
715
- * @param {number} length
716
- * @param {string} value
717
- * @returns {string}
718
- */
719
- const stringFill = (length, value = ' ') => new Array(length).fill(value).join('');
720
- /**
721
- * 解析URL查询参数
722
- * @param {string} searchStr
723
- * @returns {Record<string, string | string[]>}
724
- */
725
- function parseQueryParams(searchStr = location.search) {
726
- const queryObj = {};
727
- Array.from(searchStr.matchAll(/[&?]?([^=&]+)=?([^=&]*)/g)).forEach((item, i) => {
728
- if (!queryObj[item[1]]) {
729
- queryObj[item[1]] = item[2];
730
- }
731
- else if (typeof queryObj[item[1]] === 'string') {
732
- queryObj[item[1]] = [queryObj[item[1]], item[2]];
733
- }
734
- else {
735
- queryObj[item[1]].push(item[2]);
736
- }
737
- });
738
- return queryObj;
739
- }
740
-
741
- /**
742
- * 判断元素是否包含某个样式名
743
- * @param {HTMLElement} el
744
- * @param {string} className
745
- * @returns {boolean}
746
- */
747
- function hasClass(el, className) {
748
- if (className.indexOf(' ') !== -1)
749
- throw new Error('className should not contain space.');
750
- return el.classList.contains(className);
751
- }
752
- const eachClassName = (classNames, func) => {
753
- const classNameList = classNames.split(/\s+/g);
754
- classNameList.forEach(func);
755
- };
756
- /**
757
- * 给元素增加样式名
758
- * @param {HTMLElement} el
759
- * @param {string} classNames
760
- */
761
- function addClass(el, classNames) {
762
- eachClassName(classNames, className => el.classList.add(className));
763
- }
764
- /**
765
- * 给元素移除样式名
766
- * @param {HTMLElement} el
767
- * @param {string} classNames
768
- */
769
- function removeClass(el, classNames) {
770
- eachClassName(classNames, className => el.classList.remove(className));
771
- }
772
- /**
773
- * 设置元素样式
774
- * @param {HTMLElement} el
775
- * @param {string | Style} key
776
- * @param {string} val
777
- */
778
- const setStyle = (el, key, val) => {
779
- if (isObject(key)) {
780
- objectEach(key, (val1, key1) => {
781
- setStyle(el, key1, val1);
782
- });
783
- }
784
- else {
785
- el.style.setProperty(stringKebabCase(key), val);
786
- }
787
- };
788
- /**
789
- * 获取元素样式
790
- * @param {HTMLElement} el 元素
791
- * @param {string} key
792
- * @returns {string}
793
- */
794
- function getStyle(el, key) {
795
- return getComputedStyle(el).getPropertyValue(key);
796
- }
797
- function smoothScroll(options) {
798
- return new Promise(resolve => {
799
- const defaults = {
800
- el: document,
801
- to: 0,
802
- duration: 567,
803
- easing: 'ease'
804
- };
805
- const { el, to, duration, easing } = objectAssign(defaults, options);
806
- const htmlEl = document.documentElement;
807
- const bodyEl = document.body;
808
- const globalMode = el === window || el === document || el === htmlEl || el === bodyEl;
809
- const els = globalMode ? [htmlEl, bodyEl] : [el];
810
- const query = () => {
811
- let value = 0;
812
- arrayEach(els, el => {
813
- if ('scrollTop' in el) {
814
- value = el.scrollTop;
815
- return false;
816
- }
817
- });
818
- return value;
819
- };
820
- const update = (val) => {
821
- els.forEach(el => {
822
- if ('scrollTop' in el) {
823
- el.scrollTop = val;
824
- }
825
- });
826
- };
827
- let startTime;
828
- const startValue = query();
829
- const length = to - startValue;
830
- const easingFn = easingFunctional(easing);
831
- const render = () => {
832
- const now = performance.now();
833
- const passingTime = startTime ? now - startTime : 0;
834
- const t = passingTime / duration;
835
- const p = easingFn(t);
836
- if (!startTime)
837
- startTime = now;
838
- update(startValue + length * p);
839
- if (t >= 1)
840
- resolve();
841
- else
842
- requestAnimationFrame(render);
843
- };
844
- render();
845
- });
846
- }
847
- /**
848
- * 获取元素样式属性的计算值
849
- * @param {HTMLElement} el
850
- * @param {string} property
851
- * @param {boolean} reNumber
852
- * @returns {string|number}
853
- */
854
- function getComputedCssVal(el, property, reNumber = true) {
855
- const originVal = getComputedStyle(el).getPropertyValue(property) ?? '';
856
- return reNumber ? Number(originVal.replace(/([0-9]*)(.*)/g, '$1')) : originVal;
857
- }
858
- /**
859
- * 字符串的像素宽度
860
- * @param {string} str 目标字符串
861
- * @param {number} fontSize 字符串字体大小
862
- * @param {boolean} isRemove 计算后是否移除创建的dom元素
863
- * @returns {*}
864
- */
865
- function getStrWidthPx(str, fontSize = 14, isRemove = true) {
866
- let strWidth = 0;
867
- console.assert(isString(str), `${str} 不是有效的字符串`);
868
- if (isString(str) && str.length > 0) {
869
- const id = 'getStrWidth1494304949567';
870
- let getEle = document.querySelector(`#${id}`);
871
- if (!getEle) {
872
- const _ele = document.createElement('span');
873
- _ele.id = id;
874
- _ele.style.fontSize = fontSize + 'px';
875
- _ele.style.whiteSpace = 'nowrap';
876
- _ele.style.visibility = 'hidden';
877
- _ele.style.position = 'absolute';
878
- _ele.style.top = '-9999px';
879
- _ele.style.left = '-9999px';
880
- _ele.textContent = str;
881
- document.body.appendChild(_ele);
882
- getEle = _ele;
883
- }
884
- getEle.textContent = str;
885
- strWidth = getEle.offsetWidth;
886
- if (isRemove) {
887
- getEle.remove();
888
- }
889
- }
890
- return strWidth;
891
- }
892
- /**
893
- * Programmatically select the text of a HTML element
894
- *
895
- * @param {HTMLElement} element The element whose text you wish to select
896
- * @returns
897
- */
898
- function select(element) {
899
- let selectedText;
900
- if (element.nodeName === 'SELECT') {
901
- element.focus();
902
- // @ts-ignore
903
- selectedText = element.value;
904
- }
905
- else if (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA') {
906
- const isReadOnly = element.hasAttribute('readonly');
907
- if (!isReadOnly) {
908
- element.setAttribute('readonly', '');
909
- }
910
- // @ts-ignore
911
- element.select();
912
- // @ts-ignore
913
- element.setSelectionRange(0, element.value.length);
914
- if (!isReadOnly) {
915
- element.removeAttribute('readonly');
916
- }
917
- // @ts-ignore
918
- selectedText = element.value;
919
- }
920
- else {
921
- if (element.hasAttribute('contenteditable')) {
922
- element.focus();
923
- }
924
- const selection = window.getSelection();
925
- const range = document.createRange();
926
- range.selectNodeContents(element);
927
- selection.removeAllRanges();
928
- selection.addRange(range);
929
- selectedText = selection.toString();
930
- }
931
- return selectedText;
932
- }
933
-
934
- /**
935
- * 复制文本,优先使用navigator.clipboard,仅在安全上下文(HTTPS/localhost)下生效,若不支持则回退使用execCommand方式
936
- * @param {string} text
937
- * @param {CopyTextOptions} options 可选参数:成功回调successCallback、失败回调failCallback、容器元素container
938
- * (默认document.body, 当不支持clipboard时必须传复制按钮元素,包裹模拟选择操作的临时元素,
939
- * 解决脱离文档流的元素无法复制的问题,如Modal内复制操作)
940
- */
941
- function copyText(text, options) {
942
- const { successCallback = void 0, failCallback = void 0 } = isNullOrUnDef(options) ? {} : options;
943
- if (navigator.clipboard) {
944
- navigator.clipboard
945
- .writeText(text)
946
- .then(() => {
947
- if (isFunction(successCallback)) {
948
- successCallback();
949
- }
950
- })
951
- .catch(err => {
952
- fallbackCopyText(text, options);
953
- });
954
- }
955
- else {
956
- // 使用旧版execCommand方法
957
- fallbackCopyText(text, options);
958
- }
959
- }
960
- /**
961
- * 使用execCommand方式复制文本
962
- * @param text
963
- * @param options
964
- */
965
- function fallbackCopyText(text, options) {
966
- const { successCallback = void 0, failCallback = void 0, container = document.body } = isNullOrUnDef(options) ? {} : options;
967
- let textEl = createFakeElement(text);
968
- container.appendChild(textEl);
969
- select(textEl);
970
- try {
971
- const res = document.execCommand('copy');
972
- if (res && isFunction(successCallback)) {
973
- successCallback();
974
- }
975
- }
976
- catch (err) {
977
- if (isFunction(failCallback)) {
978
- failCallback(err);
979
- }
980
- }
981
- finally {
982
- container.removeChild(textEl);
983
- // @ts-ignore
984
- textEl = null;
985
- window.getSelection()?.removeAllRanges(); // 清除选区
986
- }
987
- }
988
- /**
989
- * Creates a fake textarea element with a value.
990
- * @param {String} value
991
- * @return {HTMLElement}
992
- */
993
- function createFakeElement(value) {
994
- const isRTL = document.documentElement.getAttribute('dir') === 'rtl';
995
- const fakeElement = document.createElement('textarea');
996
- // Prevent zooming on iOS
997
- fakeElement.style.fontSize = '12pt';
998
- // Reset box model
999
- fakeElement.style.border = '0';
1000
- fakeElement.style.padding = '0';
1001
- fakeElement.style.margin = '0';
1002
- // Move element out of screen horizontally
1003
- fakeElement.style.position = 'absolute';
1004
- fakeElement.style[isRTL ? 'right' : 'left'] = '-9999px';
1005
- // Move element to the same position vertically
1006
- const yPosition = window.pageYOffset || document.documentElement.scrollTop;
1007
- fakeElement.style.top = `${yPosition}px`;
1008
- fakeElement.setAttribute('readonly', '');
1009
- fakeElement.value = value;
1010
- return fakeElement;
1011
- }
1012
-
1013
- /**
1014
- * 获取cookie
1015
- * @param {string} name
1016
- * @returns {string}
1017
- */
1018
- function cookieGet(name) {
1019
- const { cookie } = document;
1020
- if (!cookie)
1021
- return '';
1022
- const result = cookie.split(';');
1023
- for (let i = 0; i < result.length; i++) {
1024
- const item = result[i];
1025
- const [key, val = ''] = item.split('=');
1026
- if (key === name)
1027
- return decodeURIComponent(val);
1028
- }
1029
- return '';
1030
- }
1031
- /**
1032
- * 设置 cookie
1033
- * @param {string} name
1034
- * @param {string} value
1035
- * @param {number | Date} [maxAge]
1036
- */
1037
- function cookieSet(name, value, maxAge) {
1038
- const metas = [];
1039
- const EXPIRES = 'expires';
1040
- metas.push([name, encodeURIComponent(value)]);
1041
- if (isNumber(maxAge)) {
1042
- const d = new Date();
1043
- d.setTime(d.getTime() + maxAge);
1044
- metas.push([EXPIRES, d.toUTCString()]);
1045
- }
1046
- else if (isDate(maxAge)) {
1047
- metas.push([EXPIRES, maxAge.toUTCString()]);
1048
- }
1049
- metas.push(['path', '/']);
1050
- document.cookie = metas
1051
- .map(item => {
1052
- const [key, val] = item;
1053
- return `${key}=${val}`;
1054
- })
1055
- .join(';');
1056
- }
1057
- /**
1058
- * 删除单个 cookie
1059
- * @param name cookie 名称
1060
- */
1061
- const cookieDel = (name) => cookieSet(name, '', -1);
1062
-
1063
- const isValidDate = (any) => isDate(any) && !isNaN(any.getTime());
1064
- /* istanbul ignore next */
1065
- const guessDateSeparator = (value) => {
1066
- if (!isString(value))
1067
- return;
1068
- const value2 = value.replace(/-/g, '/');
1069
- return new Date(value2);
1070
- };
1071
- /* istanbul ignore next */
1072
- const guessDateTimezone = (value) => {
1073
- if (!isString(value))
1074
- return;
1075
- const re = /([+-])(\d\d)(\d\d)$/;
1076
- const matches = re.exec(value);
1077
- if (!matches)
1078
- return;
1079
- const value2 = value.replace(re, 'Z');
1080
- const d = new Date(value2);
1081
- if (!isValidDate(d))
1082
- return;
1083
- const [, flag, hours, minutes] = matches;
1084
- const hours2 = parseInt(hours, 10);
1085
- const minutes2 = parseInt(minutes, 10);
1086
- const offset = (a, b) => (flag === '+' ? a - b : a + b);
1087
- d.setHours(offset(d.getHours(), hours2));
1088
- d.setMinutes(offset(d.getMinutes(), minutes2));
1089
- return d;
1090
- };
1091
- /**
1092
- * 解析为Date对象
1093
- * @param {DateValue} value - 可以是数值、字符串或 Date 对象
1094
- * @returns {Date} - 转换后的目标Date
1095
- */
1096
- function dateParse(value) {
1097
- const d1 = new Date(value);
1098
- if (isValidDate(d1))
1099
- return d1;
1100
- // safari 浏览器的日期解析有问题
1101
- // new Date('2020-06-26 18:06:15') 返回值是一个非法日期对象
1102
- /* istanbul ignore next */
1103
- const d2 = guessDateSeparator(value);
1104
- /* istanbul ignore next */
1105
- if (isValidDate(d2))
1106
- return d2;
1107
- // safari 浏览器的日期解析有问题
1108
- // new Date('2020-06-26T18:06:15.000+0800') 返回值是一个非法日期对象
1109
- /* istanbul ignore next */
1110
- const d3 = guessDateTimezone(value);
1111
- /* istanbul ignore next */
1112
- if (isValidDate(d3))
1113
- return d3;
1114
- throw new SyntaxError(`${value.toString()} 不是一个合法的日期描述`);
1115
- }
1116
- /**
1117
- * 格式化为日期对象(带自定义格式化模板)
1118
- * @param {DateValue} value 可以是数值、字符串或 Date 对象
1119
- * @param {string} [format] 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
1120
- * - YYYY:年
1121
- * - yyyy: 年
1122
- * - MM:月
1123
- * - DD:日
1124
- * - dd: 日
1125
- * - HH:时(24 小时制)
1126
- * - hh:时(12 小时制)
1127
- * - mm:分
1128
- * - ss:秒
1129
- * - SSS:毫秒
1130
- * @returns {string}
1131
- */
1132
- // export const dateStringify = (value: DateValue, format = 'YYYY-MM-DD HH:mm:ss'): string => {
1133
- // const date = dateParse(value);
1134
- // let fmt = format;
1135
- // let ret;
1136
- // const opt: DateObj = {
1137
- // 'Y+': `${date.getFullYear()}`, // 年
1138
- // 'y+': `${date.getFullYear()}`, // 年
1139
- // 'M+': `${date.getMonth() + 1}`, // 月
1140
- // 'D+': `${date.getDate()}`, // 日
1141
- // 'd+': `${date.getDate()}`, // 日
1142
- // 'H+': `${date.getHours()}`, // 时
1143
- // 'm+': `${date.getMinutes()}`, // 分
1144
- // 's+': `${date.getSeconds()}`, // 秒
1145
- // 'S+': `${date.getMilliseconds()}` // 豪秒
1146
- // };
1147
- // for (const k in opt) {
1148
- // ret = new RegExp(`(${k})`).exec(fmt);
1149
- // if (ret) {
1150
- // fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
1151
- // }
1152
- // }
1153
- // return fmt;
1154
- // };
1155
- /**
1156
- * 将日期转换为一天的开始时间,即0点0分0秒0毫秒
1157
- * @param {DateValue} value
1158
- * @returns {Date}
1159
- */
1160
- function dateToStart(value) {
1161
- const d = dateParse(value);
1162
- return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
1163
- }
1164
- /**
1165
- * 将日期转换为一天的结束时间,即23点59分59秒999毫秒
1166
- * @param {DateValue} value
1167
- * @returns {Date}
1168
- */
1169
- function dateToEnd(value) {
1170
- const d = dateToStart(value);
1171
- d.setDate(d.getDate() + 1);
1172
- return dateParse(d.getTime() - 1);
1173
- }
1174
- /**
1175
- * 格式化为日期对象(带自定义格式化模板)
1176
- * @param {Date} value - 可以是数值、字符串或 Date 对象
1177
- * @param {string} [format] - 模板,默认是 YYYY-MM-DD HH:mm:ss,模板字符:
1178
- * - YYYY:年
1179
- * - yyyy: 年
1180
- * - MM:月
1181
- * - DD:日
1182
- * - dd: 日
1183
- * - HH:时(24 小时制)
1184
- * - mm:分
1185
- * - ss:秒
1186
- * - SSS:毫秒
1187
- * - ww: 周
1188
- * @returns {string} 格式化后的日期字符串
1189
- */
1190
- function formatDate(value, format = 'YYYY-MM-DD HH:mm:ss') {
1191
- const date = dateParse(value);
1192
- let fmt = format;
1193
- let ret;
1194
- const opt = {
1195
- 'Y+': `${date.getFullYear()}`,
1196
- 'y+': `${date.getFullYear()}`,
1197
- 'M+': `${date.getMonth() + 1}`,
1198
- 'D+': `${date.getDate()}`,
1199
- 'd+': `${date.getDate()}`,
1200
- 'H+': `${date.getHours()}`,
1201
- 'm+': `${date.getMinutes()}`,
1202
- 's+': `${date.getSeconds()}`,
1203
- 'S+': `${date.getMilliseconds()}`,
1204
- 'w+': ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()] // 周
1205
- // 有其他格式化字符需求可以继续添加,必须转化成字符串
1206
- };
1207
- for (const k in opt) {
1208
- ret = new RegExp('(' + k + ')').exec(fmt);
1209
- if (ret) {
1210
- fmt = fmt.replace(ret[1], ret[1].length === 1 ? opt[k] : opt[k].padStart(ret[1].length, '0'));
1211
- }
1212
- }
1213
- return fmt;
1214
- }
1215
- /**
1216
- * 计算向前或向后N天的具体日期
1217
- * @param {DateValue} originDate - 参考日期
1218
- * @param {number} n - 正数:向后推算;负数:向前推算
1219
- * @param {string} sep - 日期格式的分隔符
1220
- * @returns {string} 计算后的目标日期
1221
- */
1222
- function calculateDate(originDate, n, sep = '-') {
1223
- //originDate 为字符串日期 如:'2019-01-01' n为你要传入的参数,当前为0,前一天为-1,后一天为1
1224
- const date = new Date(originDate); //这边给定一个特定时间
1225
- const newDate = new Date(date.getFullYear(), date.getMonth(), date.getDate());
1226
- const millisecondGap = newDate.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
1227
- const targetDate = new Date(millisecondGap);
1228
- const finalNewDate = targetDate.getFullYear() +
1229
- sep +
1230
- String(targetDate.getMonth() + 1).padStart(2, '0') +
1231
- '-' +
1232
- String(targetDate.getDate()).padStart(2, '0');
1233
- return finalNewDate;
1234
- }
1235
- /**
1236
- * 计算向前或向后N天的具体日期时间
1237
- * @param {DateValue} originDateTime - 参考日期时间
1238
- * @param {number} n - 正数:向后推算;负数:向前推算
1239
- * @param {string} dateSep - 日期分隔符
1240
- * @param {string} timeSep - 时间分隔符
1241
- * @returns {string} 转换后的目标日期时间
1242
- */
1243
- function calculateDateTime(originDateTime, n, dateSep = '-', timeSep = ':') {
1244
- const date = new Date(originDateTime);
1245
- const separator1 = dateSep;
1246
- const separator2 = timeSep;
1247
- const dateTime = new Date(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds());
1248
- const millisecondGap = dateTime.getTime() + 1000 * 60 * 60 * 24 * parseInt(String(n)); //计算前几天用减,计算后几天用加,最后一个就是多少天的数量
1249
- const targetDateTime = new Date(millisecondGap);
1250
- return (targetDateTime.getFullYear() +
1251
- separator1 +
1252
- String(targetDateTime.getMonth() + 1).padStart(2, '0') +
1253
- separator1 +
1254
- String(targetDateTime.getDate()).padStart(2, '0') +
1255
- ' ' +
1256
- String(targetDateTime.getHours()).padStart(2, '0') +
1257
- separator2 +
1258
- String(targetDateTime.getMinutes()).padStart(2, '0') +
1259
- separator2 +
1260
- String(targetDateTime.getSeconds()).padStart(2, '0'));
1261
- }
1262
-
1263
- /**
1264
- * 标准化路径
1265
- * @param {string} path
1266
- * @returns {string}
1267
- */
1268
- const pathNormalize = (path) => {
1269
- const slices = path
1270
- .replace(/\\/g, '/')
1271
- .replace(/\/{2,}/g, '/')
1272
- .replace(/\.{3,}/g, '..')
1273
- .replace(/\/\.\//g, '/')
1274
- .split('/')
1275
- .map(point => point.trim());
1276
- const isCurrentSlice = (slice) => slice === '.';
1277
- const isParentSlice = (slice) => slice === '..';
1278
- const points = [];
1279
- let inPoints = false;
1280
- const push = (point) => {
1281
- points.push(point);
1282
- };
1283
- const back = () => {
1284
- if (points.length === 0)
1285
- return;
1286
- const lastSlice = points[points.length - 1];
1287
- if (isParentSlice(lastSlice)) {
1288
- points.push('..');
1289
- }
1290
- else {
1291
- points.pop();
1292
- }
1293
- };
1294
- slices.forEach(slice => {
1295
- const isCurrent = isCurrentSlice(slice);
1296
- const isParent = isParentSlice(slice);
1297
- // 未进入实际路径
1298
- if (!inPoints) {
1299
- push(slice);
1300
- inPoints = !isCurrent && !isParent;
1301
- return;
1302
- }
1303
- if (isCurrent)
1304
- return;
1305
- if (isParent)
1306
- return back();
1307
- push(slice);
1308
- });
1309
- return points.join('/');
1310
- };
1311
- /**
1312
- * 路径合并
1313
- * @param {string} from
1314
- * @param {string} to
1315
- * @returns {string}
1316
- */
1317
- const pathJoin = (from, ...to) => pathNormalize([from, ...to].join('/'));
1318
-
1319
- /**
1320
- * 解析查询参数,内部使用的是浏览器内置的 URLSearchParams 进行处理
1321
- * @param {string} queryString
1322
- * @returns {Params}
1323
- */
1324
- function qsParse(queryString) {
1325
- const params = new URLSearchParams(queryString);
1326
- const result = {};
1327
- for (const [key, val] of params.entries()) {
1328
- if (isUndefined(result[key])) {
1329
- result[key] = val;
1330
- continue;
1331
- }
1332
- if (isArray(result[key])) {
1333
- continue;
1334
- }
1335
- result[key] = params.getAll(key);
1336
- }
1337
- return result;
1338
- }
1339
- const defaultReplacer = (val) => {
1340
- if (isString(val))
1341
- return val;
1342
- if (isNumber(val))
1343
- return String(val);
1344
- if (isBoolean(val))
1345
- return val ? 'true' : 'false';
1346
- if (isDate(val))
1347
- return val.toISOString();
1348
- return null;
1349
- };
1350
- /**
1351
- * 字符化查询对象,内部使用的是浏览器内置的 URLSearchParams 进行处理
1352
- * @param {LooseParams} query
1353
- * @param {Replacer} replacer
1354
- * @returns {string}
1355
- */
1356
- function qsStringify(query, replacer = defaultReplacer) {
1357
- const params = new URLSearchParams();
1358
- objectEach(query, (val, key) => {
1359
- if (isArray(val)) {
1360
- val.forEach(i => {
1361
- const replaced = replacer(i);
1362
- if (replaced === null)
1363
- return;
1364
- params.append(key.toString(), replaced);
1365
- });
1366
- }
1367
- else {
1368
- const replaced = replacer(val);
1369
- if (replaced === null)
1370
- return;
1371
- params.set(key.toString(), replaced);
1372
- }
1373
- });
1374
- return params.toString();
1375
- }
1376
-
1377
- /**
1378
- * url 解析
1379
- * @param {string} url
1380
- * @param {boolean} isModernApi 使用现代API:URL, 默认true (对无效url解析会抛错), 否则使用a标签来解析(兼容性更强)
1381
- * @returns {Url}
1382
- */
1383
- const urlParse = (url, isModernApi = true) => {
1384
- // @ts-ignore
1385
- let urlObj = null;
1386
- if (isFunction(URL) && isModernApi) {
1387
- urlObj = new URL(url);
1388
- }
1389
- else {
1390
- urlObj = document.createElement('a');
1391
- urlObj.href = url;
1392
- }
1393
- const { protocol, username, password, host, port, hostname, hash, search, pathname: _pathname } = urlObj;
1394
- // fix: ie 浏览器下,解析出来的 pathname 是没有 / 根的
1395
- const pathname = pathJoin('/', _pathname);
1396
- const auth = username && password ? `${username}:${password}` : '';
1397
- const query = search.replace(/^\?/, '');
1398
- const searchParams = qsParse(query);
1399
- const path = `${pathname}${search}`;
1400
- urlObj = null;
1401
- return {
1402
- protocol,
1403
- auth,
1404
- username,
1405
- password,
1406
- host,
1407
- port,
1408
- hostname,
1409
- hash,
1410
- search,
1411
- searchParams,
1412
- query,
1413
- pathname,
1414
- path,
1415
- href: url
1416
- };
1417
- };
1418
- /**
1419
- * url 字符化,url 对象里的 searchParams 会覆盖 url 原有的查询参数
1420
- * @param {Url} url
1421
- * @returns {string}
1422
- */
1423
- const urlStringify = (url) => {
1424
- const { protocol, auth, host, pathname, searchParams, hash } = url;
1425
- const authorize = auth ? `${auth}@` : '';
1426
- const querystring = qsStringify(searchParams);
1427
- const search = querystring ? `?${querystring}` : '';
1428
- let hashstring = hash.replace(/^#/, '');
1429
- hashstring = hashstring ? '#' + hashstring : '';
1430
- return `${protocol}//${authorize}${host}${pathname}${search}${hashstring}`;
1431
- };
1432
- /**
1433
- * 设置 url 查询参数
1434
- * @param {string} url
1435
- * @param {AnyObject} setter
1436
- * @returns {string}
1437
- */
1438
- const urlSetParams = (url, setter) => {
1439
- const p = urlParse(url);
1440
- Object.assign(p.searchParams, setter);
1441
- return urlStringify(p);
1442
- };
1443
- /**
1444
- * 删除 url 查询参数
1445
- * @param {string} url
1446
- * @param {string[]} removeKeys
1447
- * @returns {string}
1448
- */
1449
- const urlDelParams = (url, removeKeys) => {
1450
- const p = urlParse(url);
1451
- removeKeys.forEach(key => delete p.searchParams[key]);
1452
- return urlStringify(p);
1453
- };
1454
-
1455
- /**
1456
- * 通过打开新窗口的方式下载
1457
- * @param {string} url
1458
- * @param {LooseParams} params
1459
- */
1460
- function downloadURL(url, params) {
1461
- window.open(params ? urlSetParams(url, params) : url);
1462
- }
1463
- /**
1464
- * 通过 A 链接的方式下载
1465
- * @param {string} href
1466
- * @param {string} filename
1467
- * @param {Function} callback
1468
- */
1469
- function downloadHref(href, filename, callback) {
1470
- let eleLink = document.createElement('a');
1471
- eleLink.download = filename;
1472
- eleLink.style.display = 'none';
1473
- eleLink.href = href;
1474
- document.body.appendChild(eleLink);
1475
- eleLink.click();
1476
- setTimeout(() => {
1477
- document.body.removeChild(eleLink);
1478
- // @ts-ignore
1479
- eleLink = null;
1480
- if (isFunction(callback)) {
1481
- callback();
1482
- }
1483
- });
1484
- }
1485
- /**
1486
- * 将大文件对象通过 A 链接的方式下载
1487
- * @param {Blob} blob
1488
- * @param {string} filename
1489
- * @param {Function} callback
1490
- */
1491
- function downloadBlob(blob, filename, callback) {
1492
- const objURL = URL.createObjectURL(blob);
1493
- downloadHref(objURL, filename);
1494
- setTimeout(() => {
1495
- URL.revokeObjectURL(objURL);
1496
- if (isFunction(callback)) {
1497
- callback();
1498
- }
1499
- });
1500
- }
1501
- /**
1502
- * 根据URL下载文件(解决跨域a.download不生效问题)
1503
- *
1504
- * 可定制下载成功的状态码status(浏览器原生状态码)
1505
- *
1506
- * 支持下载操作成功、失败后的回调
1507
- * @param {string} url
1508
- * @param {string} filename
1509
- * @param {CrossOriginDownloadParams} options
1510
- */
1511
- function crossOriginDownload(url, filename, options) {
1512
- const { successCode = 200, successCallback, failCallback } = isNullOrUnDef(options) ? { successCode: 200, successCallback: void 0, failCallback: void 0 } : options;
1513
- const xhr = new XMLHttpRequest();
1514
- xhr.open('GET', url, true);
1515
- xhr.responseType = 'blob';
1516
- xhr.onload = function () {
1517
- if (xhr.status === successCode)
1518
- downloadBlob(xhr.response, filename, successCallback);
1519
- else if (isFunction(failCallback)) {
1520
- const status = xhr.status;
1521
- const responseType = xhr.getResponseHeader('Content-Type');
1522
- if (isString(responseType) && responseType.includes('application/json')) {
1523
- const reader = new FileReader();
1524
- reader.onload = () => {
1525
- failCallback({ status, response: reader.result });
1526
- };
1527
- reader.readAsText(xhr.response);
1528
- }
1529
- else {
1530
- failCallback(xhr);
1531
- }
1532
- }
1533
- };
1534
- xhr.onerror = e => {
1535
- if (isFunction(failCallback)) {
1536
- failCallback({ status: 0, code: 'ERROR_CONNECTION_REFUSED' });
1537
- }
1538
- };
1539
- xhr.send();
1540
- }
1541
- /**
1542
- * 将指定数据格式通过 A 链接的方式下载
1543
- * @param {AnyObject | AnyObject[]} data
1544
- * @param {FileType} fileType 支持 json/csv/xls/xlsx 四种格式
1545
- * @param {string} filename
1546
- * @param {string[]} [headers]
1547
- */
1548
- function downloadData(data, fileType, filename, headers) {
1549
- filename = filename.replace(`.${fileType}`, '') + `.${fileType}`;
1550
- if (fileType === 'json') {
1551
- const blob = new Blob([JSON.stringify(data, null, 4)]);
1552
- downloadBlob(blob, filename);
1553
- }
1554
- else {
1555
- // xlsx实际生成的也为csv,仅后缀名名不同
1556
- if (!headers || !headers.length)
1557
- throw new Error('未传入表头数据');
1558
- if (!Array.isArray(data))
1559
- throw new Error('data error! expected array!');
1560
- const headerStr = headers.join(',') + '\n';
1561
- let bodyStr = '';
1562
- data.forEach(row => {
1563
- // \t防止数字被科学计数法显示
1564
- bodyStr += Object.values(row).join(',\t') + ',\n';
1565
- });
1566
- const MIMETypes = {
1567
- csv: 'text/csv',
1568
- xls: 'application/vnd.ms-excel',
1569
- xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
1570
- };
1571
- // encodeURIComponent解决中文乱码
1572
- const href = 'data:' + MIMETypes[fileType] + ';charset=utf-8,\ufeff' + encodeURIComponent(headerStr + bodyStr);
1573
- downloadHref(href, filename);
1574
- }
1575
- }
1576
-
1577
- /**
1578
- * 等待一段时间
1579
- * @param {number} timeout 等待时间,单位毫秒
1580
- * @returns {Promise<void>}
1581
- */
1582
- function wait(timeout = 1) {
1583
- return new Promise(resolve => setTimeout(resolve, timeout));
1584
- }
1585
- /**
1586
- * 异步遍历
1587
- * @ref https://github.com/Kevnz/async-tools/blob/master/src/mapper.js
1588
- * @ref https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/@@iterator
1589
- * @param {Array<T>} list
1590
- * @param {(val: T, idx: number, list: ArrayLike<T>) => Promise<R>} mapper
1591
- * @param {number} concurrency 并发数量,默认无限
1592
- * @returns {Promise<R[]>}
1593
- */
1594
- function asyncMap(list, mapper, concurrency = Infinity) {
1595
- return new Promise((resolve, reject) => {
1596
- const iterator = list[Symbol.iterator]();
1597
- const limit = Math.min(list.length, concurrency);
1598
- const resolves = [];
1599
- let resolvedLength = 0;
1600
- let rejected;
1601
- let index = 0;
1602
- const next = () => {
1603
- if (rejected)
1604
- return reject(rejected);
1605
- const it = iterator.next();
1606
- if (it.done) {
1607
- if (resolvedLength === list.length)
1608
- resolve(resolves);
1609
- return;
1610
- }
1611
- const current = index++;
1612
- mapper(it.value, current, list)
1613
- .then(value => {
1614
- resolvedLength++;
1615
- resolves[current] = value;
1616
- next();
1617
- })
1618
- .catch(err => {
1619
- rejected = err;
1620
- next();
1621
- });
1622
- };
1623
- // 开始
1624
- for (let i = 0; i < limit; i++) {
1625
- next();
1626
- }
1627
- });
1628
- }
1629
- /**
1630
- * Execute a promise safely
1631
- *
1632
- * @param { Promise } promise
1633
- * @param { Object= } errorExt - Additional Information you can pass safeAwait the err object
1634
- * @return { Promise }
1635
- * @example
1636
- * async function asyncTaskWithCb(cb) {
1637
- let err, user, savedTask, notification;
1638
-
1639
- [ err, user ] = await safeAwait(UserModel.findById(1));
1640
- if(!user) return cb('No user found');
1641
-
1642
- [ err, savedTask ] = await safeAwait(TaskModel({userId: user.id, name: 'Demo Task'}));
1643
- if(err) return cb('Error occurred while saving task')
1644
-
1645
- cb(null, savedTask);
1646
- }
1647
- */
1648
- function safeAwait(promise, errorExt) {
1649
- return promise
1650
- .then((data) => [null, data])
1651
- .catch((err) => {
1652
- if (errorExt) {
1653
- const parsedError = Object.assign({}, err, errorExt);
1654
- return [parsedError, undefined];
1655
- }
1656
- return [err, undefined];
1657
- });
1658
- }
1659
-
1660
- /**
1661
- * 判断是否支持canvas
1662
- * @returns {boolean}
1663
- */
1664
- function supportCanvas() {
1665
- return !!document.createElement('canvas').getContext;
1666
- }
1667
- /**
1668
- * 选择本地文件
1669
- * @param {string} accept 上传的文件类型,用于过滤
1670
- * @param {Function} changeCb 选择文件回调
1671
- * @returns {HTMLInputElement}
1672
- */
1673
- function chooseLocalFile(accept, changeCb) {
1674
- let inputObj = document.createElement('input');
1675
- inputObj.setAttribute('id', String(Date.now()));
1676
- inputObj.setAttribute('type', 'file');
1677
- inputObj.setAttribute('style', 'visibility:hidden');
1678
- inputObj.setAttribute('accept', accept);
1679
- document.body.appendChild(inputObj);
1680
- inputObj.click();
1681
- // @ts-ignore
1682
- inputObj.onchange = (e) => {
1683
- changeCb(e.target.files);
1684
- setTimeout(() => {
1685
- document.body.removeChild(inputObj);
1686
- // @ts-ignore
1687
- inputObj = null;
1688
- });
1689
- };
1690
- }
1691
- /**
1692
- * 计算图片压缩后的尺寸
1693
- *
1694
- * @param {number} maxWidth
1695
- * @param {number} maxHeight
1696
- * @param {number} originWidth
1697
- * @param {number} originHeight
1698
- * @returns {*}
1699
- */
1700
- function calculateSize({ maxWidth, maxHeight, originWidth, originHeight }) {
1701
- let width = originWidth, height = originHeight;
1702
- // 图片尺寸超过限制
1703
- if (originWidth > maxWidth || originHeight > maxHeight) {
1704
- if (originWidth / originHeight > maxWidth / maxHeight) {
1705
- // 更宽,按照宽度限定尺寸
1706
- width = maxWidth;
1707
- height = Math.round(maxWidth * (originHeight / originWidth));
1708
- }
1709
- else {
1710
- height = maxHeight;
1711
- width = Math.round(maxHeight * (originWidth / originHeight));
1712
- }
1713
- }
1714
- return { width, height };
1715
- }
1716
- /**
1717
- * 根据原始图片的不同尺寸计算等比例缩放后的宽高尺寸
1718
- *
1719
- * @param {number} sizeKB Image volume size, unit KB
1720
- * @param {number} maxSize Image max size
1721
- * @param {number} originWidth Image original width, unit px
1722
- * @param {number} originHeight Image original height, unit px
1723
- * @returns {*} {width, height}
1724
- */
1725
- function scalingByAspectRatio({ sizeKB, maxSize, originWidth, originHeight }) {
1726
- let targetWidth = originWidth, targetHeight = originHeight;
1727
- if (isNumber(maxSize)) {
1728
- const { width, height } = calculateSize({ maxWidth: maxSize, maxHeight: maxSize, originWidth, originHeight });
1729
- targetWidth = width;
1730
- targetHeight = height;
1731
- }
1732
- else if (sizeKB < 500) {
1733
- // [50KB, 500KB)
1734
- const maxWidth = 1200, maxHeight = 1200;
1735
- const { width, height } = calculateSize({ maxWidth, maxHeight, originWidth, originHeight });
1736
- targetWidth = width;
1737
- targetHeight = height;
1738
- }
1739
- else if (sizeKB < 5 * 1024) {
1740
- // [500KB, 5MB)
1741
- const maxWidth = 1400, maxHeight = 1400;
1742
- const { width, height } = calculateSize({ maxWidth, maxHeight, originWidth, originHeight });
1743
- targetWidth = width;
1744
- targetHeight = height;
1745
- }
1746
- else if (sizeKB < 10 * 1024) {
1747
- // [5MB, 10MB)
1748
- const maxWidth = 1600, maxHeight = 1600;
1749
- const { width, height } = calculateSize({ maxWidth, maxHeight, originWidth, originHeight });
1750
- targetWidth = width;
1751
- targetHeight = height;
1752
- }
1753
- else if (10 * 1024 <= sizeKB) {
1754
- // [10MB, Infinity)
1755
- const maxWidth = originWidth > 15000 ? 8192 : originWidth > 10000 ? 4096 : 2048, maxHeight = originHeight > 15000 ? 8192 : originHeight > 10000 ? 4096 : 2048;
1756
- const { width, height } = calculateSize({ maxWidth, maxHeight, originWidth, originHeight });
1757
- targetWidth = width;
1758
- targetHeight = height;
1759
- }
1760
- return { width: targetWidth, height: targetHeight };
1761
- }
1762
- /**
1763
- * Web端:等比例压缩图片批量处理 (小于minFileSizeKB:50,不压缩), 支持压缩全景图或长截图
1764
- *
1765
- * 1. 默认根据图片原始size及宽高适当地调整quality、width、height
1766
- * 2. 可指定压缩的图片质量 quality(若不指定则根据原始图片大小来计算), 来适当调整width、height
1767
- * 3. 可指定压缩的图片最大宽高 maxSize(若不指定则根据原始图片宽高来计算), 满足大屏幕图片展示的场景
1768
- *
1769
- * @param {File | FileList} file 图片或图片数组
1770
- * @param {ICompressOptions} options 压缩图片配置项,default: {mime:'image/jpeg', minFileSizeKB: 50}
1771
- * @returns {Promise<ICompressImgResult | ICompressImgResult[] | null>}
1772
- */
1773
- function compressImg(file, options = { mime: 'image/jpeg', minFileSizeKB: 50 }) {
1774
- if (!(file instanceof File || file instanceof FileList)) {
1775
- throw new Error(`${file} require be File or FileList`);
1776
- }
1777
- else if (!supportCanvas()) {
1778
- throw new Error(`Current runtime environment not support Canvas`);
1779
- }
1780
- const { quality, mime = 'image/jpeg', maxSize: size, minFileSizeKB = 50 } = isObject(options) ? options : {};
1781
- let targetQuality = quality, maxSize;
1782
- if (quality) {
1783
- targetQuality = quality;
1784
- }
1785
- else if (file instanceof File) {
1786
- const sizeKB = +parseInt((file.size / 1024).toFixed(2));
1787
- if (sizeKB < minFileSizeKB) {
1788
- targetQuality = 1;
1789
- }
1790
- else if (sizeKB < 1 * 1024) {
1791
- targetQuality = 0.85;
1792
- }
1793
- else if (sizeKB < 5 * 1024) {
1794
- targetQuality = 0.8;
1795
- }
1796
- else {
1797
- targetQuality = 0.75;
1798
- }
1799
- }
1800
- if (isNumber(size)) {
1801
- maxSize = size >= 1200 ? size : 1200;
1802
- }
1803
- if (file instanceof FileList) {
1804
- return Promise.all(Array.from(file).map(el => compressImg(el, { maxSize, mime: mime, quality: targetQuality }))); // 如果是 file 数组返回 Promise 数组
1805
- }
1806
- else if (file instanceof File) {
1807
- return new Promise(resolve => {
1808
- const ext = {
1809
- 'image/webp': 'webp',
1810
- 'image/jpeg': 'jpg',
1811
- 'image/png': 'png'
1812
- };
1813
- const fileName = [...file.name.split('.').slice(0, -1), ext[mime]].join('.');
1814
- const sizeKB = +parseInt((file.size / 1024).toFixed(2));
1815
- if (sizeKB < minFileSizeKB) {
1816
- resolve({
1817
- file: file
1818
- });
1819
- }
1820
- else {
1821
- const reader = new FileReader(); // 创建 FileReader
1822
- // @ts-ignore
1823
- reader.onload = ({ target: { result: src } }) => {
1824
- const image = new Image(); // 创建 img 元素
1825
- image.onload = () => {
1826
- const canvas = document.createElement('canvas'); // 创建 canvas 元素
1827
- const context = canvas.getContext('2d');
1828
- const originWidth = image.width;
1829
- const originHeight = image.height;
1830
- const { width, height } = scalingByAspectRatio({ sizeKB, maxSize, originWidth, originHeight });
1831
- canvas.width = width;
1832
- canvas.height = height;
1833
- context.clearRect(0, 0, width, height);
1834
- context.drawImage(image, 0, 0, width, height); // 绘制 canvas
1835
- const canvasURL = canvas.toDataURL(mime, targetQuality);
1836
- const buffer = atob(canvasURL.split(',')[1]);
1837
- let length = buffer.length;
1838
- const bufferArray = new Uint8Array(new ArrayBuffer(length));
1839
- while (length--) {
1840
- bufferArray[length] = buffer.charCodeAt(length);
1841
- }
1842
- const miniFile = new File([bufferArray], fileName, {
1843
- type: mime
1844
- });
1845
- resolve({
1846
- file: miniFile,
1847
- bufferArray,
1848
- origin: file,
1849
- beforeSrc: src,
1850
- afterSrc: canvasURL,
1851
- beforeKB: sizeKB,
1852
- afterKB: Number((miniFile.size / 1024).toFixed(2))
1853
- });
1854
- };
1855
- image.src = src;
1856
- };
1857
- reader.readAsDataURL(file);
1858
- }
1859
- });
1860
- }
1861
- return Promise.resolve(null);
1862
- }
1863
-
1864
- /*
1865
- * @created: Saturday, 2020-04-18 14:38:23
1866
- * @author: chendq
1867
- * ---------
1868
- * @desc 网页加水印的工具类
1869
- */
1870
- /**
1871
- * canvas 实现 水印, 具备防删除功能
1872
- * @param {ICanvasWM} canvasWM
1873
- * @example genCanvasWM({ content: 'QQMusicFE' })
1874
- */
1875
- function genCanvasWM(content = '请勿外传', canvasWM) {
1876
- const { rootContainer = document.body, width = '300px', height = '150px', textAlign = 'center', textBaseline = 'middle', font = '20px PingFangSC-Medium,PingFang SC',
1877
- // fontWeight = 500,
1878
- fillStyle = 'rgba(189, 177, 167, .3)', rotate = -20, zIndex = 2147483647, watermarkId = '__wm' } = isNullOrUnDef(canvasWM) ? {} : canvasWM;
1879
- const container = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer;
1880
- if (!container) {
1881
- throw new Error(`${rootContainer} is not valid Html Element or element selector`);
1882
- }
1883
- const canvas = document.createElement('canvas');
1884
- canvas.setAttribute('width', width);
1885
- canvas.setAttribute('height', height);
1886
- const ctx = canvas.getContext('2d');
1887
- ctx.textAlign = textAlign;
1888
- ctx.textBaseline = textBaseline;
1889
- ctx.font = font;
1890
- // ctx!.fontWeight = fontWeight;
1891
- ctx.fillStyle = fillStyle;
1892
- ctx.rotate((Math.PI / 180) * rotate);
1893
- ctx.fillText(content, parseFloat(width) / 4, parseFloat(height) / 2);
1894
- const base64Url = canvas.toDataURL();
1895
- const __wm = document.querySelector(`#${watermarkId}`);
1896
- const watermarkDiv = __wm || document.createElement('div');
1897
- const styleStr = `opacity: 1 !important; display: block !important; visibility: visible !important; position:absolute; left:0; top:0; width:100%; height:100%; z-index:${zIndex}; pointer-events:none; background-repeat:repeat; background-image:url('${base64Url}')`;
1898
- watermarkDiv.setAttribute('style', styleStr);
1899
- watermarkDiv.setAttribute('id', watermarkId);
1900
- watermarkDiv.classList.add('nav-height');
1901
- if (!__wm) {
1902
- container.style.position = 'relative';
1903
- container.appendChild(watermarkDiv);
1904
- }
1905
- const getMutableStyle = (ele) => {
1906
- const computedStyle = getComputedStyle(ele);
1907
- return {
1908
- opacity: computedStyle.getPropertyValue('opacity'),
1909
- zIndex: computedStyle.getPropertyValue('z-index'),
1910
- display: computedStyle.getPropertyValue('display'),
1911
- visibility: computedStyle.getPropertyValue('visibility')
1912
- };
1913
- };
1914
- //@ts-ignore
1915
- const MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
1916
- if (MutationObserver) {
1917
- let mo = new MutationObserver(function () {
1918
- let __wm = document.querySelector(`#${watermarkId}`); // 只在__wm元素变动才重新调用 __canvasWM
1919
- if (!__wm) {
1920
- // 避免一直触发
1921
- // console.log('regenerate watermark by delete::')
1922
- mo.disconnect();
1923
- mo = null;
1924
- genCanvasWM(content, canvasWM);
1925
- }
1926
- else {
1927
- const { opacity, zIndex, display, visibility } = getMutableStyle(__wm);
1928
- if ((__wm && __wm.getAttribute('style') !== styleStr) ||
1929
- !__wm ||
1930
- !(opacity === '1' && zIndex === '2147483647' && display === 'block' && visibility === 'visible')) {
1931
- // 避免一直触发
1932
- // console.log('regenerate watermark by inline style changed ::')
1933
- mo.disconnect();
1934
- mo = null;
1935
- container.removeChild(__wm);
1936
- // @ts-ignore
1937
- __wm = null;
1938
- genCanvasWM(content, canvasWM);
1939
- }
1940
- }
1941
- });
1942
- mo.observe(container, { attributes: true, subtree: true, childList: true });
1943
- }
1944
- }
1945
-
1946
- /**
1947
- * 防抖函数
1948
- * 当函数被连续调用时,该函数并不执行,只有当其全部停止调用超过一定时间后才执行1次。
1949
- * 例如:上电梯的时候,大家陆陆续续进来,电梯的门不会关上,只有当一段时间都没有人上来,电梯才会关门。
1950
- * @param {F} func
1951
- * @param {number} wait
1952
- * @returns {DebounceFunc<F>}
1953
- */
1954
- const debounce = (func, wait) => {
1955
- let timeout;
1956
- let canceled = false;
1957
- const f = function (...args) {
1958
- if (canceled)
1959
- return;
1960
- clearTimeout(timeout);
1961
- timeout = setTimeout(() => {
1962
- func.call(this, ...args);
1963
- }, wait);
1964
- };
1965
- f.cancel = () => {
1966
- clearTimeout(timeout);
1967
- canceled = true;
1968
- };
1969
- return f;
1970
- };
1971
- /**
1972
- * 节流函数
1973
- * 节流就是节约流量,将连续触发的事件稀释成预设评率。 比如每间隔1秒执行一次函数,无论这期间触发多少次事件。
1974
- * 这有点像公交车,无论在站点等车的人多不多,公交车只会按时来一班,不会来一个人就来一辆公交车。
1975
- * @param {F} func
1976
- * @param {number} wait
1977
- * @param {boolean} immediate
1978
- * @returns {ThrottleFunc<F>}
1979
- */
1980
- const throttle = (func, wait, immediate) => {
1981
- let timeout;
1982
- let canceled = false;
1983
- let lastCalledTime = 0;
1984
- const f = function (...args) {
1985
- if (canceled)
1986
- return;
1987
- const now = Date.now();
1988
- const call = () => {
1989
- lastCalledTime = now;
1990
- func.call(this, ...args);
1991
- };
1992
- // 第一次执行
1993
- if (lastCalledTime === 0) {
1994
- if (immediate) {
1995
- return call();
1996
- }
1997
- else {
1998
- lastCalledTime = now;
1999
- return;
2000
- }
2001
- }
2002
- const remain = lastCalledTime + wait - now;
2003
- if (remain > 0) {
2004
- clearTimeout(timeout);
2005
- timeout = setTimeout(() => call(), wait);
2006
- }
2007
- else {
2008
- call();
2009
- }
2010
- };
2011
- f.cancel = () => {
2012
- clearTimeout(timeout);
2013
- canceled = true;
2014
- };
2015
- return f;
2016
- };
2017
- /**
2018
- * 单次函数
2019
- * @param {AnyFunc} func
2020
- * @returns {AnyFunc}
2021
- */
2022
- const once = (func) => {
2023
- let called = false;
2024
- let result;
2025
- return function (...args) {
2026
- if (called)
2027
- return result;
2028
- called = true;
2029
- result = func.call(this, ...args);
2030
- return result;
2031
- };
2032
- };
2033
- /**
2034
- * 设置全局变量
2035
- * @param {string | number | symbol} key
2036
- * @param val
2037
- */
2038
- function setGlobal(key, val) {
2039
- if (typeof globalThis !== 'undefined')
2040
- globalThis[key] = val;
2041
- else if (typeof window !== 'undefined')
2042
- window[key] = val;
2043
- else if (typeof global !== 'undefined')
2044
- global[key] = val;
2045
- else if (typeof self !== 'undefined')
2046
- self[key] = val;
2047
- else
2048
- throw new SyntaxError('当前环境下无法设置全局属性');
2049
- }
2050
- /**
2051
- * 获取全局变量
2052
- * @param {string | number | symbol} key
2053
- * @param val
2054
- */
2055
- function getGlobal(key) {
2056
- if (typeof globalThis !== 'undefined')
2057
- return globalThis[key];
2058
- else if (typeof window !== 'undefined')
2059
- return window[key];
2060
- else if (typeof global !== 'undefined')
2061
- return global[key];
2062
- else if (typeof self !== 'undefined')
2063
- return self[key];
2064
- }
2065
-
2066
- /**
2067
- * 随机整数
2068
- * @param {number} min
2069
- * @param {number} max
2070
- * @returns {number}
2071
- */
2072
- const randomNumber = (min, max) => Math.floor(Math.random() * (max - min + 1) + min);
2073
- const STRING_POOL = `${STRING_ARABIC_NUMERALS}${STRING_UPPERCASE_ALPHA}${STRING_LOWERCASE_ALPHA}`;
2074
- /**
2075
- * 随机字符串
2076
- * @param {number | string} length
2077
- * @param {string} pool
2078
- * @returns {string}
2079
- */
2080
- const randomString = (length, pool) => {
2081
- let _length = 0;
2082
- let _pool = STRING_POOL;
2083
- if (isString(pool)) {
2084
- _length = length;
2085
- _pool = pool;
2086
- }
2087
- else if (isNumber(length)) {
2088
- _length = length;
2089
- }
2090
- else if (isString(length)) {
2091
- _pool = length;
2092
- }
2093
- let times = Math.max(_length, 1);
2094
- let result = '';
2095
- const min = 0;
2096
- const max = _pool.length - 1;
2097
- if (max < 2)
2098
- throw new Error('字符串池长度不能少于 2');
2099
- while (times--) {
2100
- const index = randomNumber(min, max);
2101
- result += _pool[index];
2102
- }
2103
- return result;
2104
- };
2105
- /**
2106
- * 优先浏览器原生能力获取 UUID v4
2107
- * @returns {string}
2108
- */
2109
- function randomUuid() {
2110
- if (typeof URL === 'undefined' || !URL.createObjectURL || typeof Blob === 'undefined') {
2111
- const hex = '0123456789abcdef';
2112
- const model = 'xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx';
2113
- let str = '';
2114
- for (let i = 0; i < model.length; i++) {
2115
- const rnd = randomNumber(0, 15);
2116
- str += model[i] == '-' || model[i] == '4' ? model[i] : hex[rnd];
2117
- }
2118
- return str;
2119
- }
2120
- return /[^/]+$/.exec(URL.createObjectURL(new Blob()).slice())[0];
2121
- }
2122
-
2123
- const HEX_POOL = `${STRING_ARABIC_NUMERALS}${STRING_UPPERCASE_ALPHA}${STRING_LOWERCASE_ALPHA}`;
2124
- const supportBigInt = typeof BigInt !== 'undefined';
2125
- const jsbi = () => getGlobal('JSBI');
2126
- const toBigInt = (n) => (supportBigInt ? BigInt(n) : jsbi().BigInt(n));
2127
- const divide$1 = (x, y) => (supportBigInt ? x / y : jsbi().divide(x, y));
2128
- const remainder = (x, y) => (supportBigInt ? x % y : jsbi().remainder(x, y));
2129
- /**
2130
- * 将十进制转换成任意进制
2131
- * @param {number | string} decimal 十进制数值或字符串,可以是任意长度,会使用大数进行计算
2132
- * @param {string} [hexPool] 进制池,默认 62 进制
2133
- * @returns {string}
2134
- */
2135
- function numberToHex(decimal, hexPool = HEX_POOL) {
2136
- if (hexPool.length < 2)
2137
- throw new Error('进制池长度不能少于 2');
2138
- if (!supportBigInt) {
2139
- throw new Error('需要安装 jsbi 模块并将 JSBI 设置为全局变量:\nimport JSBI from "jsbi"; window.JSBI = JSBI;');
2140
- }
2141
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
2142
- let bigInt = toBigInt(decimal);
2143
- const ret = [];
2144
- const { length } = hexPool;
2145
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
2146
- const bigLength = toBigInt(length);
2147
- const execute = () => {
2148
- const y = Number(remainder(bigInt, bigLength));
2149
- bigInt = divide$1(bigInt, bigLength);
2150
- ret.unshift(hexPool[y]);
2151
- if (bigInt > 0) {
2152
- execute();
2153
- }
2154
- };
2155
- execute();
2156
- return ret.join('');
2157
- }
2158
- /**
2159
- * 将数字转换为携带单位的字符串
2160
- * @param {number | string} num
2161
- * @param {Array<string>} units
2162
- * @param {INumberAbbr} options default: { ratio: 1000, decimals: 0, separator: ' ' }
2163
- * @returns {string}
2164
- */
2165
- const numberAbbr = (num, units, options = { ratio: 1000, decimals: 0, separator: ' ' }) => {
2166
- const { ratio = 1000, decimals = 0, separator = ' ' } = options;
2167
- const { length } = units;
2168
- if (length === 0)
2169
- throw new Error('At least one unit is required');
2170
- let num2 = Number(num);
2171
- let times = 0;
2172
- while (num2 >= ratio && times < length - 1) {
2173
- num2 = num2 / ratio;
2174
- times++;
2175
- }
2176
- const value = num2.toFixed(decimals);
2177
- const unit = units[times];
2178
- return String(value) + separator + unit;
2179
- };
2180
- /**
2181
- * Converting file size in bytes to human-readable string
2182
- * reference: https://zh.wikipedia.org/wiki/%E5%8D%83%E5%AD%97%E8%8A%82
2183
- * @param {number | string} num bytes Number in Bytes
2184
- * @param {IHumanFileSizeOptions} options default: { decimals = 0, si = false, separator = ' ' }
2185
- * si: True to use metric (SI) units, aka powers of 1000, the units is
2186
- * ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'].
2187
- * False to use binary (IEC), aka powers of 1024, the units is
2188
- * ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']
2189
- * @returns
2190
- */
2191
- function humanFileSize(num, options = { decimals: 0, si: false, separator: ' ' }) {
2192
- const { decimals = 0, si = false, separator = ' ', baseUnit, maxUnit } = options;
2193
- let units = si
2194
- ? ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
2195
- : ['Byte', 'KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
2196
- if (!isNullOrUnDef(baseUnit)) {
2197
- const targetIndex = units.findIndex(el => el === baseUnit);
2198
- if (targetIndex !== -1) {
2199
- units = units.slice(targetIndex);
2200
- }
2201
- }
2202
- if (!isNullOrUnDef(maxUnit)) {
2203
- const targetIndex = units.findIndex(el => el === maxUnit);
2204
- if (targetIndex !== -1) {
2205
- units.splice(targetIndex + 1);
2206
- }
2207
- }
2208
- return numberAbbr(num, units, { ratio: si ? 1000 : 1024, decimals, separator });
2209
- }
2210
- /**
2211
- * 将数字格式化成千位分隔符显示的字符串
2212
- * @param {number|string} num 数字
2213
- * @param {number} decimals 格式化成指定小数位精度的参数
2214
- * @returns {string}
2215
- */
2216
- function formatNumber(num, decimals) {
2217
- if (isNullOrUnDef(decimals)) {
2218
- return parseInt(String(num)).toLocaleString();
2219
- }
2220
- let prec = 0;
2221
- if (!isNumber(decimals)) {
2222
- throw new Error('Decimals must be a positive number not less than zero');
2223
- }
2224
- else if (decimals > 0) {
2225
- prec = decimals;
2226
- }
2227
- return Number(Number(num).toFixed(prec)).toLocaleString('en-US');
2228
- }
2229
-
2230
- const padStartWithZero = (str, maxLength = 2) => String(str).padStart(maxLength, '0');
2231
- let safeNo = 0;
2232
- let lastTimestamp = 0;
2233
- // 安全后缀长度,按 1 毫秒运算 99999 次 JS 代码来进行估算
2234
- // 那么,补足长度为 5
2235
- // 时间戳模式长度为 13
2236
- // 取最长 5 + 13 = 18
2237
- const UNIQUE_NUMBER_SAFE_LENGTH = 18;
2238
- const FIX_SAFE_LENGTH = 5;
2239
- const TIMESTAMP_LENGTH = 13;
2240
- /**
2241
- * 生成唯一不重复数值
2242
- * @param {number} length
2243
- * @returns {string}
2244
- */
2245
- const uniqueNumber = (length = UNIQUE_NUMBER_SAFE_LENGTH) => {
2246
- const now = Date.now();
2247
- length = Math.max(length, UNIQUE_NUMBER_SAFE_LENGTH);
2248
- if (now !== lastTimestamp) {
2249
- lastTimestamp = now;
2250
- safeNo = 0;
2251
- }
2252
- const timestamp = `${now}`;
2253
- let random = '';
2254
- const rndLength = length - FIX_SAFE_LENGTH - TIMESTAMP_LENGTH;
2255
- if (rndLength > 0) {
2256
- const rndMin = 10 ** (rndLength - 1);
2257
- const rndMax = 10 ** rndLength - 1;
2258
- const rnd = randomNumber(rndMin, rndMax);
2259
- random = `${rnd}`;
2260
- }
2261
- const safe = padStartWithZero(safeNo, FIX_SAFE_LENGTH);
2262
- safeNo++;
2263
- return `${timestamp}${random}${safe}`;
2264
- };
2265
- const randomFromPool = (pool) => {
2266
- const poolIndex = randomNumber(0, pool.length - 1);
2267
- return pool[poolIndex];
2268
- };
2269
- /**
2270
- * 生成唯一不重复字符串
2271
- * @param {number | string} length
2272
- * @param {string} pool
2273
- * @returns {string}
2274
- */
2275
- const uniqueString = (length, pool) => {
2276
- let _length = 0;
2277
- let _pool = HEX_POOL;
2278
- if (isString(pool)) {
2279
- _length = length;
2280
- _pool = pool;
2281
- }
2282
- else if (isNumber(length)) {
2283
- _length = length;
2284
- }
2285
- else if (isString(length)) {
2286
- _pool = length;
2287
- }
2288
- let uniqueString = numberToHex(uniqueNumber(), _pool);
2289
- let insertLength = _length - uniqueString.length;
2290
- if (insertLength <= 0)
2291
- return uniqueString;
2292
- while (insertLength--) {
2293
- uniqueString += randomFromPool(_pool);
2294
- }
2295
- return uniqueString;
2296
- };
2297
-
2298
- /**
2299
- * 自定义的 tooltip, 支持鼠标移动动悬浮提示
2300
- * @Desc 自定义的tooltip方法, 支持拖动悬浮提示
2301
- * Created by chendeqiao on 2017/5/8.
2302
- * @example
2303
- * <span onmouseleave="handleMouseLeave('#root')"
2304
- * onmousemove="handleMouseEnter({rootContainer: '#root', title: 'title content', event: event})"
2305
- * onmouseenter="handleMouseEnter({rootContainer:'#root', title: 'title content', event: event})">
2306
- * title content
2307
- * </span>
2308
- */
2309
- /**
2310
- * 自定义title提示功能的mouseenter事件句柄
2311
- * @param {ITooltipParams} param
2312
- * @returns {*}
2313
- */
2314
- function handleMouseEnter({ rootContainer = '#root', title, event, bgColor = '#000', color = '#fff' }) {
2315
- try {
2316
- const $rootEl = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer;
2317
- if (!$rootEl) {
2318
- throw new Error(`${rootContainer} is not valid Html Element or element selector`);
2319
- }
2320
- let $customTitle = null;
2321
- const styleId = 'style-tooltip-inner1494304949567';
2322
- // 动态创建class样式,并加入到head中
2323
- if (!document.querySelector(`#${styleId}`)) {
2324
- const tooltipWrapperClass = document.createElement('style');
2325
- tooltipWrapperClass.type = 'text/css';
2326
- tooltipWrapperClass.id = styleId;
2327
- tooltipWrapperClass.innerHTML = `
2328
- .tooltip-inner1494304949567 {
2329
- max-width: 250px;
2330
- padding: 3px 8px;
2331
- color: ${color};
2332
- text-decoration: none;
2333
- border-radius: 4px;
2334
- text-align: left;
2335
- background-color: ${bgColor};
2336
- }
2337
- `;
2338
- document.querySelector('head').appendChild(tooltipWrapperClass);
2339
- }
2340
- $customTitle = document.querySelector('#customTitle1494304949567');
2341
- if ($customTitle) {
2342
- mouseenter($customTitle, title, event);
2343
- }
2344
- else {
2345
- const $contentContainer = document.createElement('div');
2346
- $contentContainer.id = 'customTitle1494304949567';
2347
- $contentContainer.style.cssText = 'z-index: 99999999; visibility: hidden; position: absolute;';
2348
- $contentContainer.innerHTML =
2349
- '<div class="tooltip-inner1494304949567" style="word-wrap: break-word; max-width: 44px;">皮肤</div>';
2350
- $rootEl.appendChild($contentContainer);
2351
- $customTitle = document.querySelector('#customTitle1494304949567');
2352
- if (title) {
2353
- //判断div显示的内容是否为空
2354
- mouseenter($customTitle, title, event);
2355
- $customTitle.style.visibility = 'visible';
2356
- }
2357
- }
2358
- }
2359
- catch (e) {
2360
- console.error(e.message);
2361
- }
2362
- }
2363
- /**
2364
- * 提示文案dom渲染的处理函数
2365
- * @param {HTMLDivElement} customTitle
2366
- * @param {string} title 提示的字符串
2367
- * @param {PointerEvent} e 事件对象
2368
- * @returns {*}
2369
- */
2370
- function mouseenter($customTitle, title, e) {
2371
- let diffValueX = 200 + 50; //默认设置弹出div的宽度为250px
2372
- let x = 13;
2373
- const y = 23;
2374
- const $contentEle = $customTitle.children[0];
2375
- if (getStrWidthPx(title, 12) < 180 + 50) {
2376
- //【弹出div自适应字符串宽度】若显示的字符串占用宽度小于180,则设置弹出div的宽度为“符串占用宽度”+20
2377
- $contentEle.style.maxWidth = getStrWidthPx(title, 12) + 20 + 50 + 'px';
2378
- diffValueX = e.clientX + (getStrWidthPx(title, 12) + 50) - document.body.offsetWidth;
2379
- }
2380
- else {
2381
- $contentEle.style.maxWidth = '250px';
2382
- diffValueX = e.clientX + 230 - document.body.offsetWidth; //计算div水平方向显示的内容超出屏幕多少宽度
2383
- }
2384
- $contentEle.innerHTML = title; //html方法可解析内容中换行标签,text方法不能
2385
- if (diffValueX > 0) {
2386
- //水平方向超出可见区域时
2387
- x -= diffValueX;
2388
- }
2389
- $customTitle.style.top = e.clientY + y + 'px';
2390
- $customTitle.style.left = e.clientX + x + 'px';
2391
- $customTitle.style.maxWidth = '250px';
2392
- const diffValueY = $customTitle.getBoundingClientRect().top + $contentEle.offsetHeight - document.body.offsetHeight;
2393
- if (diffValueY > 0) {
2394
- //垂直方向超出可见区域时
2395
- $customTitle.style.top = e.clientY - diffValueY + 'px';
2396
- }
2397
- }
2398
- /**
2399
- * 移除提示文案dom的事件句柄
2400
- * @param {string} rootContainer
2401
- * @returns {*}
2402
- */
2403
- function handleMouseLeave(rootContainer = '#root') {
2404
- const rootEl = isString(rootContainer) ? document.querySelector(rootContainer) : rootContainer;
2405
- let titleEl = document.querySelector('#customTitle1494304949567');
2406
- if (rootEl && titleEl) {
2407
- rootEl.removeChild(titleEl);
2408
- // @ts-ignore
2409
- titleEl = null;
2410
- }
2411
- }
2412
- const tooltipEvent = { handleMouseEnter, handleMouseLeave };
2413
-
2414
- const defaultFieldOptions = { keyField: 'key', childField: 'children', pidField: 'pid' };
2415
- const defaultSearchTreeOptions = {
2416
- childField: 'children',
2417
- nameField: 'name',
2418
- removeEmptyChild: false,
2419
- ignoreCase: true
2420
- };
2421
- /**
2422
- * 树遍历函数(支持continue和break操作), 可用于遍历Array和NodeList类型的数据
2423
- * @param {ArrayLike<V>} tree 树形数据
2424
- * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
2425
- * @param {options} options 支持定制子元素名称、反向遍历、广度优先遍历,默认{
2426
- childField: 'children',
2427
- reverse: false,
2428
- breadthFirst: false
2429
- }
2430
- * @returns {*}
2431
- */
2432
- function forEachDeep(tree, iterator, options = {
2433
- childField: 'children',
2434
- reverse: false,
2435
- breadthFirst: false,
2436
- isDomNode: false
2437
- }) {
2438
- const { childField = 'children', reverse = false, breadthFirst = false, isDomNode = false } = isObject(options) ? options : {};
2439
- let isBreak = false;
2440
- const queue = [];
2441
- const walk = (arr, parent, level = 0) => {
2442
- if (reverse) {
2443
- for (let index = arr.length - 1; index >= 0; index--) {
2444
- if (isBreak) {
2445
- break;
2446
- }
2447
- const item = arr[index];
2448
- // 广度优先
2449
- if (breadthFirst) {
2450
- queue.push({ item, index, array: arr, tree, parent, level });
2451
- }
2452
- else {
2453
- const re = iterator(item, index, arr, tree, parent, level);
2454
- if (re === false) {
2455
- isBreak = true;
2456
- break;
2457
- }
2458
- else if (re === true) {
2459
- continue;
2460
- }
2461
- if (item && (isDomNode ? isNodeList(item[childField]) : Array.isArray(item[childField]))) {
2462
- walk(item[childField], item, level + 1);
2463
- }
2464
- }
2465
- }
2466
- if (breadthFirst) {
2467
- // Process queue
2468
- while (queue.length > 0 && !isBreak) {
2469
- const current = queue.shift();
2470
- const { item, index, array, tree, parent, level } = current;
2471
- const re = iterator(item, index, array, tree, parent, level);
2472
- if (re === false) {
2473
- isBreak = true;
2474
- break;
2475
- }
2476
- else if (re === true) {
2477
- continue;
2478
- }
2479
- if (item && (isDomNode ? isNodeList(item[childField]) : Array.isArray(item[childField]))) {
2480
- walk(item[childField], item, level + 1);
2481
- }
2482
- }
2483
- }
2484
- }
2485
- else {
2486
- for (let index = 0, len = arr.length; index < len; index++) {
2487
- if (isBreak) {
2488
- break;
2489
- }
2490
- const item = arr[index];
2491
- if (breadthFirst) {
2492
- // 广度优先
2493
- queue.push({ item, index: index, array: arr, tree, parent, level });
2494
- }
2495
- else {
2496
- // 深度优先
2497
- const re = iterator(item, index, arr, tree, parent, level);
2498
- if (re === false) {
2499
- isBreak = true;
2500
- break;
2501
- }
2502
- else if (re === true) {
2503
- continue;
2504
- }
2505
- if (item && (isDomNode ? isNodeList(item[childField]) : Array.isArray(item[childField]))) {
2506
- walk(item[childField], item, level + 1);
2507
- }
2508
- }
2509
- }
2510
- if (breadthFirst) {
2511
- while (queue.length > 0 && !isBreak) {
2512
- const current = queue.shift();
2513
- if (!current)
2514
- break;
2515
- const { item, index, array, tree, parent, level } = current;
2516
- const re = iterator(item, index, array, tree, parent, level);
2517
- if (re === false) {
2518
- isBreak = true;
2519
- break;
2520
- }
2521
- else if (re === true) {
2522
- continue;
2523
- }
2524
- if (item && (isDomNode ? isNodeList(item[childField]) : Array.isArray(item[childField]))) {
2525
- walk(item[childField], item, level + 1);
2526
- }
2527
- }
2528
- }
2529
- }
2530
- };
2531
- walk(tree, null, 0);
2532
- // @ts-ignore
2533
- tree = null;
2534
- }
2535
- /**
2536
- * 创建一个新数组, 深度优先遍历的Map函数(支持continue和break操作), 可用于insert tree item 和 remove tree item
2537
- *
2538
- * 可遍历任何带有 length 属性和数字键的类数组对象
2539
- * @param {ArrayLike<V>} tree 树形数据
2540
- * @param {Function} iterator 迭代函数, 返回值为true时continue, 返回值为false时break
2541
- * @param {options} options 支持定制子元素名称、反向遍历,默认{
2542
- childField: 'children',
2543
- reverse: false,
2544
- }
2545
- * @returns {any[]} 新的一棵树
2546
- */
2547
- function mapDeep(tree, iterator, options = {
2548
- childField: 'children',
2549
- reverse: false
2550
- }) {
2551
- const { childField = 'children', reverse = false } = isObject(options) ? options : {};
2552
- let isBreak = false;
2553
- const newTree = [];
2554
- const walk = (arr, parent, newTree, level = 0) => {
2555
- if (reverse) {
2556
- for (let i = arr.length - 1; i >= 0; i--) {
2557
- if (isBreak) {
2558
- break;
2559
- }
2560
- const item = arr[i];
2561
- const re = iterator(item, i, arr, tree, parent, level);
2562
- if (re === false) {
2563
- isBreak = true;
2564
- break;
2565
- }
2566
- else if (re === true) {
2567
- continue;
2568
- }
2569
- newTree.push(objectOmit(re, [childField]));
2570
- if (item && Array.isArray(item[childField])) {
2571
- newTree[newTree.length - 1][childField] = [];
2572
- walk(item[childField], item, newTree[newTree.length - 1][childField], level + 1);
2573
- }
2574
- else {
2575
- // children非有效数组时,移除该属性字段
2576
- delete re[childField];
2577
- }
2578
- }
2579
- }
2580
- else {
2581
- for (let i = 0; i < arr.length; i++) {
2582
- if (isBreak) {
2583
- break;
2584
- }
2585
- const item = arr[i];
2586
- const re = iterator(item, i, arr, tree, parent, level);
2587
- if (re === false) {
2588
- isBreak = true;
2589
- break;
2590
- }
2591
- else if (re === true) {
2592
- continue;
2593
- }
2594
- newTree.push(objectOmit(re, [childField]));
2595
- if (item && Array.isArray(item[childField])) {
2596
- newTree[newTree.length - 1][childField] = [];
2597
- walk(item[childField], item, newTree[newTree.length - 1][childField], level + 1);
2598
- }
2599
- else {
2600
- // children非有效数组时,移除该属性字段
2601
- delete re[childField];
2602
- }
2603
- }
2604
- }
2605
- };
2606
- walk(tree, null, newTree);
2607
- // @ts-ignore
2608
- tree = null;
2609
- return newTree;
2610
- }
2611
- /**
2612
- * 在树中找到 id 为某个值的节点,并返回上游的所有父级节点
2613
- *
2614
- * @param {ArrayLike<T>} tree - 树形数据
2615
- * @param {number | string} nodeId - 目标元素ID
2616
- * @param {ITreeConf} options - 迭代配置项, 默认:{ children = 'children', id = 'id' }
2617
- * @returns {[(number | string)[], V[]]} - 由parentId...childId, parentObject-childObject组成的二维数组
2618
- */
2619
- function searchTreeById(tree, nodeId, options = { childField: 'children', keyField: 'id' }) {
2620
- const { childField = 'children', keyField = 'id' } = isObject(options) ? options : {};
2621
- const toFlatArray = (tree, parentId, parent) => {
2622
- return tree.reduce((t, _) => {
2623
- const child = _[childField];
2624
- return [
2625
- ...t,
2626
- parentId ? { ..._, parentId, parent } : _,
2627
- ...(child && child.length ? toFlatArray(child, _[keyField], _) : [])
2628
- ];
2629
- }, []);
2630
- };
2631
- const getIds = (flatArray) => {
2632
- let child = flatArray.find(_ => _[keyField] === nodeId);
2633
- const { parent, parentId, ...other } = child;
2634
- let ids = [nodeId], nodes = [other];
2635
- while (child && child.parentId) {
2636
- ids = [child.parentId, ...ids];
2637
- nodes = [child.parent, ...nodes];
2638
- child = flatArray.find(_ => _[keyField] === child.parentId); // eslint-disable-line
2639
- }
2640
- return [ids, nodes];
2641
- };
2642
- return getIds(toFlatArray(tree));
2643
- }
2644
- /**
2645
- * 扁平化数组转换成树
2646
- * @param {any[]} list
2647
- * @param {IFieldOptions} options 定制id字段名,子元素字段名,父元素字段名,默认
2648
- * { keyField: 'key', childField: 'children', pidField: 'pid' }
2649
- * @returns {any[]}
2650
- */
2651
- function formatTree(list, options = defaultFieldOptions) {
2652
- const { keyField = 'key', childField = 'children', pidField = 'pid' } = isObject(options) ? options : {};
2653
- const treeArr = [];
2654
- const sourceMap = {};
2655
- for (let i = 0, len = list.length; i < len; i++) {
2656
- const item = list[i];
2657
- sourceMap[item[keyField]] = item;
2658
- }
2659
- for (let i = 0, len = list.length; i < len; i++) {
2660
- const item = list[i];
2661
- const parent = sourceMap[item[pidField]];
2662
- if (parent) {
2663
- (parent[childField] || (parent[childField] = [])).push(item);
2664
- }
2665
- else {
2666
- treeArr.push(item);
2667
- }
2668
- }
2669
- // @ts-ignore
2670
- list = null;
2671
- return treeArr;
2672
- }
2673
- /**
2674
- * 树形结构转扁平化
2675
- * @param {any[]} treeList
2676
- * @param {IFieldOptions} options 定制id字段名,子元素字段名,父元素字段名,默认
2677
- * { keyField: 'key', childField: 'children', pidField: 'pid' }
2678
- * @returns {any[]}
2679
- */
2680
- function flatTree(treeList, options = defaultFieldOptions) {
2681
- const { keyField = 'key', childField = 'children', pidField = 'pid' } = isObject(options) ? options : {};
2682
- let res = [];
2683
- for (let i = 0, len = treeList.length; i < len; i++) {
2684
- const node = treeList[i];
2685
- const item = {
2686
- ...node,
2687
- [childField]: [] // 清空子级
2688
- };
2689
- objectHas(item, childField) && delete item[childField];
2690
- res.push(item);
2691
- if (node[childField]) {
2692
- const children = node[childField].map(item => ({
2693
- ...item,
2694
- [pidField]: node[keyField] || item.pid // 给子级设置pid
2695
- }));
2696
- res = res.concat(flatTree(children, options));
2697
- }
2698
- }
2699
- return res;
2700
- }
2701
- /**
2702
- * 模糊搜索函数,返回包含搜索字符的节点及其祖先节点, 适用于树型组件的字符过滤功能
2703
- * 以下搜索条件二选一,按先后优先级处理:
2704
- * 1. 过滤函数filter, 返回true/false
2705
- * 2. 匹配关键词,支持是否启用忽略大小写来判断
2706
- *
2707
- * 有以下特性:
2708
- * 1. 可配置removeEmptyChild字段,来决定是否移除搜索结果中的空children字段
2709
- * 2. 若无任何过滤条件或keyword模式匹配且keyword为空串,返回原对象;其他情况返回新数组
2710
- * @param {V[]} nodes
2711
- * @param {IFilterCondition} filterCondition
2712
- * @param {ISearchTreeOpts} options 默认配置项 {
2713
- childField: 'children',
2714
- nameField: 'name',
2715
- removeEmptyChild: false,
2716
- ignoreCase: true
2717
- }
2718
- * @returns {V[]}
2719
- */
2720
- function fuzzySearchTree(nodes, filterCondition, options = defaultSearchTreeOptions) {
2721
- if (!objectHas(filterCondition, 'filter') &&
2722
- (!objectHas(filterCondition, 'keyword') || isEmpty(filterCondition.keyword))) {
2723
- return nodes;
2724
- }
2725
- const result = [];
2726
- for (let i = 0, len = nodes.length; i < len; i++) {
2727
- const node = nodes[i];
2728
- // 递归检查子节点是否匹配
2729
- const matchedChildren = node[options.childField] && node[options.childField].length > 0
2730
- ? fuzzySearchTree(node[options.childField] || [], filterCondition, options)
2731
- : [];
2732
- // 检查当前节点是否匹配或者有匹配的子节点
2733
- if ((objectHas(filterCondition, 'filter')
2734
- ? filterCondition.filter(node)
2735
- : !options.ignoreCase
2736
- ? node[options.nameField].includes(filterCondition.keyword)
2737
- : node[options.nameField].toLowerCase().includes(filterCondition.keyword.toLowerCase())) ||
2738
- matchedChildren.length > 0) {
2739
- // 将当前节点加入结果中
2740
- if (node[options.childField]) {
2741
- if (matchedChildren.length > 0) {
2742
- result.push({
2743
- ...node,
2744
- [options.childField]: matchedChildren // 包含匹配的子节点
2745
- });
2746
- }
2747
- else if (options.removeEmptyChild) {
2748
- const { [options.childField]: _, ...other } = node;
2749
- result.push(other);
2750
- }
2751
- else {
2752
- result.push({
2753
- ...node,
2754
- [options.childField]: []
2755
- });
2756
- }
2757
- }
2758
- else {
2759
- const { [options.childField]: _, ...other } = node;
2760
- result.push(other);
2761
- }
2762
- }
2763
- }
2764
- return result;
2765
- }
2766
-
2767
- /**
2768
- * 数值安全乘法
2769
- * @param arg1 数值1
2770
- * @param arg2 数值2
2771
- */
2772
- const multiply = (arg1, arg2) => {
2773
- let m = 0;
2774
- const s1 = arg1.toString();
2775
- const s2 = arg2.toString();
2776
- if (s1.split('.')[1] !== undefined)
2777
- m += s1.split('.')[1].length;
2778
- if (s2.split('.')[1] !== undefined)
2779
- m += s2.split('.')[1].length;
2780
- return (Number(s1.replace('.', '')) * Number(s2.replace('.', ''))) / Math.pow(10, m);
2781
- };
2782
- /**
2783
- * 数值安全加法
2784
- * @param arg1 数值1
2785
- * @param arg2 数值2
2786
- */
2787
- const add = (arg1, arg2) => {
2788
- let r1 = 0;
2789
- let r2 = 0;
2790
- let m = 0;
2791
- try {
2792
- r1 = arg1.toString().split('.')[1].length;
2793
- }
2794
- catch (e) {
2795
- r1 = 0;
2796
- }
2797
- try {
2798
- r2 = arg2.toString().split('.')[1].length;
2799
- }
2800
- catch (e) {
2801
- r2 = 0;
2802
- }
2803
- m = 10 ** Math.max(r1, r2);
2804
- return (multiply(arg1, m) + multiply(arg2, m)) / m;
2805
- };
2806
- /**
2807
- * 数值安全减法
2808
- * @param arg1 数值1
2809
- * @param arg2 数值2
2810
- */
2811
- const subtract = (arg1, arg2) => add(arg1, -arg2);
2812
- /**
2813
- * 数值安全除法
2814
- * @param arg1 数值1
2815
- * @param arg2 数值2
2816
- */
2817
- const divide = (arg1, arg2) => {
2818
- let t1 = 0;
2819
- let t2 = 0;
2820
- let r1 = 0;
2821
- let r2 = 0;
2822
- if (arg1.toString().split('.')[1] !== undefined)
2823
- t1 = arg1.toString().split('.')[1].length;
2824
- if (arg2.toString().split('.')[1] !== undefined)
2825
- t2 = arg2.toString().split('.')[1].length;
2826
- r1 = Number(arg1.toString().replace('.', ''));
2827
- r2 = Number(arg2.toString().replace('.', ''));
2828
- return (r1 / r2) * Math.pow(10, t2 - t1);
2829
- };
2830
- /**
2831
- * Correct the given number to specifying significant digits.
2832
- *
2833
- * @param num The input number
2834
- * @param precision An integer specifying the number of significant digits
2835
- *
2836
- * @example strip(0.09999999999999998) === 0.1 // true
2837
- */
2838
- function strip(num, precision = 15) {
2839
- return +parseFloat(Number(num).toPrecision(precision));
2840
- }
2841
-
2842
- const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
2843
- // eslint-disable-next-line
2844
- const b64re = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
2845
- /**
2846
- * 字符串编码成Base64, 平替浏览器的btoa, 不包含中文的处理 (适用于任何环境,包括小程序)
2847
- * @param {string} string
2848
- * @returns {string}
2849
- */
2850
- function weBtoa(string) {
2851
- // 同window.btoa: 字符串编码成Base64
2852
- string = String(string);
2853
- let bitmap, a, b, c, result = '', i = 0;
2854
- const strLen = string.length;
2855
- const rest = strLen % 3;
2856
- for (; i < strLen;) {
2857
- if ((a = string.charCodeAt(i++)) > 255 || (b = string.charCodeAt(i++)) > 255 || (c = string.charCodeAt(i++)) > 255)
2858
- throw new TypeError("Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.");
2859
- bitmap = (a << 16) | (b << 8) | c;
2860
- result +=
2861
- b64.charAt((bitmap >> 18) & 63) +
2862
- b64.charAt((bitmap >> 12) & 63) +
2863
- b64.charAt((bitmap >> 6) & 63) +
2864
- b64.charAt(bitmap & 63);
2865
- }
2866
- return rest ? result.slice(0, rest - 3) + '==='.substring(rest) : result;
2867
- }
2868
- /**
2869
- * Base64解码为原始字符串,平替浏览器的atob, 不包含中文的处理(适用于任何环境,包括小程序)
2870
- * @param {string} string
2871
- * @returns {string}
2872
- */
2873
- function weAtob(string) {
2874
- // 同window.atob: Base64解码为原始字符串
2875
- string = String(string).replace(/[\t\n\f\r ]+/g, '');
2876
- if (!b64re.test(string))
2877
- throw new TypeError("Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.");
2878
- string += '=='.slice(2 - (string.length & 3));
2879
- let bitmap, result = '', r1, r2, i = 0;
2880
- for (const strLen = string.length; i < strLen;) {
2881
- bitmap =
2882
- (b64.indexOf(string.charAt(i++)) << 18) |
2883
- (b64.indexOf(string.charAt(i++)) << 12) |
2884
- ((r1 = b64.indexOf(string.charAt(i++))) << 6) |
2885
- (r2 = b64.indexOf(string.charAt(i++)));
2886
- result +=
2887
- r1 === 64
2888
- ? String.fromCharCode((bitmap >> 16) & 255)
2889
- : r2 === 64
2890
- ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255)
2891
- : String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255, bitmap & 255);
2892
- }
2893
- return result;
2894
- }
2895
- function stringToUint8Array(str) {
2896
- const utf8 = encodeURIComponent(str); // 将字符串转换为 UTF-8 编码
2897
- const uint8Array = new Uint8Array(utf8.length); // 创建 Uint8Array
2898
- for (let i = 0; i < utf8.length; i++) {
2899
- uint8Array[i] = utf8.charCodeAt(i); // 填充 Uint8Array
2900
- }
2901
- return uint8Array;
2902
- }
2903
- function uint8ArrayToString(uint8Array) {
2904
- const utf8 = String.fromCharCode.apply(null, uint8Array); // 将 Uint8Array 转为字符串
2905
- return decodeURIComponent(utf8); // 将 UTF-8 字符串解码回正常字符串
2906
- }
2907
- /**
2908
- * 将base64编码的字符串转换为原始字符串,包括对中文内容的处理(高性能,且支持Web、Node、小程序等任意平台)
2909
- * @param base64 base64编码的字符串
2910
- * @returns 原始字符串,包括中文内容
2911
- */
2912
- function b64decode(base64) {
2913
- const binaryString = !isNullOrUnDef(getGlobal('atob')) ? getGlobal('atob')(base64) : weAtob(base64);
2914
- const len = binaryString.length;
2915
- const bytes = new Uint8Array(len);
2916
- for (let i = 0; i < len; i++) {
2917
- bytes[i] = binaryString.charCodeAt(i);
2918
- }
2919
- // 使用TextDecoder将Uint8Array转换为原始字符串,包括中文内容
2920
- return !isNullOrUnDef(getGlobal('TextDecoder'))
2921
- ? new (getGlobal('TextDecoder'))('utf-8').decode(bytes)
2922
- : uint8ArrayToString(bytes);
2923
- }
2924
- /**
2925
- * 将原始字符串,包括中文内容,转换为base64编码的字符串(高性能,且支持Web、Node、小程序等任意平台)
2926
- * @param rawStr 原始字符串,包括中文内容
2927
- * @returns base64编码的字符串
2928
- */
2929
- function b64encode(rawStr) {
2930
- const utf8Array = !isNullOrUnDef(getGlobal('TextEncoder'))
2931
- ? new (getGlobal('TextEncoder'))().encode(rawStr)
2932
- : stringToUint8Array(rawStr);
2933
- // 将 Uint8Array 转换为二进制字符串
2934
- let binaryString = '';
2935
- const len = utf8Array.length;
2936
- for (let i = 0; i < len; i++) {
2937
- binaryString += String.fromCharCode(utf8Array[i]);
2938
- }
2939
- // 将二进制字符串转换为base64编码的字符串
2940
- return !isNullOrUnDef(getGlobal('btoa')) ? getGlobal('btoa')(binaryString) : weBtoa(binaryString);
2941
- }
2942
-
2943
- // 邮箱
2944
- const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
2945
- /**
2946
- * 判断字符串是否为邮箱格式,不对邮箱真实性做验证,如域名是否正确等
2947
- * @param {string} value
2948
- * @returns {boolean}
2949
- */
2950
- const isEmail = (value) => EMAIL_REGEX.test(value);
2951
- // 手机号码 (中国大陆)
2952
- // reference: https://www.runoob.com/regexp/regexp-syntax.html (?: 是非捕获元之一)
2953
- const PHONE_REGEX = /^(?:(?:\+|00)86)?1\d{10}$/;
2954
- /**
2955
- * 判断字符串是否为宽松手机格式,即首位为 1 的 11 位数字都属于手机号
2956
- * @param {string} value
2957
- * @returns {boolean}
2958
- */
2959
- const isPhone = (value) => PHONE_REGEX.test(value);
2960
- // 身份证号码
2961
- // http://www.stats.gov.cn/tjsj/tjbz/xzqhdm/
2962
- // ["北京市", "天津市", "河北省", "山西省", "内蒙古自治区",
2963
- // "辽宁省", "吉林省", "黑龙江省",
2964
- // "上海市", "江苏省", "浙江省", "安徽省", "福建省", "江西省", "山东省",
2965
- // "河南省", "湖北省", "湖南省", "广东省", "广西壮族自治区", "海南省",
2966
- // "重庆市", "四川省", "贵州省", "云南省", "西藏自治区",
2967
- // "陕西省", "甘肃省", "青海省","宁夏回族自治区", "新疆维吾尔自治区",
2968
- // "台湾省",
2969
- // "香港特别行政区", "澳门特别行政区"]
2970
- // ["11", "12", "13", "14", "15",
2971
- // "21", "22", "23",
2972
- // "31", "32", "33", "34", "35", "36", "37",
2973
- // "41", "42", "43", "44", "45", "46",
2974
- // "50", "51", "52", "53", "54",
2975
- // "61", "62", "63", "64", "65",
2976
- // "71",
2977
- // "81", "82"]
2978
- // 91 国外
2979
- const IDNO_RE = /^(1[1-5]|2[1-3]|3[1-7]|4[1-6]|5[0-4]|6[1-5]|7[1]|8[1-2]|9[1])\d{4}(18|19|20)\d{2}[01]\d[0123]\d{4}[\dxX]$/;
2980
- /**
2981
- * 判断字符串是否为身份证号码格式
2982
- * @param {string} value
2983
- * @returns {boolean}
2984
- */
2985
- const isIdNo = (value) => {
2986
- const isSameFormat = IDNO_RE.test(value);
2987
- if (!isSameFormat)
2988
- return false;
2989
- const year = Number(value.slice(6, 10));
2990
- const month = Number(value.slice(10, 12));
2991
- const date = Number(value.slice(12, 14));
2992
- const d = new Date(year, month - 1, date);
2993
- const isSameDate = d.getFullYear() === year && d.getMonth() + 1 === month && d.getDate() === date;
2994
- if (!isSameDate)
2995
- return false;
2996
- // 将身份证号码前面的17位数分别乘以不同的系数;
2997
- // 从第一位到第十七位的系数分别为:7-9-10-5-8-4-2-1-6-3-7-9-10-5-8-4-2
2998
- // 将这17位数字和系数相乘的结果相加;
2999
- // 用加出来和除以11,看余数是多少;
3000
- // 余数只可能有0-1-2-3-4-5-6-7-8-9-10这11个数字;
3001
- // 其分别对应的最后一位身份证的号码为1-0-X-9-8-7-6-5-4-3-2
3002
- // 通过上面得知如果余数是2,就会在身份证的第18位数字上出现罗马数字的Ⅹ。如果余数是10,身份证的最后一位号码就是2。
3003
- const coefficientList = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
3004
- const residueList = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
3005
- let sum = 0;
3006
- for (let start = 0; start < 17; start++) {
3007
- sum += Number(value.slice(start, start + 1)) * coefficientList[start];
3008
- }
3009
- return residueList[sum % 11] === value.slice(-1);
3010
- };
3011
- const URL_REGEX = /^(https?|ftp):\/\/([^\s/$.?#].[^\s]*)$/i;
3012
- const HTTP_URL_REGEX = /^https?:\/\/([^\s/$.?#].[^\s]*)$/i;
3013
- /**
3014
- * 判断字符串是否为 url 格式,支持 http、https、ftp 协议,支持域名或者 ipV4
3015
- * @param {string} value
3016
- * @returns {boolean}
3017
- */
3018
- const isUrl = (url, includeFtp = false) => {
3019
- const regex = includeFtp ? URL_REGEX : HTTP_URL_REGEX;
3020
- return regex.test(url);
3021
- };
3022
- // ipv4
3023
- const IPV4_REGEX = /^(?:(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.){3}(?:\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])$/;
3024
- // ipv6
3025
- const IPV6_REGEX = /^(([\da-fA-F]{1,4}:){7}[\da-fA-F]{1,4}|([\da-fA-F]{1,4}:){1,7}:|([\da-fA-F]{1,4}:){1,6}:[\da-fA-F]{1,4}|([\da-fA-F]{1,4}:){1,5}(:[\da-fA-F]{1,4}){1,2}|([\da-fA-F]{1,4}:){1,4}(:[\da-fA-F]{1,4}){1,3}|([\da-fA-F]{1,4}:){1,3}(:[\da-fA-F]{1,4}){1,4}|([\da-fA-F]{1,4}:){1,2}(:[\da-fA-F]{1,4}){1,5}|[\da-fA-F]{1,4}:((:[\da-fA-F]{1,4}){1,6})|:((:[\da-fA-F]{1,4}){1,7}|:)|fe80:(:[\da-fA-F]{0,4}){0,4}%[\da-zA-Z]+|::(ffff(:0{1,4})?:)?((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d)|([\da-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1?\d)?\d)\.){3}(25[0-5]|(2[0-4]|1?\d)?\d))$/i;
3026
- /**
3027
- * 判断字符串是否为 IPV4 格式,不对 ip 真实性做验证
3028
- * @param {string} value
3029
- * @returns {boolean}
3030
- */
3031
- const isIpV4 = (value) => IPV4_REGEX.test(value);
3032
- /**
3033
- * 判断字符串是否为 IPV6 格式,不对 ip 真实性做验证
3034
- * @param {string} value
3035
- * @returns {boolean}
3036
- */
3037
- const isIpV6 = (value) => IPV6_REGEX.test(value);
3038
- const INTEGER_RE = /^(-?[1-9]\d*|0)$/;
3039
- /**
3040
- * 判断字符串是否为整数(自然数),即 ...,-3,-2,-1,0,1,2,3,...
3041
- * @param {string} value
3042
- * @returns {boolean}
3043
- */
3044
- const isInteger = (value) => INTEGER_RE.test(value);
3045
- const FLOAT_RE = /^-?([1-9]\d*|0)\.\d*[1-9]$/;
3046
- /**
3047
- * 判断字符串是否为浮点数,即必须有小数点的有理数
3048
- * @param {string} value
3049
- * @returns {boolean}
3050
- */
3051
- const isFloat = (value) => FLOAT_RE.test(value);
3052
- /**
3053
- * 判断字符串是否为正确数值,包括整数和浮点数
3054
- * @param {string} value
3055
- * @returns {boolean}
3056
- */
3057
- const isNumerical = (value) => isInteger(value) || isFloat(value);
3058
- const DIGIT_RE = /^\d+$/;
3059
- /**
3060
- * 判断字符串是否为数字,例如六位数字短信验证码(093031)
3061
- * @param {string} value
3062
- * @returns {boolean}
3063
- */
3064
- const isDigit = (value) => DIGIT_RE.test(value);
3065
-
3066
- /**
3067
- * 去除字符串中重复字符
3068
- * @param {string} str
3069
- * @returns string
3070
- * @example
3071
- *
3072
- * uniqueSymbol('1a1bac');
3073
- * // => '1abc'
3074
- */
3075
- function uniqueSymbol(str) {
3076
- return [...new Set(str.trim().split(''))].join('');
3077
- }
3078
- /**
3079
- * 转义所有特殊字符
3080
- * @param {string} str 原字符串
3081
- * reference: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions
3082
- * @returns string
3083
- */
3084
- function escapeRegExp(str) {
3085
- return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); //$&表示整个被匹配的字符串
3086
- }
3087
- /**
3088
- * 根据左右匹配符号生产解析变量(自动删除变量内的空白)
3089
- * @param {string} leftMatchSymbol
3090
- * @param {string} rightMatchSymbol
3091
- * @returns RegExp
3092
- */
3093
- function parseVariableRegExp(leftMatchSymbol, rightMatchSymbol) {
3094
- return new RegExp(`${escapeRegExp(leftMatchSymbol.trim())}\\s*([^${escapeRegExp(uniqueSymbol(leftMatchSymbol))}${escapeRegExp(uniqueSymbol(rightMatchSymbol))}\\s]*)\\s*${rightMatchSymbol.trim()}`, 'g');
3095
- }
3096
- /**
3097
- * 解析字符串的插值变量
3098
- * @param {string} str 字符串
3099
- * @param {string} leftMatchSymbol 变量左侧匹配符号,默认:{
3100
- * @param {string} rightMatchSymbol 变量右侧匹配符号,默认:}
3101
- * @returns string[]
3102
- * @example
3103
- *
3104
- * default match symbol {} same as /{\s*([^{}\s]*)\s*}/g
3105
- */
3106
- function parseVarFromString(str, leftMatchSymbol = '{', rightMatchSymbol = '}') {
3107
- // @ts-ignore
3108
- return Array.from(str.matchAll(parseVariableRegExp(leftMatchSymbol, rightMatchSymbol))).map(el => isNullOrUnDef(el) ? void 0 : el[1]);
3109
- }
3110
- /**
3111
- * 替换字符串中的插值变量
3112
- * @param {string} sourceStr
3113
- * @param {Record<string, any>} targetObj
3114
- * @param {string} leftMatchSymbol 变量左侧匹配符号,默认:{
3115
- * @param {string} rightMatchSymbol 变量右侧匹配符号,默认:}
3116
- * @returns string
3117
- */
3118
- function replaceVarFromString(sourceStr, targetObj, leftMatchSymbol = '{', rightMatchSymbol = '}') {
3119
- return sourceStr.replace(new RegExp(parseVariableRegExp(leftMatchSymbol, rightMatchSymbol)), function (m, p1) {
3120
- return objectHas(targetObj, p1) ? targetObj[p1] : m;
3121
- });
3122
- }
3123
- /**
3124
- * 在指定作用域中执行代码
3125
- * @param {string} code 要执行的代码(需包含 return 语句或表达式)
3126
- * @param {Object} scope 作用域对象(键值对形式的变量环境)
3127
- * @returns 代码执行结果
3128
- *
3129
- * @example
3130
- * // 测试用例 1: 基本变量访问
3131
- * executeInScope("return a + b;", { a: 1, b: 2 });
3132
- * // 3
3133
- *
3134
- * // 测试用例 2: 支持复杂表达式和运算
3135
- * executeInScope(
3136
- * "return Array.from({ length: 3 }, (_, i) => base + i);",
3137
- * { base: 100 }
3138
- * );
3139
- * // [100, 101, 102]
3140
- *
3141
- * // 支持外传函数作用域执行
3142
- * const scope = {
3143
- * $: {
3144
- * fun: {
3145
- * time: {
3146
- * now: function () {
3147
- * return new Date();
3148
- * },
3149
- * },
3150
- * },
3151
- * },
3152
- * };
3153
- * executeInScope("return $.fun.time.now()", scope)
3154
- */
3155
- function executeInScope(code, scope = {}) {
3156
- // 提取作用域对象的键和值
3157
- const keys = Object.keys(scope);
3158
- const values = keys.map(key => scope[key]);
3159
- try {
3160
- // 动态创建函数,将作用域的键作为参数,代码作为函数体
3161
- const func = new Function(...keys, `return (() => { ${code} })()`);
3162
- // 调用函数并传入作用域的值
3163
- return func(...values);
3164
- }
3165
- catch (error) {
3166
- throw new Error(`代码执行失败: ${error.message}`);
3167
- }
3168
- }
3169
-
3170
- /**
3171
- * 深拷贝堪称完全体 即:任何类型的数据都会被深拷贝
3172
- *
3173
- * 包含对null、原始值、对象循环引用的处理
3174
- *
3175
- * 对Map、Set、ArrayBuffer、Date、RegExp、Array、Object及原型链属性方法执行深拷贝
3176
- * @param {T} source
3177
- * @param {WeakMap} map
3178
- * @returns {T}
3179
- */
3180
- function cloneDeep(source, map = new WeakMap()) {
3181
- // 处理原始类型和 null/undefined
3182
- if (source === null || typeof source !== 'object') {
3183
- return source;
3184
- }
3185
- // 处理循环引用
3186
- if (map.has(source)) {
3187
- return map.get(source);
3188
- }
3189
- // 处理 ArrayBuffer
3190
- if (source instanceof ArrayBuffer) {
3191
- const copy = new ArrayBuffer(source.byteLength);
3192
- new Uint8Array(copy).set(new Uint8Array(source));
3193
- map.set(source, copy);
3194
- return copy;
3195
- }
3196
- // 处理 DataView 和 TypedArray (Uint8Array 等)
3197
- if (ArrayBuffer.isView(source)) {
3198
- const constructor = source.constructor;
3199
- const bufferCopy = cloneDeep(source.buffer, map);
3200
- return new constructor(bufferCopy, source.byteOffset, source.length);
3201
- }
3202
- // 处理 Date 对象
3203
- if (source instanceof Date) {
3204
- const copy = new Date(source.getTime());
3205
- map.set(source, copy);
3206
- return copy;
3207
- }
3208
- // 处理 RegExp 对象
3209
- if (source instanceof RegExp) {
3210
- const copy = new RegExp(source.source, source.flags);
3211
- copy.lastIndex = source.lastIndex; // 保留匹配状态
3212
- map.set(source, copy);
3213
- return copy;
3214
- }
3215
- // 处理 Map
3216
- if (source instanceof Map) {
3217
- const copy = new Map();
3218
- map.set(source, copy);
3219
- source.forEach((value, key) => {
3220
- copy.set(cloneDeep(key, map), cloneDeep(value, map));
3221
- });
3222
- return copy;
3223
- }
3224
- // 处理 Set
3225
- if (source instanceof Set) {
3226
- const copy = new Set();
3227
- map.set(source, copy);
3228
- source.forEach(value => {
3229
- copy.add(cloneDeep(value, map));
3230
- });
3231
- return copy;
3232
- }
3233
- // 处理数组 (包含稀疏数组)
3234
- if (Array.isArray(source)) {
3235
- const copy = new Array(source.length);
3236
- map.set(source, copy);
3237
- // 克隆所有有效索引
3238
- for (let i = 0, len = source.length; i < len; i++) {
3239
- if (i in source) {
3240
- // 保留空位
3241
- copy[i] = cloneDeep(source[i], map);
3242
- }
3243
- }
3244
- // 克隆数组的自定义属性
3245
- const descriptors = Object.getOwnPropertyDescriptors(source);
3246
- for (const key of Reflect.ownKeys(descriptors)) {
3247
- Object.defineProperty(copy, key, {
3248
- ...descriptors[key],
3249
- value: cloneDeep(descriptors[key].value, map)
3250
- });
3251
- }
3252
- return copy;
3253
- }
3254
- // 处理普通对象和类实例
3255
- const copy = Object.create(Object.getPrototypeOf(source));
3256
- map.set(source, copy);
3257
- const descriptors = Object.getOwnPropertyDescriptors(source);
3258
- for (const key of Reflect.ownKeys(descriptors)) {
3259
- const descriptor = descriptors[key];
3260
- if ('value' in descriptor) {
3261
- // 克隆数据属性
3262
- descriptor.value = cloneDeep(descriptor.value, map);
3263
- }
3264
- else {
3265
- // 处理访问器属性 (getter/setter)
3266
- if (descriptor.get) {
3267
- descriptor.get = cloneDeep(descriptor.get, map);
3268
- }
3269
- if (descriptor.set) {
3270
- descriptor.set = cloneDeep(descriptor.set, map);
3271
- }
3272
- }
3273
- Object.defineProperty(copy, key, descriptor);
3274
- }
3275
- return copy;
3276
- }
3277
-
3278
- export { EMAIL_REGEX, HEX_POOL, HTTP_URL_REGEX, IPV4_REGEX, IPV6_REGEX, PHONE_REGEX, STRING_ARABIC_NUMERALS, STRING_LOWERCASE_ALPHA, STRING_POOL, STRING_UPPERCASE_ALPHA, UNIQUE_NUMBER_SAFE_LENGTH, URL_REGEX, add, addClass, arrayEach, arrayEachAsync, arrayInsertBefore, arrayLike, arrayRemove, asyncMap, b64decode, b64encode, calculateDate, calculateDateTime, chooseLocalFile, cloneDeep, compressImg, cookieDel, cookieGet, cookieSet, copyText, crossOriginDownload, dateParse, dateToEnd, dateToStart, debounce, divide, downloadBlob, downloadData, downloadHref, downloadURL, easingFunctional, easingStringify, escapeRegExp, executeInScope, fallbackCopyText, flatTree, forEachDeep, formatDate, formatNumber as formatMoney, formatNumber, formatTree, fuzzySearchTree, genCanvasWM, getComputedCssVal, getEasing, getGlobal, getStrWidthPx, getStyle, hasClass, humanFileSize, isArray, isBigInt, isBoolean, isDate, isDigit, isEmail, isEmpty, isError, isFloat, isFunction, isIdNo, isInteger, isIpV4, isIpV6, isJsonString, isNaN, isNodeList, isNull, isNullOrUnDef, isNullOrUnDef as isNullish, isNumber, isNumerical, isObject, isPhone, isPlainObject, isPrimitive, isRegExp, isString, isSymbol, isUndefined, isUrl, isValidDate, mapDeep, multiply, numberAbbr, numberToHex, objectAssign, objectEach, objectEachAsync, objectFill, objectGet, objectHas, objectMap, objectAssign as objectMerge, objectOmit, objectPick, once, parseQueryParams, parseVarFromString, pathJoin, pathNormalize, qsParse, qsStringify, randomNumber, randomString, randomUuid, removeClass, replaceVarFromString, safeAwait, searchTreeById, select, setEasing, setGlobal, setStyle, smoothScroll, stringAssign, stringCamelCase, stringEscapeHtml, stringFill, stringFormat, stringKebabCase, strip, subtract, supportCanvas, throttle, tooltipEvent, typeIs, uniqueNumber, uniqueString, uniqueSymbol, urlDelParams, urlParse, urlSetParams, urlStringify, wait, weAtob, weBtoa };