nhanh-pure-function 1.4.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,856 +0,0 @@
1
- import { EXTENSION_TO_MIME, FILE_EXTENSIONS } from "../Constant";
2
-
3
- /**
4
- * 非null | undefined判断
5
- * @param value any
6
- * @returns boolean
7
- */
8
- export function _NotNull(value) {
9
- return value !== null && value !== undefined;
10
- }
11
-
12
- /**
13
- * 是正常对象吗
14
- * @param {} value
15
- * @returns boolean
16
- */
17
- export function _IsObject(value) {
18
- return !(value === null || typeof value !== "object" || Array.isArray(value));
19
- }
20
-
21
- /**
22
- * 寻找空闲时机执行传入方法
23
- * @param callback 需执行的方法
24
- */
25
- export function _ExecuteWhenIdle(callback) {
26
- if (typeof callback !== "function")
27
- return console.error("非函数:", callback);
28
- const loop = function (deadline) {
29
- if (deadline.didTimeout || deadline.timeRemaining() <= 0)
30
- requestIdleCallback(loop);
31
- else callback();
32
- };
33
- requestIdleCallback(loop);
34
- }
35
-
36
- /**
37
- * 等待条件满足
38
- * @param conditionChecker 条件检查器
39
- * @param timeoutMillis 超时毫秒数
40
- * @returns Promise<unknown>
41
- */
42
- export function _WaitForCondition(conditionChecker, timeoutMillis) {
43
- const startTime = new Date() - 0;
44
- return new Promise((resolve, reject) => {
45
- const checkCondition = () => {
46
- const nowTime = new Date() - 0;
47
- if (nowTime - startTime >= timeoutMillis) return reject("超时");
48
- if (conditionChecker()) return resolve("完成");
49
- requestIdleCallback(checkCondition);
50
- };
51
- checkCondition();
52
- });
53
- }
54
-
55
- /**
56
- * 排除子串
57
- * @param inputString 需裁剪字符串
58
- * @param substringToDelete 被裁减字符串
59
- * @param delimiter 分隔符
60
- * @returns 裁减后的字符串
61
- */
62
- export function _ExcludeSubstring(
63
- inputString,
64
- substringToDelete,
65
- delimiter = ","
66
- ) {
67
- const regex = new RegExp(
68
- `(^|${delimiter})${substringToDelete}(${delimiter}|$)`,
69
- "g"
70
- );
71
- return inputString.replace(regex, function ($0, $1, $2) {
72
- return $1 === $2 ? delimiter : "";
73
- });
74
- }
75
-
76
- /**
77
- * 首字母大写
78
- * @param str
79
- * @returns string
80
- */
81
- export function _CapitalizeFirstLetter(string) {
82
- return string.charAt(0).toUpperCase() + string.slice(1);
83
- }
84
-
85
- /**
86
- * 合并对象 注意: 本函数会直接操作 A
87
- * @param {Object | Array} A
88
- * @param {Object | Array} B
89
- * @returns A&B || B
90
- */
91
- export function _MergeObjects(
92
- A,
93
- B,
94
- visitedObjects = [],
95
- outTime = +new Date()
96
- ) {
97
- /** 疑似死循环 */
98
- if (outTime < +new Date() - 300) {
99
- console.error("_MergeObjects 合并异常:疑似死循环");
100
- return null;
101
- }
102
-
103
- const getType = (v) => (Array.isArray(v) ? "array" : typeof v);
104
- const TA = getType(A);
105
- const TB = getType(B);
106
-
107
- if (TA != TB) return B;
108
-
109
- if (TA == "object" || TA == "array") {
110
- if (visitedObjects.some(([a, b]) => a == A && b == B)) return A;
111
- visitedObjects.push([A, B]);
112
-
113
- if (TA == "object") {
114
- for (const key in B) {
115
- if (Object.prototype.hasOwnProperty.call(B, key)) {
116
- const BC = B[key];
117
- const AC = A[key];
118
- const fianlValue = _MergeObjects(AC, BC, visitedObjects, outTime);
119
- A[key] = fianlValue;
120
- }
121
- }
122
- return A;
123
- } else if (TA == "array") {
124
- B.forEach((item, index) => {
125
- const BC = item;
126
- const AC = A[index];
127
- const fianlValue = _MergeObjects(AC, BC, visitedObjects, outTime);
128
- A[index] = fianlValue;
129
- });
130
- return A;
131
- }
132
- } else return B;
133
- }
134
-
135
- /**
136
- * 时间戳转换字符串
137
- * @param {Number | Date} time 时间戳或Date对象
138
- * @param {String} template 完整模板 --> YYYY MM DD hh mm ss ms
139
- * @param {Boolean} pad 补0
140
- */
141
- export function _TimeTransition(
142
- time,
143
- template = "YYYY-MM-DD hh:mm:ss",
144
- pad = true
145
- ) {
146
- const date = new Date(time);
147
-
148
- if (isNaN(date.getTime())) {
149
- console.error("Invalid date");
150
- return "";
151
- }
152
-
153
- const dictionary = {
154
- YYYY: (date) => date.getFullYear(),
155
- MM: (date) => date.getMonth() + 1, // Adjust for 0-based month
156
- DD: (date) => date.getDate(),
157
- hh: (date) => date.getHours(),
158
- mm: (date) => date.getMinutes(),
159
- ss: (date) => date.getSeconds(),
160
- ms: (date) => date.getMilliseconds(),
161
- };
162
-
163
- return template.replace(/YYYY|MM|DD|hh|mm|ss|ms/g, (match) => {
164
- const value = dictionary[match](date);
165
- return pad ? String(value).padStart(2, "0") : String(value);
166
- });
167
- }
168
-
169
- /**
170
- * 读取文件
171
- * @param src 文件地址
172
- * @returns 文件的字符串内容
173
- */
174
- export function _ReadFile(src) {
175
- return new Promise((resolve, reject) => {
176
- fetch(src)
177
- .then((response) => resolve(response.text()))
178
- .catch((error) => {
179
- console.error("Error fetching :", error);
180
- reject(error);
181
- });
182
- });
183
- }
184
-
185
- /**
186
- * 从给定的href中提取名称部分
187
- * 该函数旨在处理URL字符串,并返回URL路径的最后一部分,去除查询参数
188
- *
189
- * @param {string} href - 待处理的URL字符串
190
- * @param {string} [defaultName="file"] - 默认的文件名,当无法提取时使用
191
- * @returns {string} URL路径的最后一部分,不包括查询参数
192
- */
193
- export function _GetHrefName(href, defaultName = "file") {
194
- // 简单检查空值和其他假值
195
- if (!href) return defaultName;
196
-
197
- // 将 href 转换为字符串以防止其他类型输入
198
- href = String(href).trim();
199
-
200
- // 如果 href 是空字符串,直接返回空字符串
201
- if (href === "") return defaultName;
202
-
203
- // 分割路径部分并获取最后一部分
204
- const pathParts = href.split("/");
205
- const lastPart = pathParts[pathParts.length - 1];
206
-
207
- // 分割查询参数并获取基础名称
208
- const name = lastPart.split("?")[0];
209
-
210
- // 返回处理后的名称部分
211
- return name;
212
- }
213
-
214
- /**
215
- * 下载文件
216
- * @param {string} href - 文件路径
217
- * @param {string} [fileName] - 导出文件名
218
- */
219
- export function _DownloadFile(href, fileName) {
220
- return new Promise((resolve, reject) => {
221
- _CheckConnectionWithXHR(href)
222
- .then(() => {
223
- try {
224
- if (typeof fileName !== "string") {
225
- fileName = _GetHrefName(href, "downloaded_file");
226
- }
227
-
228
- fetch(href)
229
- .then((response) => {
230
- if (!response.ok) {
231
- throw new Error(`文件下载失败,状态码: ${response.status}`);
232
- }
233
- return response.blob();
234
- })
235
- .then((blob) => {
236
- const url = URL.createObjectURL(blob); // 创建文件 URL
237
-
238
- const a = document.createElement("a");
239
- a.href = url;
240
- a.download = decodeURIComponent(fileName);
241
-
242
- // 临时将 a 标签添加到 DOM,然后触发点击事件,最后移除
243
- document.body.appendChild(a);
244
- a.click();
245
- document.body.removeChild(a);
246
-
247
- // 释放 URL 对象
248
- URL.revokeObjectURL(url);
249
-
250
- resolve();
251
- })
252
- .catch((error) => {
253
- console.error("下载文件时发生错误:", error.message);
254
- reject(error);
255
- });
256
- } catch (error) {
257
- console.error("下载文件时发生错误:", error.message);
258
- reject(error);
259
- }
260
- })
261
- .catch(reject);
262
- });
263
- }
264
-
265
- /**
266
- * 获取帧率
267
- * @param {(fps , frameTime)=>void} callback callback( 帧率 , 每帧时间 )
268
- * @param {Number} referenceNode 参考节点数量
269
- */
270
- export function _GetFrameRate(callback, referenceNode = 10) {
271
- let t,
272
- l = referenceNode;
273
- function loop() {
274
- if (l > 0) {
275
- l--;
276
- requestAnimationFrame(loop);
277
- } else {
278
- const time = new Date() - t;
279
- const frameTime = time / referenceNode;
280
- const fps = 1000 / frameTime;
281
- callback(Number(fps.toFixed(2)), Number(frameTime.toFixed(2)));
282
- }
283
- }
284
- requestAnimationFrame(() => {
285
- t = new Date() - 0;
286
- loop();
287
- });
288
- }
289
-
290
- /**
291
- * 单位转换 12** -> **px
292
- * @param {string} width
293
- * @returns 对应的单位为px的宽
294
- */
295
- export function _GetOtherSizeInPixels(width) {
296
- if (/px/.test(width)) return width;
297
- const dom = document.createElement("div");
298
- dom.style.width = width;
299
- document.body.appendChild(dom);
300
- width = parseFloat(window.getComputedStyle(dom).width);
301
- document.body.removeChild(dom);
302
- return width;
303
- }
304
-
305
- /**
306
- * 驼峰命名
307
- * @param {字符串} str
308
- * @param {是否删除分割字符} isRemoveDelimiter
309
- * @returns 'wq1wqw-qw2qw' -> 'wq1Wqw-Qw2Qw' / 'wqWqwQwQw'
310
- */
311
- export function _ConvertToCamelCase(str, isRemoveDelimiter) {
312
- str = str.replace(/([^a-zA-Z][a-z])/g, (match) => match.toUpperCase());
313
- if (isRemoveDelimiter) return str.replace(/[^a-zA-Z]+/g, "");
314
- return str;
315
- }
316
-
317
- /**
318
- * 创建文件并下载
319
- * @param {BlobPart[]} content 文件内容
320
- * @param {string} fileName 文件名称
321
- * @param {BlobPropertyBag} options Blob 配置
322
- */
323
- export function _CreateAndDownloadFile(content, fileName, options) {
324
- if (!options) {
325
- let type = fileName.replace(/^[^.]+./, "");
326
- type = type == fileName ? "text/plain" : "application/" + type;
327
- options = { type };
328
- }
329
- const bolb = new Blob(content, options);
330
- // 创建一个 URL,该 URL 可以用于在浏览器中引用 Blob 对象(例如,在 <a> 标签的 href 属性中)
331
- const url = URL.createObjectURL(bolb);
332
- // 你可以创建一个链接来下载这个 Blob 对象
333
- const downloadLink = document.createElement("a");
334
- downloadLink.href = url;
335
- downloadLink.download = fileName; // 设置下载文件的名称
336
- document.body.appendChild(downloadLink); // 添加到文档中
337
- downloadLink.click(); // 模拟点击以开始下载
338
- document.body.removeChild(downloadLink); // 然后从文档中移除
339
- // 最后,别忘了撤销 Blob 对象的 URL,以释放资源
340
- URL.revokeObjectURL(url);
341
- }
342
-
343
- /**
344
- * 获取url参数
345
- * @param {string} url
346
- * @returns {Object}
347
- */
348
- export function _GetQueryParams(url) {
349
- const queryString = url.split("?")[1] || "";
350
- const params = new URLSearchParams(queryString);
351
- const result = {};
352
-
353
- params.forEach((value, key) => {
354
- result[key] = value;
355
- });
356
-
357
- return result;
358
- }
359
-
360
- /**
361
- * 生成一个UUID(通用唯一标识符)字符串
362
- * 可以选择性地在UUID前面添加前缀
363
- *
364
- * @param {string} prefix - 可选参数,要添加到UUID前面的前缀
365
- * @returns {string} 一个带有可选前缀的UUID字符串
366
- */
367
- export function _GenerateUUID(prefix = "") {
368
- return (
369
- prefix +
370
- "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
371
- const r = (Math.random() * 16) | 0; // 随机生成一个0到15的数
372
- const v = c === "x" ? r : (r & 0x3) | 0x8; // 对于'y'位, v = (r & 0x3 | 0x8) 确保变体正确
373
- return v.toString(16); // 将数字转换为16进制
374
- })
375
- );
376
- }
377
-
378
- /**
379
- * 防抖
380
- * @param {Function} fn
381
- * @param {number} delay
382
- * @returns {Function}
383
- */
384
- export function _Debounce(fn, delay) {
385
- let timeoutId;
386
- return function (...args) {
387
- clearTimeout(timeoutId);
388
- timeoutId = setTimeout(() => fn.apply(this, args), delay);
389
- };
390
- }
391
-
392
- /**
393
- * 节流
394
- * @param {Function} fn
395
- * @param {number} delay
396
- * @returns {Function}
397
- */
398
- export function _Throttle(fn, delay) {
399
- let lastCallTime = -Infinity;
400
-
401
- return function (...args) {
402
- const now = performance.now();
403
- if (now - lastCallTime > delay) {
404
- lastCallTime = now;
405
- try {
406
- fn(...args);
407
- } catch (error) {
408
- console.error("Throttled function execution failed:", error);
409
- }
410
- }
411
- };
412
- }
413
-
414
- /**
415
- * 数据类型
416
- * @param {any} value
417
- * @returns string
418
- */
419
- export function _DataType(value) {
420
- if (Array.isArray(value)) return "array";
421
- if (value === null) return "null";
422
- return typeof value;
423
- }
424
-
425
- /**
426
- * 复制到剪贴板
427
- * @param {string} text
428
- */
429
- export function _CopyToClipboard(text) {
430
- const handleSuccess = () => Promise.resolve();
431
- const handleError = (error) => {
432
- console.error(error);
433
- return Promise.reject(error);
434
- };
435
-
436
- /** 最新方式 */
437
- function writeText() {
438
- return navigator.clipboard
439
- .writeText(text)
440
- .then(handleSuccess)
441
- .catch(handleError);
442
- }
443
- /** 旧方式 - createRange */
444
- function createRange() {
445
- const div = document.createElement("div");
446
- div.innerText = text;
447
- document.body.appendChild(div);
448
-
449
- const range = document.createRange();
450
- range.selectNodeContents(div);
451
- const selection = window.getSelection();
452
-
453
- let isFinished = false;
454
- if (selection) {
455
- selection.removeAllRanges();
456
- selection.addRange(range);
457
-
458
- isFinished = document.execCommand("copy");
459
- }
460
- document.body.removeChild(div);
461
- return isFinished ? Promise.resolve() : Promise.reject();
462
- }
463
- /** 旧方式 - execCommand */
464
- function execCommand() {
465
- const textarea = document.createElement("textarea");
466
- textarea.value = text;
467
- document.body.appendChild(textarea);
468
-
469
- textarea.select();
470
- textarea.setSelectionRange(0, text.length); // 对于移动设备
471
-
472
- let isFinished = false;
473
-
474
- /** aria-hidden 及 tabindex 可能会影响聚焦元素 */
475
- if (document.activeElement === textarea)
476
- isFinished = document.execCommand("Copy", true);
477
-
478
- document.body.removeChild(textarea);
479
- return isFinished ? Promise.resolve() : Promise.reject();
480
- }
481
-
482
- function old() {
483
- return createRange()
484
- .then(handleSuccess)
485
- .catch(() => {
486
- execCommand()
487
- .then(handleSuccess)
488
- .catch(() => handleError("复制方式尽皆失效"));
489
- });
490
- }
491
-
492
- if (navigator.clipboard) return writeText().catch(old);
493
- return old();
494
- }
495
-
496
- /**
497
- * 格式化文件大小
498
- * @param {number} size
499
- * @returns string
500
- */
501
- export function _FormatFileSize(size) {
502
- const units = ["B", "KB", "MB", "GB", "TB", "PB"];
503
- let unitIndex = 0;
504
- while (size > 1024) {
505
- size /= 1024;
506
- unitIndex++;
507
- }
508
- return `${Math.round(size * 100) / 100} ${units[unitIndex]}`;
509
- }
510
-
511
- /**
512
- * 根据路径初始化目标对象
513
- * 如果路径中某个属性不存在,则会创建该属性及其所有父属性
514
- * 最终返回路径的最后一个属性对应的值或undefined(如果路径不存在)
515
- *
516
- * @param {Object} model - 要初始化的模型对象
517
- * @param {string} path - 属性路径,使用英文句点分隔
518
- * @returns {any} 路径的最后一个属性对应的值或undefined
519
- */
520
- export function _InitTargetByPath(model, path) {
521
- const arr = path.split(".");
522
- return arr.reduce((prev, curr, index) => {
523
- if (!(curr in prev)) {
524
- if (index === arr.length - 1) prev[curr] = undefined;
525
- else prev[curr] = {};
526
- }
527
- return prev[curr];
528
- }, model);
529
- }
530
- /**
531
- * 根据路径获取目标对象
532
- * 该函数用于在给定的模型中,通过路径字符串来获取深层嵌套的目标对象如果路径中的某一部分不存在,则会创建一个新的对象(除非已经是路径的最后一部分)
533
- *
534
- * @param {Object} model - 包含要查询的数据的模型对象
535
- * @param {string} path - 用点分隔的路径字符串,表示要访问的对象属性路径
536
- * @returns {Object|undefined} - 返回目标对象,如果路径不存在则返回undefined
537
- */
538
- export function _GetTargetByPath(model, path) {
539
- const arr = path.split(".");
540
- return arr.reduce((prev, curr, index) => {
541
- if (prev.hasOwnProperty(curr)) return prev[curr];
542
- return (prev[curr] = index == arr.length - 1 ? undefined : {});
543
- }, model);
544
- }
545
- /**
546
- * 根据路径更新目标值
547
- *
548
- * 该函数通过一个点分隔的路径来更新一个对象中的嵌套属性值
549
- * 它使用了reduce方法来遍历路径数组,并在路径的终点设置新的值
550
- *
551
- * @param {Object} model - 包含要更新数据的模型对象
552
- * @param {string} path - 点分隔的字符串路径,指示如何到达目标属性
553
- * @param {*} value - 要设置的新值
554
- * @returns {*} - 返回更新后的模型对象中的值
555
- */
556
- export function _UpdateTargetByPath(model, path, value) {
557
- const arr = path.split(".");
558
- return arr.reduce((prev, curr, index) => {
559
- if (index === arr.length - 1) prev[curr] = value;
560
- return prev[curr];
561
- }, model);
562
- }
563
-
564
- /**
565
- * 使用 XMLHttpRequest 检查指定 URL 的连接状态
566
- *
567
- * 此函数通过发送一个 HEAD 请求来检查给定 URL 是否可访问 HEAD 请求仅请求文档头部信息,
568
- * 而不是整个页面,因此比 GET 或 POST 请求更快此方法常用于检查 URL 是否有效,以及服务器的响应时间等
569
- *
570
- * @param {string} url - 需要检查连接的 URL 地址
571
- * @returns {Promise} - 返回一个 Promise 对象,该对象在连接成功时解析,在连接失败时拒绝
572
- */
573
- export function _CheckConnectionWithXHR(url) {
574
- return new Promise((resolve, reject) => {
575
- // 前置校验:确保 URL 合法
576
- if (typeof url !== "string" || url.trim() === "" || !url.includes("://")) {
577
- reject(new Error("Invalid URL: Must be a non-empty string"));
578
- return;
579
- }
580
-
581
- // 显式处理浏览器兼容性错误(如无效协议或非法字符)
582
- try {
583
- const xhr = new XMLHttpRequest();
584
- xhr.open("HEAD", url, true);
585
- } catch (error) {
586
- reject(new Error(`Invalid URL format: ${error.message}`));
587
- return;
588
- }
589
-
590
- const xhr = new XMLHttpRequest();
591
- xhr.open("HEAD", url, true);
592
-
593
- // 统一错误处理逻辑
594
- const handleError = (event) => {
595
- reject(new Error(`Request failed: ${event.type}`));
596
- };
597
-
598
- xhr.onreadystatechange = function () {
599
- if (xhr.readyState === XMLHttpRequest.DONE) {
600
- // 兼容性处理:status=0 可能是跨域或网络错误
601
- if (xhr.status === 0) {
602
- reject(new Error("Network error or CORS blocked"));
603
- } else if (xhr.status >= 200 && xhr.status < 300) {
604
- resolve();
605
- } else {
606
- reject(new Error(`HTTP Error: ${xhr.status}`));
607
- }
608
- }
609
- };
610
-
611
- // 绑定所有可能的错误事件
612
- xhr.onerror = handleError; // 网络层错误(如 DNS 解析失败)
613
- xhr.onabort = handleError; // 请求被中止
614
- xhr.ontimeout = handleError; // 超时
615
-
616
- try {
617
- xhr.send();
618
- } catch (error) {
619
- reject(new Error(`Request send failed: ${error.message}`));
620
- }
621
- });
622
- }
623
-
624
- /**
625
- * 判断给定URL是否指向一个安全上下文
626
- *
627
- * 安全上下文是指通过一系列安全协议访问的资源,这些协议提供了数据的加密传输和身份验证
628
- * 本函数通过检查URL的协议前缀来判断是否属于安全上下文
629
- *
630
- * @param {string} url - 待检查的URL字符串
631
- * @returns {boolean} - 如果URL指向安全上下文,则返回true;否则返回false
632
- */
633
- export function _IsSecureContext(url) {
634
- // 定义一个包含安全协议前缀的数组
635
- // 这里列出的协议代表了数据在传输过程中是加密的,从而保护了数据的机密性和完整性
636
- const secureProtocols = [
637
- "https:", // HTTPS协议,用于安全地浏览网页
638
- "wss:", // WebSocket Secure协议,用于安全的WebSocket通信
639
- "ftps:", // FTP Secure协议,用于安全的文件传输
640
- "sftp:", // SSH File Transfer Protocol,通过SSH安全地传输文件
641
- "smpts:", // Secure SMTP协议,用于安全地发送邮件
642
- "smtp+tls:", // SMTP协议结合STARTTLS扩展,用于升级到安全连接
643
- "imap+tls:", // IMAP协议结合STARTTLS扩展,用于安全地访问邮件
644
- "pop3+tls:", // POP3协议结合STARTTLS扩展,用于安全地接收邮件
645
- "rdp:", // Remote Desktop Protocol,用于安全的远程桌面连接
646
- "vpn:", // VPN协议,用于创建安全的网络连接
647
- ];
648
-
649
- // 遍历安全协议数组,检查给定URL是否以任一安全协议前缀开始
650
- // 使用startsWith方法来判断URL是否使用了安全协议
651
- // 如果找到匹配的安全协议前缀,则返回true,表示URL指向安全上下文;否则返回false
652
- return secureProtocols.some((protocol) => url.startsWith(protocol));
653
- }
654
-
655
- /**
656
- * 文件类型检查器类
657
- * 用于检查文件URL的类型
658
- */
659
- export class _FileTypeChecker {
660
- // 缓存文件扩展名的条目,以提高性能
661
- static cachedEntries = Object.entries(FILE_EXTENSIONS);
662
-
663
- /**
664
- * 检查给定URL的文件类型
665
- * @param {string} url - 文件的URL
666
- * @param {string} [type] - 可选参数,指定要检查的文件类型
667
- * @returns {string} - 如果URL与指定类型或任何已知类型匹配,则返回文件类型,否则返回"unknown"
668
- */
669
- static check(url, type) {
670
- // 确保提供的URL是字符串且非空
671
- if (!url || typeof url !== "string") {
672
- console.error("Invalid URL provided");
673
- return type ? false : "unknown";
674
- }
675
-
676
- // 将URL转换为小写,以确保文件扩展名匹配不区分大小写
677
- const lowerCaseUrl = _GetHrefName(url).toLowerCase();
678
-
679
- // 如果指定了文件类型,则检查URL是否具有该类型的任何文件扩展名
680
- if (type) {
681
- // 确保指定的文件类型是已知的
682
- if (!FILE_EXTENSIONS.hasOwnProperty(type)) {
683
- console.error(`Unknown file type: ${type}`);
684
- return "unknown";
685
- }
686
- const extensions = FILE_EXTENSIONS[type];
687
- return _FileTypeChecker._checkExtension(lowerCaseUrl, extensions);
688
- }
689
-
690
- // 如果未指定文件类型,则检测URL属于哪种文件类型
691
- return _FileTypeChecker._detectFileType(lowerCaseUrl);
692
- }
693
-
694
- /**
695
- * 静态方法,用于解析地址信息
696
- * 该方法接受一个URL字符串,将其解析为一个包含地址详情的对象数组
697
- * 主要用于批量处理以逗号分隔的URL列表,为每个URL生成相应的名称和类型
698
- *
699
- * @param {string} url - 以逗号分隔的URL字符串,每个URL代表一个资源的位置
700
- * @returns {Array} - 包含每个URL及其相关信息(名称和类型)的对象数组
701
- */
702
- static parseAddresses(url) {
703
- // 确保提供的URL是字符串且非空
704
- if (!url || typeof url !== "string") {
705
- console.error("Invalid URL provided");
706
- return [];
707
- }
708
-
709
- // 分割URL字符串并映射每个URL到包含其详细信息的对象
710
- return url.split(",").map((url) => {
711
- // 从URL中提取名称
712
- const name = _GetHrefName(url);
713
- // 检查URL的类型
714
- const type = this.check(url);
715
- // 返回包含URL、名称和类型的对象
716
- return { url, name, type };
717
- });
718
- }
719
-
720
- /**
721
- * 检查 MIME 类型是否与指定的模式匹配
722
- * @param {string} type - 要检查的 MIME 类型(如 "image/png")
723
- * @param {string} [accept] - 可接受的 MIME 类型模式(如 "image/*, text/plain")
724
- * @returns {boolean} - 如果类型匹配,则返回 true,否则返回 false
725
- */
726
- static matchesMimeType(type, accept) {
727
- // 参数有效性检查
728
- if (
729
- !accept ||
730
- !type ||
731
- typeof accept !== "string" ||
732
- typeof type !== "string"
733
- ) {
734
- return true;
735
- }
736
-
737
- // 标准化类型和接受模式
738
- const normalizedType = _FileTypeChecker._normalizeType(type);
739
- const mimePatterns = accept
740
- .split(",")
741
- .map((pattern) => _FileTypeChecker._normalizeType(pattern.trim()));
742
-
743
- // 拆分主/子类型
744
- const [typeMain, typeSub = "*"] = normalizedType.split("/");
745
-
746
- return mimePatterns.some((pattern) => {
747
- const [patternMain, patternSub = "*"] = pattern.split("/");
748
-
749
- // 主类型匹配逻辑
750
- const mainMatch =
751
- patternMain === "*" || typeMain === "*" || patternMain === typeMain;
752
-
753
- // 子类型匹配逻辑
754
- const subMatch =
755
- patternSub === "*" || typeSub === "*" || patternSub === typeSub;
756
-
757
- return mainMatch && subMatch;
758
- });
759
- }
760
-
761
- /**
762
- * 类型标准化函数
763
- * 该函数旨在将文件类型或MIME类型字符串转换为标准格式
764
- * 主要处理三种情况:带扩展名的字符串、简写格式的类型以及已标准格式的类型
765
- *
766
- * @param {string} type - 文件类型或MIME类型字符串
767
- * @returns {string} 标准化的MIME类型字符串,如果无法识别则返回原始输入
768
- */
769
- static _normalizeType(type) {
770
- // 处理扩展名(如 .mp3)
771
- if (type.startsWith(".") && !type.includes("/")) {
772
- return EXTENSION_TO_MIME[type.toLowerCase()] || type;
773
- }
774
-
775
- // 处理简写格式(如 "image" 转换为 "image/*")
776
- if (!type.includes("/")) {
777
- return `${type}/*`;
778
- }
779
-
780
- // 返回原始输入,因为它已经是标准格式
781
- return type;
782
- }
783
-
784
- /**
785
- * 检查URL是否具有任何指定的文件扩展名
786
- * @param {string} url - 文件的URL
787
- * @param {string[]} validExtensions - 有效文件扩展名的数组
788
- * @returns {boolean} - 如果URL具有任何指定的文件扩展名,则返回true,否则返回false
789
- */
790
- static _checkExtension(url, validExtensions) {
791
- return validExtensions.some((extension) => url.endsWith(extension));
792
- }
793
-
794
- /**
795
- * 检测文件URL的类型
796
- * @param {string} url - 文件的URL
797
- * @returns {string} - 如果URL与任何已知类型匹配,则返回文件类型,否则返回"unknown"
798
- */
799
- static _detectFileType(url) {
800
- for (const [type, extensions] of _FileTypeChecker.cachedEntries) {
801
- if (extensions.some((extension) => url.endsWith(extension))) {
802
- return type;
803
- }
804
- }
805
- return "unknown";
806
- }
807
- }
808
-
809
- /**
810
- * 旋转列表函数
811
- *
812
- * 该函数接受一个列表作为参数,并返回一个二维数组,其中每个内部数组都是原列表的一种旋转形式
813
- * 旋转列表的原理是将原列表分割成两部分,并将这两部分重新组合,形成一个新的列表
814
- *
815
- * @param list T[] - 需要旋转的列表,列表元素类型为泛型T
816
- * @returns T[][] - 返回一个二维数组,每个内部数组代表原列表的一种旋转形式
817
- */
818
- export function _RotateList(list) {
819
- // 使用map函数遍历列表,对于列表中的每个元素(这里不需要元素本身,所以用_表示)
820
- // i表示当前元素的索引,利用这个索引对列表进行分割和重组
821
- return list.map((_, i) => {
822
- // 将当前索引i之后的元素与从列表开头到索引i之前的元素拼接成一个新的列表
823
- // 这样做可以实现列表的旋转效果
824
- return list.slice(i).concat(list.slice(0, i));
825
- });
826
- }
827
-
828
- /**
829
- * 克隆给定值的函数
830
- * 该函数尝试使用window.structuredClone方法进行深克隆,如果失败则使用自定义方法
831
- * @param {any} val - 需要克隆的值
832
- * @returns {any} - 克隆后的值
833
- */
834
- export function _Clone(val) {
835
- // 保存原始的structuredClone方法引用
836
- const oldClone = window.structuredClone;
837
-
838
- // 定义一个新的克隆方法,用于处理非对象或null值,以及对象的合并
839
- const newClone = (val) => {
840
- // 如果val为null或不是对象,则直接返回val
841
- if (val === null || typeof val !== "object") return val;
842
- // 使用_MergeObjects函数合并对象,如果是数组则传递空数组作为第一个参数,否则传递空对象
843
- return _MergeObjects(Array.isArray(val) ? [] : {}, val);
844
- };
845
-
846
- // 尝试使用原始的structuredClone方法或自定义的newClone方法进行克隆
847
- try {
848
- // 如果oldClone存在,则使用oldClone方法进行克隆,否则使用newClone方法
849
- return oldClone ? oldClone(val) : newClone(val);
850
- } catch (error) {
851
- // 使用日志系统或其他方式记录错误信息
852
- console.error("structuredClone error:", error);
853
- // 如果oldClone存在且之前的尝试失败,则再次使用newClone方法尝试克隆
854
- return oldClone && newClone(val);
855
- }
856
- }