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