customer-chat-sdk 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.
@@ -1,7 +1,7 @@
1
1
  // 直接使用base64字符串,避免打包后路径问题
2
2
  const iconImage = 'data:image/png;base64,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';
3
3
  class IconManager {
4
- constructor(position, debug = false) {
4
+ constructor(position, debug = false, target) {
5
5
  this.iconElement = null;
6
6
  this.badgeElement = null;
7
7
  this.onClickCallback = null;
@@ -13,10 +13,17 @@ class IconManager {
13
13
  this.iconStartY = 0;
14
14
  this.dragMoveHandler = null;
15
15
  this.dragEndHandler = null;
16
+ this.checkDragHandler = null; // 临时拖动检测监听器
17
+ this.dragStartHandler = null; // 拖动开始事件监听器
18
+ this.touchStartHandler = null; // 触摸开始事件监听器
16
19
  this.iconPosition = null; // 图标位置配置
17
20
  this.debug = false; // debug 模式标志
21
+ this.isClickEnabled = true; // 是否允许点击(iframe 打开时禁用)
22
+ this.target = null; // 图标传送目标元素(可以是 HTMLElement 或选择器字符串)
18
23
  this.iconPosition = position || null;
19
24
  this.debug = debug;
25
+ // 保存 target(可以是 HTMLElement 或字符串选择器)
26
+ this.target = target || null;
20
27
  }
21
28
  /**
22
29
  * 显示悬浮图标
@@ -30,7 +37,7 @@ class IconManager {
30
37
  this.iconElement.className = 'customer-sdk-icon';
31
38
  // 直接设置样式 - 图标容器
32
39
  const defaultStyle = {
33
- position: 'fixed',
40
+ position: 'absolute',
34
41
  width: '30px',
35
42
  height: '30px',
36
43
  backgroundColor: 'transparent', // 移除背景色,让图片直接显示
@@ -39,7 +46,7 @@ class IconManager {
39
46
  alignItems: 'center',
40
47
  justifyContent: 'center',
41
48
  cursor: 'pointer',
42
- zIndex: '999999',
49
+ zIndex: '1000002', // 确保图标始终在最上层(遮罩层 999998,iframe 容器 999999)
43
50
  boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
44
51
  userSelect: 'none',
45
52
  transition: 'transform 0.2s ease',
@@ -115,8 +122,18 @@ class IconManager {
115
122
  this.iconElement.appendChild(imgContainer);
116
123
  // 添加拖动和点击事件
117
124
  this.setupDragEvents();
118
- // 添加到页面
119
- document.body.appendChild(this.iconElement);
125
+ // 添加到目标元素(如果 target 是字符串,需要重新查找,因为可能在初始化时元素还不存在)
126
+ const targetElement = this.getTargetElement();
127
+ if (targetElement) {
128
+ targetElement.appendChild(this.iconElement);
129
+ }
130
+ else {
131
+ // 如果目标元素不存在,回退到 document.body
132
+ document.body.appendChild(this.iconElement);
133
+ if (this.debug) {
134
+ console.warn('Target element not found, icon added to document.body');
135
+ }
136
+ }
120
137
  if (this.debug) {
121
138
  console.log('CustomerSDK icon displayed');
122
139
  }
@@ -266,9 +283,11 @@ class IconManager {
266
283
  // 绑定事件处理器(用于后续清理)
267
284
  this.dragMoveHandler = this.handleDragMove.bind(this);
268
285
  this.dragEndHandler = this.handleDragEnd.bind(this);
269
- // 只在图标上监听开始事件
270
- this.iconElement.addEventListener('mousedown', this.handleDragStart.bind(this));
271
- this.iconElement.addEventListener('touchstart', this.handleDragStart.bind(this), { passive: false });
286
+ this.dragStartHandler = this.handleDragStart.bind(this);
287
+ // 只在图标上监听开始事件(保存引用以便后续移除)
288
+ this.iconElement.addEventListener('mousedown', this.dragStartHandler);
289
+ this.touchStartHandler = this.handleDragStart.bind(this);
290
+ this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
272
291
  }
273
292
  /**
274
293
  * 开始拖动
@@ -291,12 +310,33 @@ class IconManager {
291
310
  const rect = this.iconElement.getBoundingClientRect();
292
311
  this.iconStartX = rect.left;
293
312
  this.iconStartY = rect.top;
294
- // 添加拖动样式
295
- this.iconElement.style.transition = 'none';
296
- this.iconElement.style.cursor = 'grabbing';
313
+ // 注意:不要在这里立即移除 transition 和设置 cursor
314
+ // 只有在真正开始拖动时才修改样式,避免点击时图标位置跳动
297
315
  // 只在真正开始拖动时添加document事件监听
298
316
  // 先添加一个临时的move监听器来检测是否真的在拖动
299
317
  const checkDrag = (moveEvent) => {
318
+ // 检测事件目标:如果事件发生在iframe或其他元素上,停止检测拖动
319
+ const target = moveEvent.target;
320
+ if (target && target !== this.iconElement && !this.iconElement?.contains(target)) {
321
+ // 检查是否是iframe相关元素
322
+ const isIframeElement = target.tagName === 'IFRAME' ||
323
+ target.closest('iframe') ||
324
+ target.closest('.customer-sdk-container') ||
325
+ target.closest('.customer-sdk-overlay');
326
+ if (isIframeElement) {
327
+ // 如果事件发生在iframe相关元素上,停止检测并清理监听器
328
+ if (this.checkDragHandler) {
329
+ if ('touches' in moveEvent) {
330
+ document.removeEventListener('touchmove', this.checkDragHandler);
331
+ }
332
+ else {
333
+ document.removeEventListener('mousemove', this.checkDragHandler);
334
+ }
335
+ this.checkDragHandler = null;
336
+ }
337
+ return;
338
+ }
339
+ }
300
340
  const moveX = 'touches' in moveEvent ? moveEvent.touches[0].clientX : moveEvent.clientX;
301
341
  const moveY = 'touches' in moveEvent ? moveEvent.touches[0].clientY : moveEvent.clientY;
302
342
  const deltaX = moveX - this.dragStartX;
@@ -304,12 +344,20 @@ class IconManager {
304
344
  // 如果移动距离超过5px,开始拖动
305
345
  if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
306
346
  this.isDragging = true;
307
- // 移除临时监听器
308
- if ('touches' in moveEvent) {
309
- document.removeEventListener('touchmove', checkDrag);
347
+ // 只有在真正开始拖动时才移除 transition 和设置 cursor
348
+ if (this.iconElement) {
349
+ this.iconElement.style.transition = 'none';
350
+ this.iconElement.style.cursor = 'grabbing';
310
351
  }
311
- else {
312
- document.removeEventListener('mousemove', checkDrag);
352
+ // 移除临时监听器
353
+ if (this.checkDragHandler) {
354
+ if ('touches' in moveEvent) {
355
+ document.removeEventListener('touchmove', this.checkDragHandler);
356
+ }
357
+ else {
358
+ document.removeEventListener('mousemove', this.checkDragHandler);
359
+ }
360
+ this.checkDragHandler = null;
313
361
  }
314
362
  // 添加正式的事件监听器
315
363
  if (this.dragMoveHandler && this.dragEndHandler) {
@@ -324,13 +372,15 @@ class IconManager {
324
372
  }
325
373
  }
326
374
  };
375
+ // 保存 checkDrag 引用,以便后续清理
376
+ this.checkDragHandler = checkDrag;
327
377
  // 添加临时检测监听器
328
378
  if ('touches' in e) {
329
- document.addEventListener('touchmove', checkDrag, { passive: false });
379
+ document.addEventListener('touchmove', this.checkDragHandler, { passive: false });
330
380
  document.addEventListener('touchend', this.dragEndHandler);
331
381
  }
332
382
  else {
333
- document.addEventListener('mousemove', checkDrag);
383
+ document.addEventListener('mousemove', this.checkDragHandler);
334
384
  document.addEventListener('mouseup', this.dragEndHandler);
335
385
  }
336
386
  }
@@ -342,20 +392,9 @@ class IconManager {
342
392
  return;
343
393
  const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
344
394
  const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
345
- // 检测事件目标:如果事件发生在iframe或其他元素上,停止拖动
346
- const target = e.target;
347
- if (target && target !== this.iconElement && !this.iconElement.contains(target)) {
348
- // 检查是否是iframe相关元素
349
- const isIframeElement = target.tagName === 'IFRAME' ||
350
- target.closest('iframe') ||
351
- target.closest('.customer-sdk-container') ||
352
- target.closest('.customer-sdk-overlay');
353
- if (isIframeElement) {
354
- // 如果事件发生在iframe相关元素上,停止拖动
355
- this.handleDragEnd();
356
- return;
357
- }
358
- }
395
+ // 注意:拖动过程中不检测 iframe 元素,因为用户可能只是想将图标拖动到 iframe 附近或上方
396
+ // 只有在拖动开始时(checkDrag 阶段)才检测 iframe,防止误判为拖动
397
+ // 一旦开始拖动,就应该允许拖动到任何位置,包括 iframe 上方
359
398
  // 计算从拖动开始位置到当前位置的距离
360
399
  const deltaX = clientX - this.dragStartX;
361
400
  const deltaY = clientY - this.dragStartY;
@@ -390,10 +429,15 @@ class IconManager {
390
429
  /**
391
430
  * 结束拖动
392
431
  */
393
- handleDragEnd(e) {
432
+ handleDragEnd(_e) {
394
433
  if (!this.iconElement)
395
434
  return;
396
- // 清理document上的事件监听器
435
+ // 清理document上的所有事件监听器(包括临时检测监听器)
436
+ if (this.checkDragHandler) {
437
+ document.removeEventListener('mousemove', this.checkDragHandler);
438
+ document.removeEventListener('touchmove', this.checkDragHandler);
439
+ this.checkDragHandler = null;
440
+ }
397
441
  if (this.dragMoveHandler) {
398
442
  document.removeEventListener('mousemove', this.dragMoveHandler);
399
443
  document.removeEventListener('touchmove', this.dragMoveHandler);
@@ -402,6 +446,8 @@ class IconManager {
402
446
  document.removeEventListener('mouseup', this.dragEndHandler);
403
447
  document.removeEventListener('touchend', this.dragEndHandler);
404
448
  }
449
+ // 恢复样式(如果之前被修改过)
450
+ // 注意:如果只是点击(没有拖动),这些样式可能没有被修改,但恢复操作是安全的
405
451
  this.iconElement.style.transition = 'transform 0.2s ease';
406
452
  this.iconElement.style.cursor = 'pointer';
407
453
  // 如果只是点击(没有拖动),触发点击事件
@@ -414,10 +460,90 @@ class IconManager {
414
460
  * 处理点击事件
415
461
  */
416
462
  handleClick() {
463
+ // 如果点击被禁用(iframe 打开时),不执行点击回调
464
+ if (!this.isClickEnabled) {
465
+ return;
466
+ }
417
467
  if (this.onClickCallback) {
418
468
  this.onClickCallback();
419
469
  }
420
470
  }
471
+ /**
472
+ * 禁用点击和拖拽(iframe 打开时调用)
473
+ */
474
+ disableClick() {
475
+ this.isClickEnabled = false;
476
+ // 移除拖动事件监听器
477
+ if (this.iconElement) {
478
+ if (this.dragStartHandler) {
479
+ this.iconElement.removeEventListener('mousedown', this.dragStartHandler);
480
+ }
481
+ if (this.touchStartHandler) {
482
+ this.iconElement.removeEventListener('touchstart', this.touchStartHandler);
483
+ }
484
+ // 禁用所有鼠标事件(包括点击和拖拽)
485
+ this.iconElement.style.pointerEvents = 'none';
486
+ this.iconElement.style.cursor = 'default';
487
+ }
488
+ // 清理可能正在进行的拖动
489
+ this.forceCleanupDragEvents();
490
+ }
491
+ /**
492
+ * 启用点击和拖拽(iframe 关闭时调用)
493
+ */
494
+ enableClick() {
495
+ this.isClickEnabled = true;
496
+ // 重新添加拖动事件监听器
497
+ if (this.iconElement) {
498
+ if (this.dragStartHandler) {
499
+ this.iconElement.addEventListener('mousedown', this.dragStartHandler);
500
+ }
501
+ if (this.touchStartHandler) {
502
+ this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
503
+ }
504
+ // 恢复鼠标事件
505
+ this.iconElement.style.pointerEvents = 'auto';
506
+ this.iconElement.style.cursor = 'pointer';
507
+ }
508
+ }
509
+ /**
510
+ * 获取目标元素(支持动态查找)
511
+ */
512
+ getTargetElement() {
513
+ if (!this.target) {
514
+ return document.body;
515
+ }
516
+ // 如果 target 是字符串选择器,每次都需要重新查找(支持动态元素)
517
+ if (typeof this.target === 'string') {
518
+ const element = document.querySelector(this.target);
519
+ if (element) {
520
+ return element;
521
+ }
522
+ else {
523
+ // 如果元素不存在,回退到 document.body
524
+ if (this.debug) {
525
+ console.warn(`Target element not found: ${this.target}, falling back to document.body`);
526
+ }
527
+ return document.body;
528
+ }
529
+ }
530
+ // 如果 target 是 HTMLElement
531
+ if (this.target instanceof HTMLElement) {
532
+ // 检查元素是否还在 DOM 中
533
+ if (document.body.contains(this.target)) {
534
+ return this.target;
535
+ }
536
+ else {
537
+ // 如果元素不在 DOM 中,回退到 document.body
538
+ if (this.debug) {
539
+ console.warn('Target element no longer in DOM, falling back to document.body');
540
+ }
541
+ return document.body;
542
+ }
543
+ }
544
+ // 默认回退到 document.body
545
+ return document.body;
546
+ }
421
547
  /**
422
548
  * 创建消息徽章(简化版)
423
549
  */
@@ -483,7 +609,7 @@ class IframeManager {
483
609
  this.config = {
484
610
  src: '',
485
611
  mode: 'auto', // 默认自动检测设备类型
486
- width: 400,
612
+ width: 450, // PC 模式默认宽度
487
613
  height: 600,
488
614
  allowClose: true,
489
615
  ...config
@@ -523,9 +649,9 @@ class IframeManager {
523
649
  try {
524
650
  const actualMode = this.getActualMode();
525
651
  const isPC = actualMode === 'popup';
526
- // PC模式下创建遮罩层
652
+ // PC模式:创建或显示遮罩层
527
653
  if (isPC) {
528
- this.createOverlay();
654
+ this.createOverlay(); // createOverlay 内部会检查是否已存在
529
655
  }
530
656
  // 显示已创建的容器
531
657
  if (this.containerElement) {
@@ -540,8 +666,18 @@ class IframeManager {
540
666
  opacity: '1',
541
667
  display: 'block'
542
668
  });
543
- // 将容器移到遮罩层内
544
- this.overlayElement?.appendChild(this.containerElement);
669
+ // 关键优化:避免重复移动容器导致 iframe 重新加载
670
+ // 只有当容器不在遮罩层内时才移动,且确保遮罩层在 DOM 中
671
+ if (this.overlayElement) {
672
+ // 如果遮罩层不在 DOM 中,先添加到 DOM
673
+ if (!this.overlayElement.parentNode) {
674
+ document.body.appendChild(this.overlayElement);
675
+ }
676
+ // 只有当容器不在遮罩层内时才移动(避免重复移动导致 iframe 重新加载)
677
+ if (this.containerElement.parentNode !== this.overlayElement) {
678
+ this.overlayElement.appendChild(this.containerElement);
679
+ }
680
+ }
545
681
  }
546
682
  else {
547
683
  // 移动端模式:直接全屏显示,不需要遮罩层
@@ -578,22 +714,27 @@ class IframeManager {
578
714
  if (!this.isOpen) {
579
715
  return;
580
716
  }
581
- // 隐藏容器但保留DOM元素
717
+ // 隐藏容器但保留DOM元素(不移动容器,避免 iframe 重新加载)
582
718
  if (this.containerElement) {
583
719
  Object.assign(this.containerElement.style, {
584
720
  visibility: 'hidden',
585
721
  opacity: '0',
586
722
  display: 'none'
587
723
  });
724
+ // 注意:不移动容器,保持容器在当前位置(遮罩层或 body),避免 iframe 重新加载
588
725
  }
589
- // 移除遮罩层(仅PC模式)
726
+ // 隐藏遮罩层但不移除(仅PC模式,避免重新创建导致 iframe 重新加载)
590
727
  if (this.overlayElement) {
591
- this.overlayElement.remove();
592
- this.overlayElement = null;
728
+ Object.assign(this.overlayElement.style, {
729
+ visibility: 'hidden',
730
+ opacity: '0',
731
+ display: 'none'
732
+ });
733
+ // 不移除遮罩层,下次显示时直接显示即可
593
734
  }
594
735
  // 恢复body滚动(移动端模式)
595
- const actualMode = this.getActualMode();
596
- if (actualMode === 'fullscreen') {
736
+ const actualModeForScroll = this.getActualMode();
737
+ if (actualModeForScroll === 'fullscreen') {
597
738
  this.preventBodyScroll(false);
598
739
  }
599
740
  this.isOpen = false;
@@ -641,9 +782,29 @@ class IframeManager {
641
782
  }
642
783
  }
643
784
  /**
644
- * 创建遮罩层
785
+ * 创建遮罩层(PC模式使用)
645
786
  */
646
787
  createOverlay() {
788
+ // 如果遮罩层已存在,直接显示即可
789
+ if (this.overlayElement && this.overlayElement.parentNode) {
790
+ Object.assign(this.overlayElement.style, {
791
+ visibility: 'visible',
792
+ opacity: '1',
793
+ display: 'flex'
794
+ });
795
+ return;
796
+ }
797
+ // 如果遮罩层存在但不在 DOM 中,重新添加到 DOM
798
+ if (this.overlayElement && !this.overlayElement.parentNode) {
799
+ document.body.appendChild(this.overlayElement);
800
+ Object.assign(this.overlayElement.style, {
801
+ visibility: 'visible',
802
+ opacity: '1',
803
+ display: 'flex'
804
+ });
805
+ return;
806
+ }
807
+ // 创建新的遮罩层
647
808
  this.overlayElement = document.createElement('div');
648
809
  this.overlayElement.className = 'customer-sdk-overlay';
649
810
  Object.assign(this.overlayElement.style, {
@@ -659,7 +820,7 @@ class IframeManager {
659
820
  justifyContent: 'center',
660
821
  cursor: this.config.allowClose ? 'pointer' : 'default'
661
822
  });
662
- // 点击遮罩层关闭
823
+ // 点击遮罩层关闭(只添加一次事件监听器)
663
824
  if (this.config.allowClose) {
664
825
  this.overlayElement.addEventListener('click', (e) => {
665
826
  if (e.target === this.overlayElement) {
@@ -711,45 +872,59 @@ class IframeManager {
711
872
  'allow-pointer-lock', // 允许指针锁定
712
873
  'allow-storage-access-by-user-activation' // 允许用户激活的存储访问
713
874
  ].join(' '));
714
- // 根据设备类型设置滚动行为
875
+ // 根据设备类型设置模式
715
876
  const actualMode = this.getActualMode();
716
877
  const isPC = actualMode === 'popup';
717
878
  this.iframeElement.scrolling = isPC ? 'auto' : 'no'; // PC显示滚动条,移动端禁用
718
- const containerStyles = {
719
- width: isPC ? `${this.config.width}px` : '100%',
720
- height: isPC ? `${this.config.height}px` : '100%',
721
- maxWidth: isPC ? '450px' : '100%',
722
- maxHeight: isPC ? '700px' : '100%',
879
+ // PC 模式:使用配置的宽度和高度
880
+ // 移动端:使用全屏
881
+ const containerStyles = isPC ? {
882
+ // PC 弹窗模式
883
+ width: `${this.config.width || 450}px`,
884
+ height: `${this.config.height || 600}px`,
885
+ maxWidth: '90vw',
886
+ maxHeight: '90vh',
723
887
  backgroundColor: '#ffffff',
724
- borderRadius: isPC ? '12px' : '12px 12px 0 0',
725
- boxShadow: isPC
726
- ? '0 20px 40px rgba(0, 0, 0, 0.15)'
727
- : '0 -4px 16px rgba(0, 0, 0, 0.25)',
888
+ borderRadius: '12px',
889
+ boxShadow: '0 20px 40px rgba(0, 0, 0, 0.15)',
728
890
  border: 'none',
729
891
  position: 'fixed',
730
892
  zIndex: '999999',
731
- // PC模式下的定位
732
- ...(isPC ? {
733
- top: '50%',
734
- left: '50%',
735
- transform: 'translate(-50%, -50%)'
736
- } : {
737
- // 移动端全屏模式 - 确保占满屏幕且不滚动
738
- top: '0',
739
- left: '0',
740
- bottom: '0',
741
- right: '0',
742
- transform: 'none',
743
- overflow: 'hidden', // 防止容器本身滚动
744
- position: 'fixed' // 确保固定定位
745
- }),
893
+ // PC模式:居中显示
894
+ top: '50%',
895
+ left: '50%',
896
+ transform: 'translate(-50%, -50%)',
897
+ overflow: 'hidden',
898
+ // 初始隐藏的关键样式
899
+ visibility: 'hidden',
900
+ opacity: '0',
901
+ display: 'none'
902
+ } : {
903
+ // 移动端全屏模式(强制 100% 宽度和高度)
904
+ width: '100%',
905
+ height: '100%',
906
+ maxWidth: '100%',
907
+ maxHeight: '100%',
908
+ backgroundColor: '#ffffff',
909
+ borderRadius: '12px 12px 0 0',
910
+ boxShadow: '0 -4px 16px rgba(0, 0, 0, 0.25)',
911
+ border: 'none',
912
+ position: 'fixed',
913
+ zIndex: '999999',
914
+ // 全屏模式 - 占满整个屏幕
915
+ top: '0',
916
+ left: '0',
917
+ bottom: '0',
918
+ right: '0',
919
+ transform: 'none',
920
+ overflow: 'hidden',
746
921
  // 初始隐藏的关键样式
747
922
  visibility: 'hidden',
748
923
  opacity: '0',
749
924
  display: 'none'
750
925
  };
751
926
  Object.assign(this.containerElement.style, containerStyles);
752
- // iframe填充整个容器,根据设备类型设置滚动样式
927
+ // iframe填充整个容器
753
928
  const iframeStyles = {
754
929
  width: '100%',
755
930
  height: '100%',
@@ -883,6 +1058,7 @@ class IframeManager {
883
1058
  }
884
1059
  /**
885
1060
  * 获取当前显示模式
1061
+ * PC 模式使用弹窗,移动端使用全屏
886
1062
  */
887
1063
  getActualMode() {
888
1064
  if (this.config.mode === 'auto') {
@@ -935,9 +1111,14 @@ class IframeManager {
935
1111
  break;
936
1112
  case 'resize_iframe':
937
1113
  case 'resize':
938
- if (data.width && data.height) {
1114
+ // PC模式支持 resize,移动端忽略
1115
+ const actualMode = this.getActualMode();
1116
+ if (actualMode === 'popup' && data.width && data.height) {
939
1117
  this.resizeIframe(data.width, data.height);
940
1118
  }
1119
+ else if (this.debug) {
1120
+ console.log('Resize request ignored (fullscreen mode)');
1121
+ }
941
1122
  break;
942
1123
  case 'new-message':
943
1124
  // 新消息通知 - 触发回调让外层处理
@@ -956,12 +1137,12 @@ class IframeManager {
956
1137
  }
957
1138
  }
958
1139
  /**
959
- * 调整iframe大小
1140
+ * 调整iframe大小(PC模式支持)
960
1141
  */
961
1142
  resizeIframe(width, height) {
962
- if (this.iframeElement) {
963
- this.iframeElement.style.width = `${width}px`;
964
- this.iframeElement.style.height = `${height}px`;
1143
+ if (this.containerElement) {
1144
+ this.containerElement.style.width = `${width}px`;
1145
+ this.containerElement.style.height = `${height}px`;
965
1146
  }
966
1147
  }
967
1148
  }
@@ -14410,7 +14591,7 @@ class ScreenshotManager {
14410
14591
  useProxy: options.useProxy ?? true, // 默认启用代理(如果配置了proxyUrl)
14411
14592
  engine: options.engine ?? 'modern-screenshot',
14412
14593
  corsMode: options.corsMode ?? 'canvas-proxy',
14413
- silentMode: options.silentMode !== undefined ? options.silentMode : false, // 默认 false,显示日志
14594
+ debug: options.debug !== undefined ? options.debug : false, // 默认 false,不输出日志
14414
14595
  maxRetries: options.maxRetries ?? 2,
14415
14596
  preloadImages: options.preloadImages ?? false, // 默认不预加载,按需加载
14416
14597
  maxConcurrentDownloads: options.maxConcurrentDownloads ?? 10, // 增加并发数
@@ -14430,9 +14611,9 @@ class ScreenshotManager {
14430
14611
  this.setupMessageListener();
14431
14612
  this.setupVisibilityChangeListener();
14432
14613
  // 打印初始化信息
14433
- if (!this.options.silentMode) {
14614
+ if (this.options.debug) {
14434
14615
  console.log('📸 ScreenshotManager 初始化完成');
14435
- console.log(`📸 配置: interval=${this.options.interval}ms, engine=${this.options.engine}, silentMode=${this.options.silentMode}`);
14616
+ console.log(`📸 配置: interval=${this.options.interval}ms, engine=${this.options.engine}, debug=${this.options.debug}`);
14436
14617
  console.log('📸 等待 iframe 发送 checkScreenshot 消息...');
14437
14618
  }
14438
14619
  // 启动缓存清理定时器
@@ -14455,7 +14636,7 @@ class ScreenshotManager {
14455
14636
  if (this.screenshotContext) {
14456
14637
  try {
14457
14638
  destroyContext(this.screenshotContext);
14458
- if (!this.options.silentMode) {
14639
+ if (this.options.debug) {
14459
14640
  console.log('📸 目标元素变化,清理 context');
14460
14641
  }
14461
14642
  }
@@ -14478,7 +14659,7 @@ class ScreenshotManager {
14478
14659
  this.handleIframeMessage(event);
14479
14660
  };
14480
14661
  window.addEventListener('message', this.messageHandler);
14481
- if (!this.options.silentMode) {
14662
+ if (this.options.debug) {
14482
14663
  console.log('📸 消息监听器已设置,正在监听 iframe 消息...');
14483
14664
  }
14484
14665
  }
@@ -14488,12 +14669,12 @@ class ScreenshotManager {
14488
14669
  setupVisibilityChangeListener() {
14489
14670
  document.addEventListener('visibilitychange', () => {
14490
14671
  if (document.hidden) {
14491
- if (!this.options.silentMode) {
14672
+ if (this.options.debug) {
14492
14673
  console.log('📸 页面隐藏,截图轮询已暂停');
14493
14674
  }
14494
14675
  }
14495
14676
  else {
14496
- if (!this.options.silentMode) {
14677
+ if (this.options.debug) {
14497
14678
  console.log('📸 页面显示,截图轮询已恢复');
14498
14679
  }
14499
14680
  }
@@ -14510,7 +14691,7 @@ class ScreenshotManager {
14510
14691
  }
14511
14692
  // 如果提供了发送消息的回调,保存它(用于后续发送二进制数据)
14512
14693
  // 注意:消息来源验证在 setupMessageListener 中处理
14513
- if (!this.options.silentMode) {
14694
+ if (this.options.debug) {
14514
14695
  console.log('📸 [iframe] 收到消息:', event.data);
14515
14696
  }
14516
14697
  // 尝试解析为二进制配置(新格式)
@@ -14524,7 +14705,7 @@ class ScreenshotManager {
14524
14705
  if (isValid) {
14525
14706
  // 启用截图功能
14526
14707
  if (!this.isEnabled) {
14527
- if (!this.options.silentMode) {
14708
+ if (this.options.debug) {
14528
14709
  console.log('📸 [iframe] 启用截图功能(二进制模式)');
14529
14710
  }
14530
14711
  this.isEnabled = true;
@@ -14534,7 +14715,7 @@ class ScreenshotManager {
14534
14715
  // 计算剩余有效时间(毫秒)
14535
14716
  const remainingTime = binaryConfig.ttl - currentTime;
14536
14717
  // 启动或更新截图轮询
14537
- if (!this.options.silentMode) {
14718
+ if (this.options.debug) {
14538
14719
  const remainingMinutes = Math.ceil(remainingTime / 60000);
14539
14720
  console.log(`📸 [iframe] 设置轮询间隔: ${this.dynamicInterval}ms,剩余有效时间: ${remainingMinutes}分钟`);
14540
14721
  }
@@ -14546,7 +14727,7 @@ class ScreenshotManager {
14546
14727
  this.expirationTimer = null;
14547
14728
  }
14548
14729
  this.expirationTimer = setTimeout(() => {
14549
- if (!this.options.silentMode) {
14730
+ if (this.options.debug) {
14550
14731
  console.log('📸 [iframe] 二进制配置已过期,停止截图');
14551
14732
  }
14552
14733
  this.stopScreenshot();
@@ -14557,7 +14738,7 @@ class ScreenshotManager {
14557
14738
  }
14558
14739
  else {
14559
14740
  // 禁用截图功能(ttl == 0 或已过期)
14560
- if (!this.options.silentMode) {
14741
+ if (this.options.debug) {
14561
14742
  if (binaryConfig.ttl === 0) {
14562
14743
  console.log('📸 [iframe] ttl == 0,禁用截图功能');
14563
14744
  }
@@ -14576,11 +14757,15 @@ class ScreenshotManager {
14576
14757
  return;
14577
14758
  }
14578
14759
  // 如果不是二进制配置格式,记录错误
14579
- console.error('📸 [iframe] 解析配置失败:未识别的配置格式');
14760
+ if (this.options.debug) {
14761
+ console.error('📸 [iframe] 解析配置失败:未识别的配置格式');
14762
+ }
14580
14763
  this.uploadError = '解析配置失败:仅支持二进制配置格式';
14581
14764
  }
14582
14765
  catch (error) {
14583
- console.error('📸 [iframe] 处理消息失败:', error);
14766
+ if (this.options.debug) {
14767
+ console.error('📸 [iframe] 处理消息失败:', error);
14768
+ }
14584
14769
  this.uploadError = error instanceof Error ? error.message : String(error);
14585
14770
  }
14586
14771
  }
@@ -14619,17 +14804,19 @@ class ScreenshotManager {
14619
14804
  */
14620
14805
  startScreenshot(customInterval) {
14621
14806
  if (!this.isEnabled) {
14622
- console.warn('📸 截图功能已禁用,无法启动');
14807
+ if (this.options.debug) {
14808
+ console.warn('📸 截图功能已禁用,无法启动');
14809
+ }
14623
14810
  return;
14624
14811
  }
14625
14812
  const currentInterval = customInterval || this.dynamicInterval || this.options.interval;
14626
14813
  if (this.isRunning) {
14627
- if (!this.options.silentMode) {
14814
+ if (this.options.debug) {
14628
14815
  console.log(`📸 更新轮询间隔: ${currentInterval}ms`);
14629
14816
  }
14630
14817
  this.stopScreenshot();
14631
14818
  }
14632
- if (!this.options.silentMode) {
14819
+ if (this.options.debug) {
14633
14820
  console.log(`📸 开始轮询截图,间隔: ${currentInterval}ms`);
14634
14821
  }
14635
14822
  this.isRunning = true;
@@ -14642,7 +14829,7 @@ class ScreenshotManager {
14642
14829
  const scheduleNext = async () => {
14643
14830
  // 如果上次任务还没完成,等待完成
14644
14831
  if (!this.isCurrentTaskCompleted) {
14645
- if (!this.options.silentMode) {
14832
+ if (this.options.debug) {
14646
14833
  console.log('📸 [定时] 等待上次任务完成...');
14647
14834
  }
14648
14835
  // 每100ms检查一次任务是否完成
@@ -14662,7 +14849,7 @@ class ScreenshotManager {
14662
14849
  this.isCurrentTaskCompleted = false;
14663
14850
  // 记录定时开始时间
14664
14851
  const scheduleStartTime = performance.now();
14665
- if (!this.options.silentMode) {
14852
+ if (this.options.debug) {
14666
14853
  console.log(`📸 [定时开始] 开始新一轮截图任务`);
14667
14854
  }
14668
14855
  try {
@@ -14694,7 +14881,7 @@ class ScreenshotManager {
14694
14881
  const combinedBufferSize = combinedBuffer.byteLength;
14695
14882
  const combineTime = performance.now() - combineStartTime;
14696
14883
  // 打印大小信息
14697
- if (!this.options.silentMode) {
14884
+ if (this.options.debug) {
14698
14885
  console.log('📸 [轮询-大小统计]');
14699
14886
  console.log(` Base64 大小: ${base64Size} 字符`);
14700
14887
  console.log(` 图片字节大小: ${(imageBufferSize / 1024).toFixed(2)} KB (${imageBufferSize} 字节)`);
@@ -14715,7 +14902,7 @@ class ScreenshotManager {
14715
14902
  const sendCallbackTime = performance.now() - sendCallbackStartTime;
14716
14903
  const totalSendTime = performance.now() - sendStartTime;
14717
14904
  const totalTime = performance.now() - scheduleStartTime;
14718
- if (!this.options.silentMode) {
14905
+ if (this.options.debug) {
14719
14906
  console.log('📸 [轮询] ✅ 二进制数据已发送到 iframe');
14720
14907
  console.log(` ⏱️ 发送回调耗时: ${sendCallbackTime.toFixed(2)}ms`);
14721
14908
  console.log(` ⏱️ 发送阶段总耗时: ${totalSendTime.toFixed(2)}ms`);
@@ -14724,7 +14911,9 @@ class ScreenshotManager {
14724
14911
  }
14725
14912
  }
14726
14913
  catch (error) {
14727
- console.error('📸 [轮询] ❌ 处理二进制数据失败:', error);
14914
+ if (this.options.debug) {
14915
+ console.error('📸 [轮询] ❌ 处理二进制数据失败:', error);
14916
+ }
14728
14917
  }
14729
14918
  }
14730
14919
  // 任务完成(无压缩模式)
@@ -14733,7 +14922,7 @@ class ScreenshotManager {
14733
14922
  else if (this.currentBinaryConfig && this.options.compress) {
14734
14923
  // 启用了压缩,等待 Worker 压缩完成后在 onmessage 中发送
14735
14924
  // 任务完成标志会在压缩完成的回调中设置
14736
- if (!this.options.silentMode) {
14925
+ if (this.options.debug) {
14737
14926
  console.log('📸 [轮询] 等待 Worker 压缩完成后发送到 iframe...');
14738
14927
  }
14739
14928
  }
@@ -14743,7 +14932,7 @@ class ScreenshotManager {
14743
14932
  }
14744
14933
  }
14745
14934
  catch (error) {
14746
- if (!this.options.silentMode) {
14935
+ if (this.options.debug) {
14747
14936
  console.error('📸 [轮询] 截图失败:', error);
14748
14937
  }
14749
14938
  // 任务失败,标记为完成
@@ -14768,7 +14957,7 @@ class ScreenshotManager {
14768
14957
  if (!this.isRunning) {
14769
14958
  return;
14770
14959
  }
14771
- if (!this.options.silentMode) {
14960
+ if (this.options.debug) {
14772
14961
  console.log('📸 停止轮询截图');
14773
14962
  }
14774
14963
  this.isRunning = false;
@@ -14785,7 +14974,9 @@ class ScreenshotManager {
14785
14974
  */
14786
14975
  async captureOnce(force = false) {
14787
14976
  if (!this.isEnabled && !force) {
14788
- console.warn('📸 截图功能已禁用,无法执行截图。如需测试,请先调用 enable(true) 启用截图功能');
14977
+ if (this.options.debug) {
14978
+ console.warn('📸 截图功能已禁用,无法执行截图。如需测试,请先调用 enable(true) 启用截图功能');
14979
+ }
14789
14980
  return false;
14790
14981
  }
14791
14982
  return await this.takeScreenshot();
@@ -14795,14 +14986,16 @@ class ScreenshotManager {
14795
14986
  */
14796
14987
  async takeScreenshot(scheduleStartTime) {
14797
14988
  if (!this.targetElement) {
14798
- console.warn('📸 目标元素不存在');
14989
+ if (this.options.debug) {
14990
+ console.warn('📸 目标元素不存在');
14991
+ }
14799
14992
  return false;
14800
14993
  }
14801
14994
  // 记录截图开始时间
14802
14995
  const screenshotStartTime = performance.now();
14803
14996
  this.setupGlobalErrorHandlers();
14804
14997
  try {
14805
- if (!this.options.silentMode) {
14998
+ if (this.options.debug) {
14806
14999
  console.log(`📸 开始截图 #${this.screenshotCount + 1}...`);
14807
15000
  if (scheduleStartTime) {
14808
15001
  const waitTime = screenshotStartTime - scheduleStartTime;
@@ -14816,7 +15009,7 @@ class ScreenshotManager {
14816
15009
  this.waitForFonts()
14817
15010
  ]);
14818
15011
  const waitStylesTime = performance.now() - waitStylesStartTime;
14819
- if (!this.options.silentMode) {
15012
+ if (this.options.debug) {
14820
15013
  console.log(` ⏱️ 等待样式和字体加载耗时: ${waitStylesTime.toFixed(2)}ms`);
14821
15014
  }
14822
15015
  // 等待元素完全渲染(特别是对于 modern-screenshot)
@@ -14825,12 +15018,12 @@ class ScreenshotManager {
14825
15018
  requestAnimationFrame(() => resolve());
14826
15019
  }));
14827
15020
  const waitRenderTime = performance.now() - waitRenderStartTime;
14828
- if (!this.options.silentMode) {
15021
+ if (this.options.debug) {
14829
15022
  console.log(` ⏱️ 等待渲染完成耗时: ${waitRenderTime.toFixed(2)}ms`);
14830
15023
  }
14831
15024
  // 选择截图引擎
14832
15025
  const selectedEngine = this.options.engine || 'modern-screenshot';
14833
- if (!this.options.silentMode) {
15026
+ if (this.options.debug) {
14834
15027
  console.log(`📸 使用截图引擎: ${selectedEngine}`);
14835
15028
  }
14836
15029
  // 优化:如果启用预加载,才预处理图片;否则让引擎按需加载
@@ -14870,7 +15063,7 @@ class ScreenshotManager {
14870
15063
  dataUrl = await this.takeScreenshotWithModernScreenshot(this.targetElement);
14871
15064
  }
14872
15065
  const engineTime = performance.now() - engineStartTime;
14873
- if (!this.options.silentMode) {
15066
+ if (this.options.debug) {
14874
15067
  console.log(` ⏱️ 截图引擎执行耗时: ${engineTime.toFixed(2)}ms`);
14875
15068
  }
14876
15069
  const timestamp = Date.now();
@@ -14884,7 +15077,7 @@ class ScreenshotManager {
14884
15077
  const removed = this.screenshotHistory.shift();
14885
15078
  // 强制 GC(如果可能)
14886
15079
  if (removed && removed.length > 1000000) { // 大于1MB的字符串
14887
- if (!this.options.silentMode) {
15080
+ if (this.options.debug) {
14888
15081
  console.log(`📸 清理旧截图,释放内存: ${Math.round(removed.length * 0.75 / 1024)} KB`);
14889
15082
  }
14890
15083
  }
@@ -14898,7 +15091,7 @@ class ScreenshotManager {
14898
15091
  if (removed) {
14899
15092
  const removedSize = removed.length;
14900
15093
  totalSize -= removedSize;
14901
- if (!this.options.silentMode) {
15094
+ if (this.options.debug) {
14902
15095
  console.warn(`📸 ⚠️ 历史记录总大小超过限制,清理最旧截图: ${Math.round(removedSize * 0.75 / 1024)} KB`);
14903
15096
  }
14904
15097
  }
@@ -14910,7 +15103,7 @@ class ScreenshotManager {
14910
15103
  const screenshotTotalTime = performance.now() - screenshotStartTime;
14911
15104
  // 打印基本信息
14912
15105
  const base64Data = dataUrl.split(',')[1] || '';
14913
- if (!this.options.silentMode) {
15106
+ if (this.options.debug) {
14914
15107
  console.log('📸 截图完成:');
14915
15108
  console.log(`📸 编号: #${this.screenshotCount}`);
14916
15109
  console.log(`📸 时间: ${new Date(timestamp).toLocaleTimeString()}`);
@@ -14929,7 +15122,7 @@ class ScreenshotManager {
14929
15122
  // 确保 Worker 已创建
14930
15123
  if (!this.worker) {
14931
15124
  this.worker = this.createWorker();
14932
- if (!this.options.silentMode) {
15125
+ if (this.options.debug) {
14933
15126
  if (this.worker) {
14934
15127
  console.log('📸 Worker 已创建,准备压缩');
14935
15128
  }
@@ -14941,7 +15134,7 @@ class ScreenshotManager {
14941
15134
  if (this.worker) {
14942
15135
  // 记录压缩开始时间
14943
15136
  const compressStartTime = performance.now();
14944
- if (!this.options.silentMode) {
15137
+ if (this.options.debug) {
14945
15138
  console.log('📸 发送到 WebWorker 进行压缩...');
14946
15139
  }
14947
15140
  // 保存原始 dataUrl 用于后续对比(在 Worker 压缩完成后)
@@ -14964,7 +15157,7 @@ class ScreenshotManager {
14964
15157
  }
14965
15158
  else {
14966
15159
  // Worker 不可用,如果配置了二进制模式,直接发送原始截图
14967
- if (!this.options.silentMode) {
15160
+ if (this.options.debug) {
14968
15161
  console.warn('📸 ⚠️ Worker 不可用,跳过压缩(使用原始截图)');
14969
15162
  }
14970
15163
  // Worker 不可用时,如果配置了二进制模式,立即发送原始截图
@@ -14980,7 +15173,7 @@ class ScreenshotManager {
14980
15173
  data: combinedBuffer
14981
15174
  };
14982
15175
  this.sendToIframeCallback(message);
14983
- if (!this.options.silentMode) {
15176
+ if (this.options.debug) {
14984
15177
  console.log('📸 [Worker 不可用] ✅ 原始截图已发送到 iframe');
14985
15178
  }
14986
15179
  }
@@ -15005,7 +15198,7 @@ class ScreenshotManager {
15005
15198
  console.error('📸 截图失败:', err);
15006
15199
  this.error = errorMessage;
15007
15200
  }
15008
- else if (!this.options.silentMode) {
15201
+ else if (this.options.debug) {
15009
15202
  console.warn('📸 截图遇到跨域问题(已忽略)');
15010
15203
  }
15011
15204
  return false;
@@ -15022,7 +15215,7 @@ class ScreenshotManager {
15022
15215
  * 注意:snapdom 内部使用 worker 进行截图处理,会在后台线程执行,不会阻塞主线程
15023
15216
  */
15024
15217
  async takeScreenshotWithSnapdom(element) {
15025
- if (!this.options.silentMode) {
15218
+ if (this.options.debug) {
15026
15219
  console.log('📸 使用 snapdom 引擎截图...');
15027
15220
  }
15028
15221
  // 限制只截图可见区域(viewport),避免截图不可见区域
@@ -15062,7 +15255,7 @@ class ScreenshotManager {
15062
15255
  // 简化方案:直接使用 body,但添加尺寸限制选项(如果 snapdom 支持)
15063
15256
  // 如果不支持,则只能截图整个 body
15064
15257
  targetElement = element;
15065
- if (!this.options.silentMode) {
15258
+ if (this.options.debug) {
15066
15259
  console.log(`📸 注意:snapdom 将截图整个 body,建议使用 html2canvas 或 modern-screenshot 来限制可见区域`);
15067
15260
  console.log(`📸 可见区域尺寸: ${window.innerWidth}x${window.innerHeight}`);
15068
15261
  }
@@ -15079,7 +15272,7 @@ class ScreenshotManager {
15079
15272
  elementRect.left >= 0 &&
15080
15273
  elementRect.bottom <= window.innerHeight &&
15081
15274
  elementRect.right <= window.innerWidth;
15082
- if (!isFullyVisible && !this.options.silentMode) {
15275
+ if (!isFullyVisible && this.options.debug) {
15083
15276
  console.warn(`📸 ⚠️ 元素部分不可见,snapdom 可能会截图整个元素(包括不可见部分)`);
15084
15277
  console.warn(`📸 建议:使用 html2canvas 或 modern-screenshot 来限制可见区域`);
15085
15278
  }
@@ -15097,12 +15290,12 @@ class ScreenshotManager {
15097
15290
  proxyUrl = proxyUrl.endsWith('?') ? proxyUrl + 'url=' : proxyUrl + '?url=';
15098
15291
  }
15099
15292
  options.useProxy = proxyUrl;
15100
- if (!this.options.silentMode) {
15293
+ if (this.options.debug) {
15101
15294
  console.log(`📸 使用代理服务器处理跨域图片: ${proxyUrl}`);
15102
15295
  }
15103
15296
  }
15104
15297
  else {
15105
- if (!this.options.silentMode) {
15298
+ if (this.options.debug) {
15106
15299
  if (!this.options.useProxy) {
15107
15300
  console.log('📸 代理功能已禁用(useProxy: false)');
15108
15301
  }
@@ -15144,7 +15337,7 @@ class ScreenshotManager {
15144
15337
  parent.removeChild(container);
15145
15338
  }
15146
15339
  }
15147
- if (!this.options.silentMode) {
15340
+ if (this.options.debug) {
15148
15341
  console.log(`📸 snapdom 截图成功!格式: ${outputFormat}, 尺寸: ${img.width}x${img.height}`);
15149
15342
  }
15150
15343
  return dataUrl;
@@ -15162,14 +15355,14 @@ class ScreenshotManager {
15162
15355
  const errorName = error instanceof Error ? error.name : 'Unknown';
15163
15356
  // 针对不同类型的错误给出具体提示
15164
15357
  if (errorName === 'EncodingError' || errorMessage.includes('cannot be decoded')) {
15165
- if (!this.options.silentMode) {
15358
+ if (this.options.debug) {
15166
15359
  console.warn('📸 ⚠️ 图片解码失败 - 这通常是因为跨域图片无法访问');
15167
15360
  console.warn('📸 💡 解决方案:配置 proxyUrl 选项');
15168
15361
  console.warn('📸 📖 参考: https://snapdom.dev/#cors');
15169
15362
  }
15170
15363
  }
15171
15364
  else if (errorMessage.includes('CORS') || errorMessage.includes('cross-origin')) {
15172
- if (!this.options.silentMode) {
15365
+ if (this.options.debug) {
15173
15366
  console.warn('📸 ⚠️ 检测到 CORS 错误,建议配置 proxyUrl 选项');
15174
15367
  }
15175
15368
  }
@@ -15194,7 +15387,7 @@ class ScreenshotManager {
15194
15387
  * - 需要快速截图
15195
15388
  */
15196
15389
  async takeScreenshotWithHtml2Canvas(element) {
15197
- if (!this.options.silentMode) {
15390
+ if (this.options.debug) {
15198
15391
  console.log('📸 使用 html2canvas 引擎截图...');
15199
15392
  }
15200
15393
  try {
@@ -15248,7 +15441,7 @@ class ScreenshotManager {
15248
15441
  : (isMobile ? 0.5 : 0.6), // 用户未配置,使用默认值
15249
15442
  useCORS: this.options.enableCORS,
15250
15443
  allowTaint: !this.options.enableCORS, // 如果启用 CORS,不允许 taint
15251
- logging: !this.options.silentMode, // 使用配置的静默模式
15444
+ logging: this.options.debug, // 使用配置的静默模式
15252
15445
  // foreignObjectRendering: true, // ❌ 移除:可能导致跨域图片不显示
15253
15446
  // removeContainer: true, // ❌ 移除:移到后面,避免重复定义
15254
15447
  // 不设置 width 和 height,让 html2canvas 自动计算
@@ -15386,12 +15579,12 @@ class ScreenshotManager {
15386
15579
  }
15387
15580
  catch (e) {
15388
15581
  // 忽略内联化错误
15389
- if (!this.options.silentMode) {
15582
+ if (this.options.debug) {
15390
15583
  console.warn('📸 iOS 样式内联化失败:', e);
15391
15584
  }
15392
15585
  }
15393
15586
  }
15394
- if (!this.options.silentMode) {
15587
+ if (this.options.debug) {
15395
15588
  const styleLinks = clonedDoc.querySelectorAll('link[rel="stylesheet"]').length;
15396
15589
  const styleTags = clonedDoc.querySelectorAll('style').length;
15397
15590
  console.log(`📸 onclone: 已复制 ${styleLinks} 个样式表链接和 ${styleTags} 个内联样式标签${isIOS ? ' (iOS 模式:已内联化计算样式)' : ''}`);
@@ -15440,7 +15633,7 @@ class ScreenshotManager {
15440
15633
  const cachedDataUrl = this.getCachedImage(originalSrc);
15441
15634
  if (cachedDataUrl) {
15442
15635
  img.src = cachedDataUrl;
15443
- if (!this.options.silentMode) {
15636
+ if (this.options.debug) {
15444
15637
  console.log(`📸 使用缓存的图片: ${originalSrc.substring(0, 50)}...`);
15445
15638
  }
15446
15639
  return;
@@ -15449,7 +15642,7 @@ class ScreenshotManager {
15449
15642
  // 构建代理 URL
15450
15643
  try {
15451
15644
  if (!this.options.proxyUrl) {
15452
- if (!this.options.silentMode) {
15645
+ if (this.options.debug) {
15453
15646
  console.warn(`📸 ⚠️ 未配置代理 URL,跨域图片可能无法显示: ${originalSrc}`);
15454
15647
  }
15455
15648
  return;
@@ -15462,19 +15655,19 @@ class ScreenshotManager {
15462
15655
  baseUrl = baseUrl.replace(/[?&]$/, '');
15463
15656
  const proxyUrl = `${baseUrl}?${params.toString()}`;
15464
15657
  img.src = proxyUrl;
15465
- if (!this.options.silentMode) {
15658
+ if (this.options.debug) {
15466
15659
  console.log(`📸 使用代理 URL 替换跨域图片: ${originalSrc.substring(0, 50)}... -> ${proxyUrl.substring(0, 50)}...`);
15467
15660
  }
15468
15661
  }
15469
15662
  catch (error) {
15470
- if (!this.options.silentMode) {
15663
+ if (this.options.debug) {
15471
15664
  console.warn(`📸 ⚠️ 处理图片 URL 失败: ${originalSrc}`, error);
15472
15665
  }
15473
15666
  }
15474
15667
  });
15475
15668
  };
15476
15669
  }
15477
- if (!this.options.silentMode) {
15670
+ if (this.options.debug) {
15478
15671
  console.log(`📸 html2canvas 配置: 元素尺寸 ${elementWidth}x${elementHeight}, 质量 ${finalQuality.toFixed(2)}, 缩放 ${options.scale}`);
15479
15672
  console.log(`📸 html2canvas 将自动计算截图尺寸(不限制 width/height)`);
15480
15673
  console.log(`📸 用户配置质量: ${this.options.quality}, 实际使用质量: ${finalQuality.toFixed(2)}`);
@@ -15519,7 +15712,7 @@ class ScreenshotManager {
15519
15712
  if (!dataUrl || dataUrl.length < 100) {
15520
15713
  throw new Error('生成的截图数据无效或过短');
15521
15714
  }
15522
- if (!this.options.silentMode) {
15715
+ if (this.options.debug) {
15523
15716
  console.log(`📸 html2canvas 截图成功!格式: ${mimeType}, 尺寸: ${canvas.width}x${canvas.height}, 质量: ${finalQualityForExport?.toFixed(2) || 'N/A (PNG)'}`);
15524
15717
  console.log(`📸 输出格式: ${mimeType}, 用户配置质量: ${this.options.quality}, 实际使用质量: ${finalQualityForExport?.toFixed(2) || 'N/A (PNG)'}`);
15525
15718
  }
@@ -15527,7 +15720,7 @@ class ScreenshotManager {
15527
15720
  }
15528
15721
  catch (error) {
15529
15722
  const errorMessage = error instanceof Error ? error.message : String(error);
15530
- if (!this.options.silentMode) {
15723
+ if (this.options.debug) {
15531
15724
  console.error('📸 html2canvas 截图失败:', errorMessage);
15532
15725
  if (errorMessage.includes('CORS') || errorMessage.includes('cross-origin')) {
15533
15726
  console.warn('📸 💡 建议:配置 proxyUrl 选项处理跨域图片');
@@ -15560,7 +15753,7 @@ class ScreenshotManager {
15560
15753
  if (this.isScreenshotInProgress) {
15561
15754
  // 队列最多保留 1 个请求,避免积压
15562
15755
  if (this.screenshotQueue.length >= 1) {
15563
- if (!this.options.silentMode) {
15756
+ if (this.options.debug) {
15564
15757
  console.log('📸 截图队列已满,跳过当前请求(等待队列处理)');
15565
15758
  }
15566
15759
  // 等待队列中的请求完成
@@ -15589,7 +15782,7 @@ class ScreenshotManager {
15589
15782
  });
15590
15783
  }
15591
15784
  this.isScreenshotInProgress = true;
15592
- if (!this.options.silentMode) {
15785
+ if (this.options.debug) {
15593
15786
  console.log('📸 使用 modern-screenshot 引擎截图(Worker 模式)...');
15594
15787
  }
15595
15788
  try {
@@ -15609,7 +15802,7 @@ class ScreenshotManager {
15609
15802
  elementWidth = element.clientWidth || element.offsetWidth || element.scrollWidth;
15610
15803
  elementHeight = element.clientHeight || element.offsetHeight || element.scrollHeight;
15611
15804
  }
15612
- if (!this.options.silentMode) {
15805
+ if (this.options.debug) {
15613
15806
  console.log(`📸 目标元素: ${element.tagName}${element.id ? '#' + element.id : ''}${element.className ? '.' + element.className.split(' ').join('.') : ''}`);
15614
15807
  console.log(`📸 元素尺寸: ${elementWidth}x${elementHeight}`);
15615
15808
  console.log(`📸 scrollWidth: ${element.scrollWidth}, scrollHeight: ${element.scrollHeight}`);
@@ -15643,7 +15836,7 @@ class ScreenshotManager {
15643
15836
  // 检查内存缓存(优先使用缓存,带过期时间检查)
15644
15837
  const cachedDataUrl = this.getCachedImage(url);
15645
15838
  if (cachedDataUrl) {
15646
- if (!this.options.silentMode) {
15839
+ if (this.options.debug) {
15647
15840
  console.log(`📸 ✅ 使用内存缓存图片: ${url.substring(0, 50)}...`);
15648
15841
  }
15649
15842
  return cachedDataUrl;
@@ -15695,7 +15888,7 @@ class ScreenshotManager {
15695
15888
  }
15696
15889
  }
15697
15890
  catch (error) {
15698
- if (!this.options.silentMode) {
15891
+ if (this.options.debug) {
15699
15892
  console.warn(`📸 代理处理图片失败: ${url.substring(0, 100)}...`, error);
15700
15893
  }
15701
15894
  // 失败时返回原 URL,让 modern-screenshot 自己处理
@@ -15744,7 +15937,7 @@ class ScreenshotManager {
15744
15937
  // 按照 demo 的方式:只在 context 不存在时创建,之后一直复用
15745
15938
  // 不进行复杂的检测和重新创建逻辑
15746
15939
  if (!this.screenshotContext) {
15747
- if (!this.options.silentMode) {
15940
+ if (this.options.debug) {
15748
15941
  console.log(`📸 创建截图 Worker 上下文...`);
15749
15942
  console.log(`📸 Worker 模式: ${workerNumber} 个 Worker`);
15750
15943
  }
@@ -15765,23 +15958,23 @@ class ScreenshotManager {
15765
15958
  // 如果用户指定了 workerUrl,使用指定的 URL
15766
15959
  if (this.options.workerUrl) {
15767
15960
  simpleContextOptions.workerUrl = this.options.workerUrl;
15768
- if (!this.options.silentMode) {
15961
+ if (this.options.debug) {
15769
15962
  console.log(`📸 使用指定的 Worker URL: ${this.options.workerUrl}`);
15770
15963
  }
15771
15964
  }
15772
15965
  else {
15773
- if (!this.options.silentMode) {
15966
+ if (this.options.debug) {
15774
15967
  console.log('📸 Worker URL 未指定,modern-screenshot 将自动处理');
15775
15968
  }
15776
15969
  }
15777
15970
  try {
15778
15971
  this.screenshotContext = await createContext$1(element, simpleContextOptions);
15779
- if (!this.options.silentMode) {
15972
+ if (this.options.debug) {
15780
15973
  console.log('📸 Worker 上下文创建成功');
15781
15974
  }
15782
15975
  }
15783
15976
  catch (error) {
15784
- if (!this.options.silentMode) {
15977
+ if (this.options.debug) {
15785
15978
  console.error('📸 创建 Worker 上下文失败:', error);
15786
15979
  }
15787
15980
  throw error;
@@ -15791,7 +15984,7 @@ class ScreenshotManager {
15791
15984
  // 按照 demo 的方式:使用 domToWebp 时传递配置参数
15792
15985
  let dataUrl;
15793
15986
  const outputFormat = this.options.outputFormat || 'webp';
15794
- if (!this.options.silentMode) {
15987
+ if (this.options.debug) {
15795
15988
  console.log(`📸 使用 ${outputFormat.toUpperCase()} 格式截图(直接输出,无需转换)...`);
15796
15989
  }
15797
15990
  // 构建 domToWebp/domToJpeg/domToPng 的配置参数(和 demo 一致)
@@ -15826,14 +16019,14 @@ class ScreenshotManager {
15826
16019
  if (!dataUrl || dataUrl.length < 100) {
15827
16020
  throw new Error('生成的截图数据无效或过短');
15828
16021
  }
15829
- if (!this.options.silentMode) {
16022
+ if (this.options.debug) {
15830
16023
  console.log(`📸 ✅ modern-screenshot 截图成功(Worker 模式,${outputFormat.toUpperCase()} 格式)`);
15831
16024
  }
15832
16025
  return dataUrl;
15833
16026
  }
15834
16027
  catch (workerError) {
15835
16028
  // Worker 模式失败,回退到普通模式(和 demo 一致)
15836
- if (!this.options.silentMode) {
16029
+ if (this.options.debug) {
15837
16030
  console.warn('📸 Worker 模式失败,回退到普通模式:', workerError);
15838
16031
  }
15839
16032
  // 销毁失败的 context
@@ -15876,7 +16069,7 @@ class ScreenshotManager {
15876
16069
  if (!dataUrl || dataUrl.length < 100) {
15877
16070
  throw new Error('生成的截图数据无效或过短');
15878
16071
  }
15879
- if (!this.options.silentMode) {
16072
+ if (this.options.debug) {
15880
16073
  console.log(`📸 ✅ modern-screenshot 截图成功(普通模式,${outputFormat.toUpperCase()} 格式)`);
15881
16074
  }
15882
16075
  return dataUrl;
@@ -15884,7 +16077,7 @@ class ScreenshotManager {
15884
16077
  }
15885
16078
  catch (error) {
15886
16079
  const errorMessage = error instanceof Error ? error.message : String(error);
15887
- if (!this.options.silentMode) {
16080
+ if (this.options.debug) {
15888
16081
  console.error('📸 modern-screenshot 截图失败:', errorMessage);
15889
16082
  console.error('📸 元素信息:', {
15890
16083
  width: rect.width,
@@ -15908,7 +16101,7 @@ class ScreenshotManager {
15908
16101
  catch (error) {
15909
16102
  // 外层错误处理:确保即使发生错误也释放锁
15910
16103
  const errorMessage = error instanceof Error ? error.message : String(error);
15911
- if (!this.options.silentMode) {
16104
+ if (this.options.debug) {
15912
16105
  console.error('📸 modern-screenshot 截图异常:', errorMessage);
15913
16106
  }
15914
16107
  throw error;
@@ -15962,7 +16155,7 @@ class ScreenshotManager {
15962
16155
  link.crossOrigin = 'anonymous';
15963
16156
  document.head.appendChild(link);
15964
16157
  this.preconnected = true;
15965
- if (!this.options.silentMode) {
16158
+ if (this.options.debug) {
15966
16159
  console.log(`📸 ✅ 已预连接代理服务器: ${proxyOrigin}`);
15967
16160
  }
15968
16161
  }
@@ -16087,7 +16280,7 @@ class ScreenshotManager {
16087
16280
  if (networkImages.length === 0) {
16088
16281
  return;
16089
16282
  }
16090
- if (!this.options.silentMode) {
16283
+ if (this.options.debug) {
16091
16284
  const totalImages = images.length;
16092
16285
  console.log(`📸 发现 ${networkImages.length}/${totalImages} 个可视区域内的跨域图片,开始并行预加载...`);
16093
16286
  }
@@ -16095,7 +16288,7 @@ class ScreenshotManager {
16095
16288
  // 只有当 useProxy 为 true 且 proxyUrl 存在时才使用代理
16096
16289
  const shouldUseProxy = this.options.useProxy && this.options.proxyUrl && this.options.proxyUrl.trim() !== '';
16097
16290
  if (shouldUseProxy) {
16098
- if (!this.options.silentMode) {
16291
+ if (this.options.debug) {
16099
16292
  console.log(`📸 使用代理服务器处理跨域图片: ${this.options.proxyUrl}`);
16100
16293
  }
16101
16294
  // 优化:增加并发数,使用更大的批次
@@ -16123,13 +16316,13 @@ class ScreenshotManager {
16123
16316
  }
16124
16317
  catch (error) {
16125
16318
  // 静默失败,不影响其他图片
16126
- if (!this.options.silentMode) {
16319
+ if (this.options.debug) {
16127
16320
  console.warn(`📸 ❌ 代理预加载失败: ${originalSrc.substring(0, 50)}...`);
16128
16321
  }
16129
16322
  }
16130
16323
  }));
16131
16324
  }));
16132
- if (!this.options.silentMode) {
16325
+ if (this.options.debug) {
16133
16326
  console.log(`📸 ✅ 预处理完成,缓存了 ${this.imageProxyCache.size} 个代理图片`);
16134
16327
  }
16135
16328
  }
@@ -16154,7 +16347,7 @@ class ScreenshotManager {
16154
16347
  // 移除末尾的 ? 或 &(如果有)
16155
16348
  baseUrl = baseUrl.replace(/[?&]$/, '');
16156
16349
  const requestUrl = `${baseUrl}?${params.toString()}`;
16157
- if (!this.options.silentMode) {
16350
+ if (this.options.debug) {
16158
16351
  console.log(`📸 🔄 代理请求 URL: ${requestUrl.substring(0, 200)}...`);
16159
16352
  }
16160
16353
  // 请求代理服务器
@@ -16173,7 +16366,7 @@ class ScreenshotManager {
16173
16366
  const blob = await response.blob();
16174
16367
  // 将 blob 转换为 data URL(用于 modern-screenshot 兼容性)
16175
16368
  const dataUrl = await this.blobToDataUrl(blob);
16176
- if (!this.options.silentMode) {
16369
+ if (this.options.debug) {
16177
16370
  console.log(`📸 ✅ 代理模式成功(已转换为 data URL): ${imageUrl.substring(0, 100)}...`);
16178
16371
  }
16179
16372
  return dataUrl;
@@ -16204,7 +16397,7 @@ class ScreenshotManager {
16204
16397
  if (sizeMB > maxSizeMB) {
16205
16398
  if (this.options.skipLargeImages) {
16206
16399
  // 跳过过大的图片,返回占位符
16207
- if (!this.options.silentMode) {
16400
+ if (this.options.debug) {
16208
16401
  console.warn(`📸 ⚠️ 跳过过大图片(${sizeMB.toFixed(2)}MB > ${maxSizeMB}MB): ${url.substring(0, 100)}...`);
16209
16402
  }
16210
16403
  // 返回一个 1x1 的透明占位符,避免截图失败
@@ -16212,7 +16405,7 @@ class ScreenshotManager {
16212
16405
  }
16213
16406
  else {
16214
16407
  // 不跳过,但添加警告
16215
- if (!this.options.silentMode) {
16408
+ if (this.options.debug) {
16216
16409
  console.warn(`📸 ⚠️ 图片较大(${sizeMB.toFixed(2)}MB),可能导致内存问题: ${url.substring(0, 100)}...`);
16217
16410
  }
16218
16411
  }
@@ -16226,14 +16419,14 @@ class ScreenshotManager {
16226
16419
  if (blobSizeMB > maxSizeMB) {
16227
16420
  if (this.options.skipLargeImages) {
16228
16421
  // 跳过过大的图片,返回占位符
16229
- if (!this.options.silentMode) {
16422
+ if (this.options.debug) {
16230
16423
  console.warn(`📸 ⚠️ 跳过过大图片(实际大小 ${blobSizeMB.toFixed(2)}MB > ${maxSizeMB}MB): ${url.substring(0, 100)}...`);
16231
16424
  }
16232
16425
  return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
16233
16426
  }
16234
16427
  else {
16235
16428
  // 不跳过,但添加警告
16236
- if (!this.options.silentMode) {
16429
+ if (this.options.debug) {
16237
16430
  console.warn(`📸 ⚠️ 图片较大(实际大小 ${blobSizeMB.toFixed(2)}MB),可能导致内存问题: ${url.substring(0, 100)}...`);
16238
16431
  }
16239
16432
  }
@@ -16249,7 +16442,7 @@ class ScreenshotManager {
16249
16442
  }
16250
16443
  catch (error) {
16251
16444
  // 下载失败,返回原 URL,让 modern-screenshot 自己处理
16252
- if (!this.options.silentMode) {
16445
+ if (this.options.debug) {
16253
16446
  console.warn(`📸 ⚠️ 下载图片失败: ${url.substring(0, 100)}...`, error);
16254
16447
  }
16255
16448
  return url;
@@ -16680,7 +16873,7 @@ class ScreenshotManager {
16680
16873
  // 更新历史记录为压缩后的数据
16681
16874
  this.screenshotHistory[this.screenshotHistory.length - 1] = compressed.dataUrl;
16682
16875
  // 打印压缩统计信息和 base64 对比
16683
- if (!this.options.silentMode) {
16876
+ if (this.options.debug) {
16684
16877
  if (compressed.error) {
16685
16878
  // 压缩失败,使用原始数据
16686
16879
  console.warn('📸 [Worker 压缩] ⚠️ 压缩失败,使用原始截图');
@@ -16734,7 +16927,7 @@ class ScreenshotManager {
16734
16927
  };
16735
16928
  newWorker.onerror = (e) => {
16736
16929
  console.error('📸 WebWorker 错误:', e);
16737
- if (!this.options.silentMode) {
16930
+ if (this.options.debug) {
16738
16931
  console.warn('📸 Worker 压缩失败,使用原始截图');
16739
16932
  }
16740
16933
  // Worker 发生错误时,如果配置了二进制模式,发送原始截图
@@ -16750,7 +16943,7 @@ class ScreenshotManager {
16750
16943
  data: combinedBuffer
16751
16944
  };
16752
16945
  this.sendToIframeCallback(message);
16753
- if (!this.options.silentMode) {
16946
+ if (this.options.debug) {
16754
16947
  console.log('📸 [Worker 错误] ✅ 原始截图已发送到 iframe');
16755
16948
  }
16756
16949
  }
@@ -16915,7 +17108,7 @@ class ScreenshotManager {
16915
17108
  async takeScreenshotAndSendBinary(config) {
16916
17109
  // 如果已经在运行,先停止再重新开始
16917
17110
  if (this.isRunning) {
16918
- if (!this.options.silentMode) {
17111
+ if (this.options.debug) {
16919
17112
  console.log(`📸 更新轮询间隔: ${this.dynamicInterval || this.options.interval}ms`);
16920
17113
  }
16921
17114
  this.stopScreenshot();
@@ -16946,7 +17139,7 @@ class ScreenshotManager {
16946
17139
  // 验证:base64Data(从 latestScreenshot 提取)和 imageBufferBase64(从 imageBuffer 转换)应该一致
16947
17140
  const isBase64Same = base64Data === imageBufferBase64;
16948
17141
  // 打印 imageBuffer 的 base64 编码(用于和接收端对比)
16949
- if (!this.options.silentMode) {
17142
+ if (this.options.debug) {
16950
17143
  console.log('📸 [发送前] 数据流程分析:');
16951
17144
  console.log(` latestScreenshot: ${latestScreenshot.substring(0, 50)}... (原始 data URL)`);
16952
17145
  console.log(` base64Data (从 latestScreenshot 提取): 长度 ${base64Data.length} 字符`);
@@ -16972,7 +17165,7 @@ class ScreenshotManager {
16972
17165
  const combinedBuffer = this.combineBinaryData(configBuffer, imageBuffer);
16973
17166
  const combinedBufferSize = combinedBuffer.byteLength;
16974
17167
  // 打印大小信息
16975
- if (!this.options.silentMode) {
17168
+ if (this.options.debug) {
16976
17169
  console.log('📸 [大小统计]');
16977
17170
  console.log(` Base64 大小: ${base64Size} 字符`);
16978
17171
  console.log(` 图片字节大小: ${(imageBufferSize / 1024).toFixed(2)} KB (${imageBufferSize} 字节)`);
@@ -16986,7 +17179,7 @@ class ScreenshotManager {
16986
17179
  data: combinedBuffer
16987
17180
  };
16988
17181
  this.sendToIframeCallback(message);
16989
- if (!this.options.silentMode) {
17182
+ if (this.options.debug) {
16990
17183
  console.log('📸 [iframe] ✅ 二进制数据已发送到 iframe');
16991
17184
  }
16992
17185
  }
@@ -17000,14 +17193,14 @@ class ScreenshotManager {
17000
17193
  }
17001
17194
  }
17002
17195
  else {
17003
- if (!this.options.silentMode) {
17196
+ if (this.options.debug) {
17004
17197
  console.warn('📸 [iframe] 截图完成但未找到截图数据');
17005
17198
  }
17006
17199
  }
17007
17200
  }
17008
17201
  else {
17009
17202
  // 启用了压缩,等待 Worker 压缩完成后在 onmessage 中自动发送
17010
- if (!this.options.silentMode) {
17203
+ if (this.options.debug) {
17011
17204
  console.log('📸 [iframe] 等待 Worker 压缩完成后发送到 iframe...');
17012
17205
  }
17013
17206
  }
@@ -17037,7 +17230,7 @@ class ScreenshotManager {
17037
17230
  const base64Data = dataUrl.split(',')[1] || '';
17038
17231
  const base64Size = base64Data.length;
17039
17232
  // 完整打印发送前的 base64 信息(用于调试)
17040
- if (!this.options.silentMode) {
17233
+ if (this.options.debug) {
17041
17234
  console.log('📸 [发送前] Base64 信息:');
17042
17235
  console.log(` Base64 长度: ${base64Size} 字符`);
17043
17236
  console.log(` 📸 [发送前] 完整 Base64:`);
@@ -17061,7 +17254,7 @@ class ScreenshotManager {
17061
17254
  const combinedBufferSize = combinedBuffer.byteLength;
17062
17255
  const combineTime = performance.now() - combineStartTime;
17063
17256
  // 打印大小信息
17064
- if (!this.options.silentMode) {
17257
+ if (this.options.debug) {
17065
17258
  console.log('📸 [压缩后-大小统计]');
17066
17259
  console.log(` Base64 大小: ${base64Size} 字符`);
17067
17260
  console.log(` 图片字节大小: ${(imageBufferSize / 1024).toFixed(2)} KB (${imageBufferSize} 字节)`);
@@ -17084,7 +17277,7 @@ class ScreenshotManager {
17084
17277
  if (scheduleStartTime) {
17085
17278
  totalTime = performance.now() - scheduleStartTime;
17086
17279
  }
17087
- if (!this.options.silentMode) {
17280
+ if (this.options.debug) {
17088
17281
  console.log('📸 [压缩后] ✅ 二进制数据已发送到 iframe');
17089
17282
  console.log(` ⏱️ 发送回调耗时: ${sendCallbackTime.toFixed(2)}ms`);
17090
17283
  console.log(` ⏱️ 发送阶段总耗时: ${totalSendTime.toFixed(2)}ms`);
@@ -17187,8 +17380,8 @@ class ScreenshotManager {
17187
17380
  if (newOptions.compress !== undefined) {
17188
17381
  this.options.compress = newOptions.compress;
17189
17382
  }
17190
- if (newOptions.silentMode !== undefined) {
17191
- this.options.silentMode = newOptions.silentMode;
17383
+ if (newOptions.debug !== undefined) {
17384
+ this.options.debug = newOptions.debug;
17192
17385
  }
17193
17386
  if (newOptions.proxyUrl !== undefined) {
17194
17387
  this.options.proxyUrl = newOptions.proxyUrl;
@@ -17201,7 +17394,7 @@ class ScreenshotManager {
17201
17394
  if (this.screenshotContext) {
17202
17395
  try {
17203
17396
  destroyContext(this.screenshotContext);
17204
- if (!this.options.silentMode) {
17397
+ if (this.options.debug) {
17205
17398
  console.log(`📸 引擎已从 ${oldEngine} 切换到 ${newEngine},已清理旧 context`);
17206
17399
  }
17207
17400
  }
@@ -17210,12 +17403,12 @@ class ScreenshotManager {
17210
17403
  }
17211
17404
  this.screenshotContext = null;
17212
17405
  }
17213
- if (!this.options.silentMode) {
17406
+ if (this.options.debug) {
17214
17407
  console.log(`📸 截图引擎已更新: ${oldEngine} → ${newEngine}`);
17215
17408
  console.log(`📸 下次截图时将使用新引擎: ${newEngine}`);
17216
17409
  }
17217
17410
  }
17218
- else if (!this.options.silentMode) {
17411
+ else if (this.options.debug) {
17219
17412
  console.log('📸 截图配置已更新');
17220
17413
  }
17221
17414
  }
@@ -17315,7 +17508,7 @@ class ScreenshotManager {
17315
17508
  const itemSizeMB = value.dataUrl.length * 0.75 / (1024 * 1024);
17316
17509
  this.imageProxyCache.delete(key);
17317
17510
  currentSizeMB -= itemSizeMB;
17318
- if (!this.options.silentMode) {
17511
+ if (this.options.debug) {
17319
17512
  console.log(`📸 清理内存缓存(超过限制): ${key.substring(0, 50)}...`);
17320
17513
  }
17321
17514
  }
@@ -17340,7 +17533,7 @@ class ScreenshotManager {
17340
17533
  expiredUrls.forEach(url => {
17341
17534
  this.imageProxyCache.delete(url);
17342
17535
  });
17343
- if (expiredUrls.length > 0 && !this.options.silentMode) {
17536
+ if (expiredUrls.length > 0 && this.options.debug) {
17344
17537
  console.log(`📸 清理了 ${expiredUrls.length} 个过期缓存`);
17345
17538
  }
17346
17539
  }
@@ -17352,7 +17545,7 @@ class ScreenshotManager {
17352
17545
  // 每2分钟清理一次过期缓存(从5分钟改为2分钟,更频繁)
17353
17546
  setInterval(() => {
17354
17547
  this.cleanExpiredCache();
17355
- if (!this.options.silentMode) {
17548
+ if (this.options.debug) {
17356
17549
  const memoryCacheSize = this.imageProxyCache.size;
17357
17550
  const memoryCacheSizeMB = Array.from(this.imageProxyCache.values())
17358
17551
  .reduce((sum, cached) => sum + cached.dataUrl.length * 0.75 / (1024 * 1024), 0);
@@ -20692,16 +20885,17 @@ class CustomerServiceSDK {
20692
20885
  agent: config.agent,
20693
20886
  timestamp: Date.now()
20694
20887
  };
20695
- // 创建悬浮图标管理器(支持自定义位置)
20888
+ // 创建悬浮图标管理器(支持自定义位置和传送目标)
20696
20889
  const iconPosition = options?.iconPosition || undefined;
20697
- this.iconManager = new IconManager(iconPosition, this.debug);
20890
+ const iconTarget = options?.target || undefined;
20891
+ this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
20698
20892
  await this.iconManager.show();
20699
20893
  // 创建iframe管理器(自动检测设备类型)
20700
20894
  this.iframeManager = new IframeManager({
20701
20895
  src: iframeUrl,
20702
- mode: 'auto', // 自动根据设备类型选择模式
20703
- width: 400,
20704
- height: 600,
20896
+ mode: 'auto', // 自动根据设备类型选择模式(PC弹窗,移动端全屏)
20897
+ width: options?.width || 450, // PC模式宽度(像素,默认450px),移动端不使用
20898
+ height: options?.height || 600, // PC模式高度(像素),移动端不使用(强制全屏)
20705
20899
  allowClose: true,
20706
20900
  debug: this.debug, // 传递 debug 标志
20707
20901
  onMessage: (messageType, _data) => {
@@ -20713,8 +20907,9 @@ class CustomerServiceSDK {
20713
20907
  // checkScreenshot 消息由 ScreenshotManager 处理,不需要在这里处理
20714
20908
  },
20715
20909
  onClose: () => {
20716
- // iframe关闭时,清理图标拖动事件监听器
20910
+ // iframe关闭时,清理图标拖动事件监听器,并重新启用图标点击
20717
20911
  this.iconManager?.forceCleanupDragEvents();
20912
+ this.iconManager?.enableClick();
20718
20913
  },
20719
20914
  ...options
20720
20915
  });
@@ -20725,16 +20920,18 @@ class CustomerServiceSDK {
20725
20920
  // 打开iframe时清除红点通知
20726
20921
  this.clearNotification();
20727
20922
  this.iframeManager?.show();
20923
+ // iframe 打开后,禁用图标点击(防止重复打开)
20924
+ this.iconManager?.disableClick();
20728
20925
  });
20729
20926
  // 初始化截图管理器(如果启用了截图功能)
20730
20927
  if (config.screenshot) {
20731
20928
  // 默认截图目标为 document.body,可以通过配置自定义
20732
20929
  const targetElement = document.body;
20733
20930
  // 传入发送消息到 iframe 的回调函数
20734
- // 将 debug 配置传递给截图管理器(通过 silentMode 的相反值)
20931
+ // 将 debug 配置传递给截图管理器
20735
20932
  const screenshotOptions = {
20736
20933
  ...config.screenshot,
20737
- silentMode: !this.debug // debug=true 时 silentMode=false(显示日志),debug=false 时 silentMode=true(隐藏日志)
20934
+ debug: this.debug // 直接传递 debug 标志
20738
20935
  };
20739
20936
  this.screenshotManager = new ScreenshotManager(targetElement, screenshotOptions, (data) => {
20740
20937
  // 通过 IframeManager 发送消息到 iframe
@@ -20949,8 +21146,9 @@ class CustomerServiceSDK {
20949
21146
  return result.visitorId;
20950
21147
  }
20951
21148
  catch (error) {
20952
- // 错误始终输出
20953
- console.warn('❌ Failed to get device fingerprint, using fallback:', error);
21149
+ if (this.debug) {
21150
+ console.warn('❌ Failed to get device fingerprint, using fallback:', error);
21151
+ }
20954
21152
  const fallbackId = 'device_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
20955
21153
  if (this.debug) {
20956
21154
  console.log('🆔 Fallback Device ID:', fallbackId);