my-uniapp-tools 1.0.14 → 1.0.16

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