nhanh-pure-function 1.3.10 → 1.3.11

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.
@@ -1,513 +1,537 @@
1
- /**
2
- * 非null | undefined判断
3
- * @param value any
4
- * @returns boolean
5
- */
6
- export function _NotNull(value) {
7
- return value !== null && value !== undefined;
8
- }
9
-
10
- /**
11
- * 是正常对象吗
12
- * @param {} value
13
- * @returns boolean
14
- */
15
- export function _IsObject(value) {
16
- return !(value === null || typeof value !== "object" || Array.isArray(value));
17
- }
18
-
19
- /**
20
- * 寻找空闲时机执行传入方法
21
- * @param callback 需执行的方法
22
- */
23
- export function _ExecuteWhenIdle(callback) {
24
- if (typeof callback !== "function")
25
- return console.error("非函数:", callback);
26
- const loop = function (deadline) {
27
- if (deadline.didTimeout || deadline.timeRemaining() <= 0)
28
- requestIdleCallback(loop);
29
- else callback();
30
- };
31
- requestIdleCallback(loop);
32
- }
33
-
34
- /**
35
- * 等待条件满足
36
- * @param conditionChecker 条件检查器
37
- * @param timeoutMillis 超时毫秒数
38
- * @returns Promise<unknown>
39
- */
40
- export function _WaitForCondition(conditionChecker, timeoutMillis) {
41
- const startTime = new Date() - 0;
42
- return new Promise((resolve, reject) => {
43
- const checkCondition = () => {
44
- const nowTime = new Date() - 0;
45
- if (nowTime - startTime >= timeoutMillis) return reject("超时");
46
- if (conditionChecker()) return resolve("完成");
47
- requestIdleCallback(checkCondition);
48
- };
49
- checkCondition();
50
- });
51
- }
52
-
53
- /**
54
- * 排除子串
55
- * @param inputString 需裁剪字符串
56
- * @param substringToDelete 被裁减字符串
57
- * @param delimiter 分隔符
58
- * @returns 裁减后的字符串
59
- */
60
- export function _ExcludeSubstring(
61
- inputString,
62
- substringToDelete,
63
- delimiter = ","
64
- ) {
65
- const regex = new RegExp(
66
- `(^|${delimiter})${substringToDelete}(${delimiter}|$)`,
67
- "g"
68
- );
69
- return inputString.replace(regex, function ($0, $1, $2) {
70
- return $1 === $2 ? delimiter : "";
71
- });
72
- }
73
-
74
- /**
75
- * 首字母大写
76
- * @param str
77
- * @returns string
78
- */
79
- export function _CapitalizeFirstLetter(string) {
80
- return string.charAt(0).toUpperCase() + string.slice(1);
81
- }
82
-
83
- /**
84
- * 合并对象 注意: 本函数会直接操作 A
85
- * @param {Object | Array} A
86
- * @param {Object | Array} B
87
- * @returns A&B || B
88
- */
89
- export function _MergeObjects(A, B, visitedObjects = []) {
90
- const getType = (v) => (Array.isArray(v) ? "array" : typeof v);
91
- const TA = getType(A);
92
- const TB = getType(B);
93
-
94
- if (TA != TB) return B;
95
- if (visitedObjects.some((item) => item == B)) return B;
96
-
97
- if (TA == "object") {
98
- visitedObjects.push(A, B);
99
- for (const key in B) {
100
- if (Object.prototype.hasOwnProperty.call(B, key)) {
101
- const BC = B[key];
102
- const AC = A[key];
103
- const fianlValue = _MergeObjects(AC, BC, visitedObjects);
104
- A[key] = fianlValue;
105
- }
106
- }
107
- return A;
108
- } else if (TA == "array") {
109
- visitedObjects.push(A, B);
110
- B.forEach((item, index) => {
111
- const BC = item;
112
- const AC = A[index];
113
- const fianlValue = _MergeObjects(AC, BC, visitedObjects);
114
- A[index] = fianlValue;
115
- });
116
- return A;
117
- } else return B;
118
- }
119
-
120
- /**
121
- * 时间戳转换字符串
122
- * @param {Number | Date} time 时间戳或Date对象
123
- * @param {String} template 完整模板 --> yyyy MM DD hh mm ss ms
124
- * @param {Boolean} pad 补0
125
- */
126
- export function _TimeTransition(time, template, pad = true) {
127
- try {
128
- time = new Date(time);
129
- } catch (error) {
130
- console.error(error);
131
- return "";
132
- }
133
- const dictionary = {
134
- yyyy: "getFullYear",
135
- MM: "getMonth",
136
- DD: "getDate",
137
- hh: "getHours",
138
- mm: "getMinutes",
139
- ss: "getSeconds",
140
- ms: (num) => +num % 1000,
141
- };
142
- for (const key in dictionary) {
143
- if (Object.hasOwnProperty.call(dictionary, key)) {
144
- if (new RegExp(key).test(template)) {
145
- let value,
146
- fun = dictionary[key];
147
-
148
- if (typeof fun == "function") value = fun(time);
149
- else value = time[fun]();
150
-
151
- if (key == "MM") value++;
152
-
153
- if (pad) value = String(value).padStart(2, "0");
154
-
155
- template = template.replace(key, value);
156
- }
157
- }
158
- }
159
- return template;
160
- }
161
-
162
- /**
163
- * 读取文件
164
- * @param src 文件地址
165
- * @returns 文件的字符串内容
166
- */
167
- export function _ReadFile(src) {
168
- return new Promise((resolve, reject) => {
169
- fetch(src)
170
- .then((response) => resolve(response.text()))
171
- .catch((error) => {
172
- console.error("Error fetching :", error);
173
- reject(error);
174
- });
175
- });
176
- }
177
-
178
- /**
179
- * 从给定的URL中提取文件名
180
- * 如果无法提取文件名,则返回默认的文件名
181
- *
182
- * @param {string} href - 包含文件路径的URL
183
- * @param {string} [defaultName="file"] - 默认的文件名,当无法提取时使用
184
- * @returns {string} 提取到的文件名或默认的文件名
185
- */
186
- export function _GetHrefName(href, defaultName = "file") {
187
- // 分割URL并获取最后一个路径段,然后去除查询字符串,最后返回文件名或默认名
188
- return href.split("/").pop().split("?")[0] || defaultName;
189
- }
190
-
191
- /**
192
- * 下载文件
193
- * @param {string} href - 文件路径
194
- * @param {string} [fileName] - 导出文件名
195
- */
196
- export async function _DownloadFile(href, fileName) {
197
- try {
198
- const response = await fetch(href); // 获取文件
199
- if (!response.ok) throw new Error("文件下载失败");
200
-
201
- const blob = await response.blob(); // 将响应转换为 Blob 对象
202
- const url = URL.createObjectURL(blob); // 创建文件 URL
203
-
204
- const a = document.createElement("a");
205
- a.href = url;
206
- a.download = fileName || _GetHrefName(href, "image");
207
-
208
- // 临时将 a 标签添加到 DOM,然后触发点击事件,最后移除
209
- document.body.appendChild(a);
210
- a.click();
211
- document.body.removeChild(a);
212
-
213
- // 释放 URL 对象
214
- URL.revokeObjectURL(url);
215
- } catch (error) {
216
- console.error("下载文件时发生错误:", error);
217
- }
218
- }
219
-
220
- /**
221
- * 获取帧率
222
- * @param {(fps , frameTime)=>void} callback callback( 帧率 , 每帧时间 )
223
- * @param {Number} referenceNode 参考节点数量
224
- */
225
- export function _GetFrameRate(callback, referenceNode = 10) {
226
- let t,
227
- l = referenceNode;
228
- function loop() {
229
- if (l > 0) {
230
- l--;
231
- requestAnimationFrame(loop);
232
- } else {
233
- const time = new Date() - t;
234
- const frameTime = time / referenceNode;
235
- const fps = 1000 / frameTime;
236
- callback(Number(fps.toFixed(2)), Number(frameTime.toFixed(2)));
237
- }
238
- }
239
- requestAnimationFrame(() => {
240
- t = new Date() - 0;
241
- loop();
242
- });
243
- }
244
-
245
- /**
246
- * 单位转换 12** -> **px
247
- * @param {string} width
248
- * @returns 对应的单位为px的宽
249
- */
250
- export function _GetOtherSizeInPixels(width) {
251
- if (/px/.test(width)) return width;
252
- const dom = document.createElement("div");
253
- dom.style.width = width;
254
- document.body.appendChild(dom);
255
- width = parseFloat(window.getComputedStyle(dom).width);
256
- document.body.removeChild(dom);
257
- return width;
258
- }
259
-
260
- /**
261
- * 驼峰命名
262
- * @param {字符串} str
263
- * @param {是否删除分割字符} isRemoveDelimiter
264
- * @returns 'wq1wqw-qw2qw' -> 'wq1Wqw-Qw2Qw' / 'wqWqwQwQw'
265
- */
266
- export function _ConvertToCamelCase(str, isRemoveDelimiter) {
267
- str = str.replace(/([^a-zA-Z][a-z])/g, (match) => match.toUpperCase());
268
- if (isRemoveDelimiter) return str.replace(/[^a-zA-Z]+/g, "");
269
- return str;
270
- }
271
-
272
- /**
273
- * 创建文件并下载
274
- * @param {BlobPart[]} content 文件内容
275
- * @param {string} fileName 文件名称
276
- * @param {BlobPropertyBag} options Blob 配置
277
- */
278
- export function _CreateAndDownloadFile(content, fileName, options) {
279
- if (!options) {
280
- let type = fileName.replace(/^[^.]+./, "");
281
- type = type == fileName ? "text/plain" : "application/" + type;
282
- options = { type };
283
- }
284
- const bolb = new Blob(content, options);
285
- // 创建一个 URL,该 URL 可以用于在浏览器中引用 Blob 对象(例如,在 <a> 标签的 href 属性中)
286
- const url = URL.createObjectURL(bolb);
287
- // 你可以创建一个链接来下载这个 Blob 对象
288
- const downloadLink = document.createElement("a");
289
- downloadLink.href = url;
290
- downloadLink.download = fileName; // 设置下载文件的名称
291
- document.body.appendChild(downloadLink); // 添加到文档中
292
- downloadLink.click(); // 模拟点击以开始下载
293
- document.body.removeChild(downloadLink); // 然后从文档中移除
294
- // 最后,别忘了撤销 Blob 对象的 URL,以释放资源
295
- URL.revokeObjectURL(url);
296
- }
297
-
298
- /**
299
- * 获取url参数
300
- * @param {string} url
301
- * @returns {Object}
302
- */
303
- export function _GetQueryParams(url) {
304
- const queryString = url.split("?")[1] || "";
305
- const params = new URLSearchParams(queryString);
306
- const result = {};
307
-
308
- params.forEach((value, key) => {
309
- result[key] = value;
310
- });
311
-
312
- return result;
313
- }
314
-
315
- /**
316
- * 生成一个UUID(通用唯一标识符)字符串
317
- * 可以选择性地在UUID前面添加前缀
318
- *
319
- * @param {string} prefix - 可选参数,要添加到UUID前面的前缀
320
- * @returns {string} 一个带有可选前缀的UUID字符串
321
- */
322
- export function _GenerateUUID(prefix = "") {
323
- return (
324
- prefix +
325
- "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
326
- const r = (Math.random() * 16) | 0; // 随机生成一个0到15的数
327
- const v = c === "x" ? r : (r & 0x3) | 0x8; // 对于'y'位, v = (r & 0x3 | 0x8) 确保变体正确
328
- return v.toString(16); // 将数字转换为16进制
329
- })
330
- );
331
- }
332
-
333
- /**
334
- * 防抖
335
- * @param {Function} fn
336
- * @param {number} delay
337
- * @returns {Function}
338
- */
339
- export function _Debounce(fn, delay) {
340
- let timeoutId;
341
- return function (...args) {
342
- clearTimeout(timeoutId);
343
- timeoutId = setTimeout(() => fn.apply(this, args), delay);
344
- };
345
- }
346
-
347
- /**
348
- * 节流
349
- * @param {Function} fn
350
- * @param {number} delay
351
- * @returns {Function}
352
- */
353
- export function _Throttle(fn, delay) {
354
- let timer;
355
- return function (...args) {
356
- if (!timer) {
357
- timer = setTimeout(() => {
358
- fn.apply(this, args);
359
- timer = null;
360
- }, delay);
361
- }
362
- };
363
- }
364
-
365
- /**
366
- * 数据类型
367
- * @param {any} value
368
- * @returns string
369
- */
370
- export function _DataType(value) {
371
- if (Array.isArray(value)) return "array";
372
- if (value === null) return "null";
373
- return typeof value;
374
- }
375
-
376
- /**
377
- * 复制到剪贴板
378
- * @param {string} text
379
- */
380
- export function _CopyToClipboard(text) {
381
- const handleSuccess = () => Promise.resolve();
382
- const handleError = (error) => {
383
- console.error(error);
384
- return Promise.reject(error);
385
- };
386
-
387
- /** 最新方式 */
388
- function writeText() {
389
- return navigator.clipboard
390
- .writeText(text)
391
- .then(handleSuccess)
392
- .catch(handleError);
393
- }
394
- /** 旧方式 - createRange */
395
- function createRange() {
396
- const div = document.createElement("div");
397
- div.innerText = text;
398
- document.body.appendChild(div);
399
-
400
- const range = document.createRange();
401
- range.selectNodeContents(div);
402
- const selection = window.getSelection();
403
-
404
- let isFinished = false;
405
- if (selection) {
406
- selection.removeAllRanges();
407
- selection.addRange(range);
408
-
409
- isFinished = document.execCommand("copy");
410
- }
411
- document.body.removeChild(div);
412
- return isFinished ? Promise.resolve() : Promise.reject();
413
- }
414
- /** 旧方式 - execCommand */
415
- function execCommand() {
416
- const textarea = document.createElement("textarea");
417
- textarea.value = text;
418
- document.body.appendChild(textarea);
419
-
420
- textarea.select();
421
- textarea.setSelectionRange(0, text.length); // 对于移动设备
422
-
423
- let isFinished = false;
424
-
425
- /** aria-hidden 及 tabindex 可能会影响聚焦元素 */
426
- if (document.activeElement === textarea)
427
- isFinished = document.execCommand("Copy", true);
428
-
429
- document.body.removeChild(textarea);
430
- return isFinished ? Promise.resolve() : Promise.reject();
431
- }
432
-
433
- function old() {
434
- return createRange()
435
- .then(handleSuccess)
436
- .catch(() => {
437
- execCommand()
438
- .then(handleSuccess)
439
- .catch(() => handleError("复制方式尽皆失效"));
440
- });
441
- }
442
-
443
- if (navigator.clipboard) return writeText().catch(old);
444
- return old();
445
- }
446
-
447
- /**
448
- * 格式化文件大小
449
- * @param {number} size
450
- * @returns string
451
- */
452
- export function _FormatFileSize(size) {
453
- const units = ["B", "KB", "MB", "GB", "TB", "PB"];
454
- let unitIndex = 0;
455
- while (size > 1024) {
456
- size /= 1024;
457
- unitIndex++;
458
- }
459
- return `${Math.round(size * 100) / 100} ${units[unitIndex]}`;
460
- }
461
-
462
- /**
463
- * 根据路径初始化目标对象
464
- * 如果路径中某个属性不存在,则会创建该属性及其所有父属性
465
- * 最终返回路径的最后一个属性对应的值或undefined(如果路径不存在)
466
- *
467
- * @param {Object} model - 要初始化的模型对象
468
- * @param {string} path - 属性路径,使用英文句点分隔
469
- * @returns {any} 路径的最后一个属性对应的值或undefined
470
- */
471
- export function _InitTargetByPath(model, path) {
472
- const arr = path.split(".");
473
- return arr.reduce((prev, curr, index) => {
474
- if (!(curr in prev)) {
475
- if (index === arr.length - 1) prev[curr] = undefined;
476
- else prev[curr] = {};
477
- }
478
- return prev[curr];
479
- }, model);
480
- }
481
- /**
482
- * 根据路径获取目标对象
483
- * 该函数用于在给定的模型中,通过路径字符串来获取深层嵌套的目标对象如果路径中的某一部分不存在,则会创建一个新的对象(除非已经是路径的最后一部分)
484
- *
485
- * @param {Object} model - 包含要查询的数据的模型对象
486
- * @param {string} path - 用点分隔的路径字符串,表示要访问的对象属性路径
487
- * @returns {Object|undefined} - 返回目标对象,如果路径不存在则返回undefined
488
- */
489
- export function _GetTargetByPath(model, path) {
490
- const arr = path.split(".");
491
- return arr.reduce((prev, curr, index) => {
492
- if (prev.hasOwnProperty(curr)) return prev[curr];
493
- return (prev[curr] = index == arr.length - 1 ? undefined : {});
494
- }, model);
495
- }
496
- /**
497
- * 根据路径更新目标值
498
- *
499
- * 该函数通过一个点分隔的路径来更新一个对象中的嵌套属性值
500
- * 它使用了reduce方法来遍历路径数组,并在路径的终点设置新的值
501
- *
502
- * @param {Object} model - 包含要更新数据的模型对象
503
- * @param {string} path - 点分隔的字符串路径,指示如何到达目标属性
504
- * @param {*} value - 要设置的新值
505
- * @returns {*} - 返回更新后的模型对象中的值
506
- */
507
- export function _UpdateTargetByPath(model, path, value) {
508
- const arr = path.split(".");
509
- return arr.reduce((prev, curr, index) => {
510
- if (index === arr.length - 1) prev[curr] = value;
511
- return prev[curr];
512
- }, model);
513
- }
1
+ /**
2
+ * 非null | undefined判断
3
+ * @param value any
4
+ * @returns boolean
5
+ */
6
+ export function _NotNull(value) {
7
+ return value !== null && value !== undefined;
8
+ }
9
+
10
+ /**
11
+ * 是正常对象吗
12
+ * @param {} value
13
+ * @returns boolean
14
+ */
15
+ export function _IsObject(value) {
16
+ return !(value === null || typeof value !== "object" || Array.isArray(value));
17
+ }
18
+
19
+ /**
20
+ * 寻找空闲时机执行传入方法
21
+ * @param callback 需执行的方法
22
+ */
23
+ export function _ExecuteWhenIdle(callback) {
24
+ if (typeof callback !== "function")
25
+ return console.error("非函数:", callback);
26
+ const loop = function (deadline) {
27
+ if (deadline.didTimeout || deadline.timeRemaining() <= 0)
28
+ requestIdleCallback(loop);
29
+ else callback();
30
+ };
31
+ requestIdleCallback(loop);
32
+ }
33
+
34
+ /**
35
+ * 等待条件满足
36
+ * @param conditionChecker 条件检查器
37
+ * @param timeoutMillis 超时毫秒数
38
+ * @returns Promise<unknown>
39
+ */
40
+ export function _WaitForCondition(conditionChecker, timeoutMillis) {
41
+ const startTime = new Date() - 0;
42
+ return new Promise((resolve, reject) => {
43
+ const checkCondition = () => {
44
+ const nowTime = new Date() - 0;
45
+ if (nowTime - startTime >= timeoutMillis) return reject("超时");
46
+ if (conditionChecker()) return resolve("完成");
47
+ requestIdleCallback(checkCondition);
48
+ };
49
+ checkCondition();
50
+ });
51
+ }
52
+
53
+ /**
54
+ * 排除子串
55
+ * @param inputString 需裁剪字符串
56
+ * @param substringToDelete 被裁减字符串
57
+ * @param delimiter 分隔符
58
+ * @returns 裁减后的字符串
59
+ */
60
+ export function _ExcludeSubstring(
61
+ inputString,
62
+ substringToDelete,
63
+ delimiter = ","
64
+ ) {
65
+ const regex = new RegExp(
66
+ `(^|${delimiter})${substringToDelete}(${delimiter}|$)`,
67
+ "g"
68
+ );
69
+ return inputString.replace(regex, function ($0, $1, $2) {
70
+ return $1 === $2 ? delimiter : "";
71
+ });
72
+ }
73
+
74
+ /**
75
+ * 首字母大写
76
+ * @param str
77
+ * @returns string
78
+ */
79
+ export function _CapitalizeFirstLetter(string) {
80
+ return string.charAt(0).toUpperCase() + string.slice(1);
81
+ }
82
+
83
+ /**
84
+ * 合并对象 注意: 本函数会直接操作 A
85
+ * @param {Object | Array} A
86
+ * @param {Object | Array} B
87
+ * @returns A&B || B
88
+ */
89
+ export function _MergeObjects(A, B, visitedObjects = []) {
90
+ const getType = (v) => (Array.isArray(v) ? "array" : typeof v);
91
+ const TA = getType(A);
92
+ const TB = getType(B);
93
+
94
+ if (TA != TB) return B;
95
+ if (visitedObjects.some((item) => item == B)) return B;
96
+
97
+ if (TA == "object") {
98
+ visitedObjects.push(A, B);
99
+ for (const key in B) {
100
+ if (Object.prototype.hasOwnProperty.call(B, key)) {
101
+ const BC = B[key];
102
+ const AC = A[key];
103
+ const fianlValue = _MergeObjects(AC, BC, visitedObjects);
104
+ A[key] = fianlValue;
105
+ }
106
+ }
107
+ return A;
108
+ } else if (TA == "array") {
109
+ visitedObjects.push(A, B);
110
+ B.forEach((item, index) => {
111
+ const BC = item;
112
+ const AC = A[index];
113
+ const fianlValue = _MergeObjects(AC, BC, visitedObjects);
114
+ A[index] = fianlValue;
115
+ });
116
+ return A;
117
+ } else return B;
118
+ }
119
+
120
+ /**
121
+ * 时间戳转换字符串
122
+ * @param {Number | Date} time 时间戳或Date对象
123
+ * @param {String} template 完整模板 --> yyyy MM DD hh mm ss ms
124
+ * @param {Boolean} pad 补0
125
+ */
126
+ export function _TimeTransition(time, template, pad = true) {
127
+ try {
128
+ time = new Date(time);
129
+ } catch (error) {
130
+ console.error(error);
131
+ return "";
132
+ }
133
+ const dictionary = {
134
+ yyyy: "getFullYear",
135
+ MM: "getMonth",
136
+ DD: "getDate",
137
+ hh: "getHours",
138
+ mm: "getMinutes",
139
+ ss: "getSeconds",
140
+ ms: (num) => +num % 1000,
141
+ };
142
+ for (const key in dictionary) {
143
+ if (Object.hasOwnProperty.call(dictionary, key)) {
144
+ if (new RegExp(key).test(template)) {
145
+ let value,
146
+ fun = dictionary[key];
147
+
148
+ if (typeof fun == "function") value = fun(time);
149
+ else value = time[fun]();
150
+
151
+ if (key == "MM") value++;
152
+
153
+ if (pad) value = String(value).padStart(2, "0");
154
+
155
+ template = template.replace(key, value);
156
+ }
157
+ }
158
+ }
159
+ return template;
160
+ }
161
+
162
+ /**
163
+ * 读取文件
164
+ * @param src 文件地址
165
+ * @returns 文件的字符串内容
166
+ */
167
+ export function _ReadFile(src) {
168
+ return new Promise((resolve, reject) => {
169
+ fetch(src)
170
+ .then((response) => resolve(response.text()))
171
+ .catch((error) => {
172
+ console.error("Error fetching :", error);
173
+ reject(error);
174
+ });
175
+ });
176
+ }
177
+
178
+ /**
179
+ * 从给定的URL中提取文件名
180
+ * 如果无法提取文件名,则返回默认的文件名
181
+ *
182
+ * @param {string} href - 包含文件路径的URL
183
+ * @param {string} [defaultName="file"] - 默认的文件名,当无法提取时使用
184
+ * @returns {string} 提取到的文件名或默认的文件名
185
+ */
186
+ export function _GetHrefName(href, defaultName = "file") {
187
+ // 分割URL并获取最后一个路径段,然后去除查询字符串,最后返回文件名或默认名
188
+ return href.split("/").pop().split("?")[0] || defaultName;
189
+ }
190
+
191
+ /**
192
+ * 下载文件
193
+ * @param {string} href - 文件路径
194
+ * @param {string} [fileName] - 导出文件名
195
+ */
196
+ export async function _DownloadFile(href, fileName) {
197
+ try {
198
+ const response = await fetch(href); // 获取文件
199
+ if (!response.ok) throw new Error("文件下载失败");
200
+
201
+ const blob = await response.blob(); // 将响应转换为 Blob 对象
202
+ const url = URL.createObjectURL(blob); // 创建文件 URL
203
+
204
+ const a = document.createElement("a");
205
+ a.href = url;
206
+ a.download = fileName || _GetHrefName(href, "image");
207
+
208
+ // 临时将 a 标签添加到 DOM,然后触发点击事件,最后移除
209
+ document.body.appendChild(a);
210
+ a.click();
211
+ document.body.removeChild(a);
212
+
213
+ // 释放 URL 对象
214
+ URL.revokeObjectURL(url);
215
+ } catch (error) {
216
+ console.error("下载文件时发生错误:", error);
217
+ }
218
+ }
219
+
220
+ /**
221
+ * 获取帧率
222
+ * @param {(fps , frameTime)=>void} callback callback( 帧率 , 每帧时间 )
223
+ * @param {Number} referenceNode 参考节点数量
224
+ */
225
+ export function _GetFrameRate(callback, referenceNode = 10) {
226
+ let t,
227
+ l = referenceNode;
228
+ function loop() {
229
+ if (l > 0) {
230
+ l--;
231
+ requestAnimationFrame(loop);
232
+ } else {
233
+ const time = new Date() - t;
234
+ const frameTime = time / referenceNode;
235
+ const fps = 1000 / frameTime;
236
+ callback(Number(fps.toFixed(2)), Number(frameTime.toFixed(2)));
237
+ }
238
+ }
239
+ requestAnimationFrame(() => {
240
+ t = new Date() - 0;
241
+ loop();
242
+ });
243
+ }
244
+
245
+ /**
246
+ * 单位转换 12** -> **px
247
+ * @param {string} width
248
+ * @returns 对应的单位为px的宽
249
+ */
250
+ export function _GetOtherSizeInPixels(width) {
251
+ if (/px/.test(width)) return width;
252
+ const dom = document.createElement("div");
253
+ dom.style.width = width;
254
+ document.body.appendChild(dom);
255
+ width = parseFloat(window.getComputedStyle(dom).width);
256
+ document.body.removeChild(dom);
257
+ return width;
258
+ }
259
+
260
+ /**
261
+ * 驼峰命名
262
+ * @param {字符串} str
263
+ * @param {是否删除分割字符} isRemoveDelimiter
264
+ * @returns 'wq1wqw-qw2qw' -> 'wq1Wqw-Qw2Qw' / 'wqWqwQwQw'
265
+ */
266
+ export function _ConvertToCamelCase(str, isRemoveDelimiter) {
267
+ str = str.replace(/([^a-zA-Z][a-z])/g, (match) => match.toUpperCase());
268
+ if (isRemoveDelimiter) return str.replace(/[^a-zA-Z]+/g, "");
269
+ return str;
270
+ }
271
+
272
+ /**
273
+ * 创建文件并下载
274
+ * @param {BlobPart[]} content 文件内容
275
+ * @param {string} fileName 文件名称
276
+ * @param {BlobPropertyBag} options Blob 配置
277
+ */
278
+ export function _CreateAndDownloadFile(content, fileName, options) {
279
+ if (!options) {
280
+ let type = fileName.replace(/^[^.]+./, "");
281
+ type = type == fileName ? "text/plain" : "application/" + type;
282
+ options = { type };
283
+ }
284
+ const bolb = new Blob(content, options);
285
+ // 创建一个 URL,该 URL 可以用于在浏览器中引用 Blob 对象(例如,在 <a> 标签的 href 属性中)
286
+ const url = URL.createObjectURL(bolb);
287
+ // 你可以创建一个链接来下载这个 Blob 对象
288
+ const downloadLink = document.createElement("a");
289
+ downloadLink.href = url;
290
+ downloadLink.download = fileName; // 设置下载文件的名称
291
+ document.body.appendChild(downloadLink); // 添加到文档中
292
+ downloadLink.click(); // 模拟点击以开始下载
293
+ document.body.removeChild(downloadLink); // 然后从文档中移除
294
+ // 最后,别忘了撤销 Blob 对象的 URL,以释放资源
295
+ URL.revokeObjectURL(url);
296
+ }
297
+
298
+ /**
299
+ * 获取url参数
300
+ * @param {string} url
301
+ * @returns {Object}
302
+ */
303
+ export function _GetQueryParams(url) {
304
+ const queryString = url.split("?")[1] || "";
305
+ const params = new URLSearchParams(queryString);
306
+ const result = {};
307
+
308
+ params.forEach((value, key) => {
309
+ result[key] = value;
310
+ });
311
+
312
+ return result;
313
+ }
314
+
315
+ /**
316
+ * 生成一个UUID(通用唯一标识符)字符串
317
+ * 可以选择性地在UUID前面添加前缀
318
+ *
319
+ * @param {string} prefix - 可选参数,要添加到UUID前面的前缀
320
+ * @returns {string} 一个带有可选前缀的UUID字符串
321
+ */
322
+ export function _GenerateUUID(prefix = "") {
323
+ return (
324
+ prefix +
325
+ "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
326
+ const r = (Math.random() * 16) | 0; // 随机生成一个0到15的数
327
+ const v = c === "x" ? r : (r & 0x3) | 0x8; // 对于'y'位, v = (r & 0x3 | 0x8) 确保变体正确
328
+ return v.toString(16); // 将数字转换为16进制
329
+ })
330
+ );
331
+ }
332
+
333
+ /**
334
+ * 防抖
335
+ * @param {Function} fn
336
+ * @param {number} delay
337
+ * @returns {Function}
338
+ */
339
+ export function _Debounce(fn, delay) {
340
+ let timeoutId;
341
+ return function (...args) {
342
+ clearTimeout(timeoutId);
343
+ timeoutId = setTimeout(() => fn.apply(this, args), delay);
344
+ };
345
+ }
346
+
347
+ /**
348
+ * 节流
349
+ * @param {Function} fn
350
+ * @param {number} delay
351
+ * @returns {Function}
352
+ */
353
+ export function _Throttle(fn, delay) {
354
+ let timer;
355
+ return function (...args) {
356
+ if (!timer) {
357
+ timer = setTimeout(() => {
358
+ fn.apply(this, args);
359
+ timer = null;
360
+ }, delay);
361
+ }
362
+ };
363
+ }
364
+
365
+ /**
366
+ * 数据类型
367
+ * @param {any} value
368
+ * @returns string
369
+ */
370
+ export function _DataType(value) {
371
+ if (Array.isArray(value)) return "array";
372
+ if (value === null) return "null";
373
+ return typeof value;
374
+ }
375
+
376
+ /**
377
+ * 复制到剪贴板
378
+ * @param {string} text
379
+ */
380
+ export function _CopyToClipboard(text) {
381
+ const handleSuccess = () => Promise.resolve();
382
+ const handleError = (error) => {
383
+ console.error(error);
384
+ return Promise.reject(error);
385
+ };
386
+
387
+ /** 最新方式 */
388
+ function writeText() {
389
+ return navigator.clipboard
390
+ .writeText(text)
391
+ .then(handleSuccess)
392
+ .catch(handleError);
393
+ }
394
+ /** 旧方式 - createRange */
395
+ function createRange() {
396
+ const div = document.createElement("div");
397
+ div.innerText = text;
398
+ document.body.appendChild(div);
399
+
400
+ const range = document.createRange();
401
+ range.selectNodeContents(div);
402
+ const selection = window.getSelection();
403
+
404
+ let isFinished = false;
405
+ if (selection) {
406
+ selection.removeAllRanges();
407
+ selection.addRange(range);
408
+
409
+ isFinished = document.execCommand("copy");
410
+ }
411
+ document.body.removeChild(div);
412
+ return isFinished ? Promise.resolve() : Promise.reject();
413
+ }
414
+ /** 旧方式 - execCommand */
415
+ function execCommand() {
416
+ const textarea = document.createElement("textarea");
417
+ textarea.value = text;
418
+ document.body.appendChild(textarea);
419
+
420
+ textarea.select();
421
+ textarea.setSelectionRange(0, text.length); // 对于移动设备
422
+
423
+ let isFinished = false;
424
+
425
+ /** aria-hidden 及 tabindex 可能会影响聚焦元素 */
426
+ if (document.activeElement === textarea)
427
+ isFinished = document.execCommand("Copy", true);
428
+
429
+ document.body.removeChild(textarea);
430
+ return isFinished ? Promise.resolve() : Promise.reject();
431
+ }
432
+
433
+ function old() {
434
+ return createRange()
435
+ .then(handleSuccess)
436
+ .catch(() => {
437
+ execCommand()
438
+ .then(handleSuccess)
439
+ .catch(() => handleError("复制方式尽皆失效"));
440
+ });
441
+ }
442
+
443
+ if (navigator.clipboard) return writeText().catch(old);
444
+ return old();
445
+ }
446
+
447
+ /**
448
+ * 格式化文件大小
449
+ * @param {number} size
450
+ * @returns string
451
+ */
452
+ export function _FormatFileSize(size) {
453
+ const units = ["B", "KB", "MB", "GB", "TB", "PB"];
454
+ let unitIndex = 0;
455
+ while (size > 1024) {
456
+ size /= 1024;
457
+ unitIndex++;
458
+ }
459
+ return `${Math.round(size * 100) / 100} ${units[unitIndex]}`;
460
+ }
461
+
462
+ /**
463
+ * 根据路径初始化目标对象
464
+ * 如果路径中某个属性不存在,则会创建该属性及其所有父属性
465
+ * 最终返回路径的最后一个属性对应的值或undefined(如果路径不存在)
466
+ *
467
+ * @param {Object} model - 要初始化的模型对象
468
+ * @param {string} path - 属性路径,使用英文句点分隔
469
+ * @returns {any} 路径的最后一个属性对应的值或undefined
470
+ */
471
+ export function _InitTargetByPath(model, path) {
472
+ const arr = path.split(".");
473
+ return arr.reduce((prev, curr, index) => {
474
+ if (!(curr in prev)) {
475
+ if (index === arr.length - 1) prev[curr] = undefined;
476
+ else prev[curr] = {};
477
+ }
478
+ return prev[curr];
479
+ }, model);
480
+ }
481
+ /**
482
+ * 根据路径获取目标对象
483
+ * 该函数用于在给定的模型中,通过路径字符串来获取深层嵌套的目标对象如果路径中的某一部分不存在,则会创建一个新的对象(除非已经是路径的最后一部分)
484
+ *
485
+ * @param {Object} model - 包含要查询的数据的模型对象
486
+ * @param {string} path - 用点分隔的路径字符串,表示要访问的对象属性路径
487
+ * @returns {Object|undefined} - 返回目标对象,如果路径不存在则返回undefined
488
+ */
489
+ export function _GetTargetByPath(model, path) {
490
+ const arr = path.split(".");
491
+ return arr.reduce((prev, curr, index) => {
492
+ if (prev.hasOwnProperty(curr)) return prev[curr];
493
+ return (prev[curr] = index == arr.length - 1 ? undefined : {});
494
+ }, model);
495
+ }
496
+ /**
497
+ * 根据路径更新目标值
498
+ *
499
+ * 该函数通过一个点分隔的路径来更新一个对象中的嵌套属性值
500
+ * 它使用了reduce方法来遍历路径数组,并在路径的终点设置新的值
501
+ *
502
+ * @param {Object} model - 包含要更新数据的模型对象
503
+ * @param {string} path - 点分隔的字符串路径,指示如何到达目标属性
504
+ * @param {*} value - 要设置的新值
505
+ * @returns {*} - 返回更新后的模型对象中的值
506
+ */
507
+ export function _UpdateTargetByPath(model, path, value) {
508
+ const arr = path.split(".");
509
+ return arr.reduce((prev, curr, index) => {
510
+ if (index === arr.length - 1) prev[curr] = value;
511
+ return prev[curr];
512
+ }, model);
513
+ }
514
+
515
+ /**
516
+ * 使用 XMLHttpRequest 检查指定 URL 的连接状态
517
+ *
518
+ * 此函数通过发送一个 HEAD 请求来检查给定 URL 是否可访问 HEAD 请求仅请求文档头部信息,
519
+ * 而不是整个页面,因此比 GET 或 POST 请求更快此方法常用于检查 URL 是否有效,以及服务器的响应时间等
520
+ *
521
+ * @param {string} url - 需要检查连接的 URL 地址
522
+ * @returns {Promise} - 返回一个 Promise 对象,该对象在连接成功时解析,在连接失败时拒绝
523
+ */
524
+ export function _CheckConnectionWithXHR(url) {
525
+ return new Promise((resolve, reject) => {
526
+ const xhr = new XMLHttpRequest();
527
+ xhr.open("HEAD", url, true); // 发送 HEAD 请求
528
+ xhr.onreadystatechange = function () {
529
+ if (xhr.readyState === XMLHttpRequest.DONE) {
530
+ if (xhr.status >= 200 && xhr.status < 300) resolve();
531
+ else reject(xhr);
532
+ }
533
+ };
534
+ xhr.onerror = reject;
535
+ xhr.send();
536
+ });
537
+ }