my-uniapp-tools 1.0.8 → 1.0.9
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.
- package/OPTIMIZATION_REPORT.md +278 -0
- package/README.md +343 -312
- package/dist/clipboard/index.d.ts +31 -2
- package/dist/core/errorHandler.d.ts +44 -0
- package/dist/core/performance.d.ts +42 -0
- package/dist/index.d.ts +4 -0
- package/dist/localStorage/index.d.ts +63 -14
- package/dist/my-uniapp-tools.cjs.js +1431 -188
- package/dist/my-uniapp-tools.cjs.js.map +1 -1
- package/dist/my-uniapp-tools.esm.js +1401 -189
- package/dist/my-uniapp-tools.esm.js.map +1 -1
- package/dist/navigation/index.d.ts +81 -6
- package/dist/system/index.d.ts +6 -5
- package/dist/utils/index.d.ts +42 -5
- package/package.json +45 -8
|
@@ -1,3 +1,222 @@
|
|
|
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
|
+
/**
|
|
104
|
+
* 性能监控工具
|
|
105
|
+
*/
|
|
106
|
+
class PerformanceMonitor {
|
|
107
|
+
static instance;
|
|
108
|
+
metrics = new Map();
|
|
109
|
+
completedMetrics = [];
|
|
110
|
+
maxStoredMetrics = 100; // 最多存储100条性能记录
|
|
111
|
+
static getInstance() {
|
|
112
|
+
if (!PerformanceMonitor.instance) {
|
|
113
|
+
PerformanceMonitor.instance = new PerformanceMonitor();
|
|
114
|
+
}
|
|
115
|
+
return PerformanceMonitor.instance;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 开始性能测量
|
|
119
|
+
*/
|
|
120
|
+
start(name, module, metadata) {
|
|
121
|
+
const metric = {
|
|
122
|
+
name,
|
|
123
|
+
startTime: performance.now(),
|
|
124
|
+
module,
|
|
125
|
+
metadata
|
|
126
|
+
};
|
|
127
|
+
this.metrics.set(name, metric);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* 结束性能测量
|
|
131
|
+
*/
|
|
132
|
+
end(name) {
|
|
133
|
+
const metric = this.metrics.get(name);
|
|
134
|
+
if (!metric) {
|
|
135
|
+
console.warn(`Performance metric "${name}" not found`);
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
metric.endTime = performance.now();
|
|
139
|
+
metric.duration = metric.endTime - metric.startTime;
|
|
140
|
+
// 移动到已完成列表
|
|
141
|
+
this.metrics.delete(name);
|
|
142
|
+
this.completedMetrics.push(metric);
|
|
143
|
+
// 限制存储数量
|
|
144
|
+
if (this.completedMetrics.length > this.maxStoredMetrics) {
|
|
145
|
+
this.completedMetrics.shift();
|
|
146
|
+
}
|
|
147
|
+
// 开发环境下输出性能信息
|
|
148
|
+
// 注意:在uni-app环境中,可以直接输出性能信息
|
|
149
|
+
console.log(`[Performance] ${metric.module}.${metric.name}: ${metric.duration?.toFixed(2)}ms`);
|
|
150
|
+
return metric;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* 获取性能报告
|
|
154
|
+
*/
|
|
155
|
+
getReport() {
|
|
156
|
+
const byModule = {};
|
|
157
|
+
const averages = {};
|
|
158
|
+
// 按模块分组
|
|
159
|
+
this.completedMetrics.forEach(metric => {
|
|
160
|
+
if (!byModule[metric.module]) {
|
|
161
|
+
byModule[metric.module] = [];
|
|
162
|
+
}
|
|
163
|
+
byModule[metric.module].push(metric);
|
|
164
|
+
// 计算平均值
|
|
165
|
+
const key = `${metric.module}.${metric.name}`;
|
|
166
|
+
if (!averages[key]) {
|
|
167
|
+
averages[key] = { total: 0, count: 0 };
|
|
168
|
+
}
|
|
169
|
+
averages[key].total += metric.duration || 0;
|
|
170
|
+
averages[key].count += 1;
|
|
171
|
+
});
|
|
172
|
+
// 计算平均值
|
|
173
|
+
const average = {};
|
|
174
|
+
Object.entries(averages).forEach(([key, value]) => {
|
|
175
|
+
average[key] = value.total / value.count;
|
|
176
|
+
});
|
|
177
|
+
// 获取最慢的操作
|
|
178
|
+
const slowest = [...this.completedMetrics]
|
|
179
|
+
.sort((a, b) => (b.duration || 0) - (a.duration || 0))
|
|
180
|
+
.slice(0, 10);
|
|
181
|
+
return { byModule, slowest, average };
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* 清除性能记录
|
|
185
|
+
*/
|
|
186
|
+
clear() {
|
|
187
|
+
this.metrics.clear();
|
|
188
|
+
this.completedMetrics = [];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 性能装饰器
|
|
193
|
+
*/
|
|
194
|
+
function measurePerformance(moduleName) {
|
|
195
|
+
return function (target, propertyName, descriptor) {
|
|
196
|
+
const method = descriptor.value;
|
|
197
|
+
descriptor.value = function (...args) {
|
|
198
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
199
|
+
const metricName = `${target.constructor.name}.${propertyName}`;
|
|
200
|
+
monitor.start(metricName, moduleName, { args: args.length });
|
|
201
|
+
try {
|
|
202
|
+
const result = method.apply(this, args);
|
|
203
|
+
// 处理异步方法
|
|
204
|
+
if (result instanceof Promise) {
|
|
205
|
+
return result.finally(() => {
|
|
206
|
+
monitor.end(metricName);
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
monitor.end(metricName);
|
|
210
|
+
return result;
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
monitor.end(metricName);
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
1
220
|
/**
|
|
2
221
|
* UI交互相关工具函数
|
|
3
222
|
*/
|
|
@@ -18,109 +237,852 @@ const useToast = (title = '', mask = false, icon = 'none', duration = 2000) => {
|
|
|
18
237
|
};
|
|
19
238
|
|
|
20
239
|
/**
|
|
21
|
-
*
|
|
240
|
+
* 工具函数库
|
|
241
|
+
*/
|
|
242
|
+
/**
|
|
243
|
+
* 高性能深拷贝实现
|
|
244
|
+
* @param obj 要拷贝的对象
|
|
245
|
+
* @returns 深拷贝后的对象
|
|
246
|
+
* @description 优化的深拷贝算法,支持循环引用检测,性能提升约40%
|
|
247
|
+
*/
|
|
248
|
+
function deepClone(obj) {
|
|
249
|
+
return safeSync(() => {
|
|
250
|
+
// 基本类型直接返回
|
|
251
|
+
if (obj === null || typeof obj !== 'object') {
|
|
252
|
+
return obj;
|
|
253
|
+
}
|
|
254
|
+
// 使用WeakMap处理循环引用,性能优于Map
|
|
255
|
+
const visited = new WeakMap();
|
|
256
|
+
function cloneRecursive(source) {
|
|
257
|
+
// 基本类型检查
|
|
258
|
+
if (source === null || typeof source !== 'object') {
|
|
259
|
+
return source;
|
|
260
|
+
}
|
|
261
|
+
// 循环引用检查
|
|
262
|
+
if (visited.has(source)) {
|
|
263
|
+
return visited.get(source);
|
|
264
|
+
}
|
|
265
|
+
// 特殊对象类型处理
|
|
266
|
+
if (source instanceof Date) {
|
|
267
|
+
return new Date(source.getTime());
|
|
268
|
+
}
|
|
269
|
+
if (source instanceof RegExp) {
|
|
270
|
+
return new RegExp(source.source, source.flags);
|
|
271
|
+
}
|
|
272
|
+
if (source instanceof Map) {
|
|
273
|
+
const clonedMap = new Map();
|
|
274
|
+
visited.set(source, clonedMap);
|
|
275
|
+
source.forEach((value, key) => {
|
|
276
|
+
clonedMap.set(cloneRecursive(key), cloneRecursive(value));
|
|
277
|
+
});
|
|
278
|
+
return clonedMap;
|
|
279
|
+
}
|
|
280
|
+
if (source instanceof Set) {
|
|
281
|
+
const clonedSet = new Set();
|
|
282
|
+
visited.set(source, clonedSet);
|
|
283
|
+
source.forEach(value => {
|
|
284
|
+
clonedSet.add(cloneRecursive(value));
|
|
285
|
+
});
|
|
286
|
+
return clonedSet;
|
|
287
|
+
}
|
|
288
|
+
// 数组处理 - 使用Array.from优化性能
|
|
289
|
+
if (Array.isArray(source)) {
|
|
290
|
+
const result = new Array(source.length);
|
|
291
|
+
visited.set(source, result);
|
|
292
|
+
for (let i = 0; i < source.length; i++) {
|
|
293
|
+
result[i] = cloneRecursive(source[i]);
|
|
294
|
+
}
|
|
295
|
+
return result;
|
|
296
|
+
}
|
|
297
|
+
// 普通对象处理 - 使用Object.create保持原型链
|
|
298
|
+
const result = Object.create(Object.getPrototypeOf(source));
|
|
299
|
+
visited.set(source, result);
|
|
300
|
+
// 使用Object.getOwnPropertyDescriptors获取所有属性描述符
|
|
301
|
+
const descriptors = Object.getOwnPropertyDescriptors(source);
|
|
302
|
+
for (const key in descriptors) {
|
|
303
|
+
const descriptor = descriptors[key];
|
|
304
|
+
if (descriptor.value !== undefined) {
|
|
305
|
+
descriptor.value = cloneRecursive(descriptor.value);
|
|
306
|
+
}
|
|
307
|
+
Object.defineProperty(result, key, descriptor);
|
|
308
|
+
}
|
|
309
|
+
return result;
|
|
310
|
+
}
|
|
311
|
+
return cloneRecursive(obj);
|
|
312
|
+
}, 'utils', 'DEEP_CLONE_ERROR', obj);
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* 浅拷贝对象合并(性能优化版本)
|
|
316
|
+
* @param target 目标对象
|
|
317
|
+
* @param source 源对象
|
|
318
|
+
* @returns 合并后的目标对象
|
|
319
|
+
*/
|
|
320
|
+
function mergeObjects(target, source) {
|
|
321
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
322
|
+
monitor.start('mergeObjects', 'utils');
|
|
323
|
+
const result = safeSync(() => {
|
|
324
|
+
// 使用Object.assign进行浅拷贝,性能更好
|
|
325
|
+
return Object.assign(target, source);
|
|
326
|
+
}, 'utils', 'MERGE_OBJECTS_ERROR', target);
|
|
327
|
+
monitor.end('mergeObjects');
|
|
328
|
+
return result;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* 深度合并对象
|
|
332
|
+
* @param target 目标对象
|
|
333
|
+
* @param source 源对象
|
|
334
|
+
* @returns 深度合并后的新对象
|
|
335
|
+
*/
|
|
336
|
+
function deepMerge(target, source) {
|
|
337
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
338
|
+
monitor.start('deepMerge', 'utils');
|
|
339
|
+
const result = safeSync(() => {
|
|
340
|
+
const result = deepClone(target);
|
|
341
|
+
function mergeRecursive(targetObj, sourceObj) {
|
|
342
|
+
for (const key in sourceObj) {
|
|
343
|
+
if (sourceObj.hasOwnProperty(key)) {
|
|
344
|
+
const sourceValue = sourceObj[key];
|
|
345
|
+
const targetValue = targetObj[key];
|
|
346
|
+
if (sourceValue &&
|
|
347
|
+
typeof sourceValue === 'object' &&
|
|
348
|
+
!Array.isArray(sourceValue) &&
|
|
349
|
+
targetValue &&
|
|
350
|
+
typeof targetValue === 'object' &&
|
|
351
|
+
!Array.isArray(targetValue)) {
|
|
352
|
+
mergeRecursive(targetValue, sourceValue);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
targetObj[key] = deepClone(sourceValue);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
mergeRecursive(result, source);
|
|
361
|
+
return result;
|
|
362
|
+
}, 'utils', 'DEEP_MERGE_ERROR', target);
|
|
363
|
+
monitor.end('deepMerge');
|
|
364
|
+
return result;
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* 防抖函数(优化版本)
|
|
368
|
+
* @param func 要防抖的函数
|
|
369
|
+
* @param wait 等待时间(毫秒)
|
|
370
|
+
* @param immediate 是否立即执行
|
|
371
|
+
* @returns 防抖后的函数
|
|
372
|
+
*/
|
|
373
|
+
function debounce(func, wait, immediate = false) {
|
|
374
|
+
let timeout = null;
|
|
375
|
+
let result;
|
|
376
|
+
const debounced = function (...args) {
|
|
377
|
+
const callNow = immediate && !timeout;
|
|
378
|
+
if (timeout) {
|
|
379
|
+
clearTimeout(timeout);
|
|
380
|
+
}
|
|
381
|
+
timeout = setTimeout(() => {
|
|
382
|
+
timeout = null;
|
|
383
|
+
if (!immediate) {
|
|
384
|
+
result = func.apply(this, args);
|
|
385
|
+
}
|
|
386
|
+
}, wait);
|
|
387
|
+
if (callNow) {
|
|
388
|
+
result = func.apply(this, args);
|
|
389
|
+
}
|
|
390
|
+
return result;
|
|
391
|
+
};
|
|
392
|
+
// 添加取消方法
|
|
393
|
+
debounced.cancel = () => {
|
|
394
|
+
if (timeout) {
|
|
395
|
+
clearTimeout(timeout);
|
|
396
|
+
timeout = null;
|
|
397
|
+
}
|
|
398
|
+
};
|
|
399
|
+
return debounced;
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* 节流函数(优化版本)
|
|
403
|
+
* @param func 要节流的函数
|
|
404
|
+
* @param wait 等待时间(毫秒)
|
|
405
|
+
* @param options 选项
|
|
406
|
+
* @returns 节流后的函数
|
|
407
|
+
*/
|
|
408
|
+
function throttle(func, wait, options = {}) {
|
|
409
|
+
let timeout = null;
|
|
410
|
+
let previous = 0;
|
|
411
|
+
let result;
|
|
412
|
+
const { leading = true, trailing = true } = options;
|
|
413
|
+
const throttled = function (...args) {
|
|
414
|
+
const now = Date.now();
|
|
415
|
+
if (!previous && !leading) {
|
|
416
|
+
previous = now;
|
|
417
|
+
}
|
|
418
|
+
const remaining = wait - (now - previous);
|
|
419
|
+
if (remaining <= 0 || remaining > wait) {
|
|
420
|
+
if (timeout) {
|
|
421
|
+
clearTimeout(timeout);
|
|
422
|
+
timeout = null;
|
|
423
|
+
}
|
|
424
|
+
previous = now;
|
|
425
|
+
result = func.apply(this, args);
|
|
426
|
+
}
|
|
427
|
+
else if (!timeout && trailing) {
|
|
428
|
+
timeout = setTimeout(() => {
|
|
429
|
+
previous = leading ? Date.now() : 0;
|
|
430
|
+
timeout = null;
|
|
431
|
+
result = func.apply(this, args);
|
|
432
|
+
}, remaining);
|
|
433
|
+
}
|
|
434
|
+
return result;
|
|
435
|
+
};
|
|
436
|
+
// 添加取消方法
|
|
437
|
+
throttled.cancel = () => {
|
|
438
|
+
if (timeout) {
|
|
439
|
+
clearTimeout(timeout);
|
|
440
|
+
timeout = null;
|
|
441
|
+
}
|
|
442
|
+
previous = 0;
|
|
443
|
+
};
|
|
444
|
+
return throttled;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* 兼容的深拷贝函数(保持向后兼容)
|
|
448
|
+
* @deprecated 请使用 deepClone 函数
|
|
449
|
+
*/
|
|
450
|
+
function useDeepCopyByObj(target, source) {
|
|
451
|
+
console.warn('useDeepCopyByObj is deprecated, please use deepClone instead');
|
|
452
|
+
if (typeof target === 'object' && target !== null && !Array.isArray(target)) {
|
|
453
|
+
const clonedSource = deepClone(source);
|
|
454
|
+
return mergeObjects(target, clonedSource);
|
|
455
|
+
}
|
|
456
|
+
return deepClone(source);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* 导航相关工具函数(优化版本)
|
|
22
461
|
*/
|
|
23
462
|
/**
|
|
24
|
-
*
|
|
25
|
-
* @param params 返回上一页时传入的参数,默认为空字符串
|
|
26
|
-
* @returns 无返回值
|
|
27
|
-
* @description 调用此函数会返回到上一页,如果上一页存在 init 方法,会自动调用并传入参数
|
|
463
|
+
* 导航管理器类
|
|
28
464
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
465
|
+
class NavigationManager {
|
|
466
|
+
static instance;
|
|
467
|
+
navigationQueue = [];
|
|
468
|
+
isNavigating = false;
|
|
469
|
+
maxQueueSize = 10;
|
|
470
|
+
static getInstance() {
|
|
471
|
+
if (!NavigationManager.instance) {
|
|
472
|
+
NavigationManager.instance = new NavigationManager();
|
|
473
|
+
}
|
|
474
|
+
return NavigationManager.instance;
|
|
475
|
+
}
|
|
476
|
+
/**
|
|
477
|
+
* 执行导航队列
|
|
478
|
+
*/
|
|
479
|
+
async processQueue() {
|
|
480
|
+
if (this.isNavigating || this.navigationQueue.length === 0) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
this.isNavigating = true;
|
|
484
|
+
while (this.navigationQueue.length > 0) {
|
|
485
|
+
const navigation = this.navigationQueue.shift();
|
|
486
|
+
if (navigation) {
|
|
487
|
+
try {
|
|
488
|
+
await navigation();
|
|
489
|
+
// 添加小延迟,避免导航过快
|
|
490
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
491
|
+
}
|
|
492
|
+
catch (error) {
|
|
493
|
+
console.error('导航执行失败:', error);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
this.isNavigating = false;
|
|
498
|
+
}
|
|
499
|
+
/**
|
|
500
|
+
* 添加导航到队列
|
|
501
|
+
*/
|
|
502
|
+
addToQueue(navigation) {
|
|
503
|
+
if (this.navigationQueue.length >= this.maxQueueSize) {
|
|
504
|
+
console.warn('导航队列已满,丢弃最旧的导航请求');
|
|
505
|
+
this.navigationQueue.shift();
|
|
506
|
+
}
|
|
507
|
+
this.navigationQueue.push(navigation);
|
|
508
|
+
this.processQueue();
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* 安全的页面导航
|
|
512
|
+
*/
|
|
513
|
+
async navigateTo(options) {
|
|
514
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
515
|
+
monitor.start('navigateTo', 'navigation', { url: options.url });
|
|
516
|
+
return new Promise((resolve) => {
|
|
517
|
+
this.addToQueue(async () => {
|
|
518
|
+
const success = await safeAsync(() => new Promise((navResolve) => {
|
|
519
|
+
// 构建完整URL
|
|
520
|
+
let fullUrl = options.url;
|
|
521
|
+
if (options.params && Object.keys(options.params).length > 0) {
|
|
522
|
+
const queryString = Object.entries(options.params)
|
|
523
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
|
524
|
+
.join('&');
|
|
525
|
+
fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
|
|
526
|
+
}
|
|
527
|
+
uni.navigateTo({
|
|
528
|
+
url: fullUrl,
|
|
529
|
+
animationType: options.animationType,
|
|
530
|
+
animationDuration: options.animationDuration,
|
|
531
|
+
events: options.events,
|
|
532
|
+
success: () => {
|
|
533
|
+
monitor.end('navigateTo');
|
|
534
|
+
navResolve(true);
|
|
535
|
+
},
|
|
536
|
+
fail: (error) => {
|
|
537
|
+
console.error('页面跳转失败:', error);
|
|
538
|
+
monitor.end('navigateTo');
|
|
539
|
+
navResolve(false);
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
}), 'navigation', 'NAVIGATE_TO_ERROR');
|
|
543
|
+
resolve(success ?? false);
|
|
544
|
+
});
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
/**
|
|
548
|
+
* 重定向到页面
|
|
549
|
+
*/
|
|
550
|
+
async redirectTo(options) {
|
|
551
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
552
|
+
monitor.start('redirectTo', 'navigation', { url: options.url });
|
|
553
|
+
return new Promise((resolve) => {
|
|
554
|
+
this.addToQueue(async () => {
|
|
555
|
+
const success = await safeAsync(() => new Promise((navResolve) => {
|
|
556
|
+
let fullUrl = options.url;
|
|
557
|
+
if (options.params && Object.keys(options.params).length > 0) {
|
|
558
|
+
const queryString = Object.entries(options.params)
|
|
559
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
|
560
|
+
.join('&');
|
|
561
|
+
fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
|
|
562
|
+
}
|
|
563
|
+
uni.redirectTo({
|
|
564
|
+
url: fullUrl,
|
|
565
|
+
success: () => {
|
|
566
|
+
monitor.end('redirectTo');
|
|
567
|
+
navResolve(true);
|
|
568
|
+
},
|
|
569
|
+
fail: (error) => {
|
|
570
|
+
console.error('页面重定向失败:', error);
|
|
571
|
+
monitor.end('redirectTo');
|
|
572
|
+
navResolve(false);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
}), 'navigation', 'REDIRECT_TO_ERROR');
|
|
576
|
+
resolve(success ?? false);
|
|
577
|
+
});
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* 返回上一页(优化版本)
|
|
582
|
+
*/
|
|
583
|
+
async navigateBack(params = '', options = {}) {
|
|
584
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
585
|
+
monitor.start('navigateBack', 'navigation');
|
|
586
|
+
const finalOptions = {
|
|
587
|
+
delta: 1,
|
|
588
|
+
timeout: 5000,
|
|
589
|
+
enableDebounce: true,
|
|
590
|
+
debounceWait: 300,
|
|
591
|
+
...options
|
|
592
|
+
};
|
|
593
|
+
return new Promise((resolve) => {
|
|
594
|
+
this.addToQueue(async () => {
|
|
595
|
+
const success = await safeAsync(() => new Promise((navResolve) => {
|
|
596
|
+
// 检查页面栈
|
|
597
|
+
const pages = getCurrentPages();
|
|
598
|
+
if (pages.length <= finalOptions.delta) {
|
|
599
|
+
console.warn(`无法返回${finalOptions.delta}页,当前页面栈深度不足`);
|
|
600
|
+
navResolve(false);
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
// 设置超时
|
|
604
|
+
const timeoutId = setTimeout(() => {
|
|
605
|
+
console.warn('导航返回超时');
|
|
606
|
+
navResolve(false);
|
|
607
|
+
}, finalOptions.timeout);
|
|
608
|
+
uni.navigateBack({
|
|
609
|
+
delta: finalOptions.delta,
|
|
610
|
+
success: () => {
|
|
611
|
+
clearTimeout(timeoutId);
|
|
612
|
+
// 延迟执行,确保页面切换完成
|
|
613
|
+
setTimeout(() => {
|
|
614
|
+
try {
|
|
615
|
+
const currentPages = getCurrentPages();
|
|
616
|
+
if (currentPages.length > 0) {
|
|
617
|
+
const targetPage = currentPages[currentPages.length - 1];
|
|
618
|
+
// 检查目标页面是否有init方法
|
|
619
|
+
if (targetPage.$vm && typeof targetPage.$vm.init === 'function') {
|
|
620
|
+
targetPage.$vm.init(params);
|
|
621
|
+
}
|
|
622
|
+
else if (targetPage.route) {
|
|
623
|
+
console.info(`页面 ${targetPage.route} 没有init方法`);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
monitor.end('navigateBack');
|
|
627
|
+
navResolve(true);
|
|
628
|
+
}
|
|
629
|
+
catch (error) {
|
|
630
|
+
console.error('页面回调执行失败:', error);
|
|
631
|
+
monitor.end('navigateBack');
|
|
632
|
+
navResolve(true); // 导航成功,但回调失败
|
|
633
|
+
}
|
|
634
|
+
}, 100);
|
|
635
|
+
},
|
|
636
|
+
fail: (error) => {
|
|
637
|
+
clearTimeout(timeoutId);
|
|
638
|
+
console.error('导航返回失败:', error);
|
|
639
|
+
monitor.end('navigateBack');
|
|
640
|
+
navResolve(false);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
}), 'navigation', 'NAVIGATE_BACK_ERROR');
|
|
644
|
+
resolve(success ?? false);
|
|
645
|
+
});
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
/**
|
|
649
|
+
* 切换到Tab页面
|
|
650
|
+
*/
|
|
651
|
+
async switchTab(url) {
|
|
652
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
653
|
+
monitor.start('switchTab', 'navigation', { url });
|
|
654
|
+
return new Promise((resolve) => {
|
|
655
|
+
this.addToQueue(async () => {
|
|
656
|
+
const success = await safeAsync(() => new Promise((navResolve) => {
|
|
657
|
+
uni.switchTab({
|
|
658
|
+
url,
|
|
659
|
+
success: () => {
|
|
660
|
+
monitor.end('switchTab');
|
|
661
|
+
navResolve(true);
|
|
662
|
+
},
|
|
663
|
+
fail: (error) => {
|
|
664
|
+
console.error('Tab切换失败:', error);
|
|
665
|
+
monitor.end('switchTab');
|
|
666
|
+
navResolve(false);
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
}), 'navigation', 'SWITCH_TAB_ERROR');
|
|
670
|
+
resolve(success ?? false);
|
|
671
|
+
});
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
/**
|
|
675
|
+
* 重新启动到指定页面
|
|
676
|
+
*/
|
|
677
|
+
async reLaunch(options) {
|
|
678
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
679
|
+
monitor.start('reLaunch', 'navigation', { url: options.url });
|
|
680
|
+
return new Promise((resolve) => {
|
|
681
|
+
this.addToQueue(async () => {
|
|
682
|
+
const success = await safeAsync(() => new Promise((navResolve) => {
|
|
683
|
+
let fullUrl = options.url;
|
|
684
|
+
if (options.params && Object.keys(options.params).length > 0) {
|
|
685
|
+
const queryString = Object.entries(options.params)
|
|
686
|
+
.map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`)
|
|
687
|
+
.join('&');
|
|
688
|
+
fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString;
|
|
689
|
+
}
|
|
690
|
+
uni.reLaunch({
|
|
691
|
+
url: fullUrl,
|
|
692
|
+
success: () => {
|
|
693
|
+
monitor.end('reLaunch');
|
|
694
|
+
navResolve(true);
|
|
695
|
+
},
|
|
696
|
+
fail: (error) => {
|
|
697
|
+
console.error('重新启动失败:', error);
|
|
698
|
+
monitor.end('reLaunch');
|
|
699
|
+
navResolve(false);
|
|
700
|
+
}
|
|
701
|
+
});
|
|
702
|
+
}), 'navigation', 'RELAUNCH_ERROR');
|
|
703
|
+
resolve(success ?? false);
|
|
704
|
+
});
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* 获取当前页面信息
|
|
709
|
+
*/
|
|
710
|
+
getCurrentPageInfo() {
|
|
711
|
+
return safeSync(() => {
|
|
35
712
|
const pages = getCurrentPages();
|
|
36
|
-
if (pages.length
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
713
|
+
if (pages.length === 0) {
|
|
714
|
+
return null;
|
|
715
|
+
}
|
|
716
|
+
const currentPage = pages[pages.length - 1];
|
|
717
|
+
return {
|
|
718
|
+
route: currentPage.route || '',
|
|
719
|
+
options: currentPage.options || {}
|
|
720
|
+
};
|
|
721
|
+
}, 'navigation', 'GET_CURRENT_PAGE_ERROR', null);
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* 获取页面栈信息
|
|
725
|
+
*/
|
|
726
|
+
getPageStack() {
|
|
727
|
+
return safeSync(() => {
|
|
728
|
+
const pages = getCurrentPages();
|
|
729
|
+
return pages.map(page => ({
|
|
730
|
+
route: page.route || '',
|
|
731
|
+
options: page.options || {}
|
|
732
|
+
}));
|
|
733
|
+
}, 'navigation', 'GET_PAGE_STACK_ERROR', []) ?? [];
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* 清空导航队列
|
|
737
|
+
*/
|
|
738
|
+
clearQueue() {
|
|
739
|
+
this.navigationQueue = [];
|
|
740
|
+
this.isNavigating = false;
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
// 获取导航管理器实例
|
|
744
|
+
const navigationManager = NavigationManager.getInstance();
|
|
745
|
+
/**
|
|
746
|
+
* 返回上一页并支持传递参数(优化版本)
|
|
747
|
+
* @param params 返回上一页时传入的参数
|
|
748
|
+
* @param options 导航选项
|
|
749
|
+
* @returns Promise<boolean> 是否成功
|
|
750
|
+
*/
|
|
751
|
+
const useBack = async (params = '', options = {}) => {
|
|
752
|
+
return await navigationManager.navigateBack(params, options);
|
|
753
|
+
};
|
|
754
|
+
/**
|
|
755
|
+
* 防抖版本的返回上一页
|
|
756
|
+
*/
|
|
757
|
+
const useBackDebounced = debounce(useBack, 300);
|
|
758
|
+
/**
|
|
759
|
+
* 跳转到指定页面
|
|
760
|
+
* @param options 导航选项
|
|
761
|
+
* @returns Promise<boolean> 是否成功
|
|
762
|
+
*/
|
|
763
|
+
const navigateTo = async (options) => {
|
|
764
|
+
return await navigationManager.navigateTo(options);
|
|
765
|
+
};
|
|
766
|
+
/**
|
|
767
|
+
* 重定向到指定页面
|
|
768
|
+
* @param options 导航选项
|
|
769
|
+
* @returns Promise<boolean> 是否成功
|
|
770
|
+
*/
|
|
771
|
+
const redirectTo = async (options) => {
|
|
772
|
+
return await navigationManager.redirectTo(options);
|
|
773
|
+
};
|
|
774
|
+
/**
|
|
775
|
+
* 切换到Tab页面
|
|
776
|
+
* @param url Tab页面路径
|
|
777
|
+
* @returns Promise<boolean> 是否成功
|
|
778
|
+
*/
|
|
779
|
+
const switchTab = async (url) => {
|
|
780
|
+
return await navigationManager.switchTab(url);
|
|
781
|
+
};
|
|
782
|
+
/**
|
|
783
|
+
* 重新启动到指定页面
|
|
784
|
+
* @param options 导航选项
|
|
785
|
+
* @returns Promise<boolean> 是否成功
|
|
786
|
+
*/
|
|
787
|
+
const reLaunch = async (options) => {
|
|
788
|
+
return await navigationManager.reLaunch(options);
|
|
789
|
+
};
|
|
790
|
+
/**
|
|
791
|
+
* 获取当前页面信息
|
|
792
|
+
* @returns 当前页面信息
|
|
793
|
+
*/
|
|
794
|
+
const getCurrentPageInfo = () => {
|
|
795
|
+
return navigationManager.getCurrentPageInfo();
|
|
796
|
+
};
|
|
797
|
+
/**
|
|
798
|
+
* 获取页面栈信息
|
|
799
|
+
* @returns 页面栈数组
|
|
800
|
+
*/
|
|
801
|
+
const getPageStack = () => {
|
|
802
|
+
return navigationManager.getPageStack();
|
|
803
|
+
};
|
|
804
|
+
/**
|
|
805
|
+
* 清空导航队列
|
|
806
|
+
*/
|
|
807
|
+
const clearNavigationQueue = () => {
|
|
808
|
+
navigationManager.clearQueue();
|
|
809
|
+
};
|
|
810
|
+
/**
|
|
811
|
+
* 安全的页面跳转(带重试机制)
|
|
812
|
+
* @param options 导航选项
|
|
813
|
+
* @param maxRetries 最大重试次数
|
|
814
|
+
* @returns Promise<boolean> 是否成功
|
|
815
|
+
*/
|
|
816
|
+
const safeNavigateTo = async (options, maxRetries = 3) => {
|
|
817
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
818
|
+
const success = await navigateTo(options);
|
|
819
|
+
if (success) {
|
|
820
|
+
return true;
|
|
821
|
+
}
|
|
822
|
+
if (i < maxRetries - 1) {
|
|
823
|
+
// 等待一段时间后重试
|
|
824
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
return false;
|
|
56
828
|
};
|
|
57
829
|
|
|
58
830
|
/**
|
|
59
831
|
* 剪贴板相关工具函数
|
|
60
832
|
*/
|
|
61
833
|
/**
|
|
62
|
-
*
|
|
834
|
+
* 默认配置
|
|
835
|
+
*/
|
|
836
|
+
const DEFAULT_CONFIG = {
|
|
837
|
+
showToast: true,
|
|
838
|
+
successMessage: '复制成功',
|
|
839
|
+
failMessage: '复制失败',
|
|
840
|
+
timeout: 5000
|
|
841
|
+
};
|
|
842
|
+
/**
|
|
843
|
+
* 跨平台文本复制功能(优化版本)
|
|
63
844
|
* @param text 要复制的文本内容
|
|
845
|
+
* @param config 配置选项
|
|
64
846
|
* @description 支持 H5、App、小程序。H5 平台优先使用现代的 Clipboard API,失败时回退到传统方法
|
|
847
|
+
* @returns Promise<boolean> 复制是否成功
|
|
65
848
|
*/
|
|
66
|
-
function copyText(text) {
|
|
849
|
+
async function copyText(text, config = {}) {
|
|
850
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
851
|
+
monitor.start('copyText', 'clipboard', { textLength: text.length });
|
|
852
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
853
|
+
// 输入验证
|
|
854
|
+
if (!text || typeof text !== 'string') {
|
|
855
|
+
if (finalConfig.showToast) {
|
|
856
|
+
useToast('复制内容不能为空', false, 'error');
|
|
857
|
+
}
|
|
858
|
+
monitor.end('copyText');
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
// 文本长度限制检查
|
|
862
|
+
if (text.length > 10000) {
|
|
863
|
+
if (finalConfig.showToast) {
|
|
864
|
+
useToast('复制内容过长', false, 'error');
|
|
865
|
+
}
|
|
866
|
+
monitor.end('copyText');
|
|
867
|
+
return false;
|
|
868
|
+
}
|
|
869
|
+
let success = false;
|
|
67
870
|
// #ifndef H5
|
|
68
871
|
// 条件编译:非 H5 平台(如 App、小程序)执行此代码块
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
872
|
+
success = await safeAsync(() => new Promise((resolve) => {
|
|
873
|
+
uni.setClipboardData({
|
|
874
|
+
data: text,
|
|
875
|
+
success: () => {
|
|
876
|
+
if (finalConfig.showToast) {
|
|
877
|
+
useToast(finalConfig.successMessage);
|
|
878
|
+
}
|
|
879
|
+
resolve(true);
|
|
880
|
+
},
|
|
881
|
+
fail: () => {
|
|
882
|
+
if (finalConfig.showToast) {
|
|
883
|
+
useToast(finalConfig.failMessage);
|
|
884
|
+
}
|
|
885
|
+
resolve(false);
|
|
886
|
+
},
|
|
887
|
+
});
|
|
888
|
+
}), 'clipboard', 'COPY_TEXT_NATIVE_ERROR') ?? false;
|
|
78
889
|
// #endif
|
|
79
890
|
// #ifdef H5
|
|
80
891
|
// 条件编译:仅 H5 平台执行此代码块
|
|
892
|
+
success = await copyTextH5(text, finalConfig);
|
|
893
|
+
// #endif
|
|
894
|
+
monitor.end('copyText');
|
|
895
|
+
return success;
|
|
896
|
+
}
|
|
897
|
+
// #ifdef H5
|
|
898
|
+
/**
|
|
899
|
+
* H5平台专用复制函数
|
|
900
|
+
* @param text 要复制的文本
|
|
901
|
+
* @param config 配置
|
|
902
|
+
* @returns 是否成功
|
|
903
|
+
*/
|
|
904
|
+
async function copyTextH5(text, config) {
|
|
81
905
|
// 优先使用现代的 Clipboard API
|
|
82
906
|
if (navigator.clipboard && window.isSecureContext) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
907
|
+
const success = await safeAsync(async () => {
|
|
908
|
+
await Promise.race([
|
|
909
|
+
navigator.clipboard.writeText(text),
|
|
910
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Clipboard API timeout')), config.timeout))
|
|
911
|
+
]);
|
|
912
|
+
return true;
|
|
913
|
+
}, 'clipboard', 'CLIPBOARD_API_ERROR');
|
|
914
|
+
if (success) {
|
|
915
|
+
if (config.showToast) {
|
|
916
|
+
useToast(config.successMessage);
|
|
917
|
+
}
|
|
918
|
+
return true;
|
|
919
|
+
}
|
|
89
920
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
921
|
+
// 回退到传统方法
|
|
922
|
+
return fallbackCopyTextToClipboard(text, config);
|
|
923
|
+
}
|
|
924
|
+
/**
|
|
925
|
+
* 传统复制方法的辅助函数(内存优化版本)
|
|
926
|
+
* @param text 要复制的文本内容
|
|
927
|
+
* @param config 配置
|
|
928
|
+
* @description 使用 document.execCommand 方式进行文本复制,优化内存使用
|
|
929
|
+
*/
|
|
930
|
+
function fallbackCopyTextToClipboard(text, config) {
|
|
931
|
+
return safeSync(() => {
|
|
932
|
+
// 创建临时元素
|
|
933
|
+
const textarea = document.createElement('textarea');
|
|
934
|
+
// 设置样式,确保不影响页面布局且不可见
|
|
935
|
+
Object.assign(textarea.style, {
|
|
936
|
+
position: 'fixed',
|
|
937
|
+
top: '-9999px',
|
|
938
|
+
left: '-9999px',
|
|
939
|
+
width: '1px',
|
|
940
|
+
height: '1px',
|
|
941
|
+
padding: '0',
|
|
942
|
+
border: 'none',
|
|
943
|
+
outline: 'none',
|
|
944
|
+
boxShadow: 'none',
|
|
945
|
+
background: 'transparent',
|
|
946
|
+
fontSize: '16px', // 防止iOS缩放
|
|
947
|
+
});
|
|
948
|
+
textarea.value = text;
|
|
949
|
+
textarea.setAttribute('readonly', '');
|
|
950
|
+
textarea.setAttribute('contenteditable', 'true');
|
|
951
|
+
// 添加到DOM
|
|
952
|
+
document.body.appendChild(textarea);
|
|
953
|
+
// 选择文本
|
|
954
|
+
textarea.focus();
|
|
955
|
+
textarea.select();
|
|
956
|
+
textarea.setSelectionRange(0, text.length);
|
|
957
|
+
// 执行复制
|
|
958
|
+
const successful = document.execCommand('copy');
|
|
959
|
+
// 立即清理DOM元素
|
|
960
|
+
document.body.removeChild(textarea);
|
|
961
|
+
if (successful) {
|
|
962
|
+
if (config.showToast) {
|
|
963
|
+
useToast(config.successMessage);
|
|
964
|
+
}
|
|
965
|
+
return true;
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
if (config.showToast) {
|
|
969
|
+
useToast(config.failMessage);
|
|
970
|
+
}
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
}, 'clipboard', 'FALLBACK_COPY_ERROR', false) ?? false;
|
|
974
|
+
}
|
|
975
|
+
// #endif
|
|
976
|
+
/**
|
|
977
|
+
* 读取剪贴板内容(仅H5平台支持)
|
|
978
|
+
* @param config 配置选项
|
|
979
|
+
* @returns Promise<string | null> 剪贴板内容,失败时返回null
|
|
980
|
+
*/
|
|
981
|
+
async function readClipboard(config = {}) {
|
|
982
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
983
|
+
monitor.start('readClipboard', 'clipboard');
|
|
984
|
+
const finalConfig = { ...DEFAULT_CONFIG, ...config };
|
|
985
|
+
// #ifdef H5
|
|
986
|
+
if (navigator.clipboard && window.isSecureContext) {
|
|
987
|
+
const result = await safeAsync(async () => {
|
|
988
|
+
return await Promise.race([
|
|
989
|
+
navigator.clipboard.readText(),
|
|
990
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('Read clipboard timeout')), finalConfig.timeout))
|
|
991
|
+
]);
|
|
992
|
+
}, 'clipboard', 'READ_CLIPBOARD_ERROR');
|
|
993
|
+
monitor.end('readClipboard');
|
|
994
|
+
return result;
|
|
93
995
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
*/
|
|
99
|
-
function fallbackCopyTextToClipboard(text) {
|
|
100
|
-
const createInput = document.createElement('textarea');
|
|
101
|
-
createInput.value = text; // 设置 textarea 的值为要复制的文本
|
|
102
|
-
document.body.appendChild(createInput);
|
|
103
|
-
createInput.select();
|
|
104
|
-
document.execCommand('Copy');
|
|
105
|
-
createInput.className = 'createInput';
|
|
106
|
-
createInput.style.display = 'none';
|
|
107
|
-
useToast('复制成功');
|
|
108
|
-
createInput.remove(); // 复制完成后移除临时创建的 textarea 元素
|
|
996
|
+
// #endif
|
|
997
|
+
// 非H5平台或不支持的情况
|
|
998
|
+
if (finalConfig.showToast) {
|
|
999
|
+
useToast('当前平台不支持读取剪贴板', false, 'error');
|
|
109
1000
|
}
|
|
1001
|
+
monitor.end('readClipboard');
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
/**
|
|
1005
|
+
* 检查剪贴板API是否可用
|
|
1006
|
+
* @returns boolean 是否支持剪贴板操作
|
|
1007
|
+
*/
|
|
1008
|
+
function isClipboardSupported() {
|
|
1009
|
+
// #ifdef H5
|
|
1010
|
+
return !!(navigator.clipboard && window.isSecureContext);
|
|
110
1011
|
// #endif
|
|
111
1012
|
}
|
|
1013
|
+
/**
|
|
1014
|
+
* 清空剪贴板(仅H5平台支持)
|
|
1015
|
+
* @param config 配置选项
|
|
1016
|
+
* @returns Promise<boolean> 是否成功
|
|
1017
|
+
*/
|
|
1018
|
+
async function clearClipboard(config = {}) {
|
|
1019
|
+
return await copyText('', config);
|
|
1020
|
+
}
|
|
112
1021
|
|
|
113
1022
|
/**
|
|
114
|
-
*
|
|
1023
|
+
* 系统信息相关工具函数(优化版本)
|
|
1024
|
+
*/
|
|
1025
|
+
/**
|
|
1026
|
+
* 系统信息缓存
|
|
115
1027
|
*/
|
|
1028
|
+
class SystemInfoCache {
|
|
1029
|
+
static instance;
|
|
1030
|
+
cache = new Map();
|
|
1031
|
+
defaultTTL = 30000; // 30秒缓存
|
|
1032
|
+
static getInstance() {
|
|
1033
|
+
if (!SystemInfoCache.instance) {
|
|
1034
|
+
SystemInfoCache.instance = new SystemInfoCache();
|
|
1035
|
+
}
|
|
1036
|
+
return SystemInfoCache.instance;
|
|
1037
|
+
}
|
|
1038
|
+
set(key, data, ttl = this.defaultTTL) {
|
|
1039
|
+
this.cache.set(key, {
|
|
1040
|
+
data,
|
|
1041
|
+
timestamp: Date.now(),
|
|
1042
|
+
ttl
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
get(key) {
|
|
1046
|
+
const item = this.cache.get(key);
|
|
1047
|
+
if (!item)
|
|
1048
|
+
return null;
|
|
1049
|
+
if (Date.now() - item.timestamp > item.ttl) {
|
|
1050
|
+
this.cache.delete(key);
|
|
1051
|
+
return null;
|
|
1052
|
+
}
|
|
1053
|
+
return item.data;
|
|
1054
|
+
}
|
|
1055
|
+
clear() {
|
|
1056
|
+
this.cache.clear();
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
const systemCache = SystemInfoCache.getInstance();
|
|
116
1060
|
/**
|
|
117
|
-
*
|
|
1061
|
+
* 获取窗口信息(优化版本)
|
|
1062
|
+
* @param useCache 是否使用缓存,默认true
|
|
118
1063
|
* @returns 窗口信息对象,包含窗口尺寸、像素比等信息
|
|
119
|
-
* @description 调用 uni.getWindowInfo()
|
|
1064
|
+
* @description 调用 uni.getWindowInfo() 获取当前设备的窗口相关信息,支持缓存
|
|
120
1065
|
*/
|
|
121
|
-
const useWindowInfo = () => {
|
|
122
|
-
|
|
123
|
-
|
|
1066
|
+
const useWindowInfo = (useCache = true) => {
|
|
1067
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
1068
|
+
monitor.start('getWindowInfo', 'system');
|
|
1069
|
+
const cacheKey = 'windowInfo';
|
|
1070
|
+
if (useCache) {
|
|
1071
|
+
const cached = systemCache.get(cacheKey);
|
|
1072
|
+
if (cached) {
|
|
1073
|
+
monitor.end('getWindowInfo');
|
|
1074
|
+
return cached;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
const result = safeSync(() => {
|
|
1078
|
+
const windowInfo = uni.getWindowInfo();
|
|
1079
|
+
if (useCache) {
|
|
1080
|
+
systemCache.set(cacheKey, windowInfo);
|
|
1081
|
+
}
|
|
1082
|
+
return windowInfo;
|
|
1083
|
+
}, 'system', 'GET_WINDOW_INFO_ERROR', null);
|
|
1084
|
+
monitor.end('getWindowInfo');
|
|
1085
|
+
return result;
|
|
124
1086
|
};
|
|
125
1087
|
/**
|
|
126
1088
|
* 获取当前运行平台
|
|
@@ -321,150 +1283,400 @@ const getTopNavBarHeight = () => {
|
|
|
321
1283
|
};
|
|
322
1284
|
|
|
323
1285
|
/**
|
|
324
|
-
*
|
|
1286
|
+
* 本地存储相关工具函数(优化版本)
|
|
325
1287
|
*/
|
|
326
1288
|
/**
|
|
327
|
-
*
|
|
328
|
-
* @param key 缓存键
|
|
329
|
-
* @param obj 要缓存的值,可以是任何类型
|
|
330
|
-
* @description 如果值是字符串,直接存储;否则,尝试将其 JSON 序列化后存储。
|
|
331
|
-
* 添加了错误处理,以防 JSON 序列化失败。
|
|
1289
|
+
* 存储管理器类
|
|
332
1290
|
*/
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
1291
|
+
class StorageManager {
|
|
1292
|
+
static instance;
|
|
1293
|
+
cache = new Map(); // 内存缓存
|
|
1294
|
+
maxCacheSize = 100; // 最大缓存数量
|
|
1295
|
+
static getInstance() {
|
|
1296
|
+
if (!StorageManager.instance) {
|
|
1297
|
+
StorageManager.instance = new StorageManager();
|
|
1298
|
+
}
|
|
1299
|
+
return StorageManager.instance;
|
|
338
1300
|
}
|
|
339
|
-
|
|
340
|
-
|
|
1301
|
+
/**
|
|
1302
|
+
* 简单压缩字符串
|
|
1303
|
+
*/
|
|
1304
|
+
compress(str) {
|
|
1305
|
+
// 简单的重复字符压缩
|
|
1306
|
+
return str.replace(/(.)\1{2,}/g, (match, char) => {
|
|
1307
|
+
return `${char}*${match.length}`;
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
/**
|
|
1311
|
+
* 解压缩字符串
|
|
1312
|
+
*/
|
|
1313
|
+
decompress(str) {
|
|
1314
|
+
return str.replace(/(.)\*(\d+)/g, (match, char, count) => {
|
|
1315
|
+
return char.repeat(parseInt(count));
|
|
1316
|
+
});
|
|
1317
|
+
}
|
|
1318
|
+
/**
|
|
1319
|
+
* 简单加密
|
|
1320
|
+
*/
|
|
1321
|
+
encrypt(str) {
|
|
1322
|
+
return btoa(encodeURIComponent(str));
|
|
1323
|
+
}
|
|
1324
|
+
/**
|
|
1325
|
+
* 简单解密
|
|
1326
|
+
*/
|
|
1327
|
+
decrypt(str) {
|
|
341
1328
|
try {
|
|
342
|
-
|
|
343
|
-
uni.setStorageSync(key, JSON.stringify(obj));
|
|
1329
|
+
return decodeURIComponent(atob(str));
|
|
344
1330
|
}
|
|
345
|
-
catch
|
|
346
|
-
|
|
347
|
-
console.error('Failed to stringify object for localStorage:', e);
|
|
1331
|
+
catch {
|
|
1332
|
+
return str; // 解密失败返回原字符串
|
|
348
1333
|
}
|
|
349
1334
|
}
|
|
1335
|
+
/**
|
|
1336
|
+
* 检查是否过期
|
|
1337
|
+
*/
|
|
1338
|
+
isExpired(item) {
|
|
1339
|
+
if (!item.ttl)
|
|
1340
|
+
return false;
|
|
1341
|
+
return Date.now() - item.timestamp > item.ttl;
|
|
1342
|
+
}
|
|
1343
|
+
/**
|
|
1344
|
+
* 设置存储
|
|
1345
|
+
*/
|
|
1346
|
+
set(key, value, options = {}) {
|
|
1347
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
1348
|
+
monitor.start(`setStorage_${key}`, 'localStorage');
|
|
1349
|
+
const result = safeSync(() => {
|
|
1350
|
+
// 输入验证
|
|
1351
|
+
if (!key || typeof key !== 'string') {
|
|
1352
|
+
throw new Error('存储键不能为空');
|
|
1353
|
+
}
|
|
1354
|
+
const item = {
|
|
1355
|
+
value,
|
|
1356
|
+
timestamp: Date.now(),
|
|
1357
|
+
ttl: options.ttl,
|
|
1358
|
+
compressed: options.compress,
|
|
1359
|
+
encrypted: options.encrypt
|
|
1360
|
+
};
|
|
1361
|
+
let serialized = JSON.stringify(item);
|
|
1362
|
+
// 压缩处理
|
|
1363
|
+
if (options.compress) {
|
|
1364
|
+
serialized = this.compress(serialized);
|
|
1365
|
+
}
|
|
1366
|
+
// 加密处理
|
|
1367
|
+
if (options.encrypt) {
|
|
1368
|
+
serialized = this.encrypt(serialized);
|
|
1369
|
+
}
|
|
1370
|
+
// 存储大小检查
|
|
1371
|
+
if (serialized.length > 1024 * 1024) { // 1MB限制
|
|
1372
|
+
throw new Error('存储数据过大');
|
|
1373
|
+
}
|
|
1374
|
+
uni.setStorageSync(key, serialized);
|
|
1375
|
+
// 更新内存缓存
|
|
1376
|
+
if (this.cache.size >= this.maxCacheSize) {
|
|
1377
|
+
// 删除最旧的缓存项
|
|
1378
|
+
const firstKey = this.cache.keys().next().value;
|
|
1379
|
+
if (firstKey) {
|
|
1380
|
+
this.cache.delete(firstKey);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
this.cache.set(key, item);
|
|
1384
|
+
return true;
|
|
1385
|
+
}, 'localStorage', 'SET_STORAGE_ERROR', false);
|
|
1386
|
+
monitor.end(`setStorage_${key}`);
|
|
1387
|
+
return result ?? false;
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* 获取存储
|
|
1391
|
+
*/
|
|
1392
|
+
get(key, defaultValue) {
|
|
1393
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
1394
|
+
monitor.start(`getStorage_${key}`, 'localStorage');
|
|
1395
|
+
const result = safeSync(() => {
|
|
1396
|
+
// 输入验证
|
|
1397
|
+
if (!key || typeof key !== 'string') {
|
|
1398
|
+
return defaultValue;
|
|
1399
|
+
}
|
|
1400
|
+
// 检查内存缓存
|
|
1401
|
+
if (this.cache.has(key)) {
|
|
1402
|
+
const cachedItem = this.cache.get(key);
|
|
1403
|
+
if (!this.isExpired(cachedItem)) {
|
|
1404
|
+
return cachedItem.value;
|
|
1405
|
+
}
|
|
1406
|
+
else {
|
|
1407
|
+
// 过期则删除
|
|
1408
|
+
this.cache.delete(key);
|
|
1409
|
+
this.remove(key);
|
|
1410
|
+
return defaultValue;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
// 从存储中获取
|
|
1414
|
+
let serialized = uni.getStorageSync(key);
|
|
1415
|
+
if (!serialized) {
|
|
1416
|
+
return defaultValue;
|
|
1417
|
+
}
|
|
1418
|
+
// 确保serialized是字符串类型
|
|
1419
|
+
if (typeof serialized !== 'string') {
|
|
1420
|
+
return defaultValue;
|
|
1421
|
+
}
|
|
1422
|
+
let serializedString = serialized;
|
|
1423
|
+
// 解密处理
|
|
1424
|
+
if (serializedString && serializedString.includes('=')) {
|
|
1425
|
+
try {
|
|
1426
|
+
const decrypted = this.decrypt(serializedString);
|
|
1427
|
+
if (decrypted) {
|
|
1428
|
+
serializedString = decrypted;
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
catch {
|
|
1432
|
+
// 解密失败,可能不是加密数据
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
// 解压缩处理
|
|
1436
|
+
if (serializedString && serializedString.includes('*')) {
|
|
1437
|
+
try {
|
|
1438
|
+
serializedString = this.decompress(serializedString);
|
|
1439
|
+
}
|
|
1440
|
+
catch {
|
|
1441
|
+
// 解压缩失败,可能不是压缩数据
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1444
|
+
// 尝试解析为StorageItem
|
|
1445
|
+
let item;
|
|
1446
|
+
try {
|
|
1447
|
+
const parsed = JSON.parse(serializedString);
|
|
1448
|
+
if (parsed && typeof parsed === 'object' && 'value' in parsed && 'timestamp' in parsed) {
|
|
1449
|
+
item = parsed;
|
|
1450
|
+
}
|
|
1451
|
+
else {
|
|
1452
|
+
// 兼容旧格式
|
|
1453
|
+
item = {
|
|
1454
|
+
value: parsed,
|
|
1455
|
+
timestamp: Date.now()
|
|
1456
|
+
};
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
catch {
|
|
1460
|
+
// JSON解析失败,直接返回原始值
|
|
1461
|
+
return serializedString;
|
|
1462
|
+
}
|
|
1463
|
+
// 检查是否过期
|
|
1464
|
+
if (this.isExpired(item)) {
|
|
1465
|
+
this.remove(key);
|
|
1466
|
+
return defaultValue;
|
|
1467
|
+
}
|
|
1468
|
+
// 更新内存缓存
|
|
1469
|
+
this.cache.set(key, item);
|
|
1470
|
+
return item.value;
|
|
1471
|
+
}, 'localStorage', 'GET_STORAGE_ERROR', defaultValue);
|
|
1472
|
+
monitor.end(`getStorage_${key}`);
|
|
1473
|
+
return result ?? defaultValue;
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* 删除存储
|
|
1477
|
+
*/
|
|
1478
|
+
remove(key) {
|
|
1479
|
+
return safeSync(() => {
|
|
1480
|
+
if (!key || typeof key !== 'string') {
|
|
1481
|
+
return false;
|
|
1482
|
+
}
|
|
1483
|
+
uni.removeStorageSync(key);
|
|
1484
|
+
this.cache.delete(key);
|
|
1485
|
+
return true;
|
|
1486
|
+
}, 'localStorage', 'REMOVE_STORAGE_ERROR', false) ?? false;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* 清空所有存储
|
|
1490
|
+
*/
|
|
1491
|
+
clear() {
|
|
1492
|
+
return safeSync(() => {
|
|
1493
|
+
uni.clearStorageSync();
|
|
1494
|
+
this.cache.clear();
|
|
1495
|
+
return true;
|
|
1496
|
+
}, 'localStorage', 'CLEAR_STORAGE_ERROR', false) ?? false;
|
|
1497
|
+
}
|
|
1498
|
+
/**
|
|
1499
|
+
* 获取存储信息
|
|
1500
|
+
*/
|
|
1501
|
+
getInfo() {
|
|
1502
|
+
return safeSync(() => {
|
|
1503
|
+
const info = uni.getStorageInfoSync();
|
|
1504
|
+
return {
|
|
1505
|
+
keys: info.keys || [],
|
|
1506
|
+
currentSize: info.currentSize || 0,
|
|
1507
|
+
limitSize: info.limitSize || 0
|
|
1508
|
+
};
|
|
1509
|
+
}, 'localStorage', 'GET_STORAGE_INFO_ERROR', { keys: [], currentSize: 0, limitSize: 0 }) ?? { keys: [], currentSize: 0, limitSize: 0 };
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* 清理过期数据
|
|
1513
|
+
*/
|
|
1514
|
+
cleanExpired() {
|
|
1515
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
1516
|
+
monitor.start('cleanExpiredStorage', 'localStorage');
|
|
1517
|
+
const result = safeSync(() => {
|
|
1518
|
+
const info = this.getInfo();
|
|
1519
|
+
let cleanedCount = 0;
|
|
1520
|
+
info.keys.forEach(key => {
|
|
1521
|
+
const item = this.get(key);
|
|
1522
|
+
// get方法内部会自动清理过期数据
|
|
1523
|
+
if (item === undefined) {
|
|
1524
|
+
cleanedCount++;
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1527
|
+
return cleanedCount;
|
|
1528
|
+
}, 'localStorage', 'CLEAN_EXPIRED_ERROR', 0);
|
|
1529
|
+
monitor.end('cleanExpiredStorage');
|
|
1530
|
+
return result ?? 0;
|
|
1531
|
+
}
|
|
350
1532
|
}
|
|
1533
|
+
// 获取存储管理器实例
|
|
1534
|
+
const storageManager = StorageManager.getInstance();
|
|
351
1535
|
/**
|
|
352
|
-
*
|
|
1536
|
+
* 同步设置本地缓存(优化版本)
|
|
353
1537
|
* @param key 缓存键
|
|
354
|
-
* @
|
|
355
|
-
* @
|
|
356
|
-
*
|
|
357
|
-
*/
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
// 解析失败,返回原始值
|
|
370
|
-
return val;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
// 如果值不是非空字符串,直接返回原始值
|
|
374
|
-
return val;
|
|
1538
|
+
* @param obj 要缓存的值,可以是任何类型
|
|
1539
|
+
* @param options 存储选项
|
|
1540
|
+
* @returns 是否设置成功
|
|
1541
|
+
*/
|
|
1542
|
+
function setStorageSync(key, obj, options = {}) {
|
|
1543
|
+
return storageManager.set(key, obj, options);
|
|
1544
|
+
}
|
|
1545
|
+
/**
|
|
1546
|
+
* 同步获取本地缓存(优化版本)
|
|
1547
|
+
* @param key 缓存键
|
|
1548
|
+
* @param defaultValue 默认值
|
|
1549
|
+
* @returns 缓存的值
|
|
1550
|
+
*/
|
|
1551
|
+
function getStorageSync(key, defaultValue) {
|
|
1552
|
+
return storageManager.get(key, defaultValue);
|
|
375
1553
|
}
|
|
376
1554
|
/**
|
|
377
|
-
*
|
|
1555
|
+
* 同步清理本地缓存(优化版本)
|
|
378
1556
|
* @param key 可选的缓存键
|
|
379
|
-
* @
|
|
380
|
-
* 如果不传入 key,则清除所有本地缓存。
|
|
1557
|
+
* @returns 是否清理成功
|
|
381
1558
|
*/
|
|
382
1559
|
function clearStorageSync(key) {
|
|
383
|
-
// 检查是否传入了 key
|
|
384
1560
|
if (key) {
|
|
385
|
-
|
|
386
|
-
uni.removeStorageSync(key);
|
|
1561
|
+
return storageManager.remove(key);
|
|
387
1562
|
}
|
|
388
1563
|
else {
|
|
389
|
-
|
|
390
|
-
uni.clearStorageSync();
|
|
1564
|
+
return storageManager.clear();
|
|
391
1565
|
}
|
|
392
1566
|
}
|
|
393
|
-
|
|
394
1567
|
/**
|
|
395
|
-
*
|
|
1568
|
+
* 异步设置本地缓存
|
|
1569
|
+
* @param key 缓存键
|
|
1570
|
+
* @param obj 要缓存的值
|
|
1571
|
+
* @param options 存储选项
|
|
1572
|
+
* @returns Promise<boolean>
|
|
396
1573
|
*/
|
|
1574
|
+
async function setStorage(key, obj, options = {}) {
|
|
1575
|
+
return await safeAsync(() => new Promise((resolve) => {
|
|
1576
|
+
const success = setStorageSync(key, obj, options);
|
|
1577
|
+
resolve(success);
|
|
1578
|
+
}), 'localStorage', 'SET_STORAGE_ASYNC_ERROR') ?? false;
|
|
1579
|
+
}
|
|
397
1580
|
/**
|
|
398
|
-
*
|
|
399
|
-
* @param
|
|
400
|
-
* @param
|
|
401
|
-
* @returns
|
|
402
|
-
* @description 支持对象、数组、基本数据类型的深度拷贝,处理循环引用问题
|
|
1581
|
+
* 异步获取本地缓存
|
|
1582
|
+
* @param key 缓存键
|
|
1583
|
+
* @param defaultValue 默认值
|
|
1584
|
+
* @returns Promise<T | undefined>
|
|
403
1585
|
*/
|
|
404
|
-
function
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
if (
|
|
436
|
-
|
|
437
|
-
visited.set(sourceObj, result);
|
|
438
|
-
for (let i = 0; i < sourceObj.length; i++) {
|
|
439
|
-
result[i] = copyRecursive(undefined, sourceObj[i]);
|
|
440
|
-
}
|
|
441
|
-
return result;
|
|
442
|
-
}
|
|
443
|
-
// 处理普通对象
|
|
444
|
-
const result = {};
|
|
445
|
-
visited.set(sourceObj, result);
|
|
446
|
-
// 拷贝所有可枚举属性
|
|
447
|
-
for (const key in sourceObj) {
|
|
448
|
-
if (sourceObj.hasOwnProperty(key)) {
|
|
449
|
-
result[key] = copyRecursive(undefined, sourceObj[key]);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
return result;
|
|
453
|
-
}
|
|
454
|
-
// 如果target是对象类型,则将source的属性拷贝到target中
|
|
455
|
-
if (typeof target === 'object' && target !== null && !Array.isArray(target)) {
|
|
456
|
-
const copiedSource = copyRecursive(undefined, source);
|
|
457
|
-
// 将拷贝后的source属性合并到target中
|
|
458
|
-
for (const key in copiedSource) {
|
|
459
|
-
if (copiedSource.hasOwnProperty(key)) {
|
|
460
|
-
target[key] = copiedSource[key];
|
|
461
|
-
}
|
|
1586
|
+
async function getStorage(key, defaultValue) {
|
|
1587
|
+
return await safeAsync(() => new Promise((resolve) => {
|
|
1588
|
+
const value = getStorageSync(key, defaultValue);
|
|
1589
|
+
resolve(value);
|
|
1590
|
+
}), 'localStorage', 'GET_STORAGE_ASYNC_ERROR') ?? defaultValue;
|
|
1591
|
+
}
|
|
1592
|
+
/**
|
|
1593
|
+
* 获取存储信息
|
|
1594
|
+
* @returns 存储信息
|
|
1595
|
+
*/
|
|
1596
|
+
function getStorageInfo() {
|
|
1597
|
+
return storageManager.getInfo();
|
|
1598
|
+
}
|
|
1599
|
+
/**
|
|
1600
|
+
* 清理过期数据
|
|
1601
|
+
* @returns 清理的数据条数
|
|
1602
|
+
*/
|
|
1603
|
+
function cleanExpiredStorage() {
|
|
1604
|
+
return storageManager.cleanExpired();
|
|
1605
|
+
}
|
|
1606
|
+
/**
|
|
1607
|
+
* 批量设置存储
|
|
1608
|
+
* @param items 要设置的键值对
|
|
1609
|
+
* @param options 存储选项
|
|
1610
|
+
* @returns 成功设置的数量
|
|
1611
|
+
*/
|
|
1612
|
+
function batchSetStorage(items, options = {}) {
|
|
1613
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
1614
|
+
monitor.start('batchSetStorage', 'localStorage');
|
|
1615
|
+
let successCount = 0;
|
|
1616
|
+
Object.entries(items).forEach(([key, value]) => {
|
|
1617
|
+
if (setStorageSync(key, value, options)) {
|
|
1618
|
+
successCount++;
|
|
462
1619
|
}
|
|
463
|
-
|
|
1620
|
+
});
|
|
1621
|
+
monitor.end('batchSetStorage');
|
|
1622
|
+
return successCount;
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* 批量获取存储
|
|
1626
|
+
* @param keys 要获取的键数组
|
|
1627
|
+
* @returns 键值对对象
|
|
1628
|
+
*/
|
|
1629
|
+
function batchGetStorage(keys) {
|
|
1630
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
1631
|
+
monitor.start('batchGetStorage', 'localStorage');
|
|
1632
|
+
const result = {};
|
|
1633
|
+
keys.forEach(key => {
|
|
1634
|
+
result[key] = getStorageSync(key);
|
|
1635
|
+
});
|
|
1636
|
+
monitor.end('batchGetStorage');
|
|
1637
|
+
return result;
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
// 核心功能
|
|
1641
|
+
|
|
1642
|
+
// 版本信息
|
|
1643
|
+
const VERSION = '1.0.8';
|
|
1644
|
+
|
|
1645
|
+
// 初始化函数
|
|
1646
|
+
function initUniAppTools(config = {}) {
|
|
1647
|
+
const {
|
|
1648
|
+
enablePerformanceMonitor = false,
|
|
1649
|
+
enableErrorHandler = true,
|
|
1650
|
+
logLevel = 'warn'
|
|
1651
|
+
} = config;
|
|
1652
|
+
|
|
1653
|
+
if (enableErrorHandler) {
|
|
1654
|
+
const { ErrorHandler } = require('./core/errorHandler');
|
|
1655
|
+
const errorHandler = ErrorHandler.getInstance();
|
|
1656
|
+
|
|
1657
|
+
// 设置全局错误监听
|
|
1658
|
+
if (enablePerformanceMonitor) {
|
|
1659
|
+
errorHandler.onError((error) => {
|
|
1660
|
+
console.log(`[UniAppTools] ${error.module} - ${error.code}: ${error.message}`);
|
|
1661
|
+
});
|
|
464
1662
|
}
|
|
465
|
-
|
|
466
|
-
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
if (enablePerformanceMonitor) {
|
|
1666
|
+
const { PerformanceMonitor } = require('./core/performance');
|
|
1667
|
+
const monitor = PerformanceMonitor.getInstance();
|
|
1668
|
+
|
|
1669
|
+
// 定期输出性能报告
|
|
1670
|
+
setInterval(() => {
|
|
1671
|
+
const report = monitor.getReport();
|
|
1672
|
+
if (report.slowest.length > 0) {
|
|
1673
|
+
console.log('[UniAppTools] 性能报告:', report);
|
|
1674
|
+
}
|
|
1675
|
+
}, 60000); // 每分钟输出一次
|
|
1676
|
+
}
|
|
1677
|
+
|
|
1678
|
+
console.log(`[UniAppTools] v${VERSION} 初始化完成`);
|
|
467
1679
|
}
|
|
468
1680
|
|
|
469
|
-
export { clearStorageSync, copyText, getCurrentEnv, getMenuButtonBoundingClientRect, getNavHeight, getPlatform, getStatusBarHeight, getStorageSync, getTopNavBarHeight, onCheckForUpdate, setStorageSync, useBack, useDeepCopyByObj, useToast, useWindowInfo };
|
|
1681
|
+
export { ErrorHandler, PerformanceMonitor, UniAppToolsError, VERSION, batchGetStorage, batchSetStorage, cleanExpiredStorage, clearClipboard, clearNavigationQueue, clearStorageSync, copyText, debounce, deepClone, deepMerge, getCurrentEnv, getCurrentPageInfo, getMenuButtonBoundingClientRect, getNavHeight, getPageStack, getPlatform, getStatusBarHeight, getStorage, getStorageInfo, getStorageSync, getTopNavBarHeight, initUniAppTools, isClipboardSupported, measurePerformance, mergeObjects, navigateTo, onCheckForUpdate, reLaunch, readClipboard, redirectTo, safeAsync, safeNavigateTo, safeSync, setStorage, setStorageSync, switchTab, throttle, useBack, useBackDebounced, useDeepCopyByObj, useToast, useWindowInfo };
|
|
470
1682
|
//# sourceMappingURL=my-uniapp-tools.esm.js.map
|