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.
@@ -5,7 +5,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
5
5
  // 直接使用base64字符串,避免打包后路径问题
6
6
  const iconImage = 'data:image/png;base64,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';
7
7
  class IconManager {
8
- constructor(position, debug = false) {
8
+ constructor(position, debug = false, target) {
9
9
  this.iconElement = null;
10
10
  this.badgeElement = null;
11
11
  this.onClickCallback = null;
@@ -17,10 +17,17 @@ class IconManager {
17
17
  this.iconStartY = 0;
18
18
  this.dragMoveHandler = null;
19
19
  this.dragEndHandler = null;
20
+ this.checkDragHandler = null; // 临时拖动检测监听器
21
+ this.dragStartHandler = null; // 拖动开始事件监听器
22
+ this.touchStartHandler = null; // 触摸开始事件监听器
20
23
  this.iconPosition = null; // 图标位置配置
21
24
  this.debug = false; // debug 模式标志
25
+ this.isClickEnabled = true; // 是否允许点击(iframe 打开时禁用)
26
+ this.target = null; // 图标传送目标元素(可以是 HTMLElement 或选择器字符串)
22
27
  this.iconPosition = position || null;
23
28
  this.debug = debug;
29
+ // 保存 target(可以是 HTMLElement 或字符串选择器)
30
+ this.target = target || null;
24
31
  }
25
32
  /**
26
33
  * 显示悬浮图标
@@ -34,7 +41,7 @@ class IconManager {
34
41
  this.iconElement.className = 'customer-sdk-icon';
35
42
  // 直接设置样式 - 图标容器
36
43
  const defaultStyle = {
37
- position: 'fixed',
44
+ position: 'absolute',
38
45
  width: '30px',
39
46
  height: '30px',
40
47
  backgroundColor: 'transparent', // 移除背景色,让图片直接显示
@@ -43,7 +50,7 @@ class IconManager {
43
50
  alignItems: 'center',
44
51
  justifyContent: 'center',
45
52
  cursor: 'pointer',
46
- zIndex: '999999',
53
+ zIndex: '1000002', // 确保图标始终在最上层(遮罩层 999998,iframe 容器 999999)
47
54
  boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
48
55
  userSelect: 'none',
49
56
  transition: 'transform 0.2s ease',
@@ -119,8 +126,18 @@ class IconManager {
119
126
  this.iconElement.appendChild(imgContainer);
120
127
  // 添加拖动和点击事件
121
128
  this.setupDragEvents();
122
- // 添加到页面
123
- document.body.appendChild(this.iconElement);
129
+ // 添加到目标元素(如果 target 是字符串,需要重新查找,因为可能在初始化时元素还不存在)
130
+ const targetElement = this.getTargetElement();
131
+ if (targetElement) {
132
+ targetElement.appendChild(this.iconElement);
133
+ }
134
+ else {
135
+ // 如果目标元素不存在,回退到 document.body
136
+ document.body.appendChild(this.iconElement);
137
+ if (this.debug) {
138
+ console.warn('Target element not found, icon added to document.body');
139
+ }
140
+ }
124
141
  if (this.debug) {
125
142
  console.log('CustomerSDK icon displayed');
126
143
  }
@@ -270,9 +287,11 @@ class IconManager {
270
287
  // 绑定事件处理器(用于后续清理)
271
288
  this.dragMoveHandler = this.handleDragMove.bind(this);
272
289
  this.dragEndHandler = this.handleDragEnd.bind(this);
273
- // 只在图标上监听开始事件
274
- this.iconElement.addEventListener('mousedown', this.handleDragStart.bind(this));
275
- this.iconElement.addEventListener('touchstart', this.handleDragStart.bind(this), { passive: false });
290
+ this.dragStartHandler = this.handleDragStart.bind(this);
291
+ // 只在图标上监听开始事件(保存引用以便后续移除)
292
+ this.iconElement.addEventListener('mousedown', this.dragStartHandler);
293
+ this.touchStartHandler = this.handleDragStart.bind(this);
294
+ this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
276
295
  }
277
296
  /**
278
297
  * 开始拖动
@@ -295,12 +314,33 @@ class IconManager {
295
314
  const rect = this.iconElement.getBoundingClientRect();
296
315
  this.iconStartX = rect.left;
297
316
  this.iconStartY = rect.top;
298
- // 添加拖动样式
299
- this.iconElement.style.transition = 'none';
300
- this.iconElement.style.cursor = 'grabbing';
317
+ // 注意:不要在这里立即移除 transition 和设置 cursor
318
+ // 只有在真正开始拖动时才修改样式,避免点击时图标位置跳动
301
319
  // 只在真正开始拖动时添加document事件监听
302
320
  // 先添加一个临时的move监听器来检测是否真的在拖动
303
321
  const checkDrag = (moveEvent) => {
322
+ // 检测事件目标:如果事件发生在iframe或其他元素上,停止检测拖动
323
+ const target = moveEvent.target;
324
+ if (target && target !== this.iconElement && !this.iconElement?.contains(target)) {
325
+ // 检查是否是iframe相关元素
326
+ const isIframeElement = target.tagName === 'IFRAME' ||
327
+ target.closest('iframe') ||
328
+ target.closest('.customer-sdk-container') ||
329
+ target.closest('.customer-sdk-overlay');
330
+ if (isIframeElement) {
331
+ // 如果事件发生在iframe相关元素上,停止检测并清理监听器
332
+ if (this.checkDragHandler) {
333
+ if ('touches' in moveEvent) {
334
+ document.removeEventListener('touchmove', this.checkDragHandler);
335
+ }
336
+ else {
337
+ document.removeEventListener('mousemove', this.checkDragHandler);
338
+ }
339
+ this.checkDragHandler = null;
340
+ }
341
+ return;
342
+ }
343
+ }
304
344
  const moveX = 'touches' in moveEvent ? moveEvent.touches[0].clientX : moveEvent.clientX;
305
345
  const moveY = 'touches' in moveEvent ? moveEvent.touches[0].clientY : moveEvent.clientY;
306
346
  const deltaX = moveX - this.dragStartX;
@@ -308,12 +348,20 @@ class IconManager {
308
348
  // 如果移动距离超过5px,开始拖动
309
349
  if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
310
350
  this.isDragging = true;
311
- // 移除临时监听器
312
- if ('touches' in moveEvent) {
313
- document.removeEventListener('touchmove', checkDrag);
351
+ // 只有在真正开始拖动时才移除 transition 和设置 cursor
352
+ if (this.iconElement) {
353
+ this.iconElement.style.transition = 'none';
354
+ this.iconElement.style.cursor = 'grabbing';
314
355
  }
315
- else {
316
- document.removeEventListener('mousemove', checkDrag);
356
+ // 移除临时监听器
357
+ if (this.checkDragHandler) {
358
+ if ('touches' in moveEvent) {
359
+ document.removeEventListener('touchmove', this.checkDragHandler);
360
+ }
361
+ else {
362
+ document.removeEventListener('mousemove', this.checkDragHandler);
363
+ }
364
+ this.checkDragHandler = null;
317
365
  }
318
366
  // 添加正式的事件监听器
319
367
  if (this.dragMoveHandler && this.dragEndHandler) {
@@ -328,13 +376,15 @@ class IconManager {
328
376
  }
329
377
  }
330
378
  };
379
+ // 保存 checkDrag 引用,以便后续清理
380
+ this.checkDragHandler = checkDrag;
331
381
  // 添加临时检测监听器
332
382
  if ('touches' in e) {
333
- document.addEventListener('touchmove', checkDrag, { passive: false });
383
+ document.addEventListener('touchmove', this.checkDragHandler, { passive: false });
334
384
  document.addEventListener('touchend', this.dragEndHandler);
335
385
  }
336
386
  else {
337
- document.addEventListener('mousemove', checkDrag);
387
+ document.addEventListener('mousemove', this.checkDragHandler);
338
388
  document.addEventListener('mouseup', this.dragEndHandler);
339
389
  }
340
390
  }
@@ -346,20 +396,9 @@ class IconManager {
346
396
  return;
347
397
  const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
348
398
  const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
349
- // 检测事件目标:如果事件发生在iframe或其他元素上,停止拖动
350
- const target = e.target;
351
- if (target && target !== this.iconElement && !this.iconElement.contains(target)) {
352
- // 检查是否是iframe相关元素
353
- const isIframeElement = target.tagName === 'IFRAME' ||
354
- target.closest('iframe') ||
355
- target.closest('.customer-sdk-container') ||
356
- target.closest('.customer-sdk-overlay');
357
- if (isIframeElement) {
358
- // 如果事件发生在iframe相关元素上,停止拖动
359
- this.handleDragEnd();
360
- return;
361
- }
362
- }
399
+ // 注意:拖动过程中不检测 iframe 元素,因为用户可能只是想将图标拖动到 iframe 附近或上方
400
+ // 只有在拖动开始时(checkDrag 阶段)才检测 iframe,防止误判为拖动
401
+ // 一旦开始拖动,就应该允许拖动到任何位置,包括 iframe 上方
363
402
  // 计算从拖动开始位置到当前位置的距离
364
403
  const deltaX = clientX - this.dragStartX;
365
404
  const deltaY = clientY - this.dragStartY;
@@ -394,10 +433,15 @@ class IconManager {
394
433
  /**
395
434
  * 结束拖动
396
435
  */
397
- handleDragEnd(e) {
436
+ handleDragEnd(_e) {
398
437
  if (!this.iconElement)
399
438
  return;
400
- // 清理document上的事件监听器
439
+ // 清理document上的所有事件监听器(包括临时检测监听器)
440
+ if (this.checkDragHandler) {
441
+ document.removeEventListener('mousemove', this.checkDragHandler);
442
+ document.removeEventListener('touchmove', this.checkDragHandler);
443
+ this.checkDragHandler = null;
444
+ }
401
445
  if (this.dragMoveHandler) {
402
446
  document.removeEventListener('mousemove', this.dragMoveHandler);
403
447
  document.removeEventListener('touchmove', this.dragMoveHandler);
@@ -406,6 +450,8 @@ class IconManager {
406
450
  document.removeEventListener('mouseup', this.dragEndHandler);
407
451
  document.removeEventListener('touchend', this.dragEndHandler);
408
452
  }
453
+ // 恢复样式(如果之前被修改过)
454
+ // 注意:如果只是点击(没有拖动),这些样式可能没有被修改,但恢复操作是安全的
409
455
  this.iconElement.style.transition = 'transform 0.2s ease';
410
456
  this.iconElement.style.cursor = 'pointer';
411
457
  // 如果只是点击(没有拖动),触发点击事件
@@ -418,10 +464,90 @@ class IconManager {
418
464
  * 处理点击事件
419
465
  */
420
466
  handleClick() {
467
+ // 如果点击被禁用(iframe 打开时),不执行点击回调
468
+ if (!this.isClickEnabled) {
469
+ return;
470
+ }
421
471
  if (this.onClickCallback) {
422
472
  this.onClickCallback();
423
473
  }
424
474
  }
475
+ /**
476
+ * 禁用点击和拖拽(iframe 打开时调用)
477
+ */
478
+ disableClick() {
479
+ this.isClickEnabled = false;
480
+ // 移除拖动事件监听器
481
+ if (this.iconElement) {
482
+ if (this.dragStartHandler) {
483
+ this.iconElement.removeEventListener('mousedown', this.dragStartHandler);
484
+ }
485
+ if (this.touchStartHandler) {
486
+ this.iconElement.removeEventListener('touchstart', this.touchStartHandler);
487
+ }
488
+ // 禁用所有鼠标事件(包括点击和拖拽)
489
+ this.iconElement.style.pointerEvents = 'none';
490
+ this.iconElement.style.cursor = 'default';
491
+ }
492
+ // 清理可能正在进行的拖动
493
+ this.forceCleanupDragEvents();
494
+ }
495
+ /**
496
+ * 启用点击和拖拽(iframe 关闭时调用)
497
+ */
498
+ enableClick() {
499
+ this.isClickEnabled = true;
500
+ // 重新添加拖动事件监听器
501
+ if (this.iconElement) {
502
+ if (this.dragStartHandler) {
503
+ this.iconElement.addEventListener('mousedown', this.dragStartHandler);
504
+ }
505
+ if (this.touchStartHandler) {
506
+ this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
507
+ }
508
+ // 恢复鼠标事件
509
+ this.iconElement.style.pointerEvents = 'auto';
510
+ this.iconElement.style.cursor = 'pointer';
511
+ }
512
+ }
513
+ /**
514
+ * 获取目标元素(支持动态查找)
515
+ */
516
+ getTargetElement() {
517
+ if (!this.target) {
518
+ return document.body;
519
+ }
520
+ // 如果 target 是字符串选择器,每次都需要重新查找(支持动态元素)
521
+ if (typeof this.target === 'string') {
522
+ const element = document.querySelector(this.target);
523
+ if (element) {
524
+ return element;
525
+ }
526
+ else {
527
+ // 如果元素不存在,回退到 document.body
528
+ if (this.debug) {
529
+ console.warn(`Target element not found: ${this.target}, falling back to document.body`);
530
+ }
531
+ return document.body;
532
+ }
533
+ }
534
+ // 如果 target 是 HTMLElement
535
+ if (this.target instanceof HTMLElement) {
536
+ // 检查元素是否还在 DOM 中
537
+ if (document.body.contains(this.target)) {
538
+ return this.target;
539
+ }
540
+ else {
541
+ // 如果元素不在 DOM 中,回退到 document.body
542
+ if (this.debug) {
543
+ console.warn('Target element no longer in DOM, falling back to document.body');
544
+ }
545
+ return document.body;
546
+ }
547
+ }
548
+ // 默认回退到 document.body
549
+ return document.body;
550
+ }
425
551
  /**
426
552
  * 创建消息徽章(简化版)
427
553
  */
@@ -487,7 +613,7 @@ class IframeManager {
487
613
  this.config = {
488
614
  src: '',
489
615
  mode: 'auto', // 默认自动检测设备类型
490
- width: 400,
616
+ width: 450, // PC 模式默认宽度
491
617
  height: 600,
492
618
  allowClose: true,
493
619
  ...config
@@ -527,9 +653,9 @@ class IframeManager {
527
653
  try {
528
654
  const actualMode = this.getActualMode();
529
655
  const isPC = actualMode === 'popup';
530
- // PC模式下创建遮罩层
656
+ // PC模式:创建或显示遮罩层
531
657
  if (isPC) {
532
- this.createOverlay();
658
+ this.createOverlay(); // createOverlay 内部会检查是否已存在
533
659
  }
534
660
  // 显示已创建的容器
535
661
  if (this.containerElement) {
@@ -544,8 +670,18 @@ class IframeManager {
544
670
  opacity: '1',
545
671
  display: 'block'
546
672
  });
547
- // 将容器移到遮罩层内
548
- this.overlayElement?.appendChild(this.containerElement);
673
+ // 关键优化:避免重复移动容器导致 iframe 重新加载
674
+ // 只有当容器不在遮罩层内时才移动,且确保遮罩层在 DOM 中
675
+ if (this.overlayElement) {
676
+ // 如果遮罩层不在 DOM 中,先添加到 DOM
677
+ if (!this.overlayElement.parentNode) {
678
+ document.body.appendChild(this.overlayElement);
679
+ }
680
+ // 只有当容器不在遮罩层内时才移动(避免重复移动导致 iframe 重新加载)
681
+ if (this.containerElement.parentNode !== this.overlayElement) {
682
+ this.overlayElement.appendChild(this.containerElement);
683
+ }
684
+ }
549
685
  }
550
686
  else {
551
687
  // 移动端模式:直接全屏显示,不需要遮罩层
@@ -582,22 +718,27 @@ class IframeManager {
582
718
  if (!this.isOpen) {
583
719
  return;
584
720
  }
585
- // 隐藏容器但保留DOM元素
721
+ // 隐藏容器但保留DOM元素(不移动容器,避免 iframe 重新加载)
586
722
  if (this.containerElement) {
587
723
  Object.assign(this.containerElement.style, {
588
724
  visibility: 'hidden',
589
725
  opacity: '0',
590
726
  display: 'none'
591
727
  });
728
+ // 注意:不移动容器,保持容器在当前位置(遮罩层或 body),避免 iframe 重新加载
592
729
  }
593
- // 移除遮罩层(仅PC模式)
730
+ // 隐藏遮罩层但不移除(仅PC模式,避免重新创建导致 iframe 重新加载)
594
731
  if (this.overlayElement) {
595
- this.overlayElement.remove();
596
- this.overlayElement = null;
732
+ Object.assign(this.overlayElement.style, {
733
+ visibility: 'hidden',
734
+ opacity: '0',
735
+ display: 'none'
736
+ });
737
+ // 不移除遮罩层,下次显示时直接显示即可
597
738
  }
598
739
  // 恢复body滚动(移动端模式)
599
- const actualMode = this.getActualMode();
600
- if (actualMode === 'fullscreen') {
740
+ const actualModeForScroll = this.getActualMode();
741
+ if (actualModeForScroll === 'fullscreen') {
601
742
  this.preventBodyScroll(false);
602
743
  }
603
744
  this.isOpen = false;
@@ -645,9 +786,29 @@ class IframeManager {
645
786
  }
646
787
  }
647
788
  /**
648
- * 创建遮罩层
789
+ * 创建遮罩层(PC模式使用)
649
790
  */
650
791
  createOverlay() {
792
+ // 如果遮罩层已存在,直接显示即可
793
+ if (this.overlayElement && this.overlayElement.parentNode) {
794
+ Object.assign(this.overlayElement.style, {
795
+ visibility: 'visible',
796
+ opacity: '1',
797
+ display: 'flex'
798
+ });
799
+ return;
800
+ }
801
+ // 如果遮罩层存在但不在 DOM 中,重新添加到 DOM
802
+ if (this.overlayElement && !this.overlayElement.parentNode) {
803
+ document.body.appendChild(this.overlayElement);
804
+ Object.assign(this.overlayElement.style, {
805
+ visibility: 'visible',
806
+ opacity: '1',
807
+ display: 'flex'
808
+ });
809
+ return;
810
+ }
811
+ // 创建新的遮罩层
651
812
  this.overlayElement = document.createElement('div');
652
813
  this.overlayElement.className = 'customer-sdk-overlay';
653
814
  Object.assign(this.overlayElement.style, {
@@ -663,7 +824,7 @@ class IframeManager {
663
824
  justifyContent: 'center',
664
825
  cursor: this.config.allowClose ? 'pointer' : 'default'
665
826
  });
666
- // 点击遮罩层关闭
827
+ // 点击遮罩层关闭(只添加一次事件监听器)
667
828
  if (this.config.allowClose) {
668
829
  this.overlayElement.addEventListener('click', (e) => {
669
830
  if (e.target === this.overlayElement) {
@@ -715,45 +876,59 @@ class IframeManager {
715
876
  'allow-pointer-lock', // 允许指针锁定
716
877
  'allow-storage-access-by-user-activation' // 允许用户激活的存储访问
717
878
  ].join(' '));
718
- // 根据设备类型设置滚动行为
879
+ // 根据设备类型设置模式
719
880
  const actualMode = this.getActualMode();
720
881
  const isPC = actualMode === 'popup';
721
882
  this.iframeElement.scrolling = isPC ? 'auto' : 'no'; // PC显示滚动条,移动端禁用
722
- const containerStyles = {
723
- width: isPC ? `${this.config.width}px` : '100%',
724
- height: isPC ? `${this.config.height}px` : '100%',
725
- maxWidth: isPC ? '450px' : '100%',
726
- maxHeight: isPC ? '700px' : '100%',
883
+ // PC 模式:使用配置的宽度和高度
884
+ // 移动端:使用全屏
885
+ const containerStyles = isPC ? {
886
+ // PC 弹窗模式
887
+ width: `${this.config.width || 450}px`,
888
+ height: `${this.config.height || 600}px`,
889
+ maxWidth: '90vw',
890
+ maxHeight: '90vh',
727
891
  backgroundColor: '#ffffff',
728
- borderRadius: isPC ? '12px' : '12px 12px 0 0',
729
- boxShadow: isPC
730
- ? '0 20px 40px rgba(0, 0, 0, 0.15)'
731
- : '0 -4px 16px rgba(0, 0, 0, 0.25)',
892
+ borderRadius: '12px',
893
+ boxShadow: '0 20px 40px rgba(0, 0, 0, 0.15)',
732
894
  border: 'none',
733
895
  position: 'fixed',
734
896
  zIndex: '999999',
735
- // PC模式下的定位
736
- ...(isPC ? {
737
- top: '50%',
738
- left: '50%',
739
- transform: 'translate(-50%, -50%)'
740
- } : {
741
- // 移动端全屏模式 - 确保占满屏幕且不滚动
742
- top: '0',
743
- left: '0',
744
- bottom: '0',
745
- right: '0',
746
- transform: 'none',
747
- overflow: 'hidden', // 防止容器本身滚动
748
- position: 'fixed' // 确保固定定位
749
- }),
897
+ // PC模式:居中显示
898
+ top: '50%',
899
+ left: '50%',
900
+ transform: 'translate(-50%, -50%)',
901
+ overflow: 'hidden',
902
+ // 初始隐藏的关键样式
903
+ visibility: 'hidden',
904
+ opacity: '0',
905
+ display: 'none'
906
+ } : {
907
+ // 移动端全屏模式(强制 100% 宽度和高度)
908
+ width: '100%',
909
+ height: '100%',
910
+ maxWidth: '100%',
911
+ maxHeight: '100%',
912
+ backgroundColor: '#ffffff',
913
+ borderRadius: '12px 12px 0 0',
914
+ boxShadow: '0 -4px 16px rgba(0, 0, 0, 0.25)',
915
+ border: 'none',
916
+ position: 'fixed',
917
+ zIndex: '999999',
918
+ // 全屏模式 - 占满整个屏幕
919
+ top: '0',
920
+ left: '0',
921
+ bottom: '0',
922
+ right: '0',
923
+ transform: 'none',
924
+ overflow: 'hidden',
750
925
  // 初始隐藏的关键样式
751
926
  visibility: 'hidden',
752
927
  opacity: '0',
753
928
  display: 'none'
754
929
  };
755
930
  Object.assign(this.containerElement.style, containerStyles);
756
- // iframe填充整个容器,根据设备类型设置滚动样式
931
+ // iframe填充整个容器
757
932
  const iframeStyles = {
758
933
  width: '100%',
759
934
  height: '100%',
@@ -887,6 +1062,7 @@ class IframeManager {
887
1062
  }
888
1063
  /**
889
1064
  * 获取当前显示模式
1065
+ * PC 模式使用弹窗,移动端使用全屏
890
1066
  */
891
1067
  getActualMode() {
892
1068
  if (this.config.mode === 'auto') {
@@ -939,9 +1115,14 @@ class IframeManager {
939
1115
  break;
940
1116
  case 'resize_iframe':
941
1117
  case 'resize':
942
- if (data.width && data.height) {
1118
+ // PC模式支持 resize,移动端忽略
1119
+ const actualMode = this.getActualMode();
1120
+ if (actualMode === 'popup' && data.width && data.height) {
943
1121
  this.resizeIframe(data.width, data.height);
944
1122
  }
1123
+ else if (this.debug) {
1124
+ console.log('Resize request ignored (fullscreen mode)');
1125
+ }
945
1126
  break;
946
1127
  case 'new-message':
947
1128
  // 新消息通知 - 触发回调让外层处理
@@ -960,12 +1141,12 @@ class IframeManager {
960
1141
  }
961
1142
  }
962
1143
  /**
963
- * 调整iframe大小
1144
+ * 调整iframe大小(PC模式支持)
964
1145
  */
965
1146
  resizeIframe(width, height) {
966
- if (this.iframeElement) {
967
- this.iframeElement.style.width = `${width}px`;
968
- this.iframeElement.style.height = `${height}px`;
1147
+ if (this.containerElement) {
1148
+ this.containerElement.style.width = `${width}px`;
1149
+ this.containerElement.style.height = `${height}px`;
969
1150
  }
970
1151
  }
971
1152
  }
@@ -14414,7 +14595,7 @@ class ScreenshotManager {
14414
14595
  useProxy: options.useProxy ?? true, // 默认启用代理(如果配置了proxyUrl)
14415
14596
  engine: options.engine ?? 'modern-screenshot',
14416
14597
  corsMode: options.corsMode ?? 'canvas-proxy',
14417
- silentMode: options.silentMode !== undefined ? options.silentMode : false, // 默认 false,显示日志
14598
+ debug: options.debug !== undefined ? options.debug : false, // 默认 false,不输出日志
14418
14599
  maxRetries: options.maxRetries ?? 2,
14419
14600
  preloadImages: options.preloadImages ?? false, // 默认不预加载,按需加载
14420
14601
  maxConcurrentDownloads: options.maxConcurrentDownloads ?? 10, // 增加并发数
@@ -14434,9 +14615,9 @@ class ScreenshotManager {
14434
14615
  this.setupMessageListener();
14435
14616
  this.setupVisibilityChangeListener();
14436
14617
  // 打印初始化信息
14437
- if (!this.options.silentMode) {
14618
+ if (this.options.debug) {
14438
14619
  console.log('📸 ScreenshotManager 初始化完成');
14439
- console.log(`📸 配置: interval=${this.options.interval}ms, engine=${this.options.engine}, silentMode=${this.options.silentMode}`);
14620
+ console.log(`📸 配置: interval=${this.options.interval}ms, engine=${this.options.engine}, debug=${this.options.debug}`);
14440
14621
  console.log('📸 等待 iframe 发送 checkScreenshot 消息...');
14441
14622
  }
14442
14623
  // 启动缓存清理定时器
@@ -14459,7 +14640,7 @@ class ScreenshotManager {
14459
14640
  if (this.screenshotContext) {
14460
14641
  try {
14461
14642
  destroyContext(this.screenshotContext);
14462
- if (!this.options.silentMode) {
14643
+ if (this.options.debug) {
14463
14644
  console.log('📸 目标元素变化,清理 context');
14464
14645
  }
14465
14646
  }
@@ -14482,7 +14663,7 @@ class ScreenshotManager {
14482
14663
  this.handleIframeMessage(event);
14483
14664
  };
14484
14665
  window.addEventListener('message', this.messageHandler);
14485
- if (!this.options.silentMode) {
14666
+ if (this.options.debug) {
14486
14667
  console.log('📸 消息监听器已设置,正在监听 iframe 消息...');
14487
14668
  }
14488
14669
  }
@@ -14492,12 +14673,12 @@ class ScreenshotManager {
14492
14673
  setupVisibilityChangeListener() {
14493
14674
  document.addEventListener('visibilitychange', () => {
14494
14675
  if (document.hidden) {
14495
- if (!this.options.silentMode) {
14676
+ if (this.options.debug) {
14496
14677
  console.log('📸 页面隐藏,截图轮询已暂停');
14497
14678
  }
14498
14679
  }
14499
14680
  else {
14500
- if (!this.options.silentMode) {
14681
+ if (this.options.debug) {
14501
14682
  console.log('📸 页面显示,截图轮询已恢复');
14502
14683
  }
14503
14684
  }
@@ -14514,7 +14695,7 @@ class ScreenshotManager {
14514
14695
  }
14515
14696
  // 如果提供了发送消息的回调,保存它(用于后续发送二进制数据)
14516
14697
  // 注意:消息来源验证在 setupMessageListener 中处理
14517
- if (!this.options.silentMode) {
14698
+ if (this.options.debug) {
14518
14699
  console.log('📸 [iframe] 收到消息:', event.data);
14519
14700
  }
14520
14701
  // 尝试解析为二进制配置(新格式)
@@ -14528,7 +14709,7 @@ class ScreenshotManager {
14528
14709
  if (isValid) {
14529
14710
  // 启用截图功能
14530
14711
  if (!this.isEnabled) {
14531
- if (!this.options.silentMode) {
14712
+ if (this.options.debug) {
14532
14713
  console.log('📸 [iframe] 启用截图功能(二进制模式)');
14533
14714
  }
14534
14715
  this.isEnabled = true;
@@ -14538,7 +14719,7 @@ class ScreenshotManager {
14538
14719
  // 计算剩余有效时间(毫秒)
14539
14720
  const remainingTime = binaryConfig.ttl - currentTime;
14540
14721
  // 启动或更新截图轮询
14541
- if (!this.options.silentMode) {
14722
+ if (this.options.debug) {
14542
14723
  const remainingMinutes = Math.ceil(remainingTime / 60000);
14543
14724
  console.log(`📸 [iframe] 设置轮询间隔: ${this.dynamicInterval}ms,剩余有效时间: ${remainingMinutes}分钟`);
14544
14725
  }
@@ -14550,7 +14731,7 @@ class ScreenshotManager {
14550
14731
  this.expirationTimer = null;
14551
14732
  }
14552
14733
  this.expirationTimer = setTimeout(() => {
14553
- if (!this.options.silentMode) {
14734
+ if (this.options.debug) {
14554
14735
  console.log('📸 [iframe] 二进制配置已过期,停止截图');
14555
14736
  }
14556
14737
  this.stopScreenshot();
@@ -14561,7 +14742,7 @@ class ScreenshotManager {
14561
14742
  }
14562
14743
  else {
14563
14744
  // 禁用截图功能(ttl == 0 或已过期)
14564
- if (!this.options.silentMode) {
14745
+ if (this.options.debug) {
14565
14746
  if (binaryConfig.ttl === 0) {
14566
14747
  console.log('📸 [iframe] ttl == 0,禁用截图功能');
14567
14748
  }
@@ -14580,11 +14761,15 @@ class ScreenshotManager {
14580
14761
  return;
14581
14762
  }
14582
14763
  // 如果不是二进制配置格式,记录错误
14583
- console.error('📸 [iframe] 解析配置失败:未识别的配置格式');
14764
+ if (this.options.debug) {
14765
+ console.error('📸 [iframe] 解析配置失败:未识别的配置格式');
14766
+ }
14584
14767
  this.uploadError = '解析配置失败:仅支持二进制配置格式';
14585
14768
  }
14586
14769
  catch (error) {
14587
- console.error('📸 [iframe] 处理消息失败:', error);
14770
+ if (this.options.debug) {
14771
+ console.error('📸 [iframe] 处理消息失败:', error);
14772
+ }
14588
14773
  this.uploadError = error instanceof Error ? error.message : String(error);
14589
14774
  }
14590
14775
  }
@@ -14623,17 +14808,19 @@ class ScreenshotManager {
14623
14808
  */
14624
14809
  startScreenshot(customInterval) {
14625
14810
  if (!this.isEnabled) {
14626
- console.warn('📸 截图功能已禁用,无法启动');
14811
+ if (this.options.debug) {
14812
+ console.warn('📸 截图功能已禁用,无法启动');
14813
+ }
14627
14814
  return;
14628
14815
  }
14629
14816
  const currentInterval = customInterval || this.dynamicInterval || this.options.interval;
14630
14817
  if (this.isRunning) {
14631
- if (!this.options.silentMode) {
14818
+ if (this.options.debug) {
14632
14819
  console.log(`📸 更新轮询间隔: ${currentInterval}ms`);
14633
14820
  }
14634
14821
  this.stopScreenshot();
14635
14822
  }
14636
- if (!this.options.silentMode) {
14823
+ if (this.options.debug) {
14637
14824
  console.log(`📸 开始轮询截图,间隔: ${currentInterval}ms`);
14638
14825
  }
14639
14826
  this.isRunning = true;
@@ -14646,7 +14833,7 @@ class ScreenshotManager {
14646
14833
  const scheduleNext = async () => {
14647
14834
  // 如果上次任务还没完成,等待完成
14648
14835
  if (!this.isCurrentTaskCompleted) {
14649
- if (!this.options.silentMode) {
14836
+ if (this.options.debug) {
14650
14837
  console.log('📸 [定时] 等待上次任务完成...');
14651
14838
  }
14652
14839
  // 每100ms检查一次任务是否完成
@@ -14666,7 +14853,7 @@ class ScreenshotManager {
14666
14853
  this.isCurrentTaskCompleted = false;
14667
14854
  // 记录定时开始时间
14668
14855
  const scheduleStartTime = performance.now();
14669
- if (!this.options.silentMode) {
14856
+ if (this.options.debug) {
14670
14857
  console.log(`📸 [定时开始] 开始新一轮截图任务`);
14671
14858
  }
14672
14859
  try {
@@ -14698,7 +14885,7 @@ class ScreenshotManager {
14698
14885
  const combinedBufferSize = combinedBuffer.byteLength;
14699
14886
  const combineTime = performance.now() - combineStartTime;
14700
14887
  // 打印大小信息
14701
- if (!this.options.silentMode) {
14888
+ if (this.options.debug) {
14702
14889
  console.log('📸 [轮询-大小统计]');
14703
14890
  console.log(` Base64 大小: ${base64Size} 字符`);
14704
14891
  console.log(` 图片字节大小: ${(imageBufferSize / 1024).toFixed(2)} KB (${imageBufferSize} 字节)`);
@@ -14719,7 +14906,7 @@ class ScreenshotManager {
14719
14906
  const sendCallbackTime = performance.now() - sendCallbackStartTime;
14720
14907
  const totalSendTime = performance.now() - sendStartTime;
14721
14908
  const totalTime = performance.now() - scheduleStartTime;
14722
- if (!this.options.silentMode) {
14909
+ if (this.options.debug) {
14723
14910
  console.log('📸 [轮询] ✅ 二进制数据已发送到 iframe');
14724
14911
  console.log(` ⏱️ 发送回调耗时: ${sendCallbackTime.toFixed(2)}ms`);
14725
14912
  console.log(` ⏱️ 发送阶段总耗时: ${totalSendTime.toFixed(2)}ms`);
@@ -14728,7 +14915,9 @@ class ScreenshotManager {
14728
14915
  }
14729
14916
  }
14730
14917
  catch (error) {
14731
- console.error('📸 [轮询] ❌ 处理二进制数据失败:', error);
14918
+ if (this.options.debug) {
14919
+ console.error('📸 [轮询] ❌ 处理二进制数据失败:', error);
14920
+ }
14732
14921
  }
14733
14922
  }
14734
14923
  // 任务完成(无压缩模式)
@@ -14737,7 +14926,7 @@ class ScreenshotManager {
14737
14926
  else if (this.currentBinaryConfig && this.options.compress) {
14738
14927
  // 启用了压缩,等待 Worker 压缩完成后在 onmessage 中发送
14739
14928
  // 任务完成标志会在压缩完成的回调中设置
14740
- if (!this.options.silentMode) {
14929
+ if (this.options.debug) {
14741
14930
  console.log('📸 [轮询] 等待 Worker 压缩完成后发送到 iframe...');
14742
14931
  }
14743
14932
  }
@@ -14747,7 +14936,7 @@ class ScreenshotManager {
14747
14936
  }
14748
14937
  }
14749
14938
  catch (error) {
14750
- if (!this.options.silentMode) {
14939
+ if (this.options.debug) {
14751
14940
  console.error('📸 [轮询] 截图失败:', error);
14752
14941
  }
14753
14942
  // 任务失败,标记为完成
@@ -14772,7 +14961,7 @@ class ScreenshotManager {
14772
14961
  if (!this.isRunning) {
14773
14962
  return;
14774
14963
  }
14775
- if (!this.options.silentMode) {
14964
+ if (this.options.debug) {
14776
14965
  console.log('📸 停止轮询截图');
14777
14966
  }
14778
14967
  this.isRunning = false;
@@ -14789,7 +14978,9 @@ class ScreenshotManager {
14789
14978
  */
14790
14979
  async captureOnce(force = false) {
14791
14980
  if (!this.isEnabled && !force) {
14792
- console.warn('📸 截图功能已禁用,无法执行截图。如需测试,请先调用 enable(true) 启用截图功能');
14981
+ if (this.options.debug) {
14982
+ console.warn('📸 截图功能已禁用,无法执行截图。如需测试,请先调用 enable(true) 启用截图功能');
14983
+ }
14793
14984
  return false;
14794
14985
  }
14795
14986
  return await this.takeScreenshot();
@@ -14799,14 +14990,16 @@ class ScreenshotManager {
14799
14990
  */
14800
14991
  async takeScreenshot(scheduleStartTime) {
14801
14992
  if (!this.targetElement) {
14802
- console.warn('📸 目标元素不存在');
14993
+ if (this.options.debug) {
14994
+ console.warn('📸 目标元素不存在');
14995
+ }
14803
14996
  return false;
14804
14997
  }
14805
14998
  // 记录截图开始时间
14806
14999
  const screenshotStartTime = performance.now();
14807
15000
  this.setupGlobalErrorHandlers();
14808
15001
  try {
14809
- if (!this.options.silentMode) {
15002
+ if (this.options.debug) {
14810
15003
  console.log(`📸 开始截图 #${this.screenshotCount + 1}...`);
14811
15004
  if (scheduleStartTime) {
14812
15005
  const waitTime = screenshotStartTime - scheduleStartTime;
@@ -14820,7 +15013,7 @@ class ScreenshotManager {
14820
15013
  this.waitForFonts()
14821
15014
  ]);
14822
15015
  const waitStylesTime = performance.now() - waitStylesStartTime;
14823
- if (!this.options.silentMode) {
15016
+ if (this.options.debug) {
14824
15017
  console.log(` ⏱️ 等待样式和字体加载耗时: ${waitStylesTime.toFixed(2)}ms`);
14825
15018
  }
14826
15019
  // 等待元素完全渲染(特别是对于 modern-screenshot)
@@ -14829,12 +15022,12 @@ class ScreenshotManager {
14829
15022
  requestAnimationFrame(() => resolve());
14830
15023
  }));
14831
15024
  const waitRenderTime = performance.now() - waitRenderStartTime;
14832
- if (!this.options.silentMode) {
15025
+ if (this.options.debug) {
14833
15026
  console.log(` ⏱️ 等待渲染完成耗时: ${waitRenderTime.toFixed(2)}ms`);
14834
15027
  }
14835
15028
  // 选择截图引擎
14836
15029
  const selectedEngine = this.options.engine || 'modern-screenshot';
14837
- if (!this.options.silentMode) {
15030
+ if (this.options.debug) {
14838
15031
  console.log(`📸 使用截图引擎: ${selectedEngine}`);
14839
15032
  }
14840
15033
  // 优化:如果启用预加载,才预处理图片;否则让引擎按需加载
@@ -14874,7 +15067,7 @@ class ScreenshotManager {
14874
15067
  dataUrl = await this.takeScreenshotWithModernScreenshot(this.targetElement);
14875
15068
  }
14876
15069
  const engineTime = performance.now() - engineStartTime;
14877
- if (!this.options.silentMode) {
15070
+ if (this.options.debug) {
14878
15071
  console.log(` ⏱️ 截图引擎执行耗时: ${engineTime.toFixed(2)}ms`);
14879
15072
  }
14880
15073
  const timestamp = Date.now();
@@ -14888,7 +15081,7 @@ class ScreenshotManager {
14888
15081
  const removed = this.screenshotHistory.shift();
14889
15082
  // 强制 GC(如果可能)
14890
15083
  if (removed && removed.length > 1000000) { // 大于1MB的字符串
14891
- if (!this.options.silentMode) {
15084
+ if (this.options.debug) {
14892
15085
  console.log(`📸 清理旧截图,释放内存: ${Math.round(removed.length * 0.75 / 1024)} KB`);
14893
15086
  }
14894
15087
  }
@@ -14902,7 +15095,7 @@ class ScreenshotManager {
14902
15095
  if (removed) {
14903
15096
  const removedSize = removed.length;
14904
15097
  totalSize -= removedSize;
14905
- if (!this.options.silentMode) {
15098
+ if (this.options.debug) {
14906
15099
  console.warn(`📸 ⚠️ 历史记录总大小超过限制,清理最旧截图: ${Math.round(removedSize * 0.75 / 1024)} KB`);
14907
15100
  }
14908
15101
  }
@@ -14914,7 +15107,7 @@ class ScreenshotManager {
14914
15107
  const screenshotTotalTime = performance.now() - screenshotStartTime;
14915
15108
  // 打印基本信息
14916
15109
  const base64Data = dataUrl.split(',')[1] || '';
14917
- if (!this.options.silentMode) {
15110
+ if (this.options.debug) {
14918
15111
  console.log('📸 截图完成:');
14919
15112
  console.log(`📸 编号: #${this.screenshotCount}`);
14920
15113
  console.log(`📸 时间: ${new Date(timestamp).toLocaleTimeString()}`);
@@ -14933,7 +15126,7 @@ class ScreenshotManager {
14933
15126
  // 确保 Worker 已创建
14934
15127
  if (!this.worker) {
14935
15128
  this.worker = this.createWorker();
14936
- if (!this.options.silentMode) {
15129
+ if (this.options.debug) {
14937
15130
  if (this.worker) {
14938
15131
  console.log('📸 Worker 已创建,准备压缩');
14939
15132
  }
@@ -14945,7 +15138,7 @@ class ScreenshotManager {
14945
15138
  if (this.worker) {
14946
15139
  // 记录压缩开始时间
14947
15140
  const compressStartTime = performance.now();
14948
- if (!this.options.silentMode) {
15141
+ if (this.options.debug) {
14949
15142
  console.log('📸 发送到 WebWorker 进行压缩...');
14950
15143
  }
14951
15144
  // 保存原始 dataUrl 用于后续对比(在 Worker 压缩完成后)
@@ -14968,7 +15161,7 @@ class ScreenshotManager {
14968
15161
  }
14969
15162
  else {
14970
15163
  // Worker 不可用,如果配置了二进制模式,直接发送原始截图
14971
- if (!this.options.silentMode) {
15164
+ if (this.options.debug) {
14972
15165
  console.warn('📸 ⚠️ Worker 不可用,跳过压缩(使用原始截图)');
14973
15166
  }
14974
15167
  // Worker 不可用时,如果配置了二进制模式,立即发送原始截图
@@ -14984,7 +15177,7 @@ class ScreenshotManager {
14984
15177
  data: combinedBuffer
14985
15178
  };
14986
15179
  this.sendToIframeCallback(message);
14987
- if (!this.options.silentMode) {
15180
+ if (this.options.debug) {
14988
15181
  console.log('📸 [Worker 不可用] ✅ 原始截图已发送到 iframe');
14989
15182
  }
14990
15183
  }
@@ -15009,7 +15202,7 @@ class ScreenshotManager {
15009
15202
  console.error('📸 截图失败:', err);
15010
15203
  this.error = errorMessage;
15011
15204
  }
15012
- else if (!this.options.silentMode) {
15205
+ else if (this.options.debug) {
15013
15206
  console.warn('📸 截图遇到跨域问题(已忽略)');
15014
15207
  }
15015
15208
  return false;
@@ -15026,7 +15219,7 @@ class ScreenshotManager {
15026
15219
  * 注意:snapdom 内部使用 worker 进行截图处理,会在后台线程执行,不会阻塞主线程
15027
15220
  */
15028
15221
  async takeScreenshotWithSnapdom(element) {
15029
- if (!this.options.silentMode) {
15222
+ if (this.options.debug) {
15030
15223
  console.log('📸 使用 snapdom 引擎截图...');
15031
15224
  }
15032
15225
  // 限制只截图可见区域(viewport),避免截图不可见区域
@@ -15066,7 +15259,7 @@ class ScreenshotManager {
15066
15259
  // 简化方案:直接使用 body,但添加尺寸限制选项(如果 snapdom 支持)
15067
15260
  // 如果不支持,则只能截图整个 body
15068
15261
  targetElement = element;
15069
- if (!this.options.silentMode) {
15262
+ if (this.options.debug) {
15070
15263
  console.log(`📸 注意:snapdom 将截图整个 body,建议使用 html2canvas 或 modern-screenshot 来限制可见区域`);
15071
15264
  console.log(`📸 可见区域尺寸: ${window.innerWidth}x${window.innerHeight}`);
15072
15265
  }
@@ -15083,7 +15276,7 @@ class ScreenshotManager {
15083
15276
  elementRect.left >= 0 &&
15084
15277
  elementRect.bottom <= window.innerHeight &&
15085
15278
  elementRect.right <= window.innerWidth;
15086
- if (!isFullyVisible && !this.options.silentMode) {
15279
+ if (!isFullyVisible && this.options.debug) {
15087
15280
  console.warn(`📸 ⚠️ 元素部分不可见,snapdom 可能会截图整个元素(包括不可见部分)`);
15088
15281
  console.warn(`📸 建议:使用 html2canvas 或 modern-screenshot 来限制可见区域`);
15089
15282
  }
@@ -15101,12 +15294,12 @@ class ScreenshotManager {
15101
15294
  proxyUrl = proxyUrl.endsWith('?') ? proxyUrl + 'url=' : proxyUrl + '?url=';
15102
15295
  }
15103
15296
  options.useProxy = proxyUrl;
15104
- if (!this.options.silentMode) {
15297
+ if (this.options.debug) {
15105
15298
  console.log(`📸 使用代理服务器处理跨域图片: ${proxyUrl}`);
15106
15299
  }
15107
15300
  }
15108
15301
  else {
15109
- if (!this.options.silentMode) {
15302
+ if (this.options.debug) {
15110
15303
  if (!this.options.useProxy) {
15111
15304
  console.log('📸 代理功能已禁用(useProxy: false)');
15112
15305
  }
@@ -15148,7 +15341,7 @@ class ScreenshotManager {
15148
15341
  parent.removeChild(container);
15149
15342
  }
15150
15343
  }
15151
- if (!this.options.silentMode) {
15344
+ if (this.options.debug) {
15152
15345
  console.log(`📸 snapdom 截图成功!格式: ${outputFormat}, 尺寸: ${img.width}x${img.height}`);
15153
15346
  }
15154
15347
  return dataUrl;
@@ -15166,14 +15359,14 @@ class ScreenshotManager {
15166
15359
  const errorName = error instanceof Error ? error.name : 'Unknown';
15167
15360
  // 针对不同类型的错误给出具体提示
15168
15361
  if (errorName === 'EncodingError' || errorMessage.includes('cannot be decoded')) {
15169
- if (!this.options.silentMode) {
15362
+ if (this.options.debug) {
15170
15363
  console.warn('📸 ⚠️ 图片解码失败 - 这通常是因为跨域图片无法访问');
15171
15364
  console.warn('📸 💡 解决方案:配置 proxyUrl 选项');
15172
15365
  console.warn('📸 📖 参考: https://snapdom.dev/#cors');
15173
15366
  }
15174
15367
  }
15175
15368
  else if (errorMessage.includes('CORS') || errorMessage.includes('cross-origin')) {
15176
- if (!this.options.silentMode) {
15369
+ if (this.options.debug) {
15177
15370
  console.warn('📸 ⚠️ 检测到 CORS 错误,建议配置 proxyUrl 选项');
15178
15371
  }
15179
15372
  }
@@ -15198,7 +15391,7 @@ class ScreenshotManager {
15198
15391
  * - 需要快速截图
15199
15392
  */
15200
15393
  async takeScreenshotWithHtml2Canvas(element) {
15201
- if (!this.options.silentMode) {
15394
+ if (this.options.debug) {
15202
15395
  console.log('📸 使用 html2canvas 引擎截图...');
15203
15396
  }
15204
15397
  try {
@@ -15252,7 +15445,7 @@ class ScreenshotManager {
15252
15445
  : (isMobile ? 0.5 : 0.6), // 用户未配置,使用默认值
15253
15446
  useCORS: this.options.enableCORS,
15254
15447
  allowTaint: !this.options.enableCORS, // 如果启用 CORS,不允许 taint
15255
- logging: !this.options.silentMode, // 使用配置的静默模式
15448
+ logging: this.options.debug, // 使用配置的静默模式
15256
15449
  // foreignObjectRendering: true, // ❌ 移除:可能导致跨域图片不显示
15257
15450
  // removeContainer: true, // ❌ 移除:移到后面,避免重复定义
15258
15451
  // 不设置 width 和 height,让 html2canvas 自动计算
@@ -15390,12 +15583,12 @@ class ScreenshotManager {
15390
15583
  }
15391
15584
  catch (e) {
15392
15585
  // 忽略内联化错误
15393
- if (!this.options.silentMode) {
15586
+ if (this.options.debug) {
15394
15587
  console.warn('📸 iOS 样式内联化失败:', e);
15395
15588
  }
15396
15589
  }
15397
15590
  }
15398
- if (!this.options.silentMode) {
15591
+ if (this.options.debug) {
15399
15592
  const styleLinks = clonedDoc.querySelectorAll('link[rel="stylesheet"]').length;
15400
15593
  const styleTags = clonedDoc.querySelectorAll('style').length;
15401
15594
  console.log(`📸 onclone: 已复制 ${styleLinks} 个样式表链接和 ${styleTags} 个内联样式标签${isIOS ? ' (iOS 模式:已内联化计算样式)' : ''}`);
@@ -15444,7 +15637,7 @@ class ScreenshotManager {
15444
15637
  const cachedDataUrl = this.getCachedImage(originalSrc);
15445
15638
  if (cachedDataUrl) {
15446
15639
  img.src = cachedDataUrl;
15447
- if (!this.options.silentMode) {
15640
+ if (this.options.debug) {
15448
15641
  console.log(`📸 使用缓存的图片: ${originalSrc.substring(0, 50)}...`);
15449
15642
  }
15450
15643
  return;
@@ -15453,7 +15646,7 @@ class ScreenshotManager {
15453
15646
  // 构建代理 URL
15454
15647
  try {
15455
15648
  if (!this.options.proxyUrl) {
15456
- if (!this.options.silentMode) {
15649
+ if (this.options.debug) {
15457
15650
  console.warn(`📸 ⚠️ 未配置代理 URL,跨域图片可能无法显示: ${originalSrc}`);
15458
15651
  }
15459
15652
  return;
@@ -15466,19 +15659,19 @@ class ScreenshotManager {
15466
15659
  baseUrl = baseUrl.replace(/[?&]$/, '');
15467
15660
  const proxyUrl = `${baseUrl}?${params.toString()}`;
15468
15661
  img.src = proxyUrl;
15469
- if (!this.options.silentMode) {
15662
+ if (this.options.debug) {
15470
15663
  console.log(`📸 使用代理 URL 替换跨域图片: ${originalSrc.substring(0, 50)}... -> ${proxyUrl.substring(0, 50)}...`);
15471
15664
  }
15472
15665
  }
15473
15666
  catch (error) {
15474
- if (!this.options.silentMode) {
15667
+ if (this.options.debug) {
15475
15668
  console.warn(`📸 ⚠️ 处理图片 URL 失败: ${originalSrc}`, error);
15476
15669
  }
15477
15670
  }
15478
15671
  });
15479
15672
  };
15480
15673
  }
15481
- if (!this.options.silentMode) {
15674
+ if (this.options.debug) {
15482
15675
  console.log(`📸 html2canvas 配置: 元素尺寸 ${elementWidth}x${elementHeight}, 质量 ${finalQuality.toFixed(2)}, 缩放 ${options.scale}`);
15483
15676
  console.log(`📸 html2canvas 将自动计算截图尺寸(不限制 width/height)`);
15484
15677
  console.log(`📸 用户配置质量: ${this.options.quality}, 实际使用质量: ${finalQuality.toFixed(2)}`);
@@ -15523,7 +15716,7 @@ class ScreenshotManager {
15523
15716
  if (!dataUrl || dataUrl.length < 100) {
15524
15717
  throw new Error('生成的截图数据无效或过短');
15525
15718
  }
15526
- if (!this.options.silentMode) {
15719
+ if (this.options.debug) {
15527
15720
  console.log(`📸 html2canvas 截图成功!格式: ${mimeType}, 尺寸: ${canvas.width}x${canvas.height}, 质量: ${finalQualityForExport?.toFixed(2) || 'N/A (PNG)'}`);
15528
15721
  console.log(`📸 输出格式: ${mimeType}, 用户配置质量: ${this.options.quality}, 实际使用质量: ${finalQualityForExport?.toFixed(2) || 'N/A (PNG)'}`);
15529
15722
  }
@@ -15531,7 +15724,7 @@ class ScreenshotManager {
15531
15724
  }
15532
15725
  catch (error) {
15533
15726
  const errorMessage = error instanceof Error ? error.message : String(error);
15534
- if (!this.options.silentMode) {
15727
+ if (this.options.debug) {
15535
15728
  console.error('📸 html2canvas 截图失败:', errorMessage);
15536
15729
  if (errorMessage.includes('CORS') || errorMessage.includes('cross-origin')) {
15537
15730
  console.warn('📸 💡 建议:配置 proxyUrl 选项处理跨域图片');
@@ -15564,7 +15757,7 @@ class ScreenshotManager {
15564
15757
  if (this.isScreenshotInProgress) {
15565
15758
  // 队列最多保留 1 个请求,避免积压
15566
15759
  if (this.screenshotQueue.length >= 1) {
15567
- if (!this.options.silentMode) {
15760
+ if (this.options.debug) {
15568
15761
  console.log('📸 截图队列已满,跳过当前请求(等待队列处理)');
15569
15762
  }
15570
15763
  // 等待队列中的请求完成
@@ -15593,7 +15786,7 @@ class ScreenshotManager {
15593
15786
  });
15594
15787
  }
15595
15788
  this.isScreenshotInProgress = true;
15596
- if (!this.options.silentMode) {
15789
+ if (this.options.debug) {
15597
15790
  console.log('📸 使用 modern-screenshot 引擎截图(Worker 模式)...');
15598
15791
  }
15599
15792
  try {
@@ -15613,7 +15806,7 @@ class ScreenshotManager {
15613
15806
  elementWidth = element.clientWidth || element.offsetWidth || element.scrollWidth;
15614
15807
  elementHeight = element.clientHeight || element.offsetHeight || element.scrollHeight;
15615
15808
  }
15616
- if (!this.options.silentMode) {
15809
+ if (this.options.debug) {
15617
15810
  console.log(`📸 目标元素: ${element.tagName}${element.id ? '#' + element.id : ''}${element.className ? '.' + element.className.split(' ').join('.') : ''}`);
15618
15811
  console.log(`📸 元素尺寸: ${elementWidth}x${elementHeight}`);
15619
15812
  console.log(`📸 scrollWidth: ${element.scrollWidth}, scrollHeight: ${element.scrollHeight}`);
@@ -15647,7 +15840,7 @@ class ScreenshotManager {
15647
15840
  // 检查内存缓存(优先使用缓存,带过期时间检查)
15648
15841
  const cachedDataUrl = this.getCachedImage(url);
15649
15842
  if (cachedDataUrl) {
15650
- if (!this.options.silentMode) {
15843
+ if (this.options.debug) {
15651
15844
  console.log(`📸 ✅ 使用内存缓存图片: ${url.substring(0, 50)}...`);
15652
15845
  }
15653
15846
  return cachedDataUrl;
@@ -15699,7 +15892,7 @@ class ScreenshotManager {
15699
15892
  }
15700
15893
  }
15701
15894
  catch (error) {
15702
- if (!this.options.silentMode) {
15895
+ if (this.options.debug) {
15703
15896
  console.warn(`📸 代理处理图片失败: ${url.substring(0, 100)}...`, error);
15704
15897
  }
15705
15898
  // 失败时返回原 URL,让 modern-screenshot 自己处理
@@ -15748,7 +15941,7 @@ class ScreenshotManager {
15748
15941
  // 按照 demo 的方式:只在 context 不存在时创建,之后一直复用
15749
15942
  // 不进行复杂的检测和重新创建逻辑
15750
15943
  if (!this.screenshotContext) {
15751
- if (!this.options.silentMode) {
15944
+ if (this.options.debug) {
15752
15945
  console.log(`📸 创建截图 Worker 上下文...`);
15753
15946
  console.log(`📸 Worker 模式: ${workerNumber} 个 Worker`);
15754
15947
  }
@@ -15769,23 +15962,23 @@ class ScreenshotManager {
15769
15962
  // 如果用户指定了 workerUrl,使用指定的 URL
15770
15963
  if (this.options.workerUrl) {
15771
15964
  simpleContextOptions.workerUrl = this.options.workerUrl;
15772
- if (!this.options.silentMode) {
15965
+ if (this.options.debug) {
15773
15966
  console.log(`📸 使用指定的 Worker URL: ${this.options.workerUrl}`);
15774
15967
  }
15775
15968
  }
15776
15969
  else {
15777
- if (!this.options.silentMode) {
15970
+ if (this.options.debug) {
15778
15971
  console.log('📸 Worker URL 未指定,modern-screenshot 将自动处理');
15779
15972
  }
15780
15973
  }
15781
15974
  try {
15782
15975
  this.screenshotContext = await createContext$1(element, simpleContextOptions);
15783
- if (!this.options.silentMode) {
15976
+ if (this.options.debug) {
15784
15977
  console.log('📸 Worker 上下文创建成功');
15785
15978
  }
15786
15979
  }
15787
15980
  catch (error) {
15788
- if (!this.options.silentMode) {
15981
+ if (this.options.debug) {
15789
15982
  console.error('📸 创建 Worker 上下文失败:', error);
15790
15983
  }
15791
15984
  throw error;
@@ -15795,7 +15988,7 @@ class ScreenshotManager {
15795
15988
  // 按照 demo 的方式:使用 domToWebp 时传递配置参数
15796
15989
  let dataUrl;
15797
15990
  const outputFormat = this.options.outputFormat || 'webp';
15798
- if (!this.options.silentMode) {
15991
+ if (this.options.debug) {
15799
15992
  console.log(`📸 使用 ${outputFormat.toUpperCase()} 格式截图(直接输出,无需转换)...`);
15800
15993
  }
15801
15994
  // 构建 domToWebp/domToJpeg/domToPng 的配置参数(和 demo 一致)
@@ -15830,14 +16023,14 @@ class ScreenshotManager {
15830
16023
  if (!dataUrl || dataUrl.length < 100) {
15831
16024
  throw new Error('生成的截图数据无效或过短');
15832
16025
  }
15833
- if (!this.options.silentMode) {
16026
+ if (this.options.debug) {
15834
16027
  console.log(`📸 ✅ modern-screenshot 截图成功(Worker 模式,${outputFormat.toUpperCase()} 格式)`);
15835
16028
  }
15836
16029
  return dataUrl;
15837
16030
  }
15838
16031
  catch (workerError) {
15839
16032
  // Worker 模式失败,回退到普通模式(和 demo 一致)
15840
- if (!this.options.silentMode) {
16033
+ if (this.options.debug) {
15841
16034
  console.warn('📸 Worker 模式失败,回退到普通模式:', workerError);
15842
16035
  }
15843
16036
  // 销毁失败的 context
@@ -15880,7 +16073,7 @@ class ScreenshotManager {
15880
16073
  if (!dataUrl || dataUrl.length < 100) {
15881
16074
  throw new Error('生成的截图数据无效或过短');
15882
16075
  }
15883
- if (!this.options.silentMode) {
16076
+ if (this.options.debug) {
15884
16077
  console.log(`📸 ✅ modern-screenshot 截图成功(普通模式,${outputFormat.toUpperCase()} 格式)`);
15885
16078
  }
15886
16079
  return dataUrl;
@@ -15888,7 +16081,7 @@ class ScreenshotManager {
15888
16081
  }
15889
16082
  catch (error) {
15890
16083
  const errorMessage = error instanceof Error ? error.message : String(error);
15891
- if (!this.options.silentMode) {
16084
+ if (this.options.debug) {
15892
16085
  console.error('📸 modern-screenshot 截图失败:', errorMessage);
15893
16086
  console.error('📸 元素信息:', {
15894
16087
  width: rect.width,
@@ -15912,7 +16105,7 @@ class ScreenshotManager {
15912
16105
  catch (error) {
15913
16106
  // 外层错误处理:确保即使发生错误也释放锁
15914
16107
  const errorMessage = error instanceof Error ? error.message : String(error);
15915
- if (!this.options.silentMode) {
16108
+ if (this.options.debug) {
15916
16109
  console.error('📸 modern-screenshot 截图异常:', errorMessage);
15917
16110
  }
15918
16111
  throw error;
@@ -15966,7 +16159,7 @@ class ScreenshotManager {
15966
16159
  link.crossOrigin = 'anonymous';
15967
16160
  document.head.appendChild(link);
15968
16161
  this.preconnected = true;
15969
- if (!this.options.silentMode) {
16162
+ if (this.options.debug) {
15970
16163
  console.log(`📸 ✅ 已预连接代理服务器: ${proxyOrigin}`);
15971
16164
  }
15972
16165
  }
@@ -16091,7 +16284,7 @@ class ScreenshotManager {
16091
16284
  if (networkImages.length === 0) {
16092
16285
  return;
16093
16286
  }
16094
- if (!this.options.silentMode) {
16287
+ if (this.options.debug) {
16095
16288
  const totalImages = images.length;
16096
16289
  console.log(`📸 发现 ${networkImages.length}/${totalImages} 个可视区域内的跨域图片,开始并行预加载...`);
16097
16290
  }
@@ -16099,7 +16292,7 @@ class ScreenshotManager {
16099
16292
  // 只有当 useProxy 为 true 且 proxyUrl 存在时才使用代理
16100
16293
  const shouldUseProxy = this.options.useProxy && this.options.proxyUrl && this.options.proxyUrl.trim() !== '';
16101
16294
  if (shouldUseProxy) {
16102
- if (!this.options.silentMode) {
16295
+ if (this.options.debug) {
16103
16296
  console.log(`📸 使用代理服务器处理跨域图片: ${this.options.proxyUrl}`);
16104
16297
  }
16105
16298
  // 优化:增加并发数,使用更大的批次
@@ -16127,13 +16320,13 @@ class ScreenshotManager {
16127
16320
  }
16128
16321
  catch (error) {
16129
16322
  // 静默失败,不影响其他图片
16130
- if (!this.options.silentMode) {
16323
+ if (this.options.debug) {
16131
16324
  console.warn(`📸 ❌ 代理预加载失败: ${originalSrc.substring(0, 50)}...`);
16132
16325
  }
16133
16326
  }
16134
16327
  }));
16135
16328
  }));
16136
- if (!this.options.silentMode) {
16329
+ if (this.options.debug) {
16137
16330
  console.log(`📸 ✅ 预处理完成,缓存了 ${this.imageProxyCache.size} 个代理图片`);
16138
16331
  }
16139
16332
  }
@@ -16158,7 +16351,7 @@ class ScreenshotManager {
16158
16351
  // 移除末尾的 ? 或 &(如果有)
16159
16352
  baseUrl = baseUrl.replace(/[?&]$/, '');
16160
16353
  const requestUrl = `${baseUrl}?${params.toString()}`;
16161
- if (!this.options.silentMode) {
16354
+ if (this.options.debug) {
16162
16355
  console.log(`📸 🔄 代理请求 URL: ${requestUrl.substring(0, 200)}...`);
16163
16356
  }
16164
16357
  // 请求代理服务器
@@ -16177,7 +16370,7 @@ class ScreenshotManager {
16177
16370
  const blob = await response.blob();
16178
16371
  // 将 blob 转换为 data URL(用于 modern-screenshot 兼容性)
16179
16372
  const dataUrl = await this.blobToDataUrl(blob);
16180
- if (!this.options.silentMode) {
16373
+ if (this.options.debug) {
16181
16374
  console.log(`📸 ✅ 代理模式成功(已转换为 data URL): ${imageUrl.substring(0, 100)}...`);
16182
16375
  }
16183
16376
  return dataUrl;
@@ -16208,7 +16401,7 @@ class ScreenshotManager {
16208
16401
  if (sizeMB > maxSizeMB) {
16209
16402
  if (this.options.skipLargeImages) {
16210
16403
  // 跳过过大的图片,返回占位符
16211
- if (!this.options.silentMode) {
16404
+ if (this.options.debug) {
16212
16405
  console.warn(`📸 ⚠️ 跳过过大图片(${sizeMB.toFixed(2)}MB > ${maxSizeMB}MB): ${url.substring(0, 100)}...`);
16213
16406
  }
16214
16407
  // 返回一个 1x1 的透明占位符,避免截图失败
@@ -16216,7 +16409,7 @@ class ScreenshotManager {
16216
16409
  }
16217
16410
  else {
16218
16411
  // 不跳过,但添加警告
16219
- if (!this.options.silentMode) {
16412
+ if (this.options.debug) {
16220
16413
  console.warn(`📸 ⚠️ 图片较大(${sizeMB.toFixed(2)}MB),可能导致内存问题: ${url.substring(0, 100)}...`);
16221
16414
  }
16222
16415
  }
@@ -16230,14 +16423,14 @@ class ScreenshotManager {
16230
16423
  if (blobSizeMB > maxSizeMB) {
16231
16424
  if (this.options.skipLargeImages) {
16232
16425
  // 跳过过大的图片,返回占位符
16233
- if (!this.options.silentMode) {
16426
+ if (this.options.debug) {
16234
16427
  console.warn(`📸 ⚠️ 跳过过大图片(实际大小 ${blobSizeMB.toFixed(2)}MB > ${maxSizeMB}MB): ${url.substring(0, 100)}...`);
16235
16428
  }
16236
16429
  return 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
16237
16430
  }
16238
16431
  else {
16239
16432
  // 不跳过,但添加警告
16240
- if (!this.options.silentMode) {
16433
+ if (this.options.debug) {
16241
16434
  console.warn(`📸 ⚠️ 图片较大(实际大小 ${blobSizeMB.toFixed(2)}MB),可能导致内存问题: ${url.substring(0, 100)}...`);
16242
16435
  }
16243
16436
  }
@@ -16253,7 +16446,7 @@ class ScreenshotManager {
16253
16446
  }
16254
16447
  catch (error) {
16255
16448
  // 下载失败,返回原 URL,让 modern-screenshot 自己处理
16256
- if (!this.options.silentMode) {
16449
+ if (this.options.debug) {
16257
16450
  console.warn(`📸 ⚠️ 下载图片失败: ${url.substring(0, 100)}...`, error);
16258
16451
  }
16259
16452
  return url;
@@ -16684,7 +16877,7 @@ class ScreenshotManager {
16684
16877
  // 更新历史记录为压缩后的数据
16685
16878
  this.screenshotHistory[this.screenshotHistory.length - 1] = compressed.dataUrl;
16686
16879
  // 打印压缩统计信息和 base64 对比
16687
- if (!this.options.silentMode) {
16880
+ if (this.options.debug) {
16688
16881
  if (compressed.error) {
16689
16882
  // 压缩失败,使用原始数据
16690
16883
  console.warn('📸 [Worker 压缩] ⚠️ 压缩失败,使用原始截图');
@@ -16738,7 +16931,7 @@ class ScreenshotManager {
16738
16931
  };
16739
16932
  newWorker.onerror = (e) => {
16740
16933
  console.error('📸 WebWorker 错误:', e);
16741
- if (!this.options.silentMode) {
16934
+ if (this.options.debug) {
16742
16935
  console.warn('📸 Worker 压缩失败,使用原始截图');
16743
16936
  }
16744
16937
  // Worker 发生错误时,如果配置了二进制模式,发送原始截图
@@ -16754,7 +16947,7 @@ class ScreenshotManager {
16754
16947
  data: combinedBuffer
16755
16948
  };
16756
16949
  this.sendToIframeCallback(message);
16757
- if (!this.options.silentMode) {
16950
+ if (this.options.debug) {
16758
16951
  console.log('📸 [Worker 错误] ✅ 原始截图已发送到 iframe');
16759
16952
  }
16760
16953
  }
@@ -16919,7 +17112,7 @@ class ScreenshotManager {
16919
17112
  async takeScreenshotAndSendBinary(config) {
16920
17113
  // 如果已经在运行,先停止再重新开始
16921
17114
  if (this.isRunning) {
16922
- if (!this.options.silentMode) {
17115
+ if (this.options.debug) {
16923
17116
  console.log(`📸 更新轮询间隔: ${this.dynamicInterval || this.options.interval}ms`);
16924
17117
  }
16925
17118
  this.stopScreenshot();
@@ -16950,7 +17143,7 @@ class ScreenshotManager {
16950
17143
  // 验证:base64Data(从 latestScreenshot 提取)和 imageBufferBase64(从 imageBuffer 转换)应该一致
16951
17144
  const isBase64Same = base64Data === imageBufferBase64;
16952
17145
  // 打印 imageBuffer 的 base64 编码(用于和接收端对比)
16953
- if (!this.options.silentMode) {
17146
+ if (this.options.debug) {
16954
17147
  console.log('📸 [发送前] 数据流程分析:');
16955
17148
  console.log(` latestScreenshot: ${latestScreenshot.substring(0, 50)}... (原始 data URL)`);
16956
17149
  console.log(` base64Data (从 latestScreenshot 提取): 长度 ${base64Data.length} 字符`);
@@ -16976,7 +17169,7 @@ class ScreenshotManager {
16976
17169
  const combinedBuffer = this.combineBinaryData(configBuffer, imageBuffer);
16977
17170
  const combinedBufferSize = combinedBuffer.byteLength;
16978
17171
  // 打印大小信息
16979
- if (!this.options.silentMode) {
17172
+ if (this.options.debug) {
16980
17173
  console.log('📸 [大小统计]');
16981
17174
  console.log(` Base64 大小: ${base64Size} 字符`);
16982
17175
  console.log(` 图片字节大小: ${(imageBufferSize / 1024).toFixed(2)} KB (${imageBufferSize} 字节)`);
@@ -16990,7 +17183,7 @@ class ScreenshotManager {
16990
17183
  data: combinedBuffer
16991
17184
  };
16992
17185
  this.sendToIframeCallback(message);
16993
- if (!this.options.silentMode) {
17186
+ if (this.options.debug) {
16994
17187
  console.log('📸 [iframe] ✅ 二进制数据已发送到 iframe');
16995
17188
  }
16996
17189
  }
@@ -17004,14 +17197,14 @@ class ScreenshotManager {
17004
17197
  }
17005
17198
  }
17006
17199
  else {
17007
- if (!this.options.silentMode) {
17200
+ if (this.options.debug) {
17008
17201
  console.warn('📸 [iframe] 截图完成但未找到截图数据');
17009
17202
  }
17010
17203
  }
17011
17204
  }
17012
17205
  else {
17013
17206
  // 启用了压缩,等待 Worker 压缩完成后在 onmessage 中自动发送
17014
- if (!this.options.silentMode) {
17207
+ if (this.options.debug) {
17015
17208
  console.log('📸 [iframe] 等待 Worker 压缩完成后发送到 iframe...');
17016
17209
  }
17017
17210
  }
@@ -17041,7 +17234,7 @@ class ScreenshotManager {
17041
17234
  const base64Data = dataUrl.split(',')[1] || '';
17042
17235
  const base64Size = base64Data.length;
17043
17236
  // 完整打印发送前的 base64 信息(用于调试)
17044
- if (!this.options.silentMode) {
17237
+ if (this.options.debug) {
17045
17238
  console.log('📸 [发送前] Base64 信息:');
17046
17239
  console.log(` Base64 长度: ${base64Size} 字符`);
17047
17240
  console.log(` 📸 [发送前] 完整 Base64:`);
@@ -17065,7 +17258,7 @@ class ScreenshotManager {
17065
17258
  const combinedBufferSize = combinedBuffer.byteLength;
17066
17259
  const combineTime = performance.now() - combineStartTime;
17067
17260
  // 打印大小信息
17068
- if (!this.options.silentMode) {
17261
+ if (this.options.debug) {
17069
17262
  console.log('📸 [压缩后-大小统计]');
17070
17263
  console.log(` Base64 大小: ${base64Size} 字符`);
17071
17264
  console.log(` 图片字节大小: ${(imageBufferSize / 1024).toFixed(2)} KB (${imageBufferSize} 字节)`);
@@ -17088,7 +17281,7 @@ class ScreenshotManager {
17088
17281
  if (scheduleStartTime) {
17089
17282
  totalTime = performance.now() - scheduleStartTime;
17090
17283
  }
17091
- if (!this.options.silentMode) {
17284
+ if (this.options.debug) {
17092
17285
  console.log('📸 [压缩后] ✅ 二进制数据已发送到 iframe');
17093
17286
  console.log(` ⏱️ 发送回调耗时: ${sendCallbackTime.toFixed(2)}ms`);
17094
17287
  console.log(` ⏱️ 发送阶段总耗时: ${totalSendTime.toFixed(2)}ms`);
@@ -17191,8 +17384,8 @@ class ScreenshotManager {
17191
17384
  if (newOptions.compress !== undefined) {
17192
17385
  this.options.compress = newOptions.compress;
17193
17386
  }
17194
- if (newOptions.silentMode !== undefined) {
17195
- this.options.silentMode = newOptions.silentMode;
17387
+ if (newOptions.debug !== undefined) {
17388
+ this.options.debug = newOptions.debug;
17196
17389
  }
17197
17390
  if (newOptions.proxyUrl !== undefined) {
17198
17391
  this.options.proxyUrl = newOptions.proxyUrl;
@@ -17205,7 +17398,7 @@ class ScreenshotManager {
17205
17398
  if (this.screenshotContext) {
17206
17399
  try {
17207
17400
  destroyContext(this.screenshotContext);
17208
- if (!this.options.silentMode) {
17401
+ if (this.options.debug) {
17209
17402
  console.log(`📸 引擎已从 ${oldEngine} 切换到 ${newEngine},已清理旧 context`);
17210
17403
  }
17211
17404
  }
@@ -17214,12 +17407,12 @@ class ScreenshotManager {
17214
17407
  }
17215
17408
  this.screenshotContext = null;
17216
17409
  }
17217
- if (!this.options.silentMode) {
17410
+ if (this.options.debug) {
17218
17411
  console.log(`📸 截图引擎已更新: ${oldEngine} → ${newEngine}`);
17219
17412
  console.log(`📸 下次截图时将使用新引擎: ${newEngine}`);
17220
17413
  }
17221
17414
  }
17222
- else if (!this.options.silentMode) {
17415
+ else if (this.options.debug) {
17223
17416
  console.log('📸 截图配置已更新');
17224
17417
  }
17225
17418
  }
@@ -17319,7 +17512,7 @@ class ScreenshotManager {
17319
17512
  const itemSizeMB = value.dataUrl.length * 0.75 / (1024 * 1024);
17320
17513
  this.imageProxyCache.delete(key);
17321
17514
  currentSizeMB -= itemSizeMB;
17322
- if (!this.options.silentMode) {
17515
+ if (this.options.debug) {
17323
17516
  console.log(`📸 清理内存缓存(超过限制): ${key.substring(0, 50)}...`);
17324
17517
  }
17325
17518
  }
@@ -17344,7 +17537,7 @@ class ScreenshotManager {
17344
17537
  expiredUrls.forEach(url => {
17345
17538
  this.imageProxyCache.delete(url);
17346
17539
  });
17347
- if (expiredUrls.length > 0 && !this.options.silentMode) {
17540
+ if (expiredUrls.length > 0 && this.options.debug) {
17348
17541
  console.log(`📸 清理了 ${expiredUrls.length} 个过期缓存`);
17349
17542
  }
17350
17543
  }
@@ -17356,7 +17549,7 @@ class ScreenshotManager {
17356
17549
  // 每2分钟清理一次过期缓存(从5分钟改为2分钟,更频繁)
17357
17550
  setInterval(() => {
17358
17551
  this.cleanExpiredCache();
17359
- if (!this.options.silentMode) {
17552
+ if (this.options.debug) {
17360
17553
  const memoryCacheSize = this.imageProxyCache.size;
17361
17554
  const memoryCacheSizeMB = Array.from(this.imageProxyCache.values())
17362
17555
  .reduce((sum, cached) => sum + cached.dataUrl.length * 0.75 / (1024 * 1024), 0);
@@ -20696,16 +20889,17 @@ class CustomerServiceSDK {
20696
20889
  agent: config.agent,
20697
20890
  timestamp: Date.now()
20698
20891
  };
20699
- // 创建悬浮图标管理器(支持自定义位置)
20892
+ // 创建悬浮图标管理器(支持自定义位置和传送目标)
20700
20893
  const iconPosition = options?.iconPosition || undefined;
20701
- this.iconManager = new IconManager(iconPosition, this.debug);
20894
+ const iconTarget = options?.target || undefined;
20895
+ this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
20702
20896
  await this.iconManager.show();
20703
20897
  // 创建iframe管理器(自动检测设备类型)
20704
20898
  this.iframeManager = new IframeManager({
20705
20899
  src: iframeUrl,
20706
- mode: 'auto', // 自动根据设备类型选择模式
20707
- width: 400,
20708
- height: 600,
20900
+ mode: 'auto', // 自动根据设备类型选择模式(PC弹窗,移动端全屏)
20901
+ width: options?.width || 450, // PC模式宽度(像素,默认450px),移动端不使用
20902
+ height: options?.height || 600, // PC模式高度(像素),移动端不使用(强制全屏)
20709
20903
  allowClose: true,
20710
20904
  debug: this.debug, // 传递 debug 标志
20711
20905
  onMessage: (messageType, _data) => {
@@ -20717,8 +20911,9 @@ class CustomerServiceSDK {
20717
20911
  // checkScreenshot 消息由 ScreenshotManager 处理,不需要在这里处理
20718
20912
  },
20719
20913
  onClose: () => {
20720
- // iframe关闭时,清理图标拖动事件监听器
20914
+ // iframe关闭时,清理图标拖动事件监听器,并重新启用图标点击
20721
20915
  this.iconManager?.forceCleanupDragEvents();
20916
+ this.iconManager?.enableClick();
20722
20917
  },
20723
20918
  ...options
20724
20919
  });
@@ -20729,16 +20924,18 @@ class CustomerServiceSDK {
20729
20924
  // 打开iframe时清除红点通知
20730
20925
  this.clearNotification();
20731
20926
  this.iframeManager?.show();
20927
+ // iframe 打开后,禁用图标点击(防止重复打开)
20928
+ this.iconManager?.disableClick();
20732
20929
  });
20733
20930
  // 初始化截图管理器(如果启用了截图功能)
20734
20931
  if (config.screenshot) {
20735
20932
  // 默认截图目标为 document.body,可以通过配置自定义
20736
20933
  const targetElement = document.body;
20737
20934
  // 传入发送消息到 iframe 的回调函数
20738
- // 将 debug 配置传递给截图管理器(通过 silentMode 的相反值)
20935
+ // 将 debug 配置传递给截图管理器
20739
20936
  const screenshotOptions = {
20740
20937
  ...config.screenshot,
20741
- silentMode: !this.debug // debug=true 时 silentMode=false(显示日志),debug=false 时 silentMode=true(隐藏日志)
20938
+ debug: this.debug // 直接传递 debug 标志
20742
20939
  };
20743
20940
  this.screenshotManager = new ScreenshotManager(targetElement, screenshotOptions, (data) => {
20744
20941
  // 通过 IframeManager 发送消息到 iframe
@@ -20953,8 +21150,9 @@ class CustomerServiceSDK {
20953
21150
  return result.visitorId;
20954
21151
  }
20955
21152
  catch (error) {
20956
- // 错误始终输出
20957
- console.warn('❌ Failed to get device fingerprint, using fallback:', error);
21153
+ if (this.debug) {
21154
+ console.warn('❌ Failed to get device fingerprint, using fallback:', error);
21155
+ }
20958
21156
  const fallbackId = 'device_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
20959
21157
  if (this.debug) {
20960
21158
  console.log('🆔 Fallback Device ID:', fallbackId);