@minson1994/ms-utils 1.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.
Files changed (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +696 -0
  3. package/dist/api/ApiConfig.d.ts +206 -0
  4. package/dist/api/ApiConfig.d.ts.map +1 -0
  5. package/dist/api/ApiError.d.ts +30 -0
  6. package/dist/api/ApiError.d.ts.map +1 -0
  7. package/dist/api/ApiRequest.d.ts +87 -0
  8. package/dist/api/ApiRequest.d.ts.map +1 -0
  9. package/dist/api/ApiResponse.d.ts +15 -0
  10. package/dist/api/ApiResponse.d.ts.map +1 -0
  11. package/dist/api/adapter/AjaxHttpAdapter.d.ts +22 -0
  12. package/dist/api/adapter/AjaxHttpAdapter.d.ts.map +1 -0
  13. package/dist/api/adapter/ApiAdapter.d.ts +56 -0
  14. package/dist/api/adapter/ApiAdapter.d.ts.map +1 -0
  15. package/dist/api/adapter/AxiosHttpAdapter.d.ts +43 -0
  16. package/dist/api/adapter/AxiosHttpAdapter.d.ts.map +1 -0
  17. package/dist/api/adapter/FetchHttpAdapter.d.ts +50 -0
  18. package/dist/api/adapter/FetchHttpAdapter.d.ts.map +1 -0
  19. package/dist/api/adapter/WxHttpAdapter.d.ts +33 -0
  20. package/dist/api/adapter/WxHttpAdapter.d.ts.map +1 -0
  21. package/dist/api/adapter/XhrHttpAdapter.d.ts +46 -0
  22. package/dist/api/adapter/XhrHttpAdapter.d.ts.map +1 -0
  23. package/dist/api/index.d.ts +22 -0
  24. package/dist/api/index.d.ts.map +1 -0
  25. package/dist/api/interceptor/ApiCacheInterceptor.d.ts +29 -0
  26. package/dist/api/interceptor/ApiCacheInterceptor.d.ts.map +1 -0
  27. package/dist/api/interceptor/ApiInterceptor.d.ts +18 -0
  28. package/dist/api/interceptor/ApiInterceptor.d.ts.map +1 -0
  29. package/dist/api/interceptor/ApiStatusInterceptor.d.ts +49 -0
  30. package/dist/api/interceptor/ApiStatusInterceptor.d.ts.map +1 -0
  31. package/dist/api/interceptor/ApiUIInterceptor.d.ts +37 -0
  32. package/dist/api/interceptor/ApiUIInterceptor.d.ts.map +1 -0
  33. package/dist/axios.cjs +151 -0
  34. package/dist/axios.d.ts +6 -0
  35. package/dist/axios.d.ts.map +1 -0
  36. package/dist/axios.js +58 -0
  37. package/dist/browser.cjs +102 -0
  38. package/dist/browser.d.ts +5 -0
  39. package/dist/browser.d.ts.map +1 -0
  40. package/dist/browser.js +75 -0
  41. package/dist/chunk-PV2X54HI.js +75 -0
  42. package/dist/index.cjs +1880 -0
  43. package/dist/index.d.ts +11 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +1753 -0
  46. package/dist/utils/AlgorithmUtils.d.ts +98 -0
  47. package/dist/utils/AlgorithmUtils.d.ts.map +1 -0
  48. package/dist/utils/Concurrency.d.ts +28 -0
  49. package/dist/utils/Concurrency.d.ts.map +1 -0
  50. package/dist/utils/CookieUtils.d.ts +38 -0
  51. package/dist/utils/CookieUtils.d.ts.map +1 -0
  52. package/dist/utils/Hook.d.ts +50 -0
  53. package/dist/utils/Hook.d.ts.map +1 -0
  54. package/dist/utils/TaskQueue.d.ts +159 -0
  55. package/dist/utils/TaskQueue.d.ts.map +1 -0
  56. package/dist/utils/TimeUtils.d.ts +82 -0
  57. package/dist/utils/TimeUtils.d.ts.map +1 -0
  58. package/dist/utils/ValidateUtils.d.ts +139 -0
  59. package/dist/utils/ValidateUtils.d.ts.map +1 -0
  60. package/dist/wx.cjs +169 -0
  61. package/dist/wx.d.ts +6 -0
  62. package/dist/wx.d.ts.map +1 -0
  63. package/dist/wx.js +76 -0
  64. package/package.json +76 -0
package/dist/index.js ADDED
@@ -0,0 +1,1753 @@
1
+ import {
2
+ ApiAdapter,
3
+ ApiResponse
4
+ } from "./chunk-PV2X54HI.js";
5
+
6
+ // src/api/ApiConfig.ts
7
+ var ApiGlobalConfig = class {
8
+ constructor(options = {}) {
9
+ /** 超时时间,单位秒。 */
10
+ this.timeout = 30;
11
+ /** 默认请求头。 */
12
+ this.headers = {};
13
+ /** 是否携带 Cookie。 */
14
+ this.withCredentials = false;
15
+ Object.assign(this, options);
16
+ }
17
+ };
18
+ var ApiConfig = class _ApiConfig extends ApiGlobalConfig {
19
+ constructor(options = {}) {
20
+ super(options);
21
+ /** 基础地址,由 ApiRequest 根据当前可用 base url 写入。 */
22
+ this.baseUrl = "";
23
+ /** 请求路径。 */
24
+ this.url = "";
25
+ /** 请求方法。 */
26
+ this.method = "GET";
27
+ /** 请求数据。 */
28
+ this.data = {};
29
+ /** 是否需要认证。 */
30
+ this.auth = false;
31
+ /** 是否为上传请求。 */
32
+ this.isUpload = false;
33
+ /** 是否以 JSON body 发送。 */
34
+ this.isJsonBody = false;
35
+ /** 是否需要签名。 */
36
+ this.isSign = false;
37
+ /** 是否显示 loading。 */
38
+ this.loading = false;
39
+ /** 是否显示错误消息。 */
40
+ this.showErrorMessage = true;
41
+ /** 是否检查 HTTP 状态码。 */
42
+ this.checkHttpStatus = false;
43
+ /** 业务失败时是否返回原结果。 */
44
+ this.backFailResult = false;
45
+ /** 成功时是否只返回业务 data 字段。 */
46
+ this.backOnlyData = true;
47
+ /** 是否启用缓存。 */
48
+ this.cache = false;
49
+ this.options = options;
50
+ Object.assign(this, options);
51
+ }
52
+ /** 设置是否需要认证。 */
53
+ useAuth(auth = true) {
54
+ this.auth = auth;
55
+ return this;
56
+ }
57
+ /** 设置超时时间,单位秒。 */
58
+ useTimeout(timeout) {
59
+ this.timeout = timeout;
60
+ return this;
61
+ }
62
+ /** 设置请求头。 */
63
+ useHeaders(headers) {
64
+ this.headers = headers;
65
+ return this;
66
+ }
67
+ /** 设置是否携带 Cookie。 */
68
+ useWithCredentials(withCredentials = true) {
69
+ this.withCredentials = withCredentials;
70
+ return this;
71
+ }
72
+ /** 设置取消请求注册回调。 */
73
+ useCancel(onCancel) {
74
+ this.onCancel = onCancel;
75
+ return this;
76
+ }
77
+ /** 设置上传进度回调。 */
78
+ useUploadProgress(onUploadProgress) {
79
+ this.onUploadProgress = onUploadProgress;
80
+ return this;
81
+ }
82
+ /** 设置下载进度回调。 */
83
+ useDownloadProgress(onDownloadProgress) {
84
+ this.onDownloadProgress = onDownloadProgress;
85
+ return this;
86
+ }
87
+ /** 同时设置上传和下载进度回调。 */
88
+ useProgress(onProgress) {
89
+ this.onUploadProgress = onProgress;
90
+ this.onDownloadProgress = onProgress;
91
+ return this;
92
+ }
93
+ /** 设置是否为上传请求。 */
94
+ useUpload(isUpload = true) {
95
+ this.isUpload = isUpload;
96
+ return this;
97
+ }
98
+ /** 设置是否以 JSON body 发送。 */
99
+ useJsonBody(isJsonBody = true) {
100
+ this.isJsonBody = isJsonBody;
101
+ return this;
102
+ }
103
+ /** 设置是否需要签名。 */
104
+ useSign(isSign = true) {
105
+ this.isSign = isSign;
106
+ return this;
107
+ }
108
+ /** 设置是否显示 loading。 */
109
+ useLoading(loading = true) {
110
+ this.loading = loading;
111
+ return this;
112
+ }
113
+ /** 设置是否显示错误消息。 */
114
+ useShowErrorMessage(showErrorMessage = false) {
115
+ this.showErrorMessage = showErrorMessage;
116
+ return this;
117
+ }
118
+ /** 设置是否检查 HTTP 状态码。 */
119
+ useCheckHttpStatus(checkHttpStatus = true) {
120
+ this.checkHttpStatus = checkHttpStatus;
121
+ return this;
122
+ }
123
+ /** 设置业务失败时是否返回原结果。 */
124
+ useBackFailResult(backFailResult = true) {
125
+ this.backFailResult = backFailResult;
126
+ return this;
127
+ }
128
+ /** 设置成功时是否只返回业务 data 字段。 */
129
+ useBackOnlyData(backOnlyData = false) {
130
+ this.backOnlyData = backOnlyData;
131
+ return this;
132
+ }
133
+ /** 设置是否启用缓存。 */
134
+ useCache(cache = true) {
135
+ this.cache = cache;
136
+ return this;
137
+ }
138
+ /**
139
+ * 使用指定请求对象执行当前配置。
140
+ */
141
+ request(request) {
142
+ return request.exec(this);
143
+ }
144
+ /**
145
+ * 使用绑定的请求对象执行当前配置。
146
+ */
147
+ emit() {
148
+ if (!this.apiRequest) {
149
+ throw new Error(
150
+ "\u6CA1\u6709\u7ED1\u5B9A ApiRequest\uFF0C\u8BF7\u4F7F\u7528 api.use(...).emit() \u6216 config.request(api)"
151
+ );
152
+ }
153
+ return this.apiRequest.exec(this);
154
+ }
155
+ /**
156
+ * exec 是 emit 的语义化别名。
157
+ */
158
+ exec() {
159
+ return this.emit();
160
+ }
161
+ /**
162
+ * 创建 ApiConfig。
163
+ */
164
+ static use(options = {}) {
165
+ return new _ApiConfig(options);
166
+ }
167
+ };
168
+ var useApiConfig = ApiConfig.use;
169
+
170
+ // src/api/interceptor/ApiInterceptor.ts
171
+ var ApiInterceptor = class {
172
+ };
173
+
174
+ // src/api/interceptor/ApiUIInterceptor.ts
175
+ var ApiUIInterceptor = class extends ApiInterceptor {
176
+ /** 请求前显示 loading。 */
177
+ async before(config) {
178
+ if (config.loading && this.loading) await this.loading();
179
+ }
180
+ /** 请求结束后隐藏 loading。 */
181
+ async after(config, _response) {
182
+ if (config.loading && this.loadingHide) await this.loadingHide();
183
+ }
184
+ /** 按请求配置显示错误消息。 */
185
+ async showErrorMessage(config, message) {
186
+ if (config.showErrorMessage && this.errorMessage) {
187
+ await this.errorMessage(message, config);
188
+ }
189
+ }
190
+ /** 设置显示 loading 回调。 */
191
+ useLoading(loading) {
192
+ this.loading = loading;
193
+ return this;
194
+ }
195
+ /** 设置隐藏 loading 回调。 */
196
+ useLoadingHide(loadingHide) {
197
+ this.loadingHide = loadingHide;
198
+ return this;
199
+ }
200
+ /** 设置错误消息回调。 */
201
+ useErrorMessage(errorMessage) {
202
+ this.errorMessage = errorMessage;
203
+ return this;
204
+ }
205
+ };
206
+
207
+ // src/api/interceptor/ApiStatusInterceptor.ts
208
+ var ApiStatusInterceptor = class extends ApiInterceptor {
209
+ constructor(options = {}) {
210
+ super();
211
+ /** 业务状态码字段名。 */
212
+ this.status = "code";
213
+ /** 业务消息字段名。 */
214
+ this.message = "message";
215
+ /** 业务数据字段名。 */
216
+ this.data = "data";
217
+ /** 成功业务状态码。 */
218
+ this.success = 200;
219
+ this.useConfig(options);
220
+ }
221
+ /**
222
+ * 状态拦截器不需要请求前处理。
223
+ */
224
+ async before(_config) {
225
+ return void 0;
226
+ }
227
+ /**
228
+ * 检查响应状态,并按配置改写 response.data。
229
+ */
230
+ async after(config, response) {
231
+ if (config.checkHttpStatus && (response.status < 200 || response.status >= 300)) {
232
+ throw new Error(`http request status ${response.status}`);
233
+ }
234
+ const body = response.data ?? {};
235
+ const businessCode = body[this.status];
236
+ const businessMessage = String(body[this.message] ?? "\u8BF7\u6C42\u5931\u8D25");
237
+ if ((businessCode === 401 || businessCode === 403) && !config.backFailResult) {
238
+ await this.onUnauthorized?.(businessMessage, response, config);
239
+ }
240
+ if (businessCode !== this.success && !config.backFailResult) {
241
+ throw new Error(businessMessage);
242
+ }
243
+ if (config.backOnlyData) {
244
+ response.data = body[this.data];
245
+ }
246
+ }
247
+ /**
248
+ * 更新业务字段映射和回调配置。
249
+ */
250
+ useConfig(options) {
251
+ if (options.status !== void 0) this.status = options.status;
252
+ if (options.message !== void 0) this.message = options.message;
253
+ if (options.data !== void 0) this.data = options.data;
254
+ if (options.success !== void 0) this.success = options.success;
255
+ if (options.onUnauthorized !== void 0) this.onUnauthorized = options.onUnauthorized;
256
+ return this;
257
+ }
258
+ };
259
+
260
+ // src/api/interceptor/ApiCacheInterceptor.ts
261
+ var defaultCacheKey = (config) => JSON.stringify({
262
+ baseUrl: config.baseUrl,
263
+ url: config.url,
264
+ method: config.method,
265
+ data: config.data
266
+ });
267
+ var ApiCacheInterceptor = class extends ApiInterceptor {
268
+ constructor(getKey = defaultCacheKey) {
269
+ super();
270
+ this.getKey = getKey;
271
+ /** 缓存数据映射。 */
272
+ this.cacheMap = /* @__PURE__ */ new Map();
273
+ }
274
+ /**
275
+ * 请求前命中缓存则直接返回缓存值。
276
+ */
277
+ async before(config) {
278
+ const key = this.getKey(config);
279
+ if (config.cache && this.cacheMap.has(key)) return this.cacheMap.get(key);
280
+ return void 0;
281
+ }
282
+ /**
283
+ * 请求后按 key 写入缓存。
284
+ */
285
+ async after(config, response) {
286
+ if (config.cache) this.cacheMap.set(this.getKey(config), response.data);
287
+ }
288
+ };
289
+
290
+ // src/api/ApiError.ts
291
+ var ApiError = class extends Error {
292
+ constructor(message, cause) {
293
+ super(message);
294
+ this.cause = cause;
295
+ this.name = new.target.name;
296
+ }
297
+ };
298
+ var ApiCancelError = class extends ApiError {
299
+ constructor(message = "\u8BF7\u6C42\u5DF2\u53D6\u6D88", cause) {
300
+ super(message, cause);
301
+ }
302
+ };
303
+ var ApiTimeoutError = class extends ApiError {
304
+ constructor(message = "\u8BF7\u6C42\u8D85\u65F6", cause) {
305
+ super(message, cause);
306
+ }
307
+ };
308
+ var ApiNetworkError = class extends ApiError {
309
+ constructor(message = "\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25", cause) {
310
+ super(message, cause);
311
+ }
312
+ };
313
+ function isApiCancelError(error) {
314
+ if (error instanceof ApiCancelError) return true;
315
+ if (error instanceof DOMException && error.name === "AbortError") return true;
316
+ const source = error;
317
+ const message = String(source?.message ?? "").toLowerCase();
318
+ return source?.name === "AbortError" || source?.code === "ERR_CANCELED" || message.includes("aborted") || message.includes("cancel");
319
+ }
320
+
321
+ // src/api/adapter/FetchHttpAdapter.ts
322
+ var FetchHttpAdapter = class extends ApiAdapter {
323
+ constructor(options = {}) {
324
+ super(
325
+ options.formDataFactory ? { formDataFactory: options.formDataFactory } : {}
326
+ );
327
+ const fetchImpl = options.fetch ?? globalThis.fetch;
328
+ if (!fetchImpl)
329
+ throw new Error("fetch is not available; pass fetch to FetchHttpAdapter");
330
+ this.fetchImpl = fetchImpl.bind(globalThis);
331
+ }
332
+ /**
333
+ * 将 ApiConfig 转换为 fetch 配置并发起请求。
334
+ */
335
+ async request(config) {
336
+ const controller = typeof AbortController !== "undefined" ? new AbortController() : void 0;
337
+ const method = (config.method || "GET").toUpperCase();
338
+ const headers = { ...config.headers };
339
+ const requestConfig = {
340
+ method,
341
+ headers,
342
+ credentials: config.withCredentials ? "include" : "same-origin"
343
+ };
344
+ if (controller) requestConfig.signal = controller.signal;
345
+ const onCancel = config.onCancel ?? config.cancelCallback;
346
+ if (onCancel && controller) {
347
+ onCancel((message) => {
348
+ controller.abort(new ApiCancelError(message));
349
+ });
350
+ }
351
+ let timer;
352
+ if (controller && config.timeout > 0) {
353
+ timer = setTimeout(
354
+ () => controller.abort(new ApiTimeoutError()),
355
+ config.timeout * 1e3
356
+ );
357
+ }
358
+ try {
359
+ let url = this.resolveUrl(config.baseUrl, config.url);
360
+ if (config.isUpload) {
361
+ const uploadTarget = {
362
+ headers,
363
+ method
364
+ };
365
+ this.formData(config, uploadTarget);
366
+ requestConfig.body = uploadTarget.data;
367
+ if (uploadTarget.method) requestConfig.method = uploadTarget.method;
368
+ delete headers["Content-Type"];
369
+ delete headers["content-type"];
370
+ } else if (method === "GET" || method === "HEAD" || method === "DELETE") {
371
+ url = this.appendQuery(url, config.data);
372
+ } else if (config.isJsonBody) {
373
+ headers["Content-Type"] ?? (headers["Content-Type"] = "application/json");
374
+ requestConfig.body = JSON.stringify(config.data ?? {});
375
+ } else {
376
+ headers["Content-Type"] ?? (headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8");
377
+ requestConfig.body = this.toSearchParams(config.data).toString();
378
+ }
379
+ let response;
380
+ try {
381
+ response = await this.fetchImpl(url, requestConfig);
382
+ } catch (error) {
383
+ if (controller?.signal.reason) throw controller.signal.reason;
384
+ throw error;
385
+ }
386
+ return new ApiResponse(
387
+ response.status,
388
+ await this.parseResponse(response, config)
389
+ );
390
+ } finally {
391
+ if (timer) clearTimeout(timer);
392
+ }
393
+ }
394
+ /**
395
+ * 拼接 baseUrl 和 url。
396
+ */
397
+ resolveUrl(baseUrl, url) {
398
+ if (/^https?:\/\//i.test(url)) return url;
399
+ return `${baseUrl}${url}`;
400
+ }
401
+ /**
402
+ * 给 URL 追加查询参数。
403
+ */
404
+ appendQuery(url, data) {
405
+ const query = this.toSearchParams(data).toString();
406
+ if (!query) return url;
407
+ return `${url}${url.includes("?") ? "&" : "?"}${query}`;
408
+ }
409
+ /**
410
+ * 转换为 URLSearchParams。
411
+ */
412
+ toSearchParams(data) {
413
+ const params = new URLSearchParams();
414
+ if (!data || typeof data !== "object") return params;
415
+ for (const [key, value] of Object.entries(
416
+ data
417
+ )) {
418
+ if (value === void 0 || value === null) continue;
419
+ if (Array.isArray(value)) {
420
+ for (const item of value) params.append(key, String(item));
421
+ } else {
422
+ params.append(key, String(value));
423
+ }
424
+ }
425
+ return params;
426
+ }
427
+ /**
428
+ * 按响应 Content-Type 解析响应体。
429
+ */
430
+ async parseResponse(response, config) {
431
+ if (response.status === 204) return void 0;
432
+ const contentType = response.headers.get("content-type") ?? "";
433
+ const text = config.onDownloadProgress ? await this.readTextWithProgress(response, config) : await response.text();
434
+ if (!text) return void 0;
435
+ if (contentType.includes("application/json")) return JSON.parse(text);
436
+ return text;
437
+ }
438
+ /**
439
+ * 读取响应文本并触发下载进度。
440
+ */
441
+ async readTextWithProgress(response, config) {
442
+ if (!response.body) {
443
+ const text = await response.text();
444
+ this.emitProgress(config.onDownloadProgress, text.length, text.length);
445
+ return text;
446
+ }
447
+ const totalHeader = response.headers.get("content-length");
448
+ const total = totalHeader ? Number(totalHeader) : void 0;
449
+ const reader = response.body.getReader();
450
+ const chunks = [];
451
+ let loaded = 0;
452
+ while (true) {
453
+ const { done, value } = await reader.read();
454
+ if (done) break;
455
+ if (!value) continue;
456
+ chunks.push(value);
457
+ loaded += value.byteLength;
458
+ this.emitProgress(config.onDownloadProgress, loaded, total);
459
+ }
460
+ const bytes = new Uint8Array(loaded);
461
+ let offset = 0;
462
+ for (const chunk of chunks) {
463
+ bytes.set(chunk, offset);
464
+ offset += chunk.byteLength;
465
+ }
466
+ return new TextDecoder().decode(bytes);
467
+ }
468
+ };
469
+
470
+ // src/api/adapter/XhrHttpAdapter.ts
471
+ var XhrHttpAdapter = class extends ApiAdapter {
472
+ constructor(options = {}) {
473
+ super(
474
+ options.formDataFactory ? { formDataFactory: options.formDataFactory } : {}
475
+ );
476
+ this.xhrFactory = options.xhrFactory ?? (() => {
477
+ if (typeof XMLHttpRequest === "undefined") {
478
+ throw new Error(
479
+ "XMLHttpRequest is not available; pass xhrFactory to XhrHttpAdapter"
480
+ );
481
+ }
482
+ return new XMLHttpRequest();
483
+ });
484
+ }
485
+ /**
486
+ * 将 ApiConfig 转换为 XMLHttpRequest 并发起请求。
487
+ */
488
+ request(config) {
489
+ return new Promise((resolve, reject) => {
490
+ const xhr = this.xhrFactory();
491
+ const method = (config.method || "GET").toUpperCase();
492
+ let url = this.resolveUrl(config.baseUrl, config.url);
493
+ let body;
494
+ const headers = { ...config.headers };
495
+ if (config.isUpload) {
496
+ const uploadTarget = {
497
+ headers,
498
+ method
499
+ };
500
+ this.formData(config, uploadTarget);
501
+ body = uploadTarget.data;
502
+ delete headers["Content-Type"];
503
+ delete headers["content-type"];
504
+ } else if (method === "GET" || method === "HEAD" || method === "DELETE") {
505
+ url = this.appendQuery(url, config.data);
506
+ } else if (config.isJsonBody) {
507
+ headers["Content-Type"] ?? (headers["Content-Type"] = "application/json");
508
+ body = JSON.stringify(config.data ?? {});
509
+ } else {
510
+ headers["Content-Type"] ?? (headers["Content-Type"] = "application/x-www-form-urlencoded;charset=UTF-8");
511
+ body = this.toSearchParams(config.data).toString();
512
+ }
513
+ xhr.open(method, url, true);
514
+ xhr.timeout = config.timeout * 1e3;
515
+ xhr.withCredentials = config.withCredentials;
516
+ for (const [key, value] of Object.entries(headers)) {
517
+ xhr.setRequestHeader(key, value);
518
+ }
519
+ if (config.onUploadProgress) {
520
+ xhr.upload.onprogress = (event) => this.emitProgressEvent(config.onUploadProgress, event);
521
+ }
522
+ if (config.onDownloadProgress) {
523
+ xhr.onprogress = (event) => this.emitProgressEvent(config.onDownloadProgress, event);
524
+ }
525
+ xhr.onload = () => {
526
+ resolve(new ApiResponse(xhr.status, this.parseResponse(xhr)));
527
+ };
528
+ xhr.onerror = () => reject(new Error("XMLHttpRequest error"));
529
+ xhr.ontimeout = () => reject(new ApiTimeoutError("XMLHttpRequest timeout"));
530
+ xhr.onabort = () => reject(new ApiCancelError("XMLHttpRequest aborted"));
531
+ const onCancel = config.onCancel ?? config.cancelCallback;
532
+ if (onCancel) onCancel(() => xhr.abort());
533
+ xhr.send(body);
534
+ });
535
+ }
536
+ /**
537
+ * 拼接 baseUrl 和 url。
538
+ */
539
+ resolveUrl(baseUrl, url) {
540
+ if (/^https?:\/\//i.test(url)) return url;
541
+ return `${baseUrl}${url}`;
542
+ }
543
+ /**
544
+ * 给 URL 追加查询参数。
545
+ */
546
+ appendQuery(url, data) {
547
+ const query = this.toSearchParams(data).toString();
548
+ if (!query) return url;
549
+ return `${url}${url.includes("?") ? "&" : "?"}${query}`;
550
+ }
551
+ /**
552
+ * 转换为 URLSearchParams。
553
+ */
554
+ toSearchParams(data) {
555
+ const params = new URLSearchParams();
556
+ if (!data || typeof data !== "object") return params;
557
+ for (const [key, value] of Object.entries(
558
+ data
559
+ )) {
560
+ if (value === void 0 || value === null) continue;
561
+ if (Array.isArray(value)) {
562
+ for (const item of value) params.append(key, String(item));
563
+ } else {
564
+ params.append(key, String(value));
565
+ }
566
+ }
567
+ return params;
568
+ }
569
+ /**
570
+ * 按响应 Content-Type 解析响应体。
571
+ */
572
+ parseResponse(xhr) {
573
+ const contentType = xhr.getResponseHeader("content-type") ?? "";
574
+ if (contentType.includes("application/json")) {
575
+ if (!xhr.responseText) return void 0;
576
+ return JSON.parse(xhr.responseText);
577
+ }
578
+ return xhr.responseText;
579
+ }
580
+ };
581
+
582
+ // src/api/ApiRequest.ts
583
+ var _ApiRequest = class _ApiRequest {
584
+ constructor(options) {
585
+ /** 自定义业务拦截器。 */
586
+ this.interceptor = [];
587
+ /** 当前使用的 base url 下标。 */
588
+ this.currentUrlIndex = 0;
589
+ /** 响应状态拦截器。 */
590
+ this.statusInterceptor = new ApiStatusInterceptor();
591
+ /** 缓存拦截器。 */
592
+ this.cacheInterceptor = new ApiCacheInterceptor();
593
+ /** 多 base url 失败重试间隔,单位毫秒。 */
594
+ this.retryDelay = 1e3;
595
+ this.urls = Array.isArray(options.url) ? options.url : [options.url];
596
+ this.adapter = options.adapter ?? _ApiRequest.createDefaultAdapter();
597
+ this.config = options.config ?? options.appConfig;
598
+ if (options.interceptor) this.interceptor = options.interceptor;
599
+ if (options.statusInterceptor)
600
+ this.statusInterceptor = options.statusInterceptor;
601
+ if (options.cacheInterceptor)
602
+ this.cacheInterceptor = options.cacheInterceptor;
603
+ if (options.retryDelay !== void 0) this.retryDelay = options.retryDelay;
604
+ }
605
+ /**
606
+ * 创建默认适配器。
607
+ *
608
+ * 浏览器环境优先使用 XHR,以支持上传/下载进度;没有 XMLHttpRequest 时使用 fetch。
609
+ */
610
+ static createDefaultAdapter() {
611
+ if (typeof XMLHttpRequest !== "undefined") return new XhrHttpAdapter();
612
+ return new FetchHttpAdapter();
613
+ }
614
+ /** 当前 base url。 */
615
+ get uri() {
616
+ return this.urls[this.currentUrlIndex] ?? "";
617
+ }
618
+ /** 添加自定义拦截器。 */
619
+ pushInterceptor(interceptor) {
620
+ this.interceptor.push(interceptor);
621
+ return this;
622
+ }
623
+ /** 执行请求并返回业务结果。 */
624
+ async exec(config) {
625
+ this.applyGlobalConfig(config);
626
+ config.baseUrl = this.uri;
627
+ try {
628
+ await _ApiRequest.apiUIInterceptor.before(config);
629
+ const cached = await this.before(config);
630
+ if (cached !== void 0) return cached;
631
+ const response = await this.request(config);
632
+ await this.after(config, response);
633
+ return response.data;
634
+ } catch (error) {
635
+ await _ApiRequest.apiUIInterceptor.showErrorMessage(
636
+ config,
637
+ error instanceof Error ? error.message : String(error)
638
+ );
639
+ throw error;
640
+ } finally {
641
+ await _ApiRequest.apiUIInterceptor.after(config);
642
+ }
643
+ }
644
+ /**
645
+ * 执行前置拦截器。
646
+ *
647
+ * 任意拦截器返回非 undefined 数据时,作为请求结果直接返回。
648
+ */
649
+ async before(config) {
650
+ for (const interceptor of this.interceptor) {
651
+ const result = await interceptor.before(config);
652
+ if (result !== void 0) return result;
653
+ }
654
+ return this.cacheInterceptor.before(config);
655
+ }
656
+ /** 执行后置拦截器。 */
657
+ async after(config, response) {
658
+ await this.statusInterceptor.after(config, response);
659
+ for (const interceptor of this.interceptor)
660
+ await interceptor.after(config, response);
661
+ await this.cacheInterceptor.after(config, response);
662
+ }
663
+ /**
664
+ * 调用底层 adapter 发起请求。
665
+ *
666
+ * 多 base url 场景下,请求失败会切换到下一个 base url 重试。
667
+ */
668
+ async request(config) {
669
+ let triedUrls = 0;
670
+ let lastError;
671
+ while (triedUrls < this.urls.length) {
672
+ config.baseUrl = this.uri;
673
+ try {
674
+ return await this.adapter.request(config);
675
+ } catch (error) {
676
+ if (isApiCancelError(error)) throw error;
677
+ this.currentUrlIndex = (this.currentUrlIndex + 1) % this.urls.length;
678
+ triedUrls += 1;
679
+ lastError = error;
680
+ if (triedUrls < this.urls.length) await this.delay(this.retryDelay);
681
+ }
682
+ }
683
+ throw lastError;
684
+ }
685
+ /** 延迟指定毫秒。 */
686
+ delay(time = 1e3) {
687
+ return new Promise((resolve) => {
688
+ setTimeout(resolve, time);
689
+ });
690
+ }
691
+ /**
692
+ * 创建 ApiConfig。
693
+ */
694
+ use(options = {}) {
695
+ const cf = new ApiConfig(options);
696
+ cf.apiRequest = this;
697
+ return cf;
698
+ }
699
+ /** 将全局配置合并到单次请求配置。 */
700
+ applyGlobalConfig(config) {
701
+ if (!this.config) return;
702
+ if (config.options.timeout === void 0) config.timeout = this.config.timeout;
703
+ config.headers = { ...this.config.headers, ...config.headers };
704
+ if (config.options.withCredentials === void 0) {
705
+ config.withCredentials = this.config.withCredentials;
706
+ }
707
+ }
708
+ };
709
+ /** 全局 UI 拦截器,所有 ApiRequest 实例共享。 */
710
+ _ApiRequest.apiUIInterceptor = new ApiUIInterceptor();
711
+ var ApiRequest = _ApiRequest;
712
+
713
+ // src/utils/Concurrency.ts
714
+ var _Concurrency = class _Concurrency {
715
+ /**
716
+ * 创建一个带并发去重能力的异步函数。
717
+ *
718
+ * @param fn 原始异步函数
719
+ * @param name 调用名称,用于隔离不同业务
720
+ * @param getKey 参数转 key 方法,默认会对对象 key 排序后序列化
721
+ */
722
+ static createConcurrentCall(fn, name, getKey = (...args) => _Concurrency.defaultKey(...args)) {
723
+ return async (...args) => {
724
+ const key = `${name}-${getKey(...args)}`;
725
+ const pending = this.pendingRequests.get(key);
726
+ if (pending) return pending;
727
+ const promise = (async () => {
728
+ try {
729
+ return await fn(...args);
730
+ } finally {
731
+ setTimeout(() => {
732
+ this.pendingRequests.delete(key);
733
+ }, 0);
734
+ }
735
+ })();
736
+ this.pendingRequests.set(key, promise);
737
+ return promise;
738
+ };
739
+ }
740
+ /**
741
+ * 默认 key 生成逻辑。
742
+ */
743
+ static defaultKey(...args) {
744
+ return args.map((arg) => {
745
+ if (arg && typeof arg === "object" && !Array.isArray(arg)) {
746
+ const sorted = Object.fromEntries(Object.entries(arg).sort(([a], [b]) => a.localeCompare(b)));
747
+ return JSON.stringify(sorted);
748
+ }
749
+ return String(arg);
750
+ }).join("-");
751
+ }
752
+ };
753
+ /**
754
+ * 正在执行中的请求映射。
755
+ */
756
+ _Concurrency.pendingRequests = /* @__PURE__ */ new Map();
757
+ var Concurrency = _Concurrency;
758
+
759
+ // src/utils/AlgorithmUtils.ts
760
+ import CryptoJS from "crypto-js";
761
+ var AlgorithmUtils = class {
762
+ /** Base64 ?? UTF-8 ??? */
763
+ static base64Encode(text) {
764
+ return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Utf8.parse(text));
765
+ }
766
+ /** Base64 ??? UTF-8 ??? */
767
+ static base64Decode(base64) {
768
+ return CryptoJS.enc.Base64.parse(base64).toString(CryptoJS.enc.Utf8);
769
+ }
770
+ /**
771
+ * ?? MD5?
772
+ *
773
+ * @param length 16 ??? 32 ? MD5 ??? 16 ??
774
+ * @param upper ??????
775
+ */
776
+ static md5(text, length = 32, upper = false) {
777
+ const value = CryptoJS.MD5(text).toString();
778
+ const result = length === 16 ? value.slice(8, 24) : value;
779
+ return upper ? result.toUpperCase() : result;
780
+ }
781
+ /** ?? 16 ? MD5? */
782
+ static md5_16(text, upper = false) {
783
+ return this.md5(text, 16, upper);
784
+ }
785
+ /** ?? 32 ? MD5? */
786
+ static md5_32(text, upper = false) {
787
+ return this.md5(text, 32, upper);
788
+ }
789
+ /** AES ????? iv ??? crypto-js passphrase ???? iv ??? CBC + PKCS7? */
790
+ static aesEncrypt(text, options) {
791
+ const aesOptions = this.normalizeAesOptions(options);
792
+ if (!aesOptions.iv)
793
+ return CryptoJS.AES.encrypt(text, aesOptions.key).toString();
794
+ return CryptoJS.AES.encrypt(text, CryptoJS.enc.Utf8.parse(aesOptions.key), {
795
+ iv: CryptoJS.enc.Utf8.parse(aesOptions.iv),
796
+ mode: CryptoJS.mode.CBC,
797
+ padding: CryptoJS.pad.Pkcs7
798
+ }).toString();
799
+ }
800
+ /** AES ??? */
801
+ static aesDecrypt(ciphertext, options) {
802
+ const aesOptions = this.normalizeAesOptions(options);
803
+ const bytes = aesOptions.iv ? CryptoJS.AES.decrypt(
804
+ ciphertext,
805
+ CryptoJS.enc.Utf8.parse(aesOptions.key),
806
+ {
807
+ iv: CryptoJS.enc.Utf8.parse(aesOptions.iv),
808
+ mode: CryptoJS.mode.CBC,
809
+ padding: CryptoJS.pad.Pkcs7
810
+ }
811
+ ) : CryptoJS.AES.decrypt(ciphertext, aesOptions.key);
812
+ return bytes.toString(CryptoJS.enc.Utf8);
813
+ }
814
+ /** ?? SHA256? */
815
+ static sha256(text, upper = false) {
816
+ const value = CryptoJS.SHA256(text).toString();
817
+ return upper ? value.toUpperCase() : value;
818
+ }
819
+ /** ?? HMAC? */
820
+ static hmac(text, key, algorithm = "SHA256", upper = false) {
821
+ const value = {
822
+ SHA256: () => CryptoJS.HmacSHA256(text, key),
823
+ SHA1: () => CryptoJS.HmacSHA1(text, key),
824
+ MD5: () => CryptoJS.HmacMD5(text, key)
825
+ }[algorithm]().toString();
826
+ return upper ? value.toUpperCase() : value;
827
+ }
828
+ /** ?? HMAC-SHA256? */
829
+ static hmacSha256(text, key, upper = false) {
830
+ return this.hmac(text, key, "SHA256", upper);
831
+ }
832
+ /** ?? UUID v4? */
833
+ static uuid() {
834
+ const bytes = this.randomBytes(16);
835
+ bytes[6] = bytes[6] & 15 | 64;
836
+ bytes[8] = bytes[8] & 63 | 128;
837
+ const hex = Array.from(
838
+ bytes,
839
+ (byte) => byte.toString(16).padStart(2, "0")
840
+ ).join("");
841
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
842
+ }
843
+ /** ??????????????? min ? max? */
844
+ static randomInt(min, max) {
845
+ const lower = Math.ceil(min);
846
+ const upper = Math.floor(max);
847
+ if (upper < lower)
848
+ throw new Error("max must be greater than or equal to min");
849
+ return lower + Math.floor(this.randomFloat() * (upper - lower + 1));
850
+ }
851
+ /** ?? 0 <= n < 1 ?????? */
852
+ static randomFloat() {
853
+ const bytes = this.randomBytes(4);
854
+ const value = (bytes[0] << 24 >>> 0) + (bytes[1] << 16) + (bytes[2] << 8) + bytes[3];
855
+ return value / 4294967296;
856
+ }
857
+ /** ???????? */
858
+ static randomString(length = 16, chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") {
859
+ if (length < 0)
860
+ throw new Error("length must be greater than or equal to 0");
861
+ if (!chars) throw new Error("chars must not be empty");
862
+ let result = "";
863
+ for (let i = 0; i < length; i += 1) {
864
+ result += chars[this.randomInt(0, chars.length - 1)];
865
+ }
866
+ return result;
867
+ }
868
+ /** ?? RSA-OAEP ???? */
869
+ static async rsaGenerateKeyPair(modulusLength = 2048) {
870
+ const keyPair = await this.subtle().generateKey(
871
+ {
872
+ name: "RSA-OAEP",
873
+ modulusLength,
874
+ publicExponent: new Uint8Array([1, 0, 1]),
875
+ hash: "SHA-256"
876
+ },
877
+ true,
878
+ ["encrypt", "decrypt"]
879
+ );
880
+ return {
881
+ publicKey: keyPair.publicKey,
882
+ privateKey: keyPair.privateKey
883
+ };
884
+ }
885
+ /** ?? RSA-OAEP PEM ???? */
886
+ static async rsaGeneratePemKeyPair(modulusLength = 2048) {
887
+ const keyPair = await this.rsaGenerateKeyPair(modulusLength);
888
+ return {
889
+ publicKey: await this.rsaExportPublicKey(keyPair.publicKey),
890
+ privateKey: await this.rsaExportPrivateKey(keyPair.privateKey)
891
+ };
892
+ }
893
+ /** RSA-OAEP ??????? Base64 ??? */
894
+ static async rsaEncrypt(text, publicKey) {
895
+ const key = typeof publicKey === "string" ? await this.rsaImportPublicKey(publicKey) : publicKey;
896
+ const encrypted = await this.subtle().encrypt(
897
+ { name: "RSA-OAEP" },
898
+ key,
899
+ new TextEncoder().encode(text)
900
+ );
901
+ return this.arrayBufferToBase64(encrypted);
902
+ }
903
+ /** RSA-OAEP ???? Base64 ??? */
904
+ static async rsaDecrypt(ciphertext, privateKey) {
905
+ const key = typeof privateKey === "string" ? await this.rsaImportPrivateKey(privateKey) : privateKey;
906
+ const decrypted = await this.subtle().decrypt(
907
+ { name: "RSA-OAEP" },
908
+ key,
909
+ this.base64ToArrayBuffer(ciphertext)
910
+ );
911
+ return new TextDecoder().decode(decrypted);
912
+ }
913
+ /** ?? RSA ??? PEM? */
914
+ static async rsaExportPublicKey(publicKey) {
915
+ const buffer = await this.subtle().exportKey("spki", publicKey);
916
+ return this.arrayBufferToPem(buffer, "PUBLIC KEY");
917
+ }
918
+ /** ?? RSA ??? PEM? */
919
+ static async rsaExportPrivateKey(privateKey) {
920
+ const buffer = await this.subtle().exportKey("pkcs8", privateKey);
921
+ return this.arrayBufferToPem(buffer, "PRIVATE KEY");
922
+ }
923
+ /** ? PEM ?? RSA ??? */
924
+ static async rsaImportPublicKey(pem) {
925
+ return this.subtle().importKey(
926
+ "spki",
927
+ this.pemToArrayBuffer(pem),
928
+ { name: "RSA-OAEP", hash: "SHA-256" },
929
+ true,
930
+ ["encrypt"]
931
+ );
932
+ }
933
+ /** ? PEM ?? RSA ??? */
934
+ static async rsaImportPrivateKey(pem) {
935
+ return this.subtle().importKey(
936
+ "pkcs8",
937
+ this.pemToArrayBuffer(pem),
938
+ { name: "RSA-OAEP", hash: "SHA-256" },
939
+ true,
940
+ ["decrypt"]
941
+ );
942
+ }
943
+ /** ??? AES ??? */
944
+ static normalizeAesOptions(options) {
945
+ return typeof options === "string" ? { key: options } : options;
946
+ }
947
+ /** ??????? */
948
+ static randomBytes(length) {
949
+ const crypto = globalThis.crypto;
950
+ if (crypto?.getRandomValues)
951
+ return crypto.getRandomValues(new Uint8Array(length));
952
+ const words = CryptoJS.lib.WordArray.random(length);
953
+ const bytes = new Uint8Array(length);
954
+ for (let i = 0; i < length; i += 1) {
955
+ bytes[i] = words.words[Math.floor(i / 4)] >> 24 - i % 4 * 8 & 255;
956
+ }
957
+ return bytes;
958
+ }
959
+ /** ?? SubtleCrypto? */
960
+ static subtle() {
961
+ const subtle = globalThis.crypto?.subtle;
962
+ if (!subtle)
963
+ throw new Error("SubtleCrypto is not available in current runtime");
964
+ return subtle;
965
+ }
966
+ /** ArrayBuffer ? Base64? */
967
+ static arrayBufferToBase64(buffer) {
968
+ const bytes = new Uint8Array(buffer);
969
+ let binary = "";
970
+ for (const byte of bytes) binary += String.fromCharCode(byte);
971
+ return this.binaryToBase64(binary);
972
+ }
973
+ /** Base64 ? ArrayBuffer? */
974
+ static base64ToArrayBuffer(base64) {
975
+ const binary = this.base64ToBinary(base64);
976
+ const bytes = new Uint8Array(binary.length);
977
+ for (let i = 0; i < binary.length; i += 1) bytes[i] = binary.charCodeAt(i);
978
+ return bytes.buffer;
979
+ }
980
+ /** ArrayBuffer ? PEM? */
981
+ static arrayBufferToPem(buffer, label) {
982
+ const base64 = this.arrayBufferToBase64(buffer).replace(/(.{64})/g, "$1\n");
983
+ return `-----BEGIN ${label}-----
984
+ ${base64}
985
+ -----END ${label}-----`;
986
+ }
987
+ /** PEM ? ArrayBuffer? */
988
+ static pemToArrayBuffer(pem) {
989
+ const base64 = pem.replace(/-----BEGIN [^-]+-----/g, "").replace(/-----END [^-]+-----/g, "").replace(/\s+/g, "");
990
+ return this.base64ToArrayBuffer(base64);
991
+ }
992
+ /** ??????? Base64? */
993
+ static binaryToBase64(binary) {
994
+ if (typeof btoa === "function") return btoa(binary);
995
+ return Buffer.from(binary, "binary").toString("base64");
996
+ }
997
+ /** Base64 ???????? */
998
+ static base64ToBinary(base64) {
999
+ if (typeof atob === "function") return atob(base64);
1000
+ return Buffer.from(base64, "base64").toString("binary");
1001
+ }
1002
+ };
1003
+
1004
+ // src/utils/Hook.ts
1005
+ var hookMap = /* @__PURE__ */ new Map();
1006
+ var Hook = class _Hook {
1007
+ /**
1008
+ * @param no Hook 编号,默认随机生成
1009
+ * @param timeout 超时时间,单位秒
1010
+ */
1011
+ constructor(no = randomText(16, 32), timeout) {
1012
+ /**
1013
+ * 超时时间,单位秒。
1014
+ */
1015
+ this.timeout = 30;
1016
+ this.no = no;
1017
+ if (timeout !== void 0) this.timeout = timeout;
1018
+ }
1019
+ /**
1020
+ * 回调
1021
+ * @param data
1022
+ */
1023
+ call(data) {
1024
+ _Hook.call(this, data);
1025
+ }
1026
+ /**
1027
+ * 注册一个 Hook,并返回等待外部唤醒的 Promise。
1028
+ */
1029
+ static on(hook) {
1030
+ return new Promise((resolve, reject) => {
1031
+ const instance = hook instanceof _Hook ? hook : new _Hook(hook);
1032
+ _Hook.clear(instance.no);
1033
+ instance.resolve = resolve;
1034
+ instance.reject = reject;
1035
+ if (instance.timeout) {
1036
+ instance.timer = setTimeout(() => {
1037
+ _Hook.clear(instance);
1038
+ reject(new Error("\u8D85\u65F6..."));
1039
+ }, instance.timeout * 1e3);
1040
+ }
1041
+ hookMap.set(instance.no.toString(), instance);
1042
+ });
1043
+ }
1044
+ /**
1045
+ * 唤醒指定 Hook。
1046
+ *
1047
+ * data 为 Error 时 reject,否则 resolve。
1048
+ */
1049
+ static call(hook, data) {
1050
+ const instance = hook instanceof _Hook ? hook : hookMap.get(hook.toString());
1051
+ if (!instance) return;
1052
+ if (data instanceof Error) instance.reject?.(data);
1053
+ else instance.resolve?.(data);
1054
+ _Hook.clear(instance);
1055
+ }
1056
+ /**
1057
+ * 清理指定 Hook。
1058
+ */
1059
+ static clear(hook) {
1060
+ const instance = hook instanceof _Hook ? hook : hookMap.get(hook.toString());
1061
+ if (!instance) return;
1062
+ if (instance.timer) clearTimeout(instance.timer);
1063
+ hookMap.delete(instance.no.toString());
1064
+ }
1065
+ /**
1066
+ * 自动生成 Hook 编号并执行回调,随后等待外部通过编号 call。
1067
+ */
1068
+ static async no(fn, timeout) {
1069
+ const hook = new _Hook(void 0, timeout);
1070
+ await fn(hook.no);
1071
+ return _Hook.on(hook);
1072
+ }
1073
+ };
1074
+ function randomText(min = 12, max = 16) {
1075
+ const num = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
1076
+ const lower = "abcdefghijklmnopqrstuvwxyz".split("");
1077
+ const upper = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
1078
+ const special = ["-", "_", "#", "@", "%", "&", "*", "$"];
1079
+ const config = [...num, ...lower, ...upper, ...special];
1080
+ const result = [pick(num), pick(lower), pick(upper), pick(special)];
1081
+ const len = min + Math.floor(Math.random() * (max - min + 1));
1082
+ for (let i = 4; i < len; i += 1) result.push(pick(config));
1083
+ return shuffle(result).join("");
1084
+ }
1085
+ function pick(values) {
1086
+ return values[Math.floor(Math.random() * values.length)] ?? "";
1087
+ }
1088
+ function shuffle(values) {
1089
+ const copy = [...values];
1090
+ for (let i = copy.length - 1; i > 0; i -= 1) {
1091
+ const j = Math.floor(Math.random() * (i + 1));
1092
+ [copy[i], copy[j]] = [copy[j], copy[i]];
1093
+ }
1094
+ return copy;
1095
+ }
1096
+
1097
+ // src/utils/TaskQueue.ts
1098
+ var TaskQueue = class _TaskQueue {
1099
+ constructor(options = {}) {
1100
+ /**
1101
+ * 当前运行中的任务数。
1102
+ */
1103
+ this.running = 0;
1104
+ /**
1105
+ * 等待执行的任务队列。
1106
+ */
1107
+ this.queue = [];
1108
+ /**
1109
+ * 是否暂停。
1110
+ */
1111
+ this.paused = false;
1112
+ /**
1113
+ * 是否停止。
1114
+ */
1115
+ this.stopped = false;
1116
+ /**
1117
+ * waitForIdle 等待回调。
1118
+ */
1119
+ this.idleResolvers = [];
1120
+ const { concurrency = 1, timeout = 3e4, taskDelayMs = 0 } = options;
1121
+ this.concurrency = normalizeConcurrency(concurrency);
1122
+ this.timeout = normalizeTimeout(timeout);
1123
+ this.taskDelayMs = normalizeTimeout(taskDelayMs);
1124
+ }
1125
+ /**
1126
+ * 延迟指定毫秒后返回 value。
1127
+ */
1128
+ static delay(ms, value) {
1129
+ return new Promise((resolve) => {
1130
+ setTimeout(() => resolve(value), ms);
1131
+ });
1132
+ }
1133
+ /**
1134
+ * 设置最大并发数。
1135
+ */
1136
+ setConcurrency(concurrency) {
1137
+ this.concurrency = normalizeConcurrency(concurrency);
1138
+ this.next();
1139
+ }
1140
+ /**
1141
+ * 设置默认任务超时时间,单位毫秒。
1142
+ */
1143
+ setTimeout(timeout) {
1144
+ this.timeout = normalizeTimeout(timeout);
1145
+ }
1146
+ /**
1147
+ * 设置任务间隔时间,单位毫秒。
1148
+ */
1149
+ setTaskDelay(delayMs) {
1150
+ this.taskDelayMs = normalizeTimeout(delayMs);
1151
+ }
1152
+ /**
1153
+ * 添加单个任务。
1154
+ *
1155
+ * @param task 要执行的任务函数
1156
+ * @param timeout 当前任务的超时时间,单位毫秒。不传则使用默认超时时间
1157
+ */
1158
+ add(task, timeout) {
1159
+ if (typeof task !== "function") {
1160
+ return Promise.reject(new TypeError("TaskQueue.add requires a task function"));
1161
+ }
1162
+ if (this.stopped) {
1163
+ return Promise.reject(new Error("TaskQueue stopped"));
1164
+ }
1165
+ return new Promise((resolve, reject) => {
1166
+ this.queue.push({
1167
+ task,
1168
+ resolve,
1169
+ reject,
1170
+ timeout: timeout == null ? this.timeout : normalizeTimeout(timeout),
1171
+ settled: false
1172
+ });
1173
+ this.next();
1174
+ });
1175
+ }
1176
+ /**
1177
+ * 批量添加任务。
1178
+ */
1179
+ addMany(tasks = []) {
1180
+ return Promise.all(tasks.map((task) => this.add(task)));
1181
+ }
1182
+ /**
1183
+ * 暂停队列。
1184
+ *
1185
+ * 已经运行的任务不会被取消,但不会启动新任务。
1186
+ */
1187
+ pause() {
1188
+ this.paused = true;
1189
+ }
1190
+ /**
1191
+ * 恢复队列。
1192
+ */
1193
+ resume() {
1194
+ if (!this.paused) return;
1195
+ this.paused = false;
1196
+ this.next();
1197
+ }
1198
+ /**
1199
+ * 清空等待队列。
1200
+ *
1201
+ * 运行中的任务不受影响。
1202
+ */
1203
+ clear(reason = new Error("TaskQueue cleared")) {
1204
+ this.rejectQueued(reason);
1205
+ this.queue = [];
1206
+ this.resolveIdleIfNeeded();
1207
+ }
1208
+ /**
1209
+ * 停止队列。
1210
+ *
1211
+ * 会拒绝所有等待中的任务,并禁止继续添加新任务。
1212
+ */
1213
+ stop(reason = new Error("TaskQueue stopped")) {
1214
+ this.stopped = true;
1215
+ this.paused = false;
1216
+ this.clear(reason);
1217
+ }
1218
+ /**
1219
+ * 重置队列状态。
1220
+ *
1221
+ * 会清空等待队列和运行计数,不会取消已经运行的异步函数本身。
1222
+ */
1223
+ reset() {
1224
+ this.stopped = false;
1225
+ this.paused = false;
1226
+ this.queue = [];
1227
+ this.running = 0;
1228
+ this.resolveIdleIfNeeded();
1229
+ }
1230
+ /**
1231
+ * 等待队列空闲。
1232
+ *
1233
+ * 当没有等待任务且没有运行中任务时 resolve。
1234
+ */
1235
+ waitForIdle() {
1236
+ if (!this.queue.length && !this.running) {
1237
+ return Promise.resolve();
1238
+ }
1239
+ return new Promise((resolve) => {
1240
+ this.idleResolvers.push(resolve);
1241
+ });
1242
+ }
1243
+ /**
1244
+ * 等待中的任务数。
1245
+ */
1246
+ get size() {
1247
+ return this.queue.length;
1248
+ }
1249
+ /**
1250
+ * 当前运行中的任务数。
1251
+ */
1252
+ get activeCount() {
1253
+ return this.running;
1254
+ }
1255
+ /**
1256
+ * 尝试启动队列中的任务。
1257
+ */
1258
+ next() {
1259
+ if (this.paused || this.stopped) return;
1260
+ while (this.canRunMore() && this.queue.length) {
1261
+ const entry = this.queue.shift();
1262
+ if (entry) void this.runTask(entry);
1263
+ }
1264
+ this.resolveIdleIfNeeded();
1265
+ }
1266
+ /**
1267
+ * 判断是否还能启动更多任务。
1268
+ */
1269
+ canRunMore() {
1270
+ if (!this.concurrency) return true;
1271
+ return this.running < this.concurrency;
1272
+ }
1273
+ /**
1274
+ * 执行单个任务。
1275
+ */
1276
+ async runTask(entry) {
1277
+ this.running += 1;
1278
+ let timer;
1279
+ if (entry.timeout > 0) {
1280
+ timer = setTimeout(() => {
1281
+ this.rejectEntry(entry, new Error("Task timeout"));
1282
+ }, entry.timeout);
1283
+ }
1284
+ try {
1285
+ const result = await entry.task();
1286
+ this.resolveEntry(entry, result);
1287
+ } catch (error) {
1288
+ this.rejectEntry(entry, error);
1289
+ } finally {
1290
+ if (timer) clearTimeout(timer);
1291
+ if (this.taskDelayMs > 0 && !this.stopped) {
1292
+ await _TaskQueue.delay(this.taskDelayMs);
1293
+ }
1294
+ this.running = Math.max(this.running - 1, 0);
1295
+ this.next();
1296
+ }
1297
+ }
1298
+ /**
1299
+ * resolve 指定任务,避免重复 settle。
1300
+ */
1301
+ resolveEntry(entry, value) {
1302
+ if (entry.settled) return;
1303
+ entry.settled = true;
1304
+ entry.resolve(value);
1305
+ }
1306
+ /**
1307
+ * reject 指定任务,避免重复 settle。
1308
+ */
1309
+ rejectEntry(entry, error) {
1310
+ if (entry.settled) return;
1311
+ entry.settled = true;
1312
+ entry.reject(error);
1313
+ }
1314
+ /**
1315
+ * reject 所有等待中的任务。
1316
+ */
1317
+ rejectQueued(reason) {
1318
+ for (const entry of this.queue) {
1319
+ this.rejectEntry(entry, reason);
1320
+ }
1321
+ }
1322
+ /**
1323
+ * 如果队列空闲,唤醒所有 waitForIdle。
1324
+ */
1325
+ resolveIdleIfNeeded() {
1326
+ if (this.queue.length || this.running) return;
1327
+ const resolvers = this.idleResolvers.splice(0);
1328
+ for (const resolve of resolvers) resolve();
1329
+ }
1330
+ };
1331
+ function normalizeConcurrency(value) {
1332
+ const number = Number(value);
1333
+ if (!Number.isFinite(number) || number < 0) return 1;
1334
+ return Math.floor(number);
1335
+ }
1336
+ function normalizeTimeout(value) {
1337
+ const number = Number(value);
1338
+ if (!Number.isFinite(number)) return 0;
1339
+ return Math.max(Math.floor(number), 0);
1340
+ }
1341
+
1342
+ // src/utils/TimeUtils.ts
1343
+ var TimeUtils = class {
1344
+ /**
1345
+ * 获取当前时间。
1346
+ */
1347
+ static now() {
1348
+ return /* @__PURE__ */ new Date();
1349
+ }
1350
+ /**
1351
+ * 获取时间戳。
1352
+ *
1353
+ * @param date 时间输入,默认当前时间
1354
+ * @param unit 返回毫秒或秒
1355
+ */
1356
+ static timestamp(date = /* @__PURE__ */ new Date(), unit = "millisecond") {
1357
+ const time = this.toDate(date).getTime();
1358
+ return unit === "second" ? Math.floor(time / 1e3) : time;
1359
+ }
1360
+ /**
1361
+ * 将 Date、时间戳、时间字符串转换为新的 Date 实例。
1362
+ *
1363
+ * 字符串中的 `-` 会替换成 `/`,兼容部分浏览器对 `yyyy-MM-dd` 的解析差异。
1364
+ */
1365
+ static toDate(value) {
1366
+ if (value instanceof Date) return new Date(value.getTime());
1367
+ if (typeof value === "number") return new Date(value);
1368
+ return new Date(value.replace(/-/g, "/"));
1369
+ }
1370
+ /**
1371
+ * 判断输入是否可转换为有效时间。
1372
+ */
1373
+ static isValid(value) {
1374
+ if (!(value instanceof Date) && typeof value !== "number" && typeof value !== "string") return false;
1375
+ return !Number.isNaN(this.toDate(value).getTime());
1376
+ }
1377
+ /**
1378
+ * 格式化时间。
1379
+ *
1380
+ * 支持 token:yyyy、MM、dd、HH、mm、ss、SSS。
1381
+ */
1382
+ static format(date = /* @__PURE__ */ new Date(), pattern = "yyyy-MM-dd HH:mm:ss") {
1383
+ const target = this.toDate(date);
1384
+ const values = {
1385
+ yyyy: String(target.getFullYear()),
1386
+ MM: this.pad(target.getMonth() + 1),
1387
+ dd: this.pad(target.getDate()),
1388
+ HH: this.pad(target.getHours()),
1389
+ mm: this.pad(target.getMinutes()),
1390
+ ss: this.pad(target.getSeconds()),
1391
+ SSS: this.pad(target.getMilliseconds(), 3)
1392
+ };
1393
+ return pattern.replace(/yyyy|MM|dd|HH|mm|ss|SSS/g, (token) => values[token] ?? token);
1394
+ }
1395
+ /**
1396
+ * 给时间增加指定单位的数量。
1397
+ */
1398
+ static add(date, amount, unit) {
1399
+ const target = this.toDate(date);
1400
+ switch (unit) {
1401
+ case "millisecond":
1402
+ target.setMilliseconds(target.getMilliseconds() + amount);
1403
+ break;
1404
+ case "second":
1405
+ target.setSeconds(target.getSeconds() + amount);
1406
+ break;
1407
+ case "minute":
1408
+ target.setMinutes(target.getMinutes() + amount);
1409
+ break;
1410
+ case "hour":
1411
+ target.setHours(target.getHours() + amount);
1412
+ break;
1413
+ case "day":
1414
+ target.setDate(target.getDate() + amount);
1415
+ break;
1416
+ case "week":
1417
+ target.setDate(target.getDate() + amount * 7);
1418
+ break;
1419
+ case "month":
1420
+ target.setMonth(target.getMonth() + amount);
1421
+ break;
1422
+ case "year":
1423
+ target.setFullYear(target.getFullYear() + amount);
1424
+ break;
1425
+ }
1426
+ return target;
1427
+ }
1428
+ /**
1429
+ * 给时间减少指定单位的数量。
1430
+ */
1431
+ static subtract(date, amount, unit) {
1432
+ return this.add(date, -amount, unit);
1433
+ }
1434
+ /**
1435
+ * 计算两个时间的差值。
1436
+ *
1437
+ * month/year 当前按 30/365 天近似计算,适合轻量工具场景。
1438
+ */
1439
+ static diff(start, end, unit = "millisecond") {
1440
+ const value = this.toDate(end).getTime() - this.toDate(start).getTime();
1441
+ const divisors = {
1442
+ millisecond: 1,
1443
+ second: 1e3,
1444
+ minute: 60 * 1e3,
1445
+ hour: 60 * 60 * 1e3,
1446
+ day: 24 * 60 * 60 * 1e3,
1447
+ week: 7 * 24 * 60 * 60 * 1e3,
1448
+ month: 30 * 24 * 60 * 60 * 1e3,
1449
+ year: 365 * 24 * 60 * 60 * 1e3
1450
+ };
1451
+ return Math.floor(value / divisors[unit]);
1452
+ }
1453
+ /**
1454
+ * 格式化为相对当前时间的描述。
1455
+ *
1456
+ * 默认返回中文短语,例如:刚刚、3 分钟前、2 小时后。
1457
+ *
1458
+ * @param date 目标时间
1459
+ * @param base 基准时间,默认当前时间
1460
+ */
1461
+ static fromNow(date, base = /* @__PURE__ */ new Date()) {
1462
+ const diff = this.toDate(base).getTime() - this.toDate(date).getTime();
1463
+ const abs = Math.abs(diff);
1464
+ const suffix = diff >= 0 ? "\u524D" : "\u540E";
1465
+ if (abs < 5 * 1e3) return "\u521A\u521A";
1466
+ const units = [
1467
+ ["\u5E74", 365 * 24 * 60 * 60 * 1e3],
1468
+ ["\u6708", 30 * 24 * 60 * 60 * 1e3],
1469
+ ["\u5929", 24 * 60 * 60 * 1e3],
1470
+ ["\u5C0F\u65F6", 60 * 60 * 1e3],
1471
+ ["\u5206\u949F", 60 * 1e3],
1472
+ ["\u79D2", 1e3]
1473
+ ];
1474
+ for (const [unit, milliseconds] of units) {
1475
+ const value = Math.floor(abs / milliseconds);
1476
+ if (value >= 1) return `${value} ${unit}${suffix}`;
1477
+ }
1478
+ return "\u521A\u521A";
1479
+ }
1480
+ /**
1481
+ * 获取当天开始时间:00:00:00.000。
1482
+ */
1483
+ static startOfDay(date = /* @__PURE__ */ new Date()) {
1484
+ const target = this.toDate(date);
1485
+ target.setHours(0, 0, 0, 0);
1486
+ return target;
1487
+ }
1488
+ /**
1489
+ * 获取当天结束时间:23:59:59.999。
1490
+ */
1491
+ static endOfDay(date = /* @__PURE__ */ new Date()) {
1492
+ const target = this.toDate(date);
1493
+ target.setHours(23, 59, 59, 999);
1494
+ return target;
1495
+ }
1496
+ /**
1497
+ * 延迟指定毫秒数。
1498
+ */
1499
+ static wait(time) {
1500
+ return new Promise((resolve) => {
1501
+ setTimeout(resolve, time);
1502
+ });
1503
+ }
1504
+ /**
1505
+ * 数字左侧补零。
1506
+ */
1507
+ static pad(value, length = 2) {
1508
+ return String(value).padStart(length, "0");
1509
+ }
1510
+ };
1511
+
1512
+ // src/utils/ValidateUtils.ts
1513
+ var ValidateUtils = class {
1514
+ /**
1515
+ * 判断是否为 null 或 undefined。
1516
+ */
1517
+ static isNil(value) {
1518
+ return value === null || value === void 0;
1519
+ }
1520
+ /**
1521
+ * 判断是否为空。
1522
+ *
1523
+ * 空字符串、空数组、空对象、null、undefined 都视为空。
1524
+ */
1525
+ static isEmpty(value) {
1526
+ if (this.isNil(value)) return true;
1527
+ if (typeof value === "string") return value.trim().length === 0;
1528
+ if (Array.isArray(value)) return value.length === 0;
1529
+ if (value instanceof Map || value instanceof Set) return value.size === 0;
1530
+ if (typeof value === "object") return Object.keys(value).length === 0;
1531
+ return false;
1532
+ }
1533
+ /**
1534
+ * 判断是否非空。
1535
+ */
1536
+ static isNotEmpty(value) {
1537
+ return !this.isEmpty(value);
1538
+ }
1539
+ /**
1540
+ * 判断是否为字符串。
1541
+ */
1542
+ static isString(value) {
1543
+ return typeof value === "string";
1544
+ }
1545
+ /**
1546
+ * 判断是否为数字且非 NaN。
1547
+ */
1548
+ static isNumber(value) {
1549
+ return typeof value === "number" && !Number.isNaN(value);
1550
+ }
1551
+ /**
1552
+ * 判断是否为整数。
1553
+ */
1554
+ static isInteger(value) {
1555
+ return Number.isInteger(value);
1556
+ }
1557
+ /**
1558
+ * 判断是否为布尔值。
1559
+ */
1560
+ static isBoolean(value) {
1561
+ return typeof value === "boolean";
1562
+ }
1563
+ /**
1564
+ * 判断是否为数组。
1565
+ */
1566
+ static isArray(value) {
1567
+ return Array.isArray(value);
1568
+ }
1569
+ /**
1570
+ * 判断是否为普通对象。
1571
+ */
1572
+ static isPlainObject(value) {
1573
+ return Object.prototype.toString.call(value) === "[object Object]";
1574
+ }
1575
+ /**
1576
+ * 判断字符串长度是否在指定范围内。
1577
+ */
1578
+ static lengthBetween(value, min, max) {
1579
+ const length = [...value].length;
1580
+ return length >= min && length <= max;
1581
+ }
1582
+ /**
1583
+ * 判断数字是否在指定范围内。
1584
+ */
1585
+ static numberBetween(value, min, max) {
1586
+ return this.isNumber(value) && value >= min && value <= max;
1587
+ }
1588
+ /**
1589
+ * 正则校验。
1590
+ */
1591
+ static match(value, pattern) {
1592
+ return pattern.test(value);
1593
+ }
1594
+ /**
1595
+ * 校验邮箱。
1596
+ */
1597
+ static isEmail(value) {
1598
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value);
1599
+ }
1600
+ /**
1601
+ * 校验中国大陆手机号。
1602
+ */
1603
+ static isMobileCN(value) {
1604
+ return /^1[3-9]\d{9}$/.test(value);
1605
+ }
1606
+ /**
1607
+ * 校验 URL。
1608
+ */
1609
+ static isUrl(value) {
1610
+ try {
1611
+ const url = new URL(value);
1612
+ return ["http:", "https:"].includes(url.protocol);
1613
+ } catch {
1614
+ return false;
1615
+ }
1616
+ }
1617
+ /**
1618
+ * 校验 IPv4。
1619
+ */
1620
+ static isIPv4(value) {
1621
+ const parts = value.split(".");
1622
+ if (parts.length !== 4) return false;
1623
+ return parts.every((part) => /^\d+$/.test(part) && Number(part) >= 0 && Number(part) <= 255);
1624
+ }
1625
+ /**
1626
+ * 校验 IPv6。
1627
+ */
1628
+ static isIPv6(value) {
1629
+ return /^(([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|::1|::)$/.test(value);
1630
+ }
1631
+ /**
1632
+ * 校验是否为合法 JSON 字符串。
1633
+ */
1634
+ static isJSON(value) {
1635
+ try {
1636
+ JSON.parse(value);
1637
+ return true;
1638
+ } catch {
1639
+ return false;
1640
+ }
1641
+ }
1642
+ /**
1643
+ * 校验中国邮政编码。
1644
+ */
1645
+ static isPostalCodeCN(value) {
1646
+ return /^\d{6}$/.test(value);
1647
+ }
1648
+ /**
1649
+ * 校验中国车牌号,覆盖普通车牌与新能源车牌。
1650
+ */
1651
+ static isPlateNumberCN(value) {
1652
+ return /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼][A-Z][A-Z0-9挂学警港澳]{5,6}$/.test(
1653
+ value
1654
+ );
1655
+ }
1656
+ /**
1657
+ * 校验银行卡号。
1658
+ *
1659
+ * 使用 Luhn 算法。
1660
+ */
1661
+ static isBankCard(value) {
1662
+ if (!/^\d{12,19}$/.test(value)) return false;
1663
+ let sum = 0;
1664
+ let shouldDouble = false;
1665
+ for (let i = value.length - 1; i >= 0; i -= 1) {
1666
+ let digit = Number(value[i]);
1667
+ if (shouldDouble) {
1668
+ digit *= 2;
1669
+ if (digit > 9) digit -= 9;
1670
+ }
1671
+ sum += digit;
1672
+ shouldDouble = !shouldDouble;
1673
+ }
1674
+ return sum % 10 === 0;
1675
+ }
1676
+ /**
1677
+ * 校验中国居民身份证号。
1678
+ */
1679
+ static isIdCardCN(value) {
1680
+ return this.validateIdCardCN(value).valid;
1681
+ }
1682
+ /**
1683
+ * 校验中国居民身份证号,并返回生日、性别等信息。
1684
+ */
1685
+ static validateIdCardCN(value) {
1686
+ const id = value.toUpperCase();
1687
+ if (!/^\d{17}[\dX]$/.test(id)) return { valid: false, reason: "format" };
1688
+ const birthday = `${id.slice(6, 10)}-${id.slice(10, 12)}-${id.slice(12, 14)}`;
1689
+ const date = new Date(birthday.replace(/-/g, "/"));
1690
+ if (Number.isNaN(date.getTime()) || date.getFullYear() !== Number(id.slice(6, 10)) || date.getMonth() + 1 !== Number(id.slice(10, 12)) || date.getDate() !== Number(id.slice(12, 14))) {
1691
+ return { valid: false, reason: "birthday" };
1692
+ }
1693
+ const factors = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
1694
+ const codes = ["1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"];
1695
+ const sum = factors.reduce((total, factor, index) => total + Number(id[index]) * factor, 0);
1696
+ if (codes[sum % 11] !== id[17]) return { valid: false, reason: "checksum" };
1697
+ return {
1698
+ valid: true,
1699
+ birthday,
1700
+ gender: Number(id[16]) % 2 === 1 ? "male" : "female"
1701
+ };
1702
+ }
1703
+ /**
1704
+ * 判断密码强度。
1705
+ *
1706
+ * weak:长度不足或类型单一;
1707
+ * medium:长度 >= 8 且至少两类字符;
1708
+ * strong:长度 >= 10 且至少三类字符。
1709
+ */
1710
+ static passwordStrength(value) {
1711
+ let types = 0;
1712
+ if (/[a-z]/.test(value)) types += 1;
1713
+ if (/[A-Z]/.test(value)) types += 1;
1714
+ if (/\d/.test(value)) types += 1;
1715
+ if (/[^a-zA-Z\d]/.test(value)) types += 1;
1716
+ if (value.length >= 10 && types >= 3) return "strong";
1717
+ if (value.length >= 8 && types >= 2) return "medium";
1718
+ return "weak";
1719
+ }
1720
+ /**
1721
+ * 判断是否为强密码。
1722
+ */
1723
+ static isStrongPassword(value) {
1724
+ return this.passwordStrength(value) === "strong";
1725
+ }
1726
+ };
1727
+ export {
1728
+ useApiConfig as $Api,
1729
+ useApiConfig as $Use,
1730
+ AlgorithmUtils,
1731
+ ApiAdapter,
1732
+ ApiCacheInterceptor,
1733
+ ApiCancelError,
1734
+ ApiConfig,
1735
+ ApiError,
1736
+ ApiGlobalConfig,
1737
+ ApiInterceptor,
1738
+ ApiNetworkError,
1739
+ ApiRequest,
1740
+ ApiResponse,
1741
+ ApiStatusInterceptor,
1742
+ ApiTimeoutError,
1743
+ ApiUIInterceptor,
1744
+ Concurrency,
1745
+ FetchHttpAdapter,
1746
+ Hook,
1747
+ TaskQueue,
1748
+ TimeUtils,
1749
+ ValidateUtils,
1750
+ XhrHttpAdapter,
1751
+ isApiCancelError,
1752
+ useApiConfig
1753
+ };