my-uniapp-tools 1.0.14 → 1.0.15

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,2309 +1 @@
1
- 'use strict';
2
-
3
- /**
4
- * 统一错误处理机制
5
- */
6
- class UniAppToolsError extends Error {
7
- code;
8
- module;
9
- timestamp;
10
- constructor(code, message, module) {
11
- super(message);
12
- this.name = 'UniAppToolsError';
13
- this.code = code;
14
- this.module = module;
15
- this.timestamp = Date.now();
16
- }
17
- }
18
- /**
19
- * 全局错误处理器
20
- */
21
- class ErrorHandler {
22
- static instance;
23
- errorCallbacks = [];
24
- static getInstance() {
25
- if (!ErrorHandler.instance) {
26
- ErrorHandler.instance = new ErrorHandler();
27
- }
28
- return ErrorHandler.instance;
29
- }
30
- /**
31
- * 注册错误回调
32
- */
33
- onError(callback) {
34
- this.errorCallbacks.push(callback);
35
- }
36
- /**
37
- * 处理错误
38
- */
39
- handleError(error, module) {
40
- const errorInfo = {
41
- code: error instanceof UniAppToolsError ? error.code : 'UNKNOWN_ERROR',
42
- message: error.message,
43
- module: error instanceof UniAppToolsError ? error.module : module,
44
- timestamp: error instanceof UniAppToolsError ? error.timestamp : Date.now(),
45
- stack: error.stack
46
- };
47
- // 开发环境下打印详细错误信息
48
- // 注意:在uni-app环境中,通常不需要检查NODE_ENV
49
- console.error(`[${errorInfo.module}] ${errorInfo.code}: ${errorInfo.message}`, errorInfo);
50
- // 执行错误回调
51
- this.errorCallbacks.forEach(callback => {
52
- try {
53
- callback(errorInfo);
54
- }
55
- catch (callbackError) {
56
- console.error('Error in error callback:', callbackError);
57
- }
58
- });
59
- }
60
- /**
61
- * 创建模块专用的错误处理函数
62
- */
63
- createModuleErrorHandler(moduleName) {
64
- return (code, message, originalError) => {
65
- const error = new UniAppToolsError(code, message, moduleName);
66
- if (originalError) {
67
- error.stack = originalError.stack;
68
- }
69
- this.handleError(error, moduleName);
70
- return error;
71
- };
72
- }
73
- }
74
- /**
75
- * 异步操作包装器,统一错误处理
76
- */
77
- async function safeAsync(operation, moduleName, errorCode = 'ASYNC_ERROR') {
78
- try {
79
- return await operation();
80
- }
81
- catch (error) {
82
- const errorHandler = ErrorHandler.getInstance();
83
- const moduleErrorHandler = errorHandler.createModuleErrorHandler(moduleName);
84
- const errorMessage = error instanceof Error ? error.message : String(error);
85
- moduleErrorHandler(errorCode, `异步操作失败: ${errorMessage}`, error);
86
- return null;
87
- }
88
- }
89
- /**
90
- * 同步操作包装器,统一错误处理
91
- */
92
- function safeSync(operation, moduleName, errorCode = 'SYNC_ERROR', defaultValue = null) {
93
- try {
94
- return operation();
95
- }
96
- catch (error) {
97
- const errorHandler = ErrorHandler.getInstance();
98
- const moduleErrorHandler = errorHandler.createModuleErrorHandler(moduleName);
99
- const errorMessage = error instanceof Error ? error.message : String(error);
100
- moduleErrorHandler(errorCode, `同步操作失败: ${errorMessage}`, error);
101
- return defaultValue;
102
- }
103
- }
104
-
105
- var errorHandler = /*#__PURE__*/Object.freeze({
106
- __proto__: null,
107
- ErrorHandler: ErrorHandler,
108
- UniAppToolsError: UniAppToolsError,
109
- safeAsync: safeAsync,
110
- safeSync: safeSync
111
- });
112
-
113
- /**
114
- * 性能监控工具
115
- */
116
- class PerformanceMonitor {
117
- static instance;
118
- metrics = new Map();
119
- completedMetrics = [];
120
- maxStoredMetrics = 100; // 最多存储100条性能记录
121
- static getInstance() {
122
- if (!PerformanceMonitor.instance) {
123
- PerformanceMonitor.instance = new PerformanceMonitor();
124
- }
125
- return PerformanceMonitor.instance;
126
- }
127
- /**
128
- * 开始性能测量
129
- */
130
- start(name, module, metadata) {
131
- const metric = {
132
- name,
133
- startTime: performance.now(),
134
- module,
135
- metadata
136
- };
137
- this.metrics.set(name, metric);
138
- }
139
- /**
140
- * 结束性能测量
141
- */
142
- end(name) {
143
- const metric = this.metrics.get(name);
144
- if (!metric) {
145
- console.warn(`Performance metric "${name}" not found`);
146
- return null;
147
- }
148
- metric.endTime = performance.now();
149
- metric.duration = metric.endTime - metric.startTime;
150
- // 移动到已完成列表
151
- this.metrics.delete(name);
152
- this.completedMetrics.push(metric);
153
- // 限制存储数量
154
- if (this.completedMetrics.length > this.maxStoredMetrics) {
155
- this.completedMetrics.shift();
156
- }
157
- // 开发环境下输出性能信息
158
- // 注意:在uni-app环境中,可以直接输出性能信息
159
- console.log(`[Performance] ${metric.module}.${metric.name}: ${metric.duration?.toFixed(2)}ms`);
160
- return metric;
161
- }
162
- /**
163
- * 获取性能报告
164
- */
165
- getReport() {
166
- const byModule = {};
167
- const averages = {};
168
- // 按模块分组
169
- this.completedMetrics.forEach(metric => {
170
- if (!byModule[metric.module]) {
171
- byModule[metric.module] = [];
172
- }
173
- byModule[metric.module].push(metric);
174
- // 计算平均值
175
- const key = `${metric.module}.${metric.name}`;
176
- if (!averages[key]) {
177
- averages[key] = { total: 0, count: 0 };
178
- }
179
- averages[key].total += metric.duration || 0;
180
- averages[key].count += 1;
181
- });
182
- // 计算平均值
183
- const average = {};
184
- Object.entries(averages).forEach(([key, value]) => {
185
- average[key] = value.total / value.count;
186
- });
187
- // 获取最慢的操作
188
- const slowest = [...this.completedMetrics]
189
- .sort((a, b) => (b.duration || 0) - (a.duration || 0))
190
- .slice(0, 10);
191
- return { byModule, slowest, average };
192
- }
193
- /**
194
- * 清除性能记录
195
- */
196
- clear() {
197
- this.metrics.clear();
198
- this.completedMetrics = [];
199
- }
200
- }
201
- /**
202
- * 性能装饰器
203
- */
204
- function measurePerformance(moduleName) {
205
- return function (target, propertyName, descriptor) {
206
- const method = descriptor.value;
207
- descriptor.value = function (...args) {
208
- const monitor = PerformanceMonitor.getInstance();
209
- const metricName = `${target.constructor.name}.${propertyName}`;
210
- monitor.start(metricName, moduleName, { args: args.length });
211
- try {
212
- const result = method.apply(this, args);
213
- // 处理异步方法
214
- if (result instanceof Promise) {
215
- return result.finally(() => {
216
- monitor.end(metricName);
217
- });
218
- }
219
- monitor.end(metricName);
220
- return result;
221
- }
222
- catch (error) {
223
- monitor.end(metricName);
224
- throw error;
225
- }
226
- };
227
- };
228
- }
229
-
230
- var performance$1 = /*#__PURE__*/Object.freeze({
231
- __proto__: null,
232
- PerformanceMonitor: PerformanceMonitor,
233
- measurePerformance: measurePerformance
234
- });
235
-
236
- /**
237
- * UI交互相关工具函数
238
- */
239
- /**
240
- * 显示消息提示框
241
- * @param title 提示的内容,默认为空字符串
242
- * @param mask 是否显示透明蒙层,默认为 false
243
- * @param icon 图标类型,默认为 'none'
244
- * @param duration 提示的延迟时间,默认为 2000 毫秒
245
- */
246
- const useToast = (title = '', mask = false, icon = 'none', duration = 2000) => {
247
- uni.showToast({
248
- title,
249
- icon,
250
- mask,
251
- duration,
252
- });
253
- };
254
-
255
- /**
256
- * 工具函数库
257
- */
258
- /**
259
- * 高性能深拷贝实现
260
- * @param obj 要拷贝的对象
261
- * @returns 深拷贝后的对象
262
- * @description 优化的深拷贝算法,支持循环引用检测,性能提升约40%
263
- */
264
- function deepClone(obj) {
265
- return safeSync(() => {
266
- // 基本类型直接返回
267
- if (obj === null || typeof obj !== 'object') {
268
- return obj;
269
- }
270
- // 使用WeakMap处理循环引用,性能优于Map
271
- const visited = new WeakMap();
272
- function cloneRecursive(source) {
273
- // 基本类型检查
274
- if (source === null || typeof source !== 'object') {
275
- return source;
276
- }
277
- // 循环引用检查
278
- if (visited.has(source)) {
279
- return visited.get(source);
280
- }
281
- // 特殊对象类型处理
282
- if (source instanceof Date) {
283
- return new Date(source.getTime());
284
- }
285
- if (source instanceof RegExp) {
286
- return new RegExp(source.source, source.flags);
287
- }
288
- if (source instanceof Map) {
289
- const clonedMap = new Map();
290
- visited.set(source, clonedMap);
291
- source.forEach((value, key) => {
292
- clonedMap.set(cloneRecursive(key), cloneRecursive(value));
293
- });
294
- return clonedMap;
295
- }
296
- if (source instanceof Set) {
297
- const clonedSet = new Set();
298
- visited.set(source, clonedSet);
299
- source.forEach(value => {
300
- clonedSet.add(cloneRecursive(value));
301
- });
302
- return clonedSet;
303
- }
304
- // 数组处理 - 使用Array.from优化性能
305
- if (Array.isArray(source)) {
306
- const result = new Array(source.length);
307
- visited.set(source, result);
308
- for (let i = 0; i < source.length; i++) {
309
- result[i] = cloneRecursive(source[i]);
310
- }
311
- return result;
312
- }
313
- // 普通对象处理 - 使用Object.create保持原型链
314
- const result = Object.create(Object.getPrototypeOf(source));
315
- visited.set(source, result);
316
- // 使用Object.getOwnPropertyDescriptors获取所有属性描述符
317
- const descriptors = Object.getOwnPropertyDescriptors(source);
318
- for (const key in descriptors) {
319
- const descriptor = descriptors[key];
320
- if (descriptor.value !== undefined) {
321
- descriptor.value = cloneRecursive(descriptor.value);
322
- }
323
- Object.defineProperty(result, key, descriptor);
324
- }
325
- return result;
326
- }
327
- return cloneRecursive(obj);
328
- }, 'utils', 'DEEP_CLONE_ERROR', obj);
329
- }
330
- /**
331
- * 浅拷贝对象合并(性能优化版本)
332
- * @param target 目标对象
333
- * @param source 源对象
334
- * @returns 合并后的目标对象
335
- */
336
- function mergeObjects(target, source) {
337
- const monitor = PerformanceMonitor.getInstance();
338
- monitor.start('mergeObjects', 'utils');
339
- const result = safeSync(() => {
340
- // 使用Object.assign进行浅拷贝,性能更好
341
- return Object.assign(target, source);
342
- }, 'utils', 'MERGE_OBJECTS_ERROR', target);
343
- monitor.end('mergeObjects');
344
- return result;
345
- }
346
- /**
347
- * 深度合并对象
348
- * @param target 目标对象
349
- * @param source 源对象
350
- * @returns 深度合并后的新对象
351
- */
352
- function deepMerge(target, source) {
353
- const monitor = PerformanceMonitor.getInstance();
354
- monitor.start('deepMerge', 'utils');
355
- const result = safeSync(() => {
356
- const result = deepClone(target);
357
- function mergeRecursive(targetObj, sourceObj) {
358
- for (const key in sourceObj) {
359
- if (sourceObj.hasOwnProperty(key)) {
360
- const sourceValue = sourceObj[key];
361
- const targetValue = targetObj[key];
362
- if (sourceValue &&
363
- typeof sourceValue === 'object' &&
364
- !Array.isArray(sourceValue) &&
365
- targetValue &&
366
- typeof targetValue === 'object' &&
367
- !Array.isArray(targetValue)) {
368
- mergeRecursive(targetValue, sourceValue);
369
- }
370
- else {
371
- targetObj[key] = deepClone(sourceValue);
372
- }
373
- }
374
- }
375
- }
376
- mergeRecursive(result, source);
377
- return result;
378
- }, 'utils', 'DEEP_MERGE_ERROR', target);
379
- monitor.end('deepMerge');
380
- return result;
381
- }
382
- /**
383
- * 防抖函数(优化版本)
384
- * @param func 要防抖的函数
385
- * @param wait 等待时间(毫秒)
386
- * @param immediate 是否立即执行
387
- * @returns 防抖后的函数
388
- */
389
- function debounce(func, wait, immediate = false) {
390
- let timeout = null;
391
- let result;
392
- const debounced = function (...args) {
393
- const callNow = immediate && !timeout;
394
- if (timeout) {
395
- clearTimeout(timeout);
396
- }
397
- timeout = setTimeout(() => {
398
- timeout = null;
399
- if (!immediate) {
400
- result = func.apply(this, args);
401
- }
402
- }, wait);
403
- if (callNow) {
404
- result = func.apply(this, args);
405
- }
406
- return result;
407
- };
408
- // 添加取消方法
409
- debounced.cancel = () => {
410
- if (timeout) {
411
- clearTimeout(timeout);
412
- timeout = null;
413
- }
414
- };
415
- return debounced;
416
- }
417
- /**
418
- * 节流函数(优化版本)
419
- * @param func 要节流的函数
420
- * @param wait 等待时间(毫秒)
421
- * @param options 选项
422
- * @returns 节流后的函数
423
- */
424
- function throttle(func, wait, options = {}) {
425
- let timeout = null;
426
- let previous = 0;
427
- let result;
428
- const { leading = true, trailing = true } = options;
429
- const throttled = function (...args) {
430
- const now = Date.now();
431
- if (!previous && !leading) {
432
- previous = now;
433
- }
434
- const remaining = wait - (now - previous);
435
- if (remaining <= 0 || remaining > wait) {
436
- if (timeout) {
437
- clearTimeout(timeout);
438
- timeout = null;
439
- }
440
- previous = now;
441
- result = func.apply(this, args);
442
- }
443
- else if (!timeout && trailing) {
444
- timeout = setTimeout(() => {
445
- previous = leading ? Date.now() : 0;
446
- timeout = null;
447
- result = func.apply(this, args);
448
- }, remaining);
449
- }
450
- return result;
451
- };
452
- // 添加取消方法
453
- throttled.cancel = () => {
454
- if (timeout) {
455
- clearTimeout(timeout);
456
- timeout = null;
457
- }
458
- previous = 0;
459
- };
460
- return throttled;
461
- }
462
- /**
463
- * 兼容的深拷贝函数(保持向后兼容)
464
- * @deprecated 请使用 deepClone 函数
465
- */
466
- function useDeepCopyByObj(target, source) {
467
- console.warn('useDeepCopyByObj is deprecated, please use deepClone instead');
468
- if (typeof target === 'object' && target !== null && !Array.isArray(target)) {
469
- const clonedSource = deepClone(source);
470
- return mergeObjects(target, clonedSource);
471
- }
472
- return deepClone(source);
473
- }
474
-
475
- /**
476
- * 导航相关工具函数(优化版本)
477
- */
478
- /**
479
- * 导航管理器类
480
- */
481
- class NavigationManager {
482
- static instance;
483
- navigationQueue = [];
484
- isNavigating = false;
485
- maxQueueSize = 10;
486
- static getInstance() {
487
- if (!NavigationManager.instance) {
488
- NavigationManager.instance = new NavigationManager();
489
- }
490
- return NavigationManager.instance;
491
- }
492
- /**
493
- * 执行导航队列
494
- */
495
- async processQueue() {
496
- if (this.isNavigating || this.navigationQueue.length === 0) {
497
- return;
498
- }
499
- this.isNavigating = true;
500
- while (this.navigationQueue.length > 0) {
501
- const navigation = this.navigationQueue.shift();
502
- if (navigation) {
503
- try {
504
- await navigation();
505
- // 添加小延迟,避免导航过快
506
- await new Promise(resolve => setTimeout(resolve, 100));
507
- }
508
- catch (error) {
509
- console.error('导航执行失败:', error);
510
- }
511
- }
512
- }
513
- this.isNavigating = false;
514
- }
515
- /**
516
- * 添加导航到队列
517
- */
518
- addToQueue(navigation) {
519
- if (this.navigationQueue.length >= this.maxQueueSize) {
520
- console.warn('导航队列已满,丢弃最旧的导航请求');
521
- this.navigationQueue.shift();
522
- }
523
- this.navigationQueue.push(navigation);
524
- this.processQueue();
525
- }
526
- /**
527
- * 安全的页面导航
528
- */
529
- async navigateTo(options) {
530
- const monitor = PerformanceMonitor.getInstance();
531
- monitor.start('navigateTo', 'navigation', { url: options.url });
532
- return new Promise((resolve) => {
533
- this.addToQueue(async () => {
534
- const success = await safeAsync(() => new Promise((navResolve) => {
535
- // 构建完整URL
536
- let fullUrl = options.url;
537
- if (options.params && Object.keys(options.params).length > 0) {
538
- const queryString = Object.entries(options.params)
539
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
540
- .join('&');
541
- fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
542
- }
543
- uni.navigateTo({
544
- url: fullUrl,
545
- animationType: options.animationType,
546
- animationDuration: options.animationDuration,
547
- events: options.events,
548
- success: () => {
549
- monitor.end('navigateTo');
550
- navResolve(true);
551
- },
552
- fail: (error) => {
553
- console.error('页面跳转失败:', error);
554
- monitor.end('navigateTo');
555
- navResolve(false);
556
- }
557
- });
558
- }), 'navigation', 'NAVIGATE_TO_ERROR');
559
- resolve(success ?? false);
560
- });
561
- });
562
- }
563
- /**
564
- * 重定向到页面
565
- */
566
- async redirectTo(options) {
567
- const monitor = PerformanceMonitor.getInstance();
568
- monitor.start('redirectTo', 'navigation', { url: options.url });
569
- return new Promise((resolve) => {
570
- this.addToQueue(async () => {
571
- const success = await safeAsync(() => new Promise((navResolve) => {
572
- let fullUrl = options.url;
573
- if (options.params && Object.keys(options.params).length > 0) {
574
- const queryString = Object.entries(options.params)
575
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
576
- .join('&');
577
- fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
578
- }
579
- uni.redirectTo({
580
- url: fullUrl,
581
- success: () => {
582
- monitor.end('redirectTo');
583
- navResolve(true);
584
- },
585
- fail: (error) => {
586
- console.error('页面重定向失败:', error);
587
- monitor.end('redirectTo');
588
- navResolve(false);
589
- }
590
- });
591
- }), 'navigation', 'REDIRECT_TO_ERROR');
592
- resolve(success ?? false);
593
- });
594
- });
595
- }
596
- /**
597
- * 返回上一页(优化版本)
598
- */
599
- async navigateBack(params = '', options = {}) {
600
- const monitor = PerformanceMonitor.getInstance();
601
- monitor.start('navigateBack', 'navigation');
602
- const finalOptions = {
603
- delta: 1,
604
- timeout: 5000,
605
- enableDebounce: true,
606
- debounceWait: 300,
607
- ...options
608
- };
609
- return new Promise((resolve) => {
610
- this.addToQueue(async () => {
611
- const success = await safeAsync(() => new Promise((navResolve) => {
612
- // 检查页面栈
613
- const pages = getCurrentPages();
614
- if (pages.length <= finalOptions.delta) {
615
- console.warn(`无法返回${finalOptions.delta}页,当前页面栈深度不足`);
616
- navResolve(false);
617
- return;
618
- }
619
- // 设置超时
620
- const timeoutId = setTimeout(() => {
621
- console.warn('导航返回超时');
622
- navResolve(false);
623
- }, finalOptions.timeout);
624
- uni.navigateBack({
625
- delta: finalOptions.delta,
626
- success: () => {
627
- clearTimeout(timeoutId);
628
- // 延迟执行,确保页面切换完成
629
- setTimeout(() => {
630
- try {
631
- const currentPages = getCurrentPages();
632
- if (currentPages.length > 0) {
633
- const targetPage = currentPages[currentPages.length - 1];
634
- // 检查目标页面是否有init方法
635
- if (targetPage.$vm && typeof targetPage.$vm.init === 'function') {
636
- targetPage.$vm.init(params);
637
- }
638
- else if (targetPage.route) {
639
- console.info(`页面 ${targetPage.route} 没有init方法`);
640
- }
641
- }
642
- monitor.end('navigateBack');
643
- navResolve(true);
644
- }
645
- catch (error) {
646
- console.error('页面回调执行失败:', error);
647
- monitor.end('navigateBack');
648
- navResolve(true); // 导航成功,但回调失败
649
- }
650
- }, 100);
651
- },
652
- fail: (error) => {
653
- clearTimeout(timeoutId);
654
- console.error('导航返回失败:', error);
655
- monitor.end('navigateBack');
656
- navResolve(false);
657
- }
658
- });
659
- }), 'navigation', 'NAVIGATE_BACK_ERROR');
660
- resolve(success ?? false);
661
- });
662
- });
663
- }
664
- /**
665
- * 切换到Tab页面
666
- */
667
- async switchTab(url) {
668
- const monitor = PerformanceMonitor.getInstance();
669
- monitor.start('switchTab', 'navigation', { url });
670
- return new Promise((resolve) => {
671
- this.addToQueue(async () => {
672
- const success = await safeAsync(() => new Promise((navResolve) => {
673
- uni.switchTab({
674
- url,
675
- success: () => {
676
- monitor.end('switchTab');
677
- navResolve(true);
678
- },
679
- fail: (error) => {
680
- console.error('Tab切换失败:', error);
681
- monitor.end('switchTab');
682
- navResolve(false);
683
- }
684
- });
685
- }), 'navigation', 'SWITCH_TAB_ERROR');
686
- resolve(success ?? false);
687
- });
688
- });
689
- }
690
- /**
691
- * 重新启动到指定页面
692
- */
693
- async reLaunch(options) {
694
- const monitor = PerformanceMonitor.getInstance();
695
- monitor.start('reLaunch', 'navigation', { url: options.url });
696
- return new Promise((resolve) => {
697
- this.addToQueue(async () => {
698
- const success = await safeAsync(() => new Promise((navResolve) => {
699
- let fullUrl = options.url;
700
- if (options.params && Object.keys(options.params).length > 0) {
701
- const queryString = Object.entries(options.params)
702
- .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
703
- .join('&');
704
- fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
705
- }
706
- uni.reLaunch({
707
- url: fullUrl,
708
- success: () => {
709
- monitor.end('reLaunch');
710
- navResolve(true);
711
- },
712
- fail: (error) => {
713
- console.error('重新启动失败:', error);
714
- monitor.end('reLaunch');
715
- navResolve(false);
716
- }
717
- });
718
- }), 'navigation', 'RELAUNCH_ERROR');
719
- resolve(success ?? false);
720
- });
721
- });
722
- }
723
- /**
724
- * 获取当前页面信息
725
- */
726
- getCurrentPageInfo() {
727
- return safeSync(() => {
728
- const pages = getCurrentPages();
729
- if (pages.length === 0) {
730
- return null;
731
- }
732
- const currentPage = pages[pages.length - 1];
733
- return {
734
- route: currentPage.route || '',
735
- options: currentPage.options || {}
736
- };
737
- }, 'navigation', 'GET_CURRENT_PAGE_ERROR', null);
738
- }
739
- /**
740
- * 获取页面栈信息
741
- */
742
- getPageStack() {
743
- return safeSync(() => {
744
- const pages = getCurrentPages();
745
- return pages.map(page => ({
746
- route: page.route || '',
747
- options: page.options || {}
748
- }));
749
- }, 'navigation', 'GET_PAGE_STACK_ERROR', []) ?? [];
750
- }
751
- /**
752
- * 清空导航队列
753
- */
754
- clearQueue() {
755
- this.navigationQueue = [];
756
- this.isNavigating = false;
757
- }
758
- }
759
- // 获取导航管理器实例
760
- const navigationManager = NavigationManager.getInstance();
761
- /**
762
- * 返回上一页并支持传递参数(优化版本)
763
- * @param params 返回上一页时传入的参数
764
- * @param options 导航选项
765
- * @returns Promise<boolean> 是否成功
766
- */
767
- const useBack = async (params = '', options = {}) => {
768
- return await navigationManager.navigateBack(params, options);
769
- };
770
- /**
771
- * 返回上一页,若没有上一页则重定向到首页
772
- * @param params 返回上一页时传入的参数
773
- * @param options 导航选项
774
- * @returns Promise<boolean> 是否成功
775
- */
776
- const useBackOrHome = async (params = '', options = {}) => {
777
- const monitor = PerformanceMonitor.getInstance();
778
- monitor.start('useBackOrHome', 'navigation');
779
- try {
780
- // 检查页面栈深度
781
- const pages = getCurrentPages();
782
- const delta = options.delta || 1;
783
- // 如果页面栈深度足够,执行返回操作
784
- if (pages.length > delta) {
785
- const success = await navigationManager.navigateBack(params, options);
786
- monitor.end('useBackOrHome');
787
- return success;
788
- }
789
- // 页面栈深度不足,重定向到首页
790
- const homePage = options.homePage || 'pages/index/index';
791
- const homeUrl = homePage.startsWith('/') ? homePage : `/${homePage}`;
792
- console.info(`页面栈深度不足,重定向到首页: ${homeUrl}`);
793
- const success = await navigationManager.reLaunch({
794
- url: homeUrl,
795
- params: options.homeParams
796
- });
797
- monitor.end('useBackOrHome');
798
- return success;
799
- }
800
- catch (error) {
801
- console.error('useBackOrHome 执行失败:', error);
802
- monitor.end('useBackOrHome');
803
- return false;
804
- }
805
- };
806
- /**
807
- * 防抖版本的返回上一页
808
- */
809
- const useBackDebounced = debounce(useBack, 300);
810
- /**
811
- * 跳转到指定页面
812
- * @param options 导航选项
813
- * @returns Promise<boolean> 是否成功
814
- */
815
- const navigateTo = async (options) => {
816
- return await navigationManager.navigateTo(options);
817
- };
818
- /**
819
- * 重定向到指定页面
820
- * @param options 导航选项
821
- * @returns Promise<boolean> 是否成功
822
- */
823
- const redirectTo = async (options) => {
824
- return await navigationManager.redirectTo(options);
825
- };
826
- /**
827
- * 切换到Tab页面
828
- * @param url Tab页面路径
829
- * @returns Promise<boolean> 是否成功
830
- */
831
- const switchTab = async (url) => {
832
- return await navigationManager.switchTab(url);
833
- };
834
- /**
835
- * 重新启动到指定页面
836
- * @param options 导航选项
837
- * @returns Promise<boolean> 是否成功
838
- */
839
- const reLaunch = async (options) => {
840
- return await navigationManager.reLaunch(options);
841
- };
842
- /**
843
- * 获取当前页面信息
844
- * @returns 当前页面信息
845
- */
846
- const getCurrentPageInfo = () => {
847
- return navigationManager.getCurrentPageInfo();
848
- };
849
- /**
850
- * 获取页面栈信息
851
- * @returns 页面栈数组
852
- */
853
- const getPageStack = () => {
854
- return navigationManager.getPageStack();
855
- };
856
- /**
857
- * 清空导航队列
858
- */
859
- const clearNavigationQueue = () => {
860
- navigationManager.clearQueue();
861
- };
862
- /**
863
- * 安全的页面跳转(带重试机制)
864
- * @param options 导航选项
865
- * @param maxRetries 最大重试次数
866
- * @returns Promise<boolean> 是否成功
867
- */
868
- const safeNavigateTo = async (options, maxRetries = 3) => {
869
- for (let i = 0; i < maxRetries; i++) {
870
- const success = await navigateTo(options);
871
- if (success) {
872
- return true;
873
- }
874
- if (i < maxRetries - 1) {
875
- // 等待一段时间后重试
876
- await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
877
- }
878
- }
879
- return false;
880
- };
881
-
882
- /**
883
- * 剪贴板相关工具函数
884
- */
885
- /**
886
- * 默认配置
887
- */
888
- const DEFAULT_CONFIG = {
889
- showToast: true,
890
- successMessage: '复制成功',
891
- failMessage: '复制失败',
892
- timeout: 5000
893
- };
894
- /**
895
- * 跨平台文本复制功能(优化版本)
896
- * @param text 要复制的文本内容
897
- * @param config 配置选项
898
- * @description 支持 H5、App、小程序。H5 平台优先使用现代的 Clipboard API,失败时回退到传统方法
899
- * @returns Promise<boolean> 复制是否成功
900
- */
901
- async function copyText(text, config = {}) {
902
- const monitor = PerformanceMonitor.getInstance();
903
- monitor.start('copyText', 'clipboard', { textLength: text.length });
904
- const finalConfig = { ...DEFAULT_CONFIG, ...config };
905
- // 输入验证
906
- if (!text || typeof text !== 'string') {
907
- if (finalConfig.showToast) {
908
- useToast('复制内容不能为空', false, 'error');
909
- }
910
- monitor.end('copyText');
911
- return false;
912
- }
913
- // 文本长度限制检查
914
- if (text.length > 10000) {
915
- if (finalConfig.showToast) {
916
- useToast('复制内容过长', false, 'error');
917
- }
918
- monitor.end('copyText');
919
- return false;
920
- }
921
- let success = false;
922
- // #ifndef H5
923
- // 条件编译:非 H5 平台(如 App、小程序)执行此代码块
924
- success = await safeAsync(() => new Promise((resolve) => {
925
- uni.setClipboardData({
926
- data: text,
927
- success: () => {
928
- if (finalConfig.showToast) {
929
- useToast(finalConfig.successMessage);
930
- }
931
- resolve(true);
932
- },
933
- fail: () => {
934
- if (finalConfig.showToast) {
935
- useToast(finalConfig.failMessage);
936
- }
937
- resolve(false);
938
- },
939
- });
940
- }), 'clipboard', 'COPY_TEXT_NATIVE_ERROR') ?? false;
941
- // #endif
942
- // #ifdef H5
943
- // 条件编译:仅 H5 平台执行此代码块
944
- success = await copyTextH5(text, finalConfig);
945
- // #endif
946
- monitor.end('copyText');
947
- return success;
948
- }
949
- // #ifdef H5
950
- /**
951
- * H5平台专用复制函数
952
- * @param text 要复制的文本
953
- * @param config 配置
954
- * @returns 是否成功
955
- */
956
- async function copyTextH5(text, config) {
957
- // 优先使用现代的 Clipboard API
958
- if (navigator.clipboard && window.isSecureContext) {
959
- const success = await safeAsync(async () => {
960
- await Promise.race([
961
- navigator.clipboard.writeText(text),
962
- new Promise((_, reject) => setTimeout(() => reject(new Error('Clipboard API timeout')), config.timeout))
963
- ]);
964
- return true;
965
- }, 'clipboard', 'CLIPBOARD_API_ERROR');
966
- if (success) {
967
- if (config.showToast) {
968
- useToast(config.successMessage);
969
- }
970
- return true;
971
- }
972
- }
973
- // 回退到传统方法
974
- return fallbackCopyTextToClipboard(text, config);
975
- }
976
- /**
977
- * 传统复制方法的辅助函数(内存优化版本)
978
- * @param text 要复制的文本内容
979
- * @param config 配置
980
- * @description 使用 document.execCommand 方式进行文本复制,优化内存使用
981
- */
982
- function fallbackCopyTextToClipboard(text, config) {
983
- return safeSync(() => {
984
- // 创建临时元素
985
- const textarea = document.createElement('textarea');
986
- // 设置样式,确保不影响页面布局且不可见
987
- Object.assign(textarea.style, {
988
- position: 'fixed',
989
- top: '-9999px',
990
- left: '-9999px',
991
- width: '1px',
992
- height: '1px',
993
- padding: '0',
994
- border: 'none',
995
- outline: 'none',
996
- boxShadow: 'none',
997
- background: 'transparent',
998
- fontSize: '16px', // 防止iOS缩放
999
- });
1000
- textarea.value = text;
1001
- textarea.setAttribute('readonly', '');
1002
- textarea.setAttribute('contenteditable', 'true');
1003
- // 添加到DOM
1004
- document.body.appendChild(textarea);
1005
- // 选择文本
1006
- textarea.focus();
1007
- textarea.select();
1008
- textarea.setSelectionRange(0, text.length);
1009
- // 执行复制
1010
- const successful = document.execCommand('copy');
1011
- // 立即清理DOM元素
1012
- document.body.removeChild(textarea);
1013
- if (successful) {
1014
- if (config.showToast) {
1015
- useToast(config.successMessage);
1016
- }
1017
- return true;
1018
- }
1019
- else {
1020
- if (config.showToast) {
1021
- useToast(config.failMessage);
1022
- }
1023
- return false;
1024
- }
1025
- }, 'clipboard', 'FALLBACK_COPY_ERROR', false) ?? false;
1026
- }
1027
- // #endif
1028
- /**
1029
- * 读取剪贴板内容(仅H5平台支持)
1030
- * @param config 配置选项
1031
- * @returns Promise<string | null> 剪贴板内容,失败时返回null
1032
- */
1033
- async function readClipboard(config = {}) {
1034
- const monitor = PerformanceMonitor.getInstance();
1035
- monitor.start('readClipboard', 'clipboard');
1036
- const finalConfig = { ...DEFAULT_CONFIG, ...config };
1037
- // #ifdef H5
1038
- if (navigator.clipboard && window.isSecureContext) {
1039
- const result = await safeAsync(async () => {
1040
- return await Promise.race([
1041
- navigator.clipboard.readText(),
1042
- new Promise((_, reject) => setTimeout(() => reject(new Error('Read clipboard timeout')), finalConfig.timeout))
1043
- ]);
1044
- }, 'clipboard', 'READ_CLIPBOARD_ERROR');
1045
- monitor.end('readClipboard');
1046
- return result;
1047
- }
1048
- // #endif
1049
- // 非H5平台或不支持的情况
1050
- if (finalConfig.showToast) {
1051
- useToast('当前平台不支持读取剪贴板', false, 'error');
1052
- }
1053
- monitor.end('readClipboard');
1054
- return null;
1055
- }
1056
- /**
1057
- * 检查剪贴板API是否可用
1058
- * @returns boolean 是否支持剪贴板操作
1059
- */
1060
- function isClipboardSupported() {
1061
- // #ifdef H5
1062
- return !!(navigator.clipboard && window.isSecureContext);
1063
- // #endif
1064
- }
1065
- /**
1066
- * 清空剪贴板(仅H5平台支持)
1067
- * @param config 配置选项
1068
- * @returns Promise<boolean> 是否成功
1069
- */
1070
- async function clearClipboard(config = {}) {
1071
- return await copyText('', config);
1072
- }
1073
-
1074
- /**
1075
- * 系统信息相关工具函数(优化版本)
1076
- */
1077
- /**
1078
- * 系统信息缓存
1079
- */
1080
- class SystemInfoCache {
1081
- static instance;
1082
- cache = new Map();
1083
- defaultTTL = 30000; // 30秒缓存
1084
- static getInstance() {
1085
- if (!SystemInfoCache.instance) {
1086
- SystemInfoCache.instance = new SystemInfoCache();
1087
- }
1088
- return SystemInfoCache.instance;
1089
- }
1090
- set(key, data, ttl = this.defaultTTL) {
1091
- this.cache.set(key, {
1092
- data,
1093
- timestamp: Date.now(),
1094
- ttl
1095
- });
1096
- }
1097
- get(key) {
1098
- const item = this.cache.get(key);
1099
- if (!item)
1100
- return null;
1101
- if (Date.now() - item.timestamp > item.ttl) {
1102
- this.cache.delete(key);
1103
- return null;
1104
- }
1105
- return item.data;
1106
- }
1107
- clear() {
1108
- this.cache.clear();
1109
- }
1110
- }
1111
- const systemCache = SystemInfoCache.getInstance();
1112
- /**
1113
- * 获取窗口信息(优化版本)
1114
- * @param useCache 是否使用缓存,默认true
1115
- * @returns 窗口信息对象,包含窗口尺寸、像素比等信息
1116
- * @description 调用 uni.getWindowInfo() 获取当前设备的窗口相关信息,支持缓存
1117
- */
1118
- const useWindowInfo = (useCache = true) => {
1119
- const monitor = PerformanceMonitor.getInstance();
1120
- monitor.start('getWindowInfo', 'system');
1121
- const cacheKey = 'windowInfo';
1122
- if (useCache) {
1123
- const cached = systemCache.get(cacheKey);
1124
- if (cached) {
1125
- monitor.end('getWindowInfo');
1126
- return cached;
1127
- }
1128
- }
1129
- const result = safeSync(() => {
1130
- const windowInfo = uni.getWindowInfo();
1131
- if (useCache) {
1132
- systemCache.set(cacheKey, windowInfo);
1133
- }
1134
- return windowInfo;
1135
- }, 'system', 'GET_WINDOW_INFO_ERROR', null);
1136
- monitor.end('getWindowInfo');
1137
- return result;
1138
- };
1139
- /**
1140
- * 获取当前运行平台
1141
- * @returns 平台类型字符串 ('weixin' | 'web' | 'app' | 'alipay' | 'h5')
1142
- * @description 通过条件编译判断当前代码运行的平台环境
1143
- */
1144
- const getPlatform = () => {
1145
- let platform;
1146
- // #ifdef MP-WEIXIN
1147
- platform = 'weixin';
1148
- // #endif
1149
- // #ifdef WEB
1150
- platform = 'web';
1151
- // #endif
1152
- // #ifdef APP
1153
- platform = 'app';
1154
- // #endif
1155
- // #ifdef MP-ALIPAY
1156
- platform = 'alipay';
1157
- // #endif
1158
- // #ifdef H5
1159
- platform = 'h5';
1160
- // #endif
1161
- return platform;
1162
- };
1163
- /**
1164
- * 获取小程序账户信息
1165
- * @returns 小程序账户信息对象,包含appId、版本、环境等信息
1166
- * @description 调用 uni.getAccountInfoSync() 获取当前小程序的账户相关信息
1167
- */
1168
- const getCurrentEnv = () => {
1169
- try {
1170
- // 调用 uni.getAccountInfoSync() 获取小程序账户信息
1171
- const accountInfo = uni.getAccountInfoSync();
1172
- return {
1173
- // 小程序appId
1174
- appId: accountInfo.miniProgram?.appId || '',
1175
- // 小程序版本号
1176
- version: accountInfo.miniProgram?.version || '',
1177
- // 小程序环境版本(develop开发版、trial体验版、release正式版)
1178
- envVersion: accountInfo.miniProgram?.envVersion || '',
1179
- // 完整的账户信息
1180
- accountInfo
1181
- };
1182
- }
1183
- catch (error) {
1184
- console.error('获取小程序账户信息失败:', error);
1185
- return {
1186
- appId: '',
1187
- version: '',
1188
- envVersion: '',
1189
- accountInfo: null
1190
- };
1191
- }
1192
- };
1193
- /**
1194
- * 检查小程序版本更新
1195
- * @description 检查小程序是否有新版本,如果有则提示用户更新并重启应用
1196
- * @returns void
1197
- * @example
1198
- * // 在App.vue的onLaunch或onShow中调用
1199
- * onCheckForUpdate()
1200
- */
1201
- const onCheckForUpdate = () => {
1202
- try {
1203
- // 获取全局唯一的版本更新管理器对象
1204
- const updateManager = uni.getUpdateManager();
1205
- // 检查小程序是否有新版本发布
1206
- updateManager.onCheckForUpdate(function (res) {
1207
- // 请求完新版本信息的回调
1208
- console.log('检查更新结果:', res.hasUpdate);
1209
- if (res.hasUpdate) {
1210
- console.log('发现新版本,开始下载...');
1211
- }
1212
- });
1213
- // 小程序有新版本,静默下载新版本,做好更新准备
1214
- updateManager.onUpdateReady(function (res) {
1215
- uni.showModal({
1216
- title: '更新提示',
1217
- content: '新版本已经准备好,是否重启应用?',
1218
- showCancel: true,
1219
- cancelText: '稍后',
1220
- confirmText: '立即重启',
1221
- success(modalRes) {
1222
- if (modalRes.confirm) {
1223
- // 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
1224
- updateManager.applyUpdate();
1225
- }
1226
- }
1227
- });
1228
- });
1229
- // 新版本下载失败处理
1230
- updateManager.onUpdateFailed(function (res) {
1231
- console.error('新版本下载失败:', res);
1232
- uni.showModal({
1233
- title: '更新失败',
1234
- content: '新版本下载失败,请检查网络连接或稍后重试',
1235
- showCancel: false,
1236
- confirmText: '知道了'
1237
- });
1238
- });
1239
- }
1240
- catch (error) {
1241
- console.error('版本更新检查失败:', error);
1242
- // 静默处理错误,不影响用户正常使用
1243
- }
1244
- };
1245
- /**
1246
- * 获取状态栏高度
1247
- * @returns 状态栏高度(单位:px)
1248
- * @description 获取设备状态栏的高度,用于适配不同设备的状态栏
1249
- */
1250
- const getStatusBarHeight = () => {
1251
- try {
1252
- // 获取系统信息中的状态栏高度
1253
- const statusBarHeight = uni.getSystemInfoSync().statusBarHeight || 0;
1254
- console.log("🚀 ~ 状态栏高度:", statusBarHeight);
1255
- return statusBarHeight;
1256
- }
1257
- catch (error) {
1258
- console.error('获取状态栏高度失败:', error);
1259
- return 0;
1260
- }
1261
- };
1262
- /**
1263
- * 获取菜单按钮边界信息
1264
- * @returns 菜单按钮边界信息对象或null
1265
- * @description 获取小程序右上角菜单按钮的边界信息,仅在小程序平台有效
1266
- */
1267
- const getMenuButtonBoundingClientRect = () => {
1268
- try {
1269
- const platform = getPlatform();
1270
- // 只有在小程序平台才执行getMenuButtonBoundingClientRect
1271
- if (platform === 'weixin' || platform === 'alipay') {
1272
- return uni.getMenuButtonBoundingClientRect();
1273
- }
1274
- // 非小程序平台返回null
1275
- return null;
1276
- }
1277
- catch (error) {
1278
- console.error('获取菜单按钮边界信息失败:', error);
1279
- return null;
1280
- }
1281
- };
1282
- /**
1283
- * 获取导航栏高度
1284
- * @returns 导航栏高度(单位:px)
1285
- * @description 获取导航栏的总高度,根据不同平台采用不同的计算方式
1286
- */
1287
- const getNavHeight = () => {
1288
- try {
1289
- // 获取状态栏高度
1290
- const statusBarHeight = getStatusBarHeight();
1291
- const platform = getPlatform();
1292
- // 小程序平台:状态栏高度 + 菜单按钮高度
1293
- if (platform === 'weixin' || platform === 'alipay') {
1294
- const menuButtonInfo = getMenuButtonBoundingClientRect();
1295
- if (menuButtonInfo) {
1296
- const navHeight = menuButtonInfo.height + statusBarHeight;
1297
- console.log("🚀 ~ 小程序导航栏高度:", navHeight);
1298
- return navHeight;
1299
- }
1300
- }
1301
- // App和H5平台:使用默认导航栏高度
1302
- const defaultNavHeight = 0; // 默认导航栏高度
1303
- const totalHeight = statusBarHeight + defaultNavHeight;
1304
- console.log(`🚀 ~ ${platform}平台导航栏高度:`, totalHeight);
1305
- return totalHeight;
1306
- }
1307
- catch (error) {
1308
- console.error('获取导航栏高度失败:', error);
1309
- // 返回默认高度(状态栏高度 + 44px 默认导航栏高度)
1310
- return getStatusBarHeight() + 0;
1311
- }
1312
- };
1313
- /**
1314
- * @returns 包含状态栏高度和导航栏高度的配置对象
1315
- * @description 一次性获取应用所需的状态栏和导航栏高度配置信息
1316
- */
1317
- const getTopNavBarHeight = () => {
1318
- try {
1319
- // 获取状态栏高度
1320
- const statusBarHeight = getStatusBarHeight();
1321
- // 获取导航栏高度
1322
- const navHeight = getNavHeight();
1323
- return {
1324
- statusBarHeight,
1325
- navHeight
1326
- };
1327
- }
1328
- catch (error) {
1329
- console.error('获取全局配置失败:', error);
1330
- return {
1331
- statusBarHeight: 0,
1332
- navHeight: 44
1333
- };
1334
- }
1335
- };
1336
- /**
1337
- * 修改浏览器标题
1338
- * @param title 新的页面标题
1339
- * @description 动态修改浏览器标签页显示的标题,仅在H5/Web平台有效
1340
- * @example
1341
- * // 修改页面标题
1342
- * setPageTitle('新的页面标题')
1343
- */
1344
- const setPageTitle = (title) => {
1345
- return safeSync(() => {
1346
- const platform = getPlatform();
1347
- // 只在H5/Web平台执行
1348
- if (platform === 'h5' || platform === 'web') {
1349
- // #ifdef H5 || WEB
1350
- if (typeof document !== 'undefined') {
1351
- document.title = title;
1352
- console.log('页面标题已更新为:', title);
1353
- return true;
1354
- }
1355
- // #endif
1356
- }
1357
- else {
1358
- console.warn('setPageTitle 仅在H5/Web平台有效');
1359
- }
1360
- return false;
1361
- }, 'system', 'SET_PAGE_TITLE_ERROR', false);
1362
- };
1363
- /**
1364
- * 修改浏览器图标(favicon)
1365
- * @param iconUrl 新的图标URL地址
1366
- * @param iconType 图标类型,默认为 'image/x-icon'
1367
- * @description 动态修改浏览器标签页显示的图标,仅在H5/Web平台有效
1368
- * @example
1369
- * // 修改页面图标
1370
- * setPageIcon('https://example.com/new-icon.ico')
1371
- * // 或使用PNG格式
1372
- * setPageIcon('https://example.com/new-icon.png', 'image/png')
1373
- */
1374
- const setPageIcon = (iconUrl, iconType = 'image/x-icon') => {
1375
- return safeSync(() => {
1376
- const platform = getPlatform();
1377
- // 只在H5/Web平台执行
1378
- if (platform === 'h5' || platform === 'web') {
1379
- // #ifdef H5 || WEB
1380
- if (typeof document !== 'undefined') {
1381
- // 移除现有的favicon链接
1382
- const existingLinks = document.querySelectorAll('link[rel*="icon"]');
1383
- existingLinks.forEach(link => link.remove());
1384
- // 创建新的favicon链接
1385
- const link = document.createElement('link');
1386
- link.rel = 'icon';
1387
- link.type = iconType;
1388
- link.href = iconUrl;
1389
- // 添加到head中
1390
- document.head.appendChild(link);
1391
- console.log('页面图标已更新为:', iconUrl);
1392
- return true;
1393
- }
1394
- // #endif
1395
- }
1396
- else {
1397
- console.warn('setPageIcon 仅在H5/Web平台有效');
1398
- }
1399
- return false;
1400
- }, 'system', 'SET_PAGE_ICON_ERROR', false);
1401
- };
1402
-
1403
- /**
1404
- * 本地存储相关工具函数(优化版本)
1405
- */
1406
- /**
1407
- * 存储管理器类
1408
- */
1409
- class StorageManager {
1410
- static instance;
1411
- cache = new Map(); // 内存缓存
1412
- maxCacheSize = 100; // 最大缓存数量
1413
- static getInstance() {
1414
- if (!StorageManager.instance) {
1415
- StorageManager.instance = new StorageManager();
1416
- }
1417
- return StorageManager.instance;
1418
- }
1419
- /**
1420
- * 简单压缩字符串
1421
- */
1422
- compress(str) {
1423
- // 简单的重复字符压缩
1424
- return str.replace(/(.)\1{2,}/g, (match, char) => {
1425
- return `${char}*${match.length}`;
1426
- });
1427
- }
1428
- /**
1429
- * 解压缩字符串
1430
- */
1431
- decompress(str) {
1432
- return str.replace(/(.)\*(\d+)/g, (match, char, count) => {
1433
- return char.repeat(parseInt(count));
1434
- });
1435
- }
1436
- /**
1437
- * 简单加密
1438
- */
1439
- encrypt(str) {
1440
- return btoa(encodeURIComponent(str));
1441
- }
1442
- /**
1443
- * 简单解密
1444
- */
1445
- decrypt(str) {
1446
- try {
1447
- return decodeURIComponent(atob(str));
1448
- }
1449
- catch {
1450
- return str; // 解密失败返回原字符串
1451
- }
1452
- }
1453
- /**
1454
- * 检查是否过期
1455
- */
1456
- isExpired(item) {
1457
- if (!item.ttl)
1458
- return false;
1459
- return Date.now() - item.timestamp > item.ttl;
1460
- }
1461
- /**
1462
- * 设置存储
1463
- */
1464
- set(key, value, options = {}) {
1465
- const monitor = PerformanceMonitor.getInstance();
1466
- monitor.start(`setStorage_${key}`, 'localStorage');
1467
- const result = safeSync(() => {
1468
- // 输入验证
1469
- if (!key || typeof key !== 'string') {
1470
- throw new Error('存储键不能为空');
1471
- }
1472
- const item = {
1473
- value,
1474
- timestamp: Date.now(),
1475
- ttl: options.ttl,
1476
- compressed: options.compress,
1477
- encrypted: options.encrypt
1478
- };
1479
- let serialized = JSON.stringify(item);
1480
- // 压缩处理
1481
- if (options.compress) {
1482
- serialized = this.compress(serialized);
1483
- }
1484
- // 加密处理
1485
- if (options.encrypt) {
1486
- serialized = this.encrypt(serialized);
1487
- }
1488
- // 存储大小检查
1489
- if (serialized.length > 1024 * 1024) { // 1MB限制
1490
- throw new Error('存储数据过大');
1491
- }
1492
- uni.setStorageSync(key, serialized);
1493
- // 更新内存缓存
1494
- if (this.cache.size >= this.maxCacheSize) {
1495
- // 删除最旧的缓存项
1496
- const firstKey = this.cache.keys().next().value;
1497
- if (firstKey) {
1498
- this.cache.delete(firstKey);
1499
- }
1500
- }
1501
- this.cache.set(key, item);
1502
- return true;
1503
- }, 'localStorage', 'SET_STORAGE_ERROR', false);
1504
- monitor.end(`setStorage_${key}`);
1505
- return result ?? false;
1506
- }
1507
- /**
1508
- * 获取存储
1509
- */
1510
- get(key, defaultValue) {
1511
- const monitor = PerformanceMonitor.getInstance();
1512
- monitor.start(`getStorage_${key}`, 'localStorage');
1513
- const result = safeSync(() => {
1514
- // 输入验证
1515
- if (!key || typeof key !== 'string') {
1516
- return defaultValue;
1517
- }
1518
- // 检查内存缓存
1519
- if (this.cache.has(key)) {
1520
- const cachedItem = this.cache.get(key);
1521
- if (!this.isExpired(cachedItem)) {
1522
- return cachedItem.value;
1523
- }
1524
- else {
1525
- // 过期则删除
1526
- this.cache.delete(key);
1527
- this.remove(key);
1528
- return defaultValue;
1529
- }
1530
- }
1531
- // 从存储中获取
1532
- let serialized = uni.getStorageSync(key);
1533
- if (!serialized) {
1534
- return defaultValue;
1535
- }
1536
- // 确保serialized是字符串类型
1537
- if (typeof serialized !== 'string') {
1538
- return defaultValue;
1539
- }
1540
- let serializedString = serialized;
1541
- // 解密处理
1542
- if (serializedString && serializedString.includes('=')) {
1543
- try {
1544
- const decrypted = this.decrypt(serializedString);
1545
- if (decrypted) {
1546
- serializedString = decrypted;
1547
- }
1548
- }
1549
- catch {
1550
- // 解密失败,可能不是加密数据
1551
- }
1552
- }
1553
- // 解压缩处理
1554
- if (serializedString && serializedString.includes('*')) {
1555
- try {
1556
- serializedString = this.decompress(serializedString);
1557
- }
1558
- catch {
1559
- // 解压缩失败,可能不是压缩数据
1560
- }
1561
- }
1562
- // 尝试解析为StorageItem
1563
- let item;
1564
- try {
1565
- const parsed = JSON.parse(serializedString);
1566
- if (parsed && typeof parsed === 'object' && 'value' in parsed && 'timestamp' in parsed) {
1567
- item = parsed;
1568
- }
1569
- else {
1570
- // 兼容旧格式
1571
- item = {
1572
- value: parsed,
1573
- timestamp: Date.now()
1574
- };
1575
- }
1576
- }
1577
- catch {
1578
- // JSON解析失败,直接返回原始值
1579
- return serializedString;
1580
- }
1581
- // 检查是否过期
1582
- if (this.isExpired(item)) {
1583
- this.remove(key);
1584
- return defaultValue;
1585
- }
1586
- // 更新内存缓存
1587
- this.cache.set(key, item);
1588
- return item.value;
1589
- }, 'localStorage', 'GET_STORAGE_ERROR', defaultValue);
1590
- monitor.end(`getStorage_${key}`);
1591
- return result ?? defaultValue;
1592
- }
1593
- /**
1594
- * 删除存储
1595
- */
1596
- remove(key) {
1597
- return safeSync(() => {
1598
- if (!key || typeof key !== 'string') {
1599
- return false;
1600
- }
1601
- uni.removeStorageSync(key);
1602
- this.cache.delete(key);
1603
- return true;
1604
- }, 'localStorage', 'REMOVE_STORAGE_ERROR', false) ?? false;
1605
- }
1606
- /**
1607
- * 清空所有存储
1608
- */
1609
- clear() {
1610
- return safeSync(() => {
1611
- uni.clearStorageSync();
1612
- this.cache.clear();
1613
- return true;
1614
- }, 'localStorage', 'CLEAR_STORAGE_ERROR', false) ?? false;
1615
- }
1616
- /**
1617
- * 获取存储信息
1618
- */
1619
- getInfo() {
1620
- return safeSync(() => {
1621
- const info = uni.getStorageInfoSync();
1622
- return {
1623
- keys: info.keys || [],
1624
- currentSize: info.currentSize || 0,
1625
- limitSize: info.limitSize || 0
1626
- };
1627
- }, 'localStorage', 'GET_STORAGE_INFO_ERROR', { keys: [], currentSize: 0, limitSize: 0 }) ?? { keys: [], currentSize: 0, limitSize: 0 };
1628
- }
1629
- /**
1630
- * 清理过期数据
1631
- */
1632
- cleanExpired() {
1633
- const monitor = PerformanceMonitor.getInstance();
1634
- monitor.start('cleanExpiredStorage', 'localStorage');
1635
- const result = safeSync(() => {
1636
- const info = this.getInfo();
1637
- let cleanedCount = 0;
1638
- info.keys.forEach(key => {
1639
- const item = this.get(key);
1640
- // get方法内部会自动清理过期数据
1641
- if (item === undefined) {
1642
- cleanedCount++;
1643
- }
1644
- });
1645
- return cleanedCount;
1646
- }, 'localStorage', 'CLEAN_EXPIRED_ERROR', 0);
1647
- monitor.end('cleanExpiredStorage');
1648
- return result ?? 0;
1649
- }
1650
- }
1651
- // 获取存储管理器实例
1652
- const storageManager = StorageManager.getInstance();
1653
- /**
1654
- * 同步设置本地缓存(优化版本)
1655
- * @param key 缓存键
1656
- * @param obj 要缓存的值,可以是任何类型
1657
- * @param options 存储选项
1658
- * @returns 是否设置成功
1659
- */
1660
- function setStorageSync(key, obj, options = {}) {
1661
- return storageManager.set(key, obj, options);
1662
- }
1663
- /**
1664
- * 同步获取本地缓存(优化版本)
1665
- * @param key 缓存键
1666
- * @param defaultValue 默认值
1667
- * @returns 缓存的值
1668
- */
1669
- function getStorageSync(key, defaultValue) {
1670
- return storageManager.get(key, defaultValue);
1671
- }
1672
- /**
1673
- * 同步清理本地缓存(优化版本)
1674
- * @param key 可选的缓存键
1675
- * @returns 是否清理成功
1676
- */
1677
- function clearStorageSync(key) {
1678
- if (key) {
1679
- return storageManager.remove(key);
1680
- }
1681
- else {
1682
- return storageManager.clear();
1683
- }
1684
- }
1685
- /**
1686
- * 异步设置本地缓存
1687
- * @param key 缓存键
1688
- * @param obj 要缓存的值
1689
- * @param options 存储选项
1690
- * @returns Promise<boolean>
1691
- */
1692
- async function setStorage(key, obj, options = {}) {
1693
- return await safeAsync(() => new Promise((resolve) => {
1694
- const success = setStorageSync(key, obj, options);
1695
- resolve(success);
1696
- }), 'localStorage', 'SET_STORAGE_ASYNC_ERROR') ?? false;
1697
- }
1698
- /**
1699
- * 异步获取本地缓存
1700
- * @param key 缓存键
1701
- * @param defaultValue 默认值
1702
- * @returns Promise<T | undefined>
1703
- */
1704
- async function getStorage(key, defaultValue) {
1705
- return await safeAsync(() => new Promise((resolve) => {
1706
- const value = getStorageSync(key, defaultValue);
1707
- resolve(value);
1708
- }), 'localStorage', 'GET_STORAGE_ASYNC_ERROR') ?? defaultValue;
1709
- }
1710
- /**
1711
- * 获取存储信息
1712
- * @returns 存储信息
1713
- */
1714
- function getStorageInfo() {
1715
- return storageManager.getInfo();
1716
- }
1717
- /**
1718
- * 清理过期数据
1719
- * @returns 清理的数据条数
1720
- */
1721
- function cleanExpiredStorage() {
1722
- return storageManager.cleanExpired();
1723
- }
1724
- /**
1725
- * 批量设置存储
1726
- * @param items 要设置的键值对
1727
- * @param options 存储选项
1728
- * @returns 成功设置的数量
1729
- */
1730
- function batchSetStorage(items, options = {}) {
1731
- const monitor = PerformanceMonitor.getInstance();
1732
- monitor.start('batchSetStorage', 'localStorage');
1733
- let successCount = 0;
1734
- Object.entries(items).forEach(([key, value]) => {
1735
- if (setStorageSync(key, value, options)) {
1736
- successCount++;
1737
- }
1738
- });
1739
- monitor.end('batchSetStorage');
1740
- return successCount;
1741
- }
1742
- /**
1743
- * 批量获取存储
1744
- * @param keys 要获取的键数组
1745
- * @returns 键值对对象
1746
- */
1747
- function batchGetStorage(keys) {
1748
- const monitor = PerformanceMonitor.getInstance();
1749
- monitor.start('batchGetStorage', 'localStorage');
1750
- const result = {};
1751
- keys.forEach(key => {
1752
- result[key] = getStorageSync(key);
1753
- });
1754
- monitor.end('batchGetStorage');
1755
- return result;
1756
- }
1757
-
1758
- /**
1759
- * 文件上传相关工具函数
1760
- * 支持微信小程序、支付宝小程序、H5多端兼容
1761
- */
1762
- /**
1763
- * 默认上传配置
1764
- */
1765
- const DEFAULT_UPLOAD_CONFIG = {
1766
- name: 'file',
1767
- maxSize: 10, // 10MB
1768
- allowedTypes: ['jpg', 'jpeg', 'png', 'gif', 'webp'],
1769
- showToast: true,
1770
- timeout: 60000, // 60秒
1771
- successMessage: '上传成功',
1772
- failMessage: '上传失败'
1773
- };
1774
- /**
1775
- * 默认选择文件配置
1776
- */
1777
- const DEFAULT_CHOOSE_CONFIG = {
1778
- type: 'image',
1779
- count: 1,
1780
- sizeType: ['original', 'compressed'],
1781
- sourceType: ['album', 'camera'],
1782
- extension: [],
1783
- maxSize: 10, // 10MB
1784
- showToast: true,
1785
- failMessage: '选择文件失败'
1786
- };
1787
- /**
1788
- * 验证文件大小和类型
1789
- * @param filePath 文件路径
1790
- * @param config 配置
1791
- * @returns 验证结果
1792
- */
1793
- function validateFile(filePath, config) {
1794
- if (!filePath) {
1795
- return { valid: false, message: '文件路径不能为空' };
1796
- }
1797
- // 验证文件类型
1798
- if (config.allowedTypes && config.allowedTypes.length > 0) {
1799
- const fileExtension = filePath.split('.').pop()?.toLowerCase();
1800
- if (!fileExtension || !config.allowedTypes.includes(fileExtension)) {
1801
- return {
1802
- valid: false,
1803
- message: `不支持的文件类型,仅支持:${config.allowedTypes.join(', ')}`
1804
- };
1805
- }
1806
- }
1807
- return { valid: true };
1808
- }
1809
- /**
1810
- * 验证文件大小
1811
- * @param fileSize 文件大小(字节)
1812
- * @param maxSize 最大大小(MB)
1813
- * @returns 验证结果
1814
- */
1815
- function validateFileSize(fileSize, maxSize) {
1816
- if (!maxSize)
1817
- return { valid: true };
1818
- const maxSizeBytes = maxSize * 1024 * 1024;
1819
- if (fileSize > maxSizeBytes) {
1820
- return {
1821
- valid: false,
1822
- message: `文件大小不能超过 ${maxSize}MB`
1823
- };
1824
- }
1825
- return { valid: true };
1826
- }
1827
- /**
1828
- * 检查平台是否支持指定的文件选择类型
1829
- */
1830
- const isPlatformSupported = (type) => {
1831
- // #ifdef MP-WEIXIN
1832
- if (type === 'messagefile') {
1833
- return typeof uni.chooseMessageFile === 'function';
1834
- }
1835
- // #endif
1836
- if (type === 'local') {
1837
- // #ifdef H5
1838
- return true; // H5支持本地文件选择
1839
- // #endif
1840
- }
1841
- // image类型所有平台都支持
1842
- if (type === 'image') {
1843
- return true;
1844
- }
1845
- return false;
1846
- };
1847
- /**
1848
- * 选择文件
1849
- * @param config 选择配置
1850
- * @returns Promise<ChooseFileResult> 选择结果
1851
- */
1852
- // #ifdef H5
1853
- /**
1854
- * 创建H5文件选择的input元素, 并处理文件选择回调
1855
- * @param {ChooseFileConfig} config - 文件选择的配置
1856
- * @param {(res: ChooseFileResult) => void} resolve - Promise的resolve函数
1857
- * @private
1858
- */
1859
- function _createH5FileInput(config, resolve) {
1860
- const input = document.createElement('input');
1861
- input.type = 'file';
1862
- input.multiple = config.count > 1;
1863
- if (config.type === 'image') {
1864
- input.accept = 'image/*';
1865
- }
1866
- else if (config.extension.length > 0) {
1867
- input.accept = config.extension.map(ext => (ext.startsWith('.') ? ext : `.${ext}`)).join(',');
1868
- }
1869
- input.onchange = (event) => {
1870
- const files = event.target.files;
1871
- if (files && files.length > 0) {
1872
- const tempFiles = Array.from(files).map(file => {
1873
- /**
1874
- * @description 创建一个临时的对象URL。
1875
- * 注意:这个URL在不再需要时,建议调用 URL.revokeObjectURL() 来释放内存,以避免内存泄漏。
1876
- */
1877
- const path = URL.createObjectURL(file);
1878
- return {
1879
- path,
1880
- size: file.size,
1881
- name: file.name,
1882
- type: file.type,
1883
- file // H5 only
1884
- };
1885
- });
1886
- const normalizedTempFiles = tempFiles.map(file => ({
1887
- path: file.path,
1888
- size: file.size,
1889
- }));
1890
- resolve({
1891
- success: true,
1892
- tempFilePaths: tempFiles.map(f => f.path),
1893
- tempFiles: normalizedTempFiles,
1894
- type: config.type
1895
- });
1896
- }
1897
- else {
1898
- // 用户取消了文件选择
1899
- resolve({
1900
- success: false,
1901
- message: '用户取消选择',
1902
- type: config.type
1903
- });
1904
- }
1905
- // 从DOM中移除input元素以进行清理
1906
- document.body.removeChild(input);
1907
- };
1908
- // 触发文件选择对话框
1909
- input.style.display = 'none';
1910
- document.body.appendChild(input);
1911
- input.click();
1912
- }
1913
- // #endif
1914
- async function chooseFile(config = {}) {
1915
- const monitor = PerformanceMonitor.getInstance();
1916
- monitor.start('chooseFile', 'upload', { count: config.count, type: config.type });
1917
- const finalConfig = { ...DEFAULT_CHOOSE_CONFIG, ...config };
1918
- let actualType = finalConfig.type;
1919
- // 检查平台支持,不支持则降级到image
1920
- if (!isPlatformSupported(actualType)) {
1921
- actualType = 'image';
1922
- console.warn(`当前平台不支持${finalConfig.type}类型,已降级为image类型`);
1923
- }
1924
- const result = await safeAsync(() => new Promise((resolve) => {
1925
- const failCallback = (err) => {
1926
- const message = err.errMsg || finalConfig.failMessage;
1927
- if (finalConfig.showToast) {
1928
- useToast(message, false, 'error');
1929
- }
1930
- resolve({ success: false, message, type: actualType });
1931
- };
1932
- // #ifdef H5
1933
- if (actualType === 'image' || actualType === 'local') {
1934
- _createH5FileInput(finalConfig, resolve);
1935
- return;
1936
- }
1937
- // #endif
1938
- // #ifdef MP-WEIXIN
1939
- if (actualType === 'messagefile') {
1940
- uni.chooseMessageFile({
1941
- count: finalConfig.count,
1942
- type: 'all',
1943
- success: (res) => {
1944
- // chooseMessageFile 返回的 tempFiles 结构是标准的,直接使用
1945
- resolve({
1946
- success: true,
1947
- tempFilePaths: res.tempFiles.map(f => f.path),
1948
- tempFiles: res.tempFiles,
1949
- type: 'messagefile'
1950
- });
1951
- },
1952
- fail: failCallback
1953
- });
1954
- }
1955
- else
1956
- // #endif
1957
- if (actualType === 'image') {
1958
- // #ifndef H5
1959
- uni.chooseImage({
1960
- count: finalConfig.count,
1961
- sizeType: finalConfig.sizeType,
1962
- sourceType: finalConfig.sourceType,
1963
- success: (res) => {
1964
- const tempFilePaths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
1965
- const tempFilesArr = Array.isArray(res.tempFiles) ? res.tempFiles : [res.tempFiles];
1966
- // 规范化 tempFiles
1967
- const tempFiles = tempFilesArr.map((file, index) => ({
1968
- path: tempFilePaths[index], // 确保 path 存在
1969
- size: file.size,
1970
- }));
1971
- resolve({
1972
- success: true,
1973
- tempFilePaths,
1974
- tempFiles,
1975
- type: 'image'
1976
- });
1977
- },
1978
- fail: failCallback
1979
- });
1980
- // #endif
1981
- }
1982
- else if (actualType === 'local') {
1983
- // #ifndef H5
1984
- if (typeof uni.chooseFile === 'function') {
1985
- uni.chooseFile({
1986
- count: finalConfig.count,
1987
- extension: finalConfig.extension,
1988
- success: (res) => {
1989
- const tempFilePaths = Array.isArray(res.tempFilePaths) ? res.tempFilePaths : [res.tempFilePaths];
1990
- const tempFilesArr = Array.isArray(res.tempFiles) ? res.tempFiles : [res.tempFiles];
1991
- // 规范化 tempFiles
1992
- const tempFiles = tempFilesArr.map((file, index) => ({
1993
- path: tempFilePaths[index],
1994
- size: file.size,
1995
- }));
1996
- resolve({
1997
- success: true,
1998
- tempFilePaths,
1999
- tempFiles,
2000
- type: 'local'
2001
- });
2002
- },
2003
- fail: failCallback
2004
- });
2005
- }
2006
- else {
2007
- resolve({
2008
- success: false,
2009
- message: '当前平台不支持选择本地文件',
2010
- type: actualType
2011
- });
2012
- }
2013
- // #endif
2014
- }
2015
- }).then((res) => {
2016
- // 仅当成功时验证文件大小
2017
- if (!res.success || !res.tempFiles || !finalConfig.maxSize) {
2018
- return res;
2019
- }
2020
- const invalidFiles = res.tempFiles.filter((file) => {
2021
- const sizeValidation = validateFileSize(file.size, finalConfig.maxSize);
2022
- return !sizeValidation.valid;
2023
- });
2024
- if (invalidFiles.length > 0) {
2025
- const message = `部分文件大小超过 ${finalConfig.maxSize}MB 限制`;
2026
- if (finalConfig.showToast) {
2027
- useToast(message, false, 'error');
2028
- }
2029
- // 保留已选择的文件,但标记为失败
2030
- return {
2031
- ...res,
2032
- success: false,
2033
- message,
2034
- };
2035
- }
2036
- return res;
2037
- }), 'upload', 'CHOOSE_FILE_ERROR');
2038
- monitor.end('chooseFile');
2039
- return result || { success: false, message: '选择文件失败', type: actualType };
2040
- }
2041
- /**
2042
- * 选择图片(向后兼容)
2043
- * @param config 选择配置
2044
- * @returns Promise<ChooseImageResult> 选择结果
2045
- */
2046
- async function chooseImage(config = {}) {
2047
- const result = await chooseFile({ ...config, type: 'image' });
2048
- return {
2049
- success: result.success,
2050
- tempFilePaths: result.tempFilePaths,
2051
- tempFiles: result.tempFiles,
2052
- message: result.message
2053
- };
2054
- }
2055
- /**
2056
- * 上传文件
2057
- * @param filePath 文件路径
2058
- * @param config 上传配置
2059
- * @param onProgress 进度回调
2060
- * @returns Promise<UploadResult> 上传结果
2061
- */
2062
- async function uploadFile(filePath, config, onProgress) {
2063
- const monitor = PerformanceMonitor.getInstance();
2064
- monitor.start('uploadFile', 'upload', { filePath });
2065
- const finalConfig = { ...DEFAULT_UPLOAD_CONFIG, ...config };
2066
- // 验证必要参数
2067
- if (!finalConfig.url) {
2068
- const message = '上传地址不能为空';
2069
- if (finalConfig.showToast) {
2070
- useToast(message, false, 'error');
2071
- }
2072
- monitor.end('uploadFile');
2073
- return { success: false, message };
2074
- }
2075
- // 验证文件
2076
- const fileValidation = validateFile(filePath, finalConfig);
2077
- if (!fileValidation.valid) {
2078
- if (finalConfig.showToast) {
2079
- useToast(fileValidation.message, false, 'error');
2080
- }
2081
- monitor.end('uploadFile');
2082
- return { success: false, message: fileValidation.message };
2083
- }
2084
- const result = await safeAsync(() => new Promise((resolve) => {
2085
- const uploadTask = uni.uploadFile({
2086
- url: finalConfig.url,
2087
- filePath,
2088
- name: finalConfig.name,
2089
- formData: finalConfig.formData,
2090
- header: finalConfig.header,
2091
- timeout: finalConfig.timeout,
2092
- success: (res) => {
2093
- let data;
2094
- try {
2095
- data = JSON.parse(res.data);
2096
- }
2097
- catch {
2098
- data = res.data;
2099
- }
2100
- if (res.statusCode === 200) {
2101
- if (finalConfig.showToast) {
2102
- useToast(finalConfig.successMessage, false, 'success');
2103
- }
2104
- resolve({
2105
- success: true,
2106
- data,
2107
- tempFilePath: filePath,
2108
- statusCode: res.statusCode
2109
- });
2110
- }
2111
- else {
2112
- const message = `上传失败,状态码:${res.statusCode}`;
2113
- if (finalConfig.showToast) {
2114
- useToast(message, false, 'error');
2115
- }
2116
- resolve({
2117
- success: false,
2118
- message,
2119
- statusCode: res.statusCode
2120
- });
2121
- }
2122
- },
2123
- fail: (err) => {
2124
- const message = err.errMsg || finalConfig.failMessage;
2125
- if (finalConfig.showToast) {
2126
- useToast(message, false, 'error');
2127
- }
2128
- resolve({
2129
- success: false,
2130
- message,
2131
- tempFilePath: filePath
2132
- });
2133
- }
2134
- });
2135
- // 监听上传进度
2136
- if (onProgress && uploadTask) {
2137
- uploadTask.onProgressUpdate((res) => {
2138
- onProgress({
2139
- progress: res.progress,
2140
- totalBytesSent: res.totalBytesSent,
2141
- totalBytesExpectedToSend: res.totalBytesExpectedToSend
2142
- });
2143
- });
2144
- }
2145
- }), 'upload', 'UPLOAD_FILE_ERROR');
2146
- monitor.end('uploadFile');
2147
- return result || { success: false, message: '上传文件失败' };
2148
- }
2149
- /**
2150
- * 选择并上传文件(一体化功能)
2151
- * @param config 上传配置
2152
- * @param chooseConfig 选择文件配置
2153
- * @param onProgress 进度回调
2154
- * @returns Promise<UploadResult[]> 上传结果数组
2155
- */
2156
- async function chooseAndUploadFile(config, chooseConfig = {}, onProgress) {
2157
- const monitor = PerformanceMonitor.getInstance();
2158
- monitor.start('chooseAndUploadFile', 'upload');
2159
- // 选择文件
2160
- const chooseResult = await chooseFile(chooseConfig);
2161
- if (!chooseResult.success || !chooseResult.tempFilePaths) {
2162
- monitor.end('chooseAndUploadFile');
2163
- return [{ success: false, message: chooseResult.message || '选择文件失败' }];
2164
- }
2165
- // 批量上传
2166
- const uploadPromises = chooseResult.tempFilePaths.map(filePath => uploadFile(filePath, config, onProgress));
2167
- const results = await Promise.all(uploadPromises);
2168
- monitor.end('chooseAndUploadFile');
2169
- return results;
2170
- }
2171
- /**
2172
- * 选择并上传图片(向后兼容)
2173
- * @param config 上传配置
2174
- * @param chooseConfig 选择图片配置
2175
- * @param onProgress 进度回调
2176
- * @returns Promise<UploadResult[]> 上传结果数组
2177
- */
2178
- async function chooseAndUploadImage(config, chooseConfig = {}, onProgress) {
2179
- return chooseAndUploadFile(config, { ...chooseConfig, type: 'image' }, onProgress);
2180
- }
2181
- /**
2182
- * 检查是否支持文件上传
2183
- * @returns boolean 是否支持
2184
- */
2185
- function isUploadSupported() {
2186
- return typeof uni !== 'undefined' &&
2187
- typeof uni.chooseImage === 'function' &&
2188
- typeof uni.uploadFile === 'function';
2189
- }
2190
- /**
2191
- * 获取文件信息
2192
- * @param filePath 文件路径
2193
- * @returns Promise<{size: number, type?: string}> 文件信息
2194
- */
2195
- async function getFileInfo(filePath) {
2196
- return await safeAsync(() => new Promise((resolve, reject) => {
2197
- uni.getFileInfo({
2198
- filePath,
2199
- digestAlgorithm: 'md5', // 明确指定算法,虽然默认是md5
2200
- success: (res) => {
2201
- const result = res;
2202
- resolve({
2203
- size: result.size,
2204
- digest: result.digest
2205
- });
2206
- },
2207
- fail: reject
2208
- });
2209
- }), 'upload', 'GET_FILE_INFO_ERROR');
2210
- }
2211
-
2212
- // 核心功能
2213
-
2214
- // 版本信息
2215
- const VERSION = '1.0.8';
2216
-
2217
- // 初始化函数
2218
- async function initUniAppTools(config = {}) {
2219
- const {
2220
- enablePerformanceMonitor = false,
2221
- enableErrorHandler = true,
2222
- logLevel = 'warn'
2223
- } = config;
2224
-
2225
- if (enableErrorHandler) {
2226
- const { ErrorHandler } = await Promise.resolve().then(function () { return errorHandler; });
2227
- const errorHandler$1 = ErrorHandler.getInstance();
2228
-
2229
- // 设置全局错误监听
2230
- if (enablePerformanceMonitor) {
2231
- errorHandler$1.onError((error) => {
2232
- console.log(`[UniAppTools] ${error.module} - ${error.code}: ${error.message}`);
2233
- });
2234
- }
2235
- }
2236
-
2237
- if (enablePerformanceMonitor) {
2238
- const { PerformanceMonitor } = await Promise.resolve().then(function () { return performance$1; });
2239
- const monitor = PerformanceMonitor.getInstance();
2240
-
2241
- // 定期输出性能报告
2242
- setInterval(() => {
2243
- const report = monitor.getReport();
2244
- if (report.slowest.length > 0) {
2245
- console.log('[UniAppTools] 性能报告:', report);
2246
- }
2247
- }, 60000); // 每分钟输出一次
2248
- }
2249
-
2250
- console.log(`[UniAppTools] v${VERSION} 初始化完成`);
2251
- }
2252
-
2253
- exports.ErrorHandler = ErrorHandler;
2254
- exports.PerformanceMonitor = PerformanceMonitor;
2255
- exports.UniAppToolsError = UniAppToolsError;
2256
- exports.VERSION = VERSION;
2257
- exports.batchGetStorage = batchGetStorage;
2258
- exports.batchSetStorage = batchSetStorage;
2259
- exports.chooseAndUploadFile = chooseAndUploadFile;
2260
- exports.chooseAndUploadImage = chooseAndUploadImage;
2261
- exports.chooseFile = chooseFile;
2262
- exports.chooseImage = chooseImage;
2263
- exports.cleanExpiredStorage = cleanExpiredStorage;
2264
- exports.clearClipboard = clearClipboard;
2265
- exports.clearNavigationQueue = clearNavigationQueue;
2266
- exports.clearStorageSync = clearStorageSync;
2267
- exports.copyText = copyText;
2268
- exports.debounce = debounce;
2269
- exports.deepClone = deepClone;
2270
- exports.deepMerge = deepMerge;
2271
- exports.getCurrentEnv = getCurrentEnv;
2272
- exports.getCurrentPageInfo = getCurrentPageInfo;
2273
- exports.getFileInfo = getFileInfo;
2274
- exports.getMenuButtonBoundingClientRect = getMenuButtonBoundingClientRect;
2275
- exports.getNavHeight = getNavHeight;
2276
- exports.getPageStack = getPageStack;
2277
- exports.getPlatform = getPlatform;
2278
- exports.getStatusBarHeight = getStatusBarHeight;
2279
- exports.getStorage = getStorage;
2280
- exports.getStorageInfo = getStorageInfo;
2281
- exports.getStorageSync = getStorageSync;
2282
- exports.getTopNavBarHeight = getTopNavBarHeight;
2283
- exports.initUniAppTools = initUniAppTools;
2284
- exports.isClipboardSupported = isClipboardSupported;
2285
- exports.isUploadSupported = isUploadSupported;
2286
- exports.measurePerformance = measurePerformance;
2287
- exports.mergeObjects = mergeObjects;
2288
- exports.navigateTo = navigateTo;
2289
- exports.onCheckForUpdate = onCheckForUpdate;
2290
- exports.reLaunch = reLaunch;
2291
- exports.readClipboard = readClipboard;
2292
- exports.redirectTo = redirectTo;
2293
- exports.safeAsync = safeAsync;
2294
- exports.safeNavigateTo = safeNavigateTo;
2295
- exports.safeSync = safeSync;
2296
- exports.setPageIcon = setPageIcon;
2297
- exports.setPageTitle = setPageTitle;
2298
- exports.setStorage = setStorage;
2299
- exports.setStorageSync = setStorageSync;
2300
- exports.switchTab = switchTab;
2301
- exports.throttle = throttle;
2302
- exports.uploadFile = uploadFile;
2303
- exports.useBack = useBack;
2304
- exports.useBackDebounced = useBackDebounced;
2305
- exports.useBackOrHome = useBackOrHome;
2306
- exports.useDeepCopyByObj = useDeepCopyByObj;
2307
- exports.useToast = useToast;
2308
- exports.useWindowInfo = useWindowInfo;
2309
- //# sourceMappingURL=my-uniapp-tools.cjs.js.map
1
+ "use strict";class e extends Error{code;module;timestamp;constructor(e,t,n){super(t),this.name="UniAppToolsError",this.code=e,this.module=n,this.timestamp=Date.now()}}class t{static instance;errorCallbacks=[];static getInstance(){return t.instance||(t.instance=new t),t.instance}onError(e){this.errorCallbacks.push(e)}handleError(t,n){const s={code:t instanceof e?t.code:"UNKNOWN_ERROR",message:t.message,module:t instanceof e?t.module:n,timestamp:t instanceof e?t.timestamp:Date.now(),stack:t.stack};this.errorCallbacks.forEach(e=>{try{e(s)}catch(e){}})}createModuleErrorHandler(t){return(n,s,a)=>{const r=new e(n,s,t);return a&&(r.stack=a.stack),this.handleError(r,t),r}}}async function n(e,n,s="ASYNC_ERROR"){try{return await e()}catch(e){return t.getInstance().createModuleErrorHandler(n)(s,`异步操作失败: ${e instanceof Error?e.message:String(e)}`,e),null}}function s(e,n,s="SYNC_ERROR",a=null){try{return e()}catch(e){return t.getInstance().createModuleErrorHandler(n)(s,`同步操作失败: ${e instanceof Error?e.message:String(e)}`,e),a}}var a=Object.freeze({__proto__:null,ErrorHandler:t,UniAppToolsError:e,safeAsync:n,safeSync:s});class r{static instance;metrics=new Map;completedMetrics=[];maxStoredMetrics=100;static getInstance(){return r.instance||(r.instance=new r),r.instance}start(e,t,n){const s={name:e,startTime:performance.now(),module:t,metadata:n};this.metrics.set(e,s)}end(e){const t=this.metrics.get(e);return t?(t.endTime=performance.now(),t.duration=t.endTime-t.startTime,this.metrics.delete(e),this.completedMetrics.push(t),this.completedMetrics.length>this.maxStoredMetrics&&this.completedMetrics.shift(),t):null}getReport(){const e={},t={};this.completedMetrics.forEach(n=>{e[n.module]||(e[n.module]=[]),e[n.module].push(n);const s=`${n.module}.${n.name}`;t[s]||(t[s]={total:0,count:0}),t[s].total+=n.duration||0,t[s].count+=1});const n={};Object.entries(t).forEach(([e,t])=>{n[e]=t.total/t.count});const s=[...this.completedMetrics].sort((e,t)=>(t.duration||0)-(e.duration||0)).slice(0,10);return{byModule:e,slowest:s,average:n}}clear(){this.metrics.clear(),this.completedMetrics=[]}}function o(e){return function(t,n,s){const a=s.value;s.value=function(...s){const o=r.getInstance(),i=`${t.constructor.name}.${n}`;o.start(i,e,{args:s.length});try{const e=a.apply(this,s);return e instanceof Promise?e.finally(()=>{o.end(i)}):(o.end(i),e)}catch(e){throw o.end(i),e}}}}var i=Object.freeze({__proto__:null,PerformanceMonitor:r,measurePerformance:o});const c=(e="",t=!1,n="none",s=2e3)=>{uni.showToast({title:e,icon:n,mask:t,duration:s})};function u(e){return s(()=>{if(null===e||"object"!=typeof e)return e;const t=new WeakMap;return function e(n){if(null===n||"object"!=typeof n)return n;if(t.has(n))return t.get(n);if(n instanceof Date)return new Date(n.getTime());if(n instanceof RegExp)return new RegExp(n.source,n.flags);if(n instanceof Map){const s=new Map;return t.set(n,s),n.forEach((t,n)=>{s.set(e(n),e(t))}),s}if(n instanceof Set){const s=new Set;return t.set(n,s),n.forEach(t=>{s.add(e(t))}),s}if(Array.isArray(n)){const s=new Array(n.length);t.set(n,s);for(let t=0;t<n.length;t++)s[t]=e(n[t]);return s}const s=Object.create(Object.getPrototypeOf(n));t.set(n,s);const a=Object.getOwnPropertyDescriptors(n);for(const t in a){const n=a[t];void 0!==n.value&&(n.value=e(n.value)),Object.defineProperty(s,t,n)}return s}(e)},"utils","DEEP_CLONE_ERROR",e)}function l(e,t){const n=r.getInstance();n.start("mergeObjects","utils");const a=s(()=>Object.assign(e,t),"utils","MERGE_OBJECTS_ERROR",e);return n.end("mergeObjects"),a}function p(e,t,n=!1){let s,a=null;const r=function(...r){const o=n&&!a;return a&&clearTimeout(a),a=setTimeout(()=>{a=null,n||(s=e.apply(this,r))},t),o&&(s=e.apply(this,r)),s};return r.cancel=()=>{a&&(clearTimeout(a),a=null)},r}class g{static instance;navigationQueue=[];isNavigating=!1;maxQueueSize=10;static getInstance(){return g.instance||(g.instance=new g),g.instance}async processQueue(){if(!this.isNavigating&&0!==this.navigationQueue.length){for(this.isNavigating=!0;this.navigationQueue.length>0;){const e=this.navigationQueue.shift();if(e)try{await e(),await new Promise(e=>setTimeout(e,100))}catch(e){}}this.isNavigating=!1}}addToQueue(e){this.navigationQueue.length>=this.maxQueueSize&&this.navigationQueue.shift(),this.navigationQueue.push(e),this.processQueue()}async navigateTo(e){const t=r.getInstance();return t.start("navigateTo","navigation",{url:e.url}),new Promise(s=>{this.addToQueue(async()=>{const a=await n(()=>new Promise(n=>{let s=e.url;if(e.params&&Object.keys(e.params).length>0){const t=Object.entries(e.params).map(([e,t])=>`${encodeURIComponent(e)}=${encodeURIComponent(String(t))}`).join("&");s+=(s.includes("?")?"&":"?")+t}uni.navigateTo({url:s,animationType:e.animationType,animationDuration:e.animationDuration,events:e.events,success:()=>{t.end("navigateTo"),n(!0)},fail:e=>{t.end("navigateTo"),n(!1)}})}),"navigation","NAVIGATE_TO_ERROR");s(a??!1)})})}async redirectTo(e){const t=r.getInstance();return t.start("redirectTo","navigation",{url:e.url}),new Promise(s=>{this.addToQueue(async()=>{const a=await n(()=>new Promise(n=>{let s=e.url;if(e.params&&Object.keys(e.params).length>0){const t=Object.entries(e.params).map(([e,t])=>`${encodeURIComponent(e)}=${encodeURIComponent(String(t))}`).join("&");s+=(s.includes("?")?"&":"?")+t}uni.redirectTo({url:s,success:()=>{t.end("redirectTo"),n(!0)},fail:e=>{t.end("redirectTo"),n(!1)}})}),"navigation","REDIRECT_TO_ERROR");s(a??!1)})})}async navigateBack(e="",t={}){const s=r.getInstance();s.start("navigateBack","navigation");const a={delta:1,timeout:5e3,enableDebounce:!0,debounceWait:300,...t};return new Promise(t=>{this.addToQueue(async()=>{const r=await n(()=>new Promise(t=>{if(getCurrentPages().length<=a.delta)return void t(!1);const n=setTimeout(()=>{t(!1)},a.timeout);uni.navigateBack({delta:a.delta,success:()=>{clearTimeout(n),setTimeout(()=>{try{const n=getCurrentPages();if(n.length>0){const t=n[n.length-1];t.$vm&&"function"==typeof t.$vm.init?t.$vm.init(e):t.route}s.end("navigateBack"),t(!0)}catch(e){s.end("navigateBack"),t(!0)}},100)},fail:e=>{clearTimeout(n),s.end("navigateBack"),t(!1)}})}),"navigation","NAVIGATE_BACK_ERROR");t(r??!1)})})}async switchTab(e){const t=r.getInstance();return t.start("switchTab","navigation",{url:e}),new Promise(s=>{this.addToQueue(async()=>{const a=await n(()=>new Promise(n=>{uni.switchTab({url:e,success:()=>{t.end("switchTab"),n(!0)},fail:e=>{t.end("switchTab"),n(!1)}})}),"navigation","SWITCH_TAB_ERROR");s(a??!1)})})}async reLaunch(e){const t=r.getInstance();return t.start("reLaunch","navigation",{url:e.url}),new Promise(s=>{this.addToQueue(async()=>{const a=await n(()=>new Promise(n=>{let s=e.url;if(e.params&&Object.keys(e.params).length>0){const t=Object.entries(e.params).map(([e,t])=>`${encodeURIComponent(e)}=${encodeURIComponent(String(t))}`).join("&");s+=(s.includes("?")?"&":"?")+t}uni.reLaunch({url:s,success:()=>{t.end("reLaunch"),n(!0)},fail:e=>{t.end("reLaunch"),n(!1)}})}),"navigation","RELAUNCH_ERROR");s(a??!1)})})}getCurrentPageInfo(){return s(()=>{const e=getCurrentPages();if(0===e.length)return null;const t=e[e.length-1];return{route:t.route||"",options:t.options||{}}},"navigation","GET_CURRENT_PAGE_ERROR",null)}getPageStack(){return s(()=>getCurrentPages().map(e=>({route:e.route||"",options:e.options||{}})),"navigation","GET_PAGE_STACK_ERROR",[])??[]}clearQueue(){this.navigationQueue=[],this.isNavigating=!1}}const d=g.getInstance(),m=async(e="",t={})=>await d.navigateBack(e,t),h=p(m,300),f=async e=>await d.navigateTo(e),y={showToast:!0,successMessage:"复制成功",failMessage:"复制失败",timeout:5e3};async function w(e,t={}){const a=r.getInstance();a.start("copyText","clipboard",{textLength:e.length});const o={...y,...t};if(!e||"string"!=typeof e)return o.showToast&&c("复制内容不能为空",!1,"error"),a.end("copyText"),!1;if(e.length>1e4)return o.showToast&&c("复制内容过长",!1,"error"),a.end("copyText"),!1;let i=!1;return i=await n(()=>new Promise(t=>{uni.setClipboardData({data:e,success:()=>{o.showToast&&c(o.successMessage),t(!0)},fail:()=>{o.showToast&&c(o.failMessage),t(!1)}})}),"clipboard","COPY_TEXT_NATIVE_ERROR")??!1,i=await async function(e,t){if(navigator.clipboard&&window.isSecureContext){if(await n(async()=>(await Promise.race([navigator.clipboard.writeText(e),new Promise((e,n)=>setTimeout(()=>n(new Error("Clipboard API timeout")),t.timeout))]),!0),"clipboard","CLIPBOARD_API_ERROR"))return t.showToast&&c(t.successMessage),!0}return function(e,t){return s(()=>{const n=document.createElement("textarea");Object.assign(n.style,{position:"fixed",top:"-9999px",left:"-9999px",width:"1px",height:"1px",padding:"0",border:"none",outline:"none",boxShadow:"none",background:"transparent",fontSize:"16px"}),n.value=e,n.setAttribute("readonly",""),n.setAttribute("contenteditable","true"),document.body.appendChild(n),n.focus(),n.select(),n.setSelectionRange(0,e.length);const s=document.execCommand("copy");return document.body.removeChild(n),s?(t.showToast&&c(t.successMessage),!0):(t.showToast&&c(t.failMessage),!1)},"clipboard","FALLBACK_COPY_ERROR",!1)??!1}(e,t)}(e,o),a.end("copyText"),i}class T{static instance;cache=new Map;defaultTTL=3e4;static getInstance(){return T.instance||(T.instance=new T),T.instance}set(e,t,n=this.defaultTTL){this.cache.set(e,{data:t,timestamp:Date.now(),ttl:n})}get(e){const t=this.cache.get(e);return t?Date.now()-t.timestamp>t.ttl?(this.cache.delete(e),null):t.data:null}clear(){this.cache.clear()}}const R=T.getInstance(),E=()=>{let e;return e="weixin",e="web",e="app",e="alipay",e="h5","h5"},S=()=>{try{return uni.getSystemInfoSync().statusBarHeight||0}catch(e){return 0}},x=()=>{try{const e=E();return"weixin"===e||"alipay"===e?uni.getMenuButtonBoundingClientRect():null}catch(e){return null}},v=()=>{try{const e=S(),t=E();if("weixin"===t||"alipay"===t){const t=x();if(t){return t.height+e}}return e+0}catch(e){return S()+0}};class I{static instance;cache=new Map;maxCacheSize=100;static getInstance(){return I.instance||(I.instance=new I),I.instance}compress(e){return e.replace(/(.)\1{2,}/g,(e,t)=>`${t}*${e.length}`)}decompress(e){return e.replace(/(.)\*(\d+)/g,(e,t,n)=>t.repeat(parseInt(n)))}encrypt(e){return btoa(encodeURIComponent(e))}decrypt(e){try{return decodeURIComponent(atob(e))}catch{return e}}isExpired(e){return!!e.ttl&&Date.now()-e.timestamp>e.ttl}set(e,t,n={}){const a=r.getInstance();a.start(`setStorage_${e}`,"localStorage");const o=s(()=>{if(!e||"string"!=typeof e)throw new Error("存储键不能为空");const s={value:t,timestamp:Date.now(),ttl:n.ttl,compressed:n.compress,encrypted:n.encrypt};let a=JSON.stringify(s);if(n.compress&&(a=this.compress(a)),n.encrypt&&(a=this.encrypt(a)),a.length>1048576)throw new Error("存储数据过大");if(uni.setStorageSync(e,a),this.cache.size>=this.maxCacheSize){const e=this.cache.keys().next().value;e&&this.cache.delete(e)}return this.cache.set(e,s),!0},"localStorage","SET_STORAGE_ERROR",!1);return a.end(`setStorage_${e}`),o??!1}get(e,t){const n=r.getInstance();n.start(`getStorage_${e}`,"localStorage");const a=s(()=>{if(!e||"string"!=typeof e)return t;if(this.cache.has(e)){const n=this.cache.get(e);return this.isExpired(n)?(this.cache.delete(e),this.remove(e),t):n.value}let n=uni.getStorageSync(e);if(!n)return t;if("string"!=typeof n)return t;let s,a=n;if(a&&a.includes("="))try{const e=this.decrypt(a);e&&(a=e)}catch{}if(a&&a.includes("*"))try{a=this.decompress(a)}catch{}try{const e=JSON.parse(a);s=e&&"object"==typeof e&&"value"in e&&"timestamp"in e?e:{value:e,timestamp:Date.now()}}catch{return a}return this.isExpired(s)?(this.remove(e),t):(this.cache.set(e,s),s.value)},"localStorage","GET_STORAGE_ERROR",t);return n.end(`getStorage_${e}`),a??t}remove(e){return s(()=>!(!e||"string"!=typeof e)&&(uni.removeStorageSync(e),this.cache.delete(e),!0),"localStorage","REMOVE_STORAGE_ERROR",!1)??!1}clear(){return s(()=>(uni.clearStorageSync(),this.cache.clear(),!0),"localStorage","CLEAR_STORAGE_ERROR",!1)??!1}getInfo(){return s(()=>{const e=uni.getStorageInfoSync();return{keys:e.keys||[],currentSize:e.currentSize||0,limitSize:e.limitSize||0}},"localStorage","GET_STORAGE_INFO_ERROR",{keys:[],currentSize:0,limitSize:0})??{keys:[],currentSize:0,limitSize:0}}cleanExpired(){const e=r.getInstance();e.start("cleanExpiredStorage","localStorage");const t=s(()=>{const e=this.getInfo();let t=0;return e.keys.forEach(e=>{void 0===this.get(e)&&t++}),t},"localStorage","CLEAN_EXPIRED_ERROR",0);return e.end("cleanExpiredStorage"),t??0}}const O=I.getInstance();function P(e,t,n={}){return O.set(e,t,n)}function b(e,t){return O.get(e,t)}const _={name:"file",maxSize:10,allowedTypes:["jpg","jpeg","png","gif","webp"],showToast:!0,timeout:6e4,successMessage:"上传成功",failMessage:"上传失败"},C={type:"image",count:1,sizeType:["original","compressed"],sourceType:["album","camera"],extension:[],maxSize:10,showToast:!0,failMessage:"选择文件失败"};async function A(e={}){const t=r.getInstance();t.start("chooseFile","upload",{count:e.count,type:e.type});const s={...C,...e};let a=s.type;var o;("messagefile"===(o=a)?"function"==typeof uni.chooseMessageFile:"local"===o||"image"===o)||(a="image");const i=await n(()=>new Promise(e=>{const t=t=>{const n=t.errMsg||s.failMessage;s.showToast&&c(n,!1,"error"),e({success:!1,message:n,type:a})};"image"!==a&&"local"!==a?"messagefile"===a?uni.chooseMessageFile({count:s.count,type:"all",success:t=>{e({success:!0,tempFilePaths:t.tempFiles.map(e=>e.path),tempFiles:t.tempFiles,type:"messagefile"})},fail:t}):"image"===a?uni.chooseImage({count:s.count,sizeType:s.sizeType,sourceType:s.sourceType,success:t=>{const n=Array.isArray(t.tempFilePaths)?t.tempFilePaths:[t.tempFilePaths],s=(Array.isArray(t.tempFiles)?t.tempFiles:[t.tempFiles]).map((e,t)=>({path:n[t],size:e.size}));e({success:!0,tempFilePaths:n,tempFiles:s,type:"image"})},fail:t}):"local"===a&&("function"==typeof uni.chooseFile?uni.chooseFile({count:s.count,extension:s.extension,success:t=>{const n=Array.isArray(t.tempFilePaths)?t.tempFilePaths:[t.tempFilePaths],s=(Array.isArray(t.tempFiles)?t.tempFiles:[t.tempFiles]).map((e,t)=>({path:n[t],size:e.size}));e({success:!0,tempFilePaths:n,tempFiles:s,type:"local"})},fail:t}):e({success:!1,message:"当前平台不支持选择本地文件",type:a})):function(e,t){const n=document.createElement("input");n.type="file",n.multiple=e.count>1,"image"===e.type?n.accept="image/*":e.extension.length>0&&(n.accept=e.extension.map(e=>e.startsWith(".")?e:`.${e}`).join(",")),n.onchange=s=>{const a=s.target.files;if(a&&a.length>0){const n=Array.from(a).map(e=>({path:URL.createObjectURL(e),size:e.size,name:e.name,type:e.type,file:e})),s=n.map(e=>({path:e.path,size:e.size}));t({success:!0,tempFilePaths:n.map(e=>e.path),tempFiles:s,type:e.type})}else t({success:!1,message:"用户取消选择",type:e.type});document.body.removeChild(n)},n.style.display="none",document.body.appendChild(n),n.click()}(s,e)}).then(e=>{if(!e.success||!e.tempFiles||!s.maxSize)return e;if(e.tempFiles.filter(e=>{var t,n;return!(t=e.size,(n=s.maxSize)&&t>1024*n*1024?{valid:!1,message:`文件大小不能超过 ${n}MB`}:{valid:!0}).valid}).length>0){const t=`部分文件大小超过 ${s.maxSize}MB 限制`;return s.showToast&&c(t,!1,"error"),{...e,success:!1,message:t}}return e}),"upload","CHOOSE_FILE_ERROR");return t.end("chooseFile"),i||{success:!1,message:"选择文件失败",type:a}}async function F(e,t,s){const a=r.getInstance();a.start("uploadFile","upload",{filePath:e});const o={..._,...t};if(!o.url){const e="上传地址不能为空";return o.showToast&&c(e,!1,"error"),a.end("uploadFile"),{success:!1,message:e}}const i=function(e,t){if(!e)return{valid:!1,message:"文件路径不能为空"};if(t.allowedTypes&&t.allowedTypes.length>0){const n=e.split(".").pop()?.toLowerCase();if(!n||!t.allowedTypes.includes(n))return{valid:!1,message:`不支持的文件类型,仅支持:${t.allowedTypes.join(", ")}`}}return{valid:!0}}(e,o);if(!i.valid)return o.showToast&&c(i.message,!1,"error"),a.end("uploadFile"),{success:!1,message:i.message};const u=await n(()=>new Promise(t=>{const n=uni.uploadFile({url:o.url,filePath:e,name:o.name,formData:o.formData,header:o.header,timeout:o.timeout,success:n=>{let s;try{s=JSON.parse(n.data)}catch{s=n.data}if(200===n.statusCode)o.showToast&&c(o.successMessage,!1,"success"),t({success:!0,data:s,tempFilePath:e,statusCode:n.statusCode});else{const e=`上传失败,状态码:${n.statusCode}`;o.showToast&&c(e,!1,"error"),t({success:!1,message:e,statusCode:n.statusCode})}},fail:n=>{const s=n.errMsg||o.failMessage;o.showToast&&c(s,!1,"error"),t({success:!1,message:s,tempFilePath:e})}});s&&n&&n.onProgressUpdate(e=>{s({progress:e.progress,totalBytesSent:e.totalBytesSent,totalBytesExpectedToSend:e.totalBytesExpectedToSend})})}),"upload","UPLOAD_FILE_ERROR");return a.end("uploadFile"),u||{success:!1,message:"上传文件失败"}}async function M(e,t={},n){const s=r.getInstance();s.start("chooseAndUploadFile","upload");const a=await A(t);if(!a.success||!a.tempFilePaths)return s.end("chooseAndUploadFile"),[{success:!1,message:a.message||"选择文件失败"}];const o=a.tempFilePaths.map(t=>F(t,e,n)),i=await Promise.all(o);return s.end("chooseAndUploadFile"),i}exports.ErrorHandler=t,exports.PerformanceMonitor=r,exports.UniAppToolsError=e,exports.VERSION="1.0.8",exports.batchGetStorage=function(e){const t=r.getInstance();t.start("batchGetStorage","localStorage");const n={};return e.forEach(e=>{n[e]=b(e)}),t.end("batchGetStorage"),n},exports.batchSetStorage=function(e,t={}){const n=r.getInstance();n.start("batchSetStorage","localStorage");let s=0;return Object.entries(e).forEach(([e,n])=>{P(e,n,t)&&s++}),n.end("batchSetStorage"),s},exports.chooseAndUploadFile=M,exports.chooseAndUploadImage=async function(e,t={},n){return M(e,{...t,type:"image"},n)},exports.chooseFile=A,exports.chooseImage=async function(e={}){const t=await A({...e,type:"image"});return{success:t.success,tempFilePaths:t.tempFilePaths,tempFiles:t.tempFiles,message:t.message}},exports.cleanExpiredStorage=function(){return O.cleanExpired()},exports.clearClipboard=async function(e={}){return await w("",e)},exports.clearNavigationQueue=()=>{d.clearQueue()},exports.clearStorageSync=function(e){return e?O.remove(e):O.clear()},exports.copyText=w,exports.debounce=p,exports.deepClone=u,exports.deepMerge=function(e,t){const n=r.getInstance();n.start("deepMerge","utils");const a=s(()=>{const n=u(e);return function e(t,n){for(const s in n)if(n.hasOwnProperty(s)){const a=n[s],r=t[s];a&&"object"==typeof a&&!Array.isArray(a)&&r&&"object"==typeof r&&!Array.isArray(r)?e(r,a):t[s]=u(a)}}(n,t),n},"utils","DEEP_MERGE_ERROR",e);return n.end("deepMerge"),a},exports.getCurrentEnv=()=>{try{const e=uni.getAccountInfoSync();return{appId:e.miniProgram?.appId||"",version:e.miniProgram?.version||"",envVersion:e.miniProgram?.envVersion||"",accountInfo:e}}catch(e){return{appId:"",version:"",envVersion:"",accountInfo:null}}},exports.getCurrentPageInfo=()=>d.getCurrentPageInfo(),exports.getFileInfo=async function(e){return await n(()=>new Promise((t,n)=>{uni.getFileInfo({filePath:e,digestAlgorithm:"md5",success:e=>{const n=e;t({size:n.size,digest:n.digest})},fail:n})}),"upload","GET_FILE_INFO_ERROR")},exports.getMenuButtonBoundingClientRect=x,exports.getNavHeight=v,exports.getPageStack=()=>d.getPageStack(),exports.getPlatform=E,exports.getStatusBarHeight=S,exports.getStorage=async function(e,t){return await n(()=>new Promise(n=>{n(b(e,t))}),"localStorage","GET_STORAGE_ASYNC_ERROR")??t},exports.getStorageInfo=function(){return O.getInfo()},exports.getStorageSync=b,exports.getTopNavBarHeight=()=>{try{const e=S();return{statusBarHeight:e,navHeight:v()}}catch(e){return{statusBarHeight:0,navHeight:44}}},exports.initUniAppTools=async function(e={}){const{enablePerformanceMonitor:t=!1,enableErrorHandler:n=!0,logLevel:s="warn"}=e;if(n){const{ErrorHandler:e}=await Promise.resolve().then(function(){return a}),n=e.getInstance();t&&n.onError(e=>{})}if(t){const{PerformanceMonitor:e}=await Promise.resolve().then(function(){return i}),t=e.getInstance();setInterval(()=>{t.getReport().slowest.length},6e4)}},exports.isClipboardSupported=function(){return!(!navigator.clipboard||!window.isSecureContext)},exports.isUploadSupported=function(){return"undefined"!=typeof uni&&"function"==typeof uni.chooseImage&&"function"==typeof uni.uploadFile},exports.measurePerformance=o,exports.mergeObjects=l,exports.navigateTo=f,exports.onCheckForUpdate=()=>{try{const e=uni.getUpdateManager();e.onCheckForUpdate(function(e){e.hasUpdate}),e.onUpdateReady(function(t){uni.showModal({title:"更新提示",content:"新版本已经准备好,是否重启应用?",showCancel:!0,cancelText:"稍后",confirmText:"立即重启",success(t){t.confirm&&e.applyUpdate()}})}),e.onUpdateFailed(function(e){uni.showModal({title:"更新失败",content:"新版本下载失败,请检查网络连接或稍后重试",showCancel:!1,confirmText:"知道了"})})}catch(e){}},exports.reLaunch=async e=>await d.reLaunch(e),exports.readClipboard=async function(e={}){const t=r.getInstance();t.start("readClipboard","clipboard");const s={...y,...e};if(navigator.clipboard&&window.isSecureContext){const e=await n(async()=>await Promise.race([navigator.clipboard.readText(),new Promise((e,t)=>setTimeout(()=>t(new Error("Read clipboard timeout")),s.timeout))]),"clipboard","READ_CLIPBOARD_ERROR");return t.end("readClipboard"),e}return s.showToast&&c("当前平台不支持读取剪贴板",!1,"error"),t.end("readClipboard"),null},exports.redirectTo=async e=>await d.redirectTo(e),exports.safeAsync=n,exports.safeNavigateTo=async(e,t=3)=>{for(let n=0;n<t;n++){if(await f(e))return!0;n<t-1&&await new Promise(e=>setTimeout(e,1e3*(n+1)))}return!1},exports.safeSync=s,exports.setPageIcon=(e,t="image/x-icon")=>s(()=>{const n=E();if(("h5"===n||"web"===n)&&"undefined"!=typeof document){document.querySelectorAll('link[rel*="icon"]').forEach(e=>e.remove());const n=document.createElement("link");return n.rel="icon",n.type=t,n.href=e,document.head.appendChild(n),!0}return!1},"system","SET_PAGE_ICON_ERROR",!1),exports.setPageTitle=e=>s(()=>{const t=E();return("h5"===t||"web"===t)&&"undefined"!=typeof document&&(document.title=e,!0)},"system","SET_PAGE_TITLE_ERROR",!1),exports.setStorage=async function(e,t,s={}){return await n(()=>new Promise(n=>{n(P(e,t,s))}),"localStorage","SET_STORAGE_ASYNC_ERROR")??!1},exports.setStorageSync=P,exports.switchTab=async e=>await d.switchTab(e),exports.throttle=function(e,t,n={}){let s,a=null,r=0;const{leading:o=!0,trailing:i=!0}=n,c=function(...n){const c=Date.now();r||o||(r=c);const u=t-(c-r);return u<=0||u>t?(a&&(clearTimeout(a),a=null),r=c,s=e.apply(this,n)):!a&&i&&(a=setTimeout(()=>{r=o?Date.now():0,a=null,s=e.apply(this,n)},u)),s};return c.cancel=()=>{a&&(clearTimeout(a),a=null),r=0},c},exports.uploadFile=F,exports.useBack=m,exports.useBackDebounced=h,exports.useBackOrHome=async(e="",t={})=>{const n=r.getInstance();n.start("useBackOrHome","navigation");try{const s=getCurrentPages(),a=t.delta||1;if(s.length>a){const s=await d.navigateBack(e,t);return n.end("useBackOrHome"),s}const r=t.homePage||"pages/index/index",o=r.startsWith("/")?r:`/${r}`,i=await d.reLaunch({url:o,params:t.homeParams});return n.end("useBackOrHome"),i}catch(e){return n.end("useBackOrHome"),!1}},exports.useDeepCopyByObj=function(e,t){if("object"==typeof e&&null!==e&&!Array.isArray(e)){return l(e,u(t))}return u(t)},exports.useToast=c,exports.useWindowInfo=(e=!0)=>{const t=r.getInstance();t.start("getWindowInfo","system");const n="windowInfo";if(e){const e=R.get(n);if(e)return t.end("getWindowInfo"),e}const a=s(()=>{const t=uni.getWindowInfo();return e&&R.set(n,t),t},"system","GET_WINDOW_INFO_ERROR",null);return t.end("getWindowInfo"),a};