@i17hush/h5-utils 1.1.0 → 1.1.2

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/dist/index.esm.js CHANGED
@@ -96,7 +96,9 @@ const browserAdapter = {
96
96
  };
97
97
  },
98
98
  async getNetworkType() {
99
- const conn = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
99
+ const conn = navigator.connection ||
100
+ navigator.mozConnection ||
101
+ navigator.webkitConnection;
100
102
  return (conn === null || conn === void 0 ? void 0 : conn.effectiveType) || 'unknown';
101
103
  },
102
104
  },
@@ -116,18 +118,140 @@ function getBrowserOS(ua) {
116
118
  return 'Unknown';
117
119
  }
118
120
 
121
+ /**
122
+ * 创建 Taro 适配器
123
+ * @param taro Taro 实例,由用户传入以避免硬依赖
124
+ */
125
+ function createTaroAdapter(taro) {
126
+ return {
127
+ storage: {
128
+ get(key) {
129
+ const value = taro.getStorageSync(key);
130
+ return value !== '' && value !== undefined && value !== null ? String(value) : null;
131
+ },
132
+ set(key, value) {
133
+ taro.setStorageSync(key, value);
134
+ },
135
+ remove(key) {
136
+ taro.removeStorageSync(key);
137
+ },
138
+ clear() {
139
+ taro.clearStorageSync();
140
+ },
141
+ },
142
+ clipboard: {
143
+ async write(text) {
144
+ await taro.setClipboardData({ data: text });
145
+ },
146
+ async read() {
147
+ const res = await taro.getClipboardData();
148
+ return res.data;
149
+ },
150
+ },
151
+ event: {
152
+ on(event, handler) {
153
+ taro.eventCenter.on(event, handler);
154
+ },
155
+ off(event, handler) {
156
+ taro.eventCenter.off(event, handler);
157
+ },
158
+ emit(event, ...args) {
159
+ taro.eventCenter.trigger(event, ...args);
160
+ },
161
+ },
162
+ dom: {
163
+ select(selector) {
164
+ const query = taro.createSelectorQuery();
165
+ const result = query.select(selector).node();
166
+ query.exec();
167
+ return result || null;
168
+ },
169
+ selectAll(selector) {
170
+ const query = taro.createSelectorQuery();
171
+ const result = query.selectAll(selector).nodes();
172
+ query.exec();
173
+ return Array.isArray(result) ? result : [];
174
+ },
175
+ },
176
+ scroll: {
177
+ scrollTo(options) {
178
+ var _a;
179
+ taro.pageScrollTo({
180
+ scrollTop: (_a = options.top) !== null && _a !== void 0 ? _a : 0,
181
+ duration: options.animated !== false ? 300 : 0,
182
+ });
183
+ },
184
+ getScrollPosition() {
185
+ // Taro 需要通过 onPageScroll 事件获取,此处返回默认值
186
+ // 实际项目中建议通过 Taro.usePageScroll hook 获取
187
+ return { scrollTop: 0, scrollLeft: 0 };
188
+ },
189
+ lockScroll() {
190
+ },
191
+ unlockScroll() {
192
+ },
193
+ },
194
+ device: {
195
+ getInfo() {
196
+ const info = taro.getSystemInfoSync();
197
+ const system = info.system || '';
198
+ const isIOS = /ios/i.test(system) || /iPhone|iPad|iPod/i.test(info.model || '');
199
+ const isAndroid = /android/i.test(system);
200
+ return {
201
+ ios: isIOS,
202
+ android: isAndroid,
203
+ mobile: isIOS || isAndroid,
204
+ weChat: true, // 在 Taro 环境中,通常是小程序
205
+ os: system.split(' ')[0] || info.platform || 'Unknown',
206
+ brand: info.brand || '',
207
+ model: info.model || '',
208
+ screenWidth: info.screenWidth,
209
+ screenHeight: info.screenHeight,
210
+ pixelRatio: info.pixelRatio,
211
+ };
212
+ },
213
+ async getNetworkType() {
214
+ const res = await taro.getNetworkType();
215
+ return res.networkType;
216
+ },
217
+ },
218
+ };
219
+ }
220
+
119
221
  let currentAdapter = null;
222
+ /**
223
+ * 运行时检测是否在 Taro 环境中
224
+ * 不依赖 process.env.TARO_ENV(构建时替换,预编译库无法获取)
225
+ * 改为检测 Taro 运行时特征
226
+ */
227
+ function isTaroEnv() {
228
+ // 1. 检测小程序全局对象(微信 wx、支付宝 my、字节 tt、百度 swan、QQ qq)
229
+ if (typeof wx !== 'undefined' ||
230
+ typeof my !== 'undefined' ||
231
+ typeof tt !== 'undefined' ||
232
+ typeof swan !== 'undefined' ||
233
+ typeof qq !== 'undefined') {
234
+ return true;
235
+ }
236
+ // 2. 检测 Taro 运行时注入的全局变量
237
+ const g = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : {};
238
+ if (g.__TARO || g.__taro__) {
239
+ return true;
240
+ }
241
+ // 3. process.env.TARO_ENV 作为补充(Taro H5 模式下可能有效)
242
+ if (typeof process !== 'undefined' && process.env && process.env.TARO_ENV) {
243
+ return true;
244
+ }
245
+ return false;
246
+ }
120
247
  /**
121
248
  * 自动检测运行环境并创建对应适配器
122
249
  */
123
250
  function detectAdapter() {
124
- // Taro 构建时会注入 process.env.TARO_ENV(如 'weapp', 'h5', 'alipay' 等)
125
- const env = typeof process !== 'undefined' && process.env ? process.env.TARO_ENV : '';
126
- if (env) {
251
+ if (isTaroEnv()) {
127
252
  try {
128
253
  // eslint-disable-next-line @typescript-eslint/no-var-requires
129
254
  const Taro = require('@tarojs/taro');
130
- const { createTaroAdapter } = require('./taro');
131
255
  return createTaroAdapter(Taro);
132
256
  }
133
257
  catch (_a) {
@@ -152,6 +276,35 @@ function setAdapter(adapter) {
152
276
  currentAdapter = adapter;
153
277
  }
154
278
 
279
+ /**
280
+ * 内部错误处理工具
281
+ */
282
+ const PREFIX = '[h5-utils]';
283
+ /**
284
+ * 安全执行函数,出错时打印错误并返回默认值
285
+ */
286
+ function safeCall(fn, fallback, method) {
287
+ try {
288
+ return fn();
289
+ }
290
+ catch (e) {
291
+ console.error(`${PREFIX} ${method} error:`, (e === null || e === void 0 ? void 0 : e.message) || e);
292
+ return fallback;
293
+ }
294
+ }
295
+ /**
296
+ * 安全执行异步函数
297
+ */
298
+ async function safeCallAsync(fn, fallback, method) {
299
+ try {
300
+ return await fn();
301
+ }
302
+ catch (e) {
303
+ console.error(`${PREFIX} ${method} error:`, (e === null || e === void 0 ? void 0 : e.message) || e);
304
+ return fallback;
305
+ }
306
+ }
307
+
155
308
  /**
156
309
  * URL 解析与操作工具
157
310
  */
@@ -159,54 +312,63 @@ function setAdapter(adapter) {
159
312
  * 解析 URL 为结构化对象
160
313
  */
161
314
  function parseUrl(url) {
162
- const a = document.createElement('a');
163
- a.href = url;
164
- const query = {};
165
- if (a.search) {
166
- a.search.slice(1).split('&').forEach((pair) => {
167
- const [key, val] = pair.split('=');
168
- if (key)
169
- query[decodeURIComponent(key)] = decodeURIComponent(val || '');
170
- });
171
- }
172
- return {
173
- protocol: a.protocol,
174
- host: a.host,
175
- port: a.port,
176
- pathname: a.pathname,
177
- search: a.search,
178
- hash: a.hash,
179
- query,
180
- };
315
+ return safeCall(() => {
316
+ const a = document.createElement('a');
317
+ a.href = url;
318
+ const query = {};
319
+ if (a.search) {
320
+ a.search
321
+ .slice(1)
322
+ .split('&')
323
+ .forEach((pair) => {
324
+ const [key, val] = pair.split('=');
325
+ if (key)
326
+ query[decodeURIComponent(key)] = decodeURIComponent(val || '');
327
+ });
328
+ }
329
+ return {
330
+ protocol: a.protocol,
331
+ host: a.host,
332
+ port: a.port,
333
+ pathname: a.pathname,
334
+ search: a.search,
335
+ hash: a.hash,
336
+ query,
337
+ };
338
+ }, { protocol: '', host: '', port: '', pathname: '', search: '', hash: '', query: {} }, 'parseUrl');
181
339
  }
182
340
  /**
183
341
  * 拼接带查询参数的 URL
184
342
  */
185
343
  function buildUrl(base, params) {
186
- const search = Object.entries(params)
187
- .filter(([, v]) => v !== undefined && v !== null)
188
- .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
189
- .join('&');
190
- if (!search)
191
- return base;
192
- const separator = base.includes('?') ? '&' : '?';
193
- return `${base}${separator}${search}`;
344
+ return safeCall(() => {
345
+ const search = Object.entries(params)
346
+ .filter(([, v]) => v !== undefined && v !== null)
347
+ .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
348
+ .join('&');
349
+ if (!search)
350
+ return base;
351
+ const separator = base.includes('?') ? '&' : '?';
352
+ return `${base}${separator}${search}`;
353
+ }, base, 'buildUrl');
194
354
  }
195
355
  /**
196
356
  * 获取当前页面指定查询参数
197
357
  */
198
358
  function getQueryParam(name) {
199
- return new URLSearchParams(window.location.search).get(name);
359
+ return safeCall(() => new URLSearchParams(window.location.search).get(name), null, 'getQueryParam');
200
360
  }
201
361
  /**
202
362
  * 获取当前页面所有查询参数
203
363
  */
204
364
  function getAllQueryParams() {
205
- const params = {};
206
- new URLSearchParams(window.location.search).forEach((value, key) => {
207
- params[key] = value;
208
- });
209
- return params;
365
+ return safeCall(() => {
366
+ const params = {};
367
+ new URLSearchParams(window.location.search).forEach((value, key) => {
368
+ params[key] = value;
369
+ });
370
+ return params;
371
+ }, {}, 'getAllQueryParams');
210
372
  }
211
373
 
212
374
  /**
@@ -218,42 +380,46 @@ function getAllQueryParams() {
218
380
  * 获取存储值,支持 JSON 自动反序列化
219
381
  */
220
382
  function get$1(key) {
221
- const raw = getAdapter().storage.get(key);
222
- if (raw === null)
223
- return null;
224
- try {
225
- const item = JSON.parse(raw);
226
- if (item.expireAt !== null && Date.now() > item.expireAt) {
227
- getAdapter().storage.remove(key);
383
+ return safeCall(() => {
384
+ const raw = getAdapter().storage.get(key);
385
+ if (raw === null)
228
386
  return null;
387
+ try {
388
+ const item = JSON.parse(raw);
389
+ if (item.expireAt !== null && Date.now() > item.expireAt) {
390
+ getAdapter().storage.remove(key);
391
+ return null;
392
+ }
393
+ return item.value;
229
394
  }
230
- return item.value;
231
- }
232
- catch (_a) {
233
- return raw;
234
- }
395
+ catch (_a) {
396
+ return raw;
397
+ }
398
+ }, null, 'getStorage');
235
399
  }
236
400
  /**
237
401
  * 设置存储值,支持过期时间
238
402
  */
239
403
  function set$1(key, value, options) {
240
- const item = {
241
- value,
242
- expireAt: (options === null || options === void 0 ? void 0 : options.expires) ? Date.now() + options.expires : null,
243
- };
244
- getAdapter().storage.set(key, JSON.stringify(item), options);
404
+ safeCall(() => {
405
+ const item = {
406
+ value,
407
+ expireAt: (options === null || options === void 0 ? void 0 : options.expires) ? Date.now() + options.expires : null,
408
+ };
409
+ getAdapter().storage.set(key, JSON.stringify(item), options);
410
+ }, undefined, 'setStorage');
245
411
  }
246
412
  /**
247
413
  * 删除存储值
248
414
  */
249
415
  function remove$1(key) {
250
- getAdapter().storage.remove(key);
416
+ safeCall(() => getAdapter().storage.remove(key), undefined, 'removeStorage');
251
417
  }
252
418
  /**
253
419
  * 清空所有存储
254
420
  */
255
421
  function clear() {
256
- getAdapter().storage.clear();
422
+ safeCall(() => getAdapter().storage.clear(), undefined, 'clearStorage');
257
423
  }
258
424
 
259
425
  /**
@@ -263,27 +429,31 @@ function clear() {
263
429
  * 获取 Cookie 值
264
430
  */
265
431
  function get(name) {
266
- const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${encodeURIComponent(name)}=([^;]*)`));
267
- return match ? decodeURIComponent(match[1]) : null;
432
+ return safeCall(() => {
433
+ const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${encodeURIComponent(name)}=([^;]*)`));
434
+ return match ? decodeURIComponent(match[1]) : null;
435
+ }, null, 'getCookie');
268
436
  }
269
437
  /**
270
438
  * 设置 Cookie
271
439
  */
272
440
  function set(name, value, options = {}) {
273
- const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
274
- if (options.days) {
275
- const expires = new Date(Date.now() + options.days * 864e5).toUTCString();
276
- parts.push(`expires=${expires}`);
277
- }
278
- if (options.path)
279
- parts.push(`path=${options.path}`);
280
- if (options.domain)
281
- parts.push(`domain=${options.domain}`);
282
- if (options.secure)
283
- parts.push('secure');
284
- if (options.sameSite)
285
- parts.push(`samesite=${options.sameSite}`);
286
- document.cookie = parts.join('; ');
441
+ safeCall(() => {
442
+ const parts = [`${encodeURIComponent(name)}=${encodeURIComponent(value)}`];
443
+ if (options.days) {
444
+ const expires = new Date(Date.now() + options.days * 864e5).toUTCString();
445
+ parts.push(`expires=${expires}`);
446
+ }
447
+ if (options.path)
448
+ parts.push(`path=${options.path}`);
449
+ if (options.domain)
450
+ parts.push(`domain=${options.domain}`);
451
+ if (options.secure)
452
+ parts.push('secure');
453
+ if (options.sameSite)
454
+ parts.push(`samesite=${options.sameSite}`);
455
+ document.cookie = parts.join('; ');
456
+ }, undefined, 'setCookie');
287
457
  }
288
458
  /**
289
459
  * 删除 Cookie
@@ -299,60 +469,73 @@ function remove(name, options = {}) {
299
469
  * 是否为 iOS
300
470
  */
301
471
  function isIOS() {
302
- return getAdapter().device.getInfo().ios;
472
+ return safeCall(() => getAdapter().device.getInfo().ios, false, 'isIOS');
303
473
  }
304
474
  /**
305
475
  * 是否为 Android
306
476
  */
307
477
  function isAndroid() {
308
- return getAdapter().device.getInfo().android;
478
+ return safeCall(() => getAdapter().device.getInfo().android, false, 'isAndroid');
309
479
  }
310
480
  /**
311
481
  * 是否为微信浏览器
312
482
  */
313
483
  function isWeChat() {
314
- return getAdapter().device.getInfo().weChat;
484
+ return safeCall(() => getAdapter().device.getInfo().weChat, false, 'isWeChat');
315
485
  }
316
486
  /**
317
487
  * 是否为移动端
318
488
  */
319
489
  function isMobile() {
320
- return getAdapter().device.getInfo().mobile;
490
+ return safeCall(() => getAdapter().device.getInfo().mobile, false, 'isMobile');
321
491
  }
322
492
  /**
323
493
  * 获取浏览器类型(H5 专用)
324
494
  */
325
495
  function getBrowserType() {
326
- const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';
327
- if (/MicroMessenger/i.test(ua))
328
- return 'weixin';
329
- if (/\bQQ\//i.test(ua))
330
- return 'qq';
331
- if (/UCBrowser/i.test(ua))
332
- return 'uc';
333
- if (/Edg/i.test(ua))
334
- return 'edge';
335
- if (/Firefox/i.test(ua))
336
- return 'firefox';
337
- if (/MSIE|Trident/i.test(ua))
338
- return 'ie';
339
- if (/Chrome/i.test(ua))
340
- return 'chrome';
341
- if (/Safari/i.test(ua))
342
- return 'safari';
343
- return 'unknown';
496
+ return safeCall(() => {
497
+ const ua = typeof navigator !== 'undefined' ? navigator.userAgent : '';
498
+ if (/MicroMessenger/i.test(ua))
499
+ return 'weixin';
500
+ if (/\bQQ\//i.test(ua))
501
+ return 'qq';
502
+ if (/UCBrowser/i.test(ua))
503
+ return 'uc';
504
+ if (/Edg/i.test(ua))
505
+ return 'edge';
506
+ if (/Firefox/i.test(ua))
507
+ return 'firefox';
508
+ if (/MSIE|Trident/i.test(ua))
509
+ return 'ie';
510
+ if (/Chrome/i.test(ua))
511
+ return 'chrome';
512
+ if (/Safari/i.test(ua))
513
+ return 'safari';
514
+ return 'unknown';
515
+ }, 'unknown', 'getBrowserType');
344
516
  }
345
517
  /**
346
518
  * 获取操作系统信息
347
519
  */
348
520
  function getOS() {
349
- return getAdapter().device.getInfo().os;
521
+ return safeCall(() => getAdapter().device.getInfo().os, 'Unknown', 'getOS');
350
522
  }
351
523
  /**
352
524
  * 获取完整设备信息
353
525
  */
354
526
  function getDeviceInfo() {
355
- return getAdapter().device.getInfo();
527
+ return safeCall(() => getAdapter().device.getInfo(), {
528
+ ios: false,
529
+ android: false,
530
+ mobile: false,
531
+ weChat: false,
532
+ os: 'Unknown',
533
+ brand: '',
534
+ model: '',
535
+ screenWidth: 0,
536
+ screenHeight: 0,
537
+ pixelRatio: 1,
538
+ }, 'getDeviceInfo');
356
539
  }
357
540
 
358
541
  /**
@@ -362,53 +545,56 @@ function getDeviceInfo() {
362
545
  * 查询单个元素
363
546
  */
364
547
  function $(selector) {
365
- return getAdapter().dom.select(selector);
548
+ return safeCall(() => getAdapter().dom.select(selector), null, '$');
366
549
  }
367
550
  /**
368
551
  * 查询多个元素
369
552
  */
370
553
  function $$(selector) {
371
- return getAdapter().dom.selectAll(selector);
554
+ return safeCall(() => getAdapter().dom.selectAll(selector), [], '$$');
372
555
  }
373
556
  /**
374
557
  * 添加 class
375
558
  */
376
559
  function addClass(el, cls) {
377
- el.classList.add(cls);
560
+ safeCall(() => el.classList.add(cls), undefined, 'addClass');
378
561
  }
379
562
  /**
380
563
  * 移除 class
381
564
  */
382
565
  function removeClass(el, cls) {
383
- el.classList.remove(cls);
566
+ safeCall(() => el.classList.remove(cls), undefined, 'removeClass');
384
567
  }
385
568
  /**
386
569
  * 切换 class
387
570
  */
388
571
  function toggleClass(el, cls) {
389
- el.classList.toggle(cls);
572
+ safeCall(() => el.classList.toggle(cls), undefined, 'toggleClass');
390
573
  }
391
574
  /**
392
575
  * 判断是否包含 class
393
576
  */
394
577
  function hasClass(el, cls) {
395
- return el.classList.contains(cls);
578
+ return safeCall(() => el.classList.contains(cls), false, 'hasClass');
396
579
  }
397
580
  /**
398
581
  * 获取元素样式
399
582
  */
400
583
  function getStyle(el, prop) {
401
- return el.style.getPropertyValue(prop) || getComputedStyle(el).getPropertyValue(prop);
584
+ return safeCall(() => el.style.getPropertyValue(prop) ||
585
+ getComputedStyle(el).getPropertyValue(prop), '', 'getStyle');
402
586
  }
403
587
  /**
404
588
  * 判断元素是否在可视区域内
405
589
  */
406
590
  function isInViewport(el) {
407
- const rect = el.getBoundingClientRect();
408
- return (rect.top >= 0 &&
409
- rect.left >= 0 &&
410
- rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
411
- rect.right <= (window.innerWidth || document.documentElement.clientWidth));
591
+ return safeCall(() => {
592
+ const rect = el.getBoundingClientRect();
593
+ return (rect.top >= 0 &&
594
+ rect.left >= 0 &&
595
+ rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
596
+ rect.right <= (window.innerWidth || document.documentElement.clientWidth));
597
+ }, false, 'isInViewport');
412
598
  }
413
599
 
414
600
  /**
@@ -418,19 +604,19 @@ function isInViewport(el) {
418
604
  * 绑定事件
419
605
  */
420
606
  function on(el, event, handler, options) {
421
- el.addEventListener(event, handler, options);
607
+ safeCall(() => el.addEventListener(event, handler, options), undefined, 'on');
422
608
  }
423
609
  /**
424
610
  * 解绑事件
425
611
  */
426
612
  function off(el, event, handler, options) {
427
- el.removeEventListener(event, handler, options);
613
+ safeCall(() => el.removeEventListener(event, handler, options), undefined, 'off');
428
614
  }
429
615
  /**
430
616
  * 单次监听事件
431
617
  */
432
618
  function once(el, event, handler) {
433
- el.addEventListener(event, handler, { once: true });
619
+ safeCall(() => el.addEventListener(event, handler, { once: true }), undefined, 'once');
434
620
  }
435
621
  /**
436
622
  * 事件委托
@@ -440,14 +626,16 @@ function once(el, event, handler) {
440
626
  * @param handler 回调函数
441
627
  */
442
628
  function delegate(parent, selector, event, handler) {
443
- const wrappedHandler = (e) => {
444
- var _a;
445
- const target = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest(selector);
446
- if (target) {
447
- handler(target, e);
448
- }
449
- };
450
- parent.addEventListener(event, wrappedHandler);
629
+ safeCall(() => {
630
+ const wrappedHandler = (e) => {
631
+ var _a;
632
+ const target = (_a = e.target) === null || _a === void 0 ? void 0 : _a.closest(selector);
633
+ if (target) {
634
+ handler(target, e);
635
+ }
636
+ };
637
+ parent.addEventListener(event, wrappedHandler);
638
+ }, undefined, 'delegate');
451
639
  }
452
640
 
453
641
  /**
@@ -457,13 +645,13 @@ function delegate(parent, selector, event, handler) {
457
645
  * 复制文本到剪贴板
458
646
  */
459
647
  function copyText(text) {
460
- return getAdapter().clipboard.write(text);
648
+ return safeCallAsync(() => getAdapter().clipboard.write(text), undefined, 'copyText');
461
649
  }
462
650
  /**
463
651
  * 读取剪贴板文本
464
652
  */
465
653
  function readText() {
466
- return getAdapter().clipboard.read();
654
+ return safeCallAsync(() => getAdapter().clipboard.read(), '', 'readText');
467
655
  }
468
656
 
469
657
  /**
@@ -474,34 +662,40 @@ let scrollLockCount = 0;
474
662
  * 滚动到页面顶部
475
663
  */
476
664
  function scrollToTop(smooth = true) {
477
- getAdapter().scroll.scrollTo({ top: 0, animated: smooth });
665
+ safeCall(() => getAdapter().scroll.scrollTo({ top: 0, animated: smooth }), undefined, 'scrollToTop');
478
666
  }
479
667
  /**
480
668
  * 滚动到指定元素
481
669
  */
482
670
  function scrollToElement(el, options = {}) {
483
- const { offset = 0, smooth = true } = options;
484
- const rect = el.getBoundingClientRect();
485
- const top = window.scrollY + rect.top + offset;
486
- getAdapter().scroll.scrollTo({ top, animated: smooth });
671
+ safeCall(() => {
672
+ const { offset = 0, smooth = true } = options;
673
+ const rect = el.getBoundingClientRect();
674
+ const top = window.scrollY + rect.top + offset;
675
+ getAdapter().scroll.scrollTo({ top, animated: smooth });
676
+ }, undefined, 'scrollToElement');
487
677
  }
488
678
  /**
489
679
  * 锁定页面滚动
490
680
  */
491
681
  function lockScroll() {
492
- scrollLockCount++;
493
- if (scrollLockCount === 1) {
494
- getAdapter().scroll.lockScroll();
495
- }
682
+ safeCall(() => {
683
+ scrollLockCount++;
684
+ if (scrollLockCount === 1) {
685
+ getAdapter().scroll.lockScroll();
686
+ }
687
+ }, undefined, 'lockScroll');
496
688
  }
497
689
  /**
498
690
  * 解锁页面滚动
499
691
  */
500
692
  function unlockScroll() {
501
- scrollLockCount = Math.max(0, scrollLockCount - 1);
502
- if (scrollLockCount === 0) {
503
- getAdapter().scroll.unlockScroll();
504
- }
693
+ safeCall(() => {
694
+ scrollLockCount = Math.max(0, scrollLockCount - 1);
695
+ if (scrollLockCount === 0) {
696
+ getAdapter().scroll.unlockScroll();
697
+ }
698
+ }, undefined, 'unlockScroll');
505
699
  }
506
700
 
507
701
  /**
@@ -512,49 +706,53 @@ function unlockScroll() {
512
706
  * @example formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
513
707
  */
514
708
  function formatDate(date, fmt = 'YYYY-MM-DD HH:mm:ss') {
515
- const d = new Date(date);
516
- const map = {
517
- YYYY: String(d.getFullYear()),
518
- MM: String(d.getMonth() + 1).padStart(2, '0'),
519
- DD: String(d.getDate()).padStart(2, '0'),
520
- HH: String(d.getHours()).padStart(2, '0'),
521
- mm: String(d.getMinutes()).padStart(2, '0'),
522
- ss: String(d.getSeconds()).padStart(2, '0'),
523
- };
524
- let result = fmt;
525
- for (const [key, val] of Object.entries(map)) {
526
- result = result.replace(key, val);
527
- }
528
- return result;
709
+ return safeCall(() => {
710
+ const d = new Date(date);
711
+ const map = {
712
+ YYYY: String(d.getFullYear()),
713
+ MM: String(d.getMonth() + 1).padStart(2, '0'),
714
+ DD: String(d.getDate()).padStart(2, '0'),
715
+ HH: String(d.getHours()).padStart(2, '0'),
716
+ mm: String(d.getMinutes()).padStart(2, '0'),
717
+ ss: String(d.getSeconds()).padStart(2, '0'),
718
+ };
719
+ let result = fmt;
720
+ for (const [key, val] of Object.entries(map)) {
721
+ result = result.replace(key, val);
722
+ }
723
+ return result;
724
+ }, '', 'formatDate');
529
725
  }
530
726
  /**
531
727
  * 数字格式化(千分位等)
532
728
  */
533
729
  function formatNumber(num, options = {}) {
534
- const { precision, separator = ',' } = options;
535
- let str = precision !== undefined ? num.toFixed(precision) : String(num);
536
- const [intPart, decPart] = str.split('.');
537
- const formatted = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
538
- return decPart !== undefined ? `${formatted}.${decPart}` : formatted;
730
+ return safeCall(() => {
731
+ const { precision, separator = ',' } = options;
732
+ let str = precision !== undefined ? num.toFixed(precision) : String(num);
733
+ const [intPart, decPart] = str.split('.');
734
+ const formatted = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, separator);
735
+ return decPart !== undefined ? `${formatted}.${decPart}` : formatted;
736
+ }, String(num), 'formatNumber');
539
737
  }
540
738
  /**
541
739
  * 手机号格式化 (xxx xxxx xxxx)
542
740
  */
543
741
  function formatPhone(phone) {
544
- return phone.replace(/(\d{3})(\d{0,4})(\d{0,4})/, (_, a, b, c) => {
742
+ return safeCall(() => phone.replace(/(\d{3})(\d{0,4})(\d{0,4})/, (_, a, b, c) => {
545
743
  let result = a;
546
744
  if (b)
547
745
  result += ` ${b}`;
548
746
  if (c)
549
747
  result += ` ${c}`;
550
748
  return result;
551
- });
749
+ }), phone, 'formatPhone');
552
750
  }
553
751
  /**
554
752
  * 金额格式化
555
753
  */
556
754
  function formatMoney(amount, precision = 2) {
557
- return formatNumber(amount, { precision });
755
+ return safeCall(() => formatNumber(amount, { precision }), String(amount), 'formatMoney');
558
756
  }
559
757
  /**
560
758
  * 节流
@@ -565,7 +763,12 @@ function throttle(fn, delay) {
565
763
  const now = Date.now();
566
764
  if (now - last >= delay) {
567
765
  last = now;
568
- fn.apply(this, args);
766
+ try {
767
+ fn.apply(this, args);
768
+ }
769
+ catch (e) {
770
+ console.error('[h5-utils] throttle error:', (e === null || e === void 0 ? void 0 : e.message) || e);
771
+ }
569
772
  }
570
773
  };
571
774
  }
@@ -577,7 +780,14 @@ function debounce(fn, delay) {
577
780
  return function (...args) {
578
781
  if (timer)
579
782
  clearTimeout(timer);
580
- timer = setTimeout(() => fn.apply(this, args), delay);
783
+ timer = setTimeout(() => {
784
+ try {
785
+ fn.apply(this, args);
786
+ }
787
+ catch (e) {
788
+ console.error('[h5-utils] debounce error:', (e === null || e === void 0 ? void 0 : e.message) || e);
789
+ }
790
+ }, delay);
581
791
  };
582
792
  }
583
793
 
@@ -595,40 +805,41 @@ const patterns = {
595
805
  * 手机号格式校验
596
806
  */
597
807
  function isMobilePhone(str) {
598
- return patterns.mobile.test(str);
808
+ return safeCall(() => patterns.mobile.test(str), false, 'isMobilePhone');
599
809
  }
600
810
  /**
601
811
  * 邮箱校验
602
812
  */
603
813
  function isEmail(str) {
604
- return patterns.email.test(str);
814
+ return safeCall(() => patterns.email.test(str), false, 'isEmail');
605
815
  }
606
816
  /**
607
817
  * 身份证号校验(18位)
608
818
  */
609
819
  function isIdCard(str) {
610
- if (!patterns.idCard.test(str))
611
- return false;
612
- // 校验位验证
613
- const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
614
- const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
615
- let sum = 0;
616
- for (let i = 0; i < 17; i++) {
617
- sum += parseInt(str[i], 10) * weights[i];
618
- }
619
- return checkCodes[sum % 11] === str[17].toUpperCase();
820
+ return safeCall(() => {
821
+ if (!patterns.idCard.test(str))
822
+ return false;
823
+ const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
824
+ const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
825
+ let sum = 0;
826
+ for (let i = 0; i < 17; i++) {
827
+ sum += parseInt(str[i], 10) * weights[i];
828
+ }
829
+ return checkCodes[sum % 11] === str[17].toUpperCase();
830
+ }, false, 'isIdCard');
620
831
  }
621
832
  /**
622
833
  * URL 校验
623
834
  */
624
835
  function isUrl(str) {
625
- return patterns.url.test(str);
836
+ return safeCall(() => patterns.url.test(str), false, 'isUrl');
626
837
  }
627
838
  /**
628
839
  * 中文校验
629
840
  */
630
841
  function isChinese(str) {
631
- return patterns.chinese.test(str);
842
+ return safeCall(() => patterns.chinese.test(str), false, 'isChinese');
632
843
  }
633
844
 
634
845
  export { $, $$, addClass, buildUrl, clear as clearStorage, copyText, debounce, delegate, formatDate, formatMoney, formatNumber, formatPhone, getAdapter, getAllQueryParams, getBrowserType, get as getCookie, getDeviceInfo, getOS, getQueryParam, get$1 as getStorage, getStyle, hasClass, isAndroid, isChinese, isEmail, isIOS, isIdCard, isInViewport, isMobile, isMobilePhone, isUrl, isWeChat, lockScroll, off, on, once, parseUrl, readText, removeClass, remove as removeCookie, remove$1 as removeStorage, scrollToElement, scrollToTop, setAdapter, set as setCookie, set$1 as setStorage, throttle, toggleClass, unlockScroll };