customer-chat-sdk 1.1.16 → 1.1.17

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.
@@ -24,6 +24,7 @@ export declare class IconManager {
24
24
  private lastTouchPosition;
25
25
  private touchStartTime;
26
26
  private clickThreshold;
27
+ private stoppedByIframe;
27
28
  private cachedContainer;
28
29
  private cachedContainerRect;
29
30
  private cachedIconSize;
@@ -1 +1 @@
1
- {"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,UAAU,YAAY;IACpB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,oBAAoB,CAAqC;IACjE,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,cAAc,CAAgB;IACtC,OAAO,CAAC,MAAM,CAAoC;IAGlD,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,WAAW,CAAQ;IAC3B,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,iBAAiB,CAAiB;IAC1C,OAAO,CAAC,cAAc,CAAI;IAC1B,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,eAAe,CAA2B;IAClD,OAAO,CAAC,mBAAmB,CAAuB;IAClD,OAAO,CAAC,cAAc,CAA0B;IAChD,OAAO,CAAC,uBAAuB,CAAI;IACnC,OAAO,CAAC,uBAAuB,CAAM;IAGrC,OAAO,CAAC,aAAa,CAA2D;IAChF,OAAO,CAAC,oBAAoB,CAAI;IAChC,OAAO,CAAC,oBAAoB,CAAK;IAGjC,OAAO,CAAC,KAAK,CAAsB;IACnC,OAAO,CAAC,eAAe,CAAqC;IAG5D,OAAO,CAAC,oBAAoB,CAAsB;IAClD,OAAO,CAAC,iBAAiB,CAA+B;IAGxD,OAAO,CAAC,aAAa,CAAsD;IAC3E,OAAO,CAAC,eAAe,CAAsD;IAC7E,OAAO,CAAC,gBAAgB,CAAsD;IAC9E,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,mBAAmB,CAA2C;IACtE,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,aAAa,CAAsB;IAC3C,OAAO,CAAC,iBAAiB,CAAsB;IAC/C,OAAO,CAAC,mBAAmB,CAAsB;IACjD,OAAO,CAAC,cAAc,CAAiB;IAGvC,OAAO,CAAC,UAAU,CAAgB;IAClC,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,gBAAgB,CAAiB;IACzC,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,eAAe,CAAe;IACtC,OAAO,CAAC,eAAe,CAAsB;gBAG3C,QAAQ,CAAC,EAAE,YAAY,EACvB,KAAK,GAAE,OAAe,EACtB,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,EAC7B,OAAO,CAAC,EAAE;QACR,UAAU,CAAC,EAAE,OAAO,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,iBAAiB,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,MAAM,CAAA;QACtC,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,eAAe,CAAC,EAAE,MAAM,CAAA;KACzB;IAmBH;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAsQ3B;;OAEG;IACH,sBAAsB,IAAI,IAAI;IAI9B;;OAEG;IACH,IAAI,IAAI,IAAI;IAuCZ;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAIhC;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAqB7C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAI1B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAsB5B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAU5B;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAwExB;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAInD;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IA8BpD;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAUzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAavB;;OAEG;IACH,OAAO,CAAC,SAAS;IAmNjB;;OAEG;IACH,OAAO,CAAC,MAAM;IA4Od;;OAEG;IACH,OAAO,CAAC,QAAQ;IAmHhB;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA+F5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IA8EzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAcnB;;OAEG;IACH,YAAY,IAAI,IAAI;IAmBpB;;OAEG;IACH,WAAW,IAAI,IAAI;IAmBnB;;OAEG;IACH,OAAO,CAAC,YAAY;IAyIpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;OAEG;IACH,OAAO,CAAC,WAAW;CAmDpB"}
1
+ {"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"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"}
@@ -88,10 +88,16 @@ export declare class IframeManager {
88
88
  */
89
89
  private handleIframeMessage;
90
90
  /**
91
- * 将消息转发给父窗口(通过 window.postMessage)
92
- * 这样 Vue 页面中的监听器也能收到消息
93
- */
94
- private forwardMessageToParent;
91
+ * 向调用 SDK 的地方(Vue 页面)广播消息
92
+ * 通过 window.dispatchEvent 触发事件,让 Vue 页面中的 window.addEventListener('message') 可以收到
93
+ *
94
+ * 重要说明:
95
+ * 1. window.dispatchEvent 创建的事件会在同一窗口内触发所有监听器,无论 origin 是什么,Vue 页面都能收到
96
+ * 2. MessageEvent 的 origin 属性不能是 '*',必须是有效的 origin 字符串(如 'http://localhost:5173')
97
+ * 3. 我们使用 window.location.origin 来标识消息来源(当前页面的 origin)
98
+ * 4. 如果 Vue 页面需要检查 origin,应该检查 window.location.origin 而不是 '*'
99
+ */
100
+ private broadcastMessageToPage;
95
101
  /**
96
102
  * 调整iframe大小(PC模式支持)
97
103
  */
@@ -1 +1 @@
1
- {"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../src/core/IframeManager.ts"],"names":[],"mappings":"AACA,UAAU,aAAa;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAA;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,cAAc,CAA+C;gBAEzD,MAAM,GAAE,aAAkB;IAetC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC3B;;OAEG;IACH,IAAI,IAAI,IAAI;IA2CZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;IA6BZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,IAAI,IAAI;IAsBf;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAM7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAa/B;;OAEG;IACH,OAAO,CAAC,YAAY;IAuLpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA8D1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAKtB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA+E3B;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA2B9B;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA0BzB"}
1
+ {"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../src/core/IframeManager.ts"],"names":[],"mappings":"AACA,UAAU,aAAa;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAA;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,MAAM,CAAC,EAAE,WAAW,GAAG,MAAM,CAAA;IAC7B,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,cAAc,CAA+C;gBAEzD,MAAM,GAAE,aAAkB;IAetC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC3B;;OAEG;IACH,IAAI,IAAI,IAAI;IA2CZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;IA6BZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,IAAI,IAAI;IAsBf;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAM7B;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAa/B;;OAEG;IACH,OAAO,CAAC,YAAY;IAuLpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA8D1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAKtB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAgC5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA8F3B;;;;;;;;;OASG;IACH,OAAO,CAAC,sBAAsB;IA+B9B;;OAEG;IACH,OAAO,CAAC,YAAY;IAOpB;;OAEG;IACH,OAAO,CAAC,gBAAgB;CA0BzB"}
@@ -22,6 +22,7 @@ class IconManager {
22
22
  this.lastTouchPosition = { x: 0, y: 0 }; // 最后触摸位置
23
23
  this.touchStartTime = 0; // 触摸开始时间
24
24
  this.clickThreshold = 15; // 点击阈值(像素)
25
+ this.stoppedByIframe = false; // 是否因为 iframe 而停止拖动(用于避免触发磁性吸附)
25
26
  // 性能优化:缓存容器信息,避免频繁查询 DOM
26
27
  this.cachedContainer = null;
27
28
  this.cachedContainerRect = null;
@@ -84,6 +85,7 @@ class IconManager {
84
85
  this.iconElement = document.createElement('div');
85
86
  this.iconElement.className = 'customer-sdk-icon';
86
87
  // 直接设置样式 - 图标容器
88
+ // 关键优化:使用 pointer-events: none 让事件穿透到 iframe,子元素使用 pointer-events: auto 保持可点击
87
89
  const defaultStyle = {
88
90
  position: 'absolute',
89
91
  width: '30px',
@@ -100,7 +102,8 @@ class IconManager {
100
102
  transition: 'transform 0.2s ease',
101
103
  border: 'none',
102
104
  outline: 'none',
103
- overflow: 'visible' // 允许红点显示在图标外部
105
+ overflow: 'visible', // 允许红点显示在图标外部
106
+ pointerEvents: 'none' // 让事件穿透到 iframe,只有子元素可以接收事件
104
107
  };
105
108
  // 如果指定了位置,使用left/top;否则使用默认的bottom/right
106
109
  if (this.iconPosition) {
@@ -198,7 +201,8 @@ class IconManager {
198
201
  height: '100%',
199
202
  borderRadius: '50%',
200
203
  overflow: 'hidden', // 限制图片溢出圆形边界
201
- position: 'relative'
204
+ position: 'relative',
205
+ pointerEvents: 'auto' // 子元素启用指针事件,让 icon 可以点击和拖动
202
206
  });
203
207
  imgContainer.appendChild(iconImg);
204
208
  this.iconElement.appendChild(imgContainer);
@@ -601,8 +605,38 @@ class IconManager {
601
605
  target.closest('.customer-sdk-overlay'))) {
602
606
  return;
603
607
  }
608
+ // 关键优化:检查触摸点是否真的在 icon 的圆形区域内
609
+ // 如果不在,让事件穿透到 iframe,不触发拖动
610
+ if (this.iconElement) {
611
+ const iconRect = this.iconElement.getBoundingClientRect();
612
+ const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
613
+ const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
614
+ // 计算触摸点相对于 icon 中心的位置
615
+ const iconCenterX = iconRect.left + iconRect.width / 2;
616
+ const iconCenterY = iconRect.top + iconRect.height / 2;
617
+ const iconRadius = Math.min(iconRect.width, iconRect.height) / 2;
618
+ const distanceFromCenter = Math.sqrt(Math.pow(clientX - iconCenterX, 2) + Math.pow(clientY - iconCenterY, 2));
619
+ // 如果触摸点在 icon 圆形区域外(留一点边距,比如 10px),不处理拖动
620
+ // 这样可以让 iframe 内的滑动正常工作
621
+ // 但边距不能太大,否则无法拖动
622
+ if (distanceFromCenter > iconRadius + 10) {
623
+ if (this.debug) {
624
+ console.log('[IconManager] Touch point outside icon circle, allowing event to pass through', {
625
+ distance: distanceFromCenter,
626
+ radius: iconRadius,
627
+ touchPoint: { x: clientX, y: clientY },
628
+ iconCenter: { x: iconCenterX, y: iconCenterY }
629
+ });
630
+ }
631
+ return;
632
+ }
633
+ }
604
634
  e.preventDefault();
605
635
  e.stopPropagation();
636
+ // 拖动开始时,临时启用 pointer-events 确保拖动事件能正常接收
637
+ if (this.iconElement) {
638
+ this.iconElement.style.pointerEvents = 'auto';
639
+ }
606
640
  // 重置状态
607
641
  this.hasMoved = false;
608
642
  this.dragStarted = false;
@@ -782,29 +816,8 @@ class IconManager {
782
816
  });
783
817
  }
784
818
  }
785
- // 检查鼠标是否在任何 iframe
786
- for (const { rect, element } of this.cachedIframes) {
787
- if (clientX >= rect.left &&
788
- clientX <= rect.right &&
789
- clientY >= rect.top &&
790
- clientY <= rect.bottom) {
791
- // 鼠标在 iframe 上,立即停止拖动
792
- if (this.debug) {
793
- console.log('[IconManager] Mouse over iframe detected, stopping drag immediately', {
794
- mousePosition: { x: clientX, y: clientY },
795
- iframeRect: {
796
- left: rect.left,
797
- top: rect.top,
798
- right: rect.right,
799
- bottom: rect.bottom
800
- },
801
- iframeSrc: element.src || 'no src'
802
- });
803
- }
804
- this.stopDrag();
805
- return;
806
- }
807
- }
819
+ // 移除 iframe 检测停止拖动的逻辑,让拖动可以在 iframe 上顺畅进行
820
+ // 不再因为检测到 iframe 而停止拖动,保持拖动的流畅性
808
821
  }
809
822
  // 更新最后记录的鼠标位置和时间戳(用于主动检测)
810
823
  const now = Date.now();
@@ -914,19 +927,18 @@ class IconManager {
914
927
  // 计算新位置
915
928
  let newX = clientX - this.dragOffset.x - containerRect.left;
916
929
  let newY = clientY - this.dragOffset.y - containerRect.top;
917
- // 限制在容器内
918
- if (container === document.body) {
919
- // 限制在视口内
920
- newX = Math.max(0, Math.min(newX, window.innerWidth - iconWidth));
921
- newY = Math.max(0, Math.min(newY, window.innerHeight - iconHeight));
922
- }
923
- else {
924
- // 限制在容器内
925
- const containerWidth = containerRect.width;
926
- const containerHeight = containerRect.height;
927
- newX = Math.max(0, Math.min(newX, containerWidth - iconWidth));
928
- newY = Math.max(0, Math.min(newY, containerHeight - iconHeight));
929
- }
930
+ // 允许拖动到容器外(允许负值),但限制在合理范围内(避免拖得太远)
931
+ // 允许拖动到容器外,最多隐藏到只剩一小部分可见
932
+ const minX = -iconWidth + 5; // 允许向左拖动,最多隐藏到只剩 5px
933
+ const maxX = container === document.body
934
+ ? window.innerWidth - 5 // 允许向右拖动,最多隐藏到只剩 5px
935
+ : containerRect.width + iconWidth - 5; // 允许超出容器,最多隐藏到只剩 5px
936
+ const minY = -iconHeight + 5; // 允许向上拖动,最多隐藏到只剩 5px
937
+ const maxY = container === document.body
938
+ ? window.innerHeight - 5 // 允许向下拖动,最多隐藏到只剩 5px
939
+ : containerRect.height + iconHeight - 5; // 允许超出容器,最多隐藏到只剩 5px
940
+ newX = Math.max(minX, Math.min(newX, maxX));
941
+ newY = Math.max(minY, Math.min(newY, maxY));
930
942
  // 性能优化:使用 requestAnimationFrame 节流位置更新
931
943
  this.pendingPosition.x = newX;
932
944
  this.pendingPosition.y = newY;
@@ -993,6 +1005,8 @@ class IconManager {
993
1005
  // 恢复样式
994
1006
  this.iconElement.style.transition = 'transform 0.2s ease';
995
1007
  this.iconElement.style.cursor = 'pointer';
1008
+ // 拖动结束时,恢复 pointer-events: none 让事件穿透到 iframe
1009
+ this.iconElement.style.pointerEvents = 'none';
996
1010
  // 检查是否是点击
997
1011
  const touchDuration = Date.now() - this.touchStartTime;
998
1012
  const isValidClick = !this.hasMoved && touchDuration < 1000 && !this.dragStarted;
@@ -1009,26 +1023,37 @@ class IconManager {
1009
1023
  }
1010
1024
  // 先保存点击状态,然后重置拖动状态
1011
1025
  const wasClick = isValidClick;
1012
- // 如果真正拖动过,执行磁性吸附
1013
- if (this.dragStarted && this.magnetic && this.iconElement) {
1014
- this.magneticSnap();
1026
+ // 保存 stoppedByIframe 状态,用于后续判断是否启动自动吸附
1027
+ const wasStoppedByIframe = this.stoppedByIframe;
1028
+ // 如果是因为 iframe 而停止拖动,不执行磁性吸附和自动吸附
1029
+ // 这样可以避免拖动到 iframe 上时 icon 缩回去
1030
+ if (this.stoppedByIframe) {
1031
+ if (this.debug) {
1032
+ console.log('[IconManager] Drag stopped by iframe, skipping magnetic snap and auto attach');
1033
+ }
1015
1034
  }
1016
- // 如果真正拖动过,保存当前位置到 iconPosition
1017
- if (this.dragStarted && this.isDragging && this.iconElement) {
1018
- const computedStyle = window.getComputedStyle(this.iconElement);
1019
- const left = computedStyle.left;
1020
- const top = computedStyle.top;
1021
- // 如果 left/top 是有效的像素值,保存到 iconPosition
1022
- if (left !== 'auto' && top !== 'auto') {
1023
- const leftValue = parseFloat(left);
1024
- const topValue = parseFloat(top);
1025
- if (!isNaN(leftValue) && !isNaN(topValue)) {
1026
- this.iconPosition = {
1027
- x: leftValue,
1028
- y: topValue
1029
- };
1030
- if (this.debug) {
1031
- console.log('Icon position saved:', this.iconPosition);
1035
+ else {
1036
+ // 如果真正拖动过,执行磁性吸附
1037
+ if (this.dragStarted && this.magnetic && this.iconElement) {
1038
+ this.magneticSnap();
1039
+ }
1040
+ // 如果真正拖动过,保存当前位置到 iconPosition
1041
+ if (this.dragStarted && this.isDragging && this.iconElement) {
1042
+ const computedStyle = window.getComputedStyle(this.iconElement);
1043
+ const left = computedStyle.left;
1044
+ const top = computedStyle.top;
1045
+ // 如果 left/top 是有效的像素值,保存到 iconPosition
1046
+ if (left !== 'auto' && top !== 'auto') {
1047
+ const leftValue = parseFloat(left);
1048
+ const topValue = parseFloat(top);
1049
+ if (!isNaN(leftValue) && !isNaN(topValue)) {
1050
+ this.iconPosition = {
1051
+ x: leftValue,
1052
+ y: topValue
1053
+ };
1054
+ if (this.debug) {
1055
+ console.log('Icon position saved:', this.iconPosition);
1056
+ }
1032
1057
  }
1033
1058
  }
1034
1059
  }
@@ -1038,8 +1063,10 @@ class IconManager {
1038
1063
  this.isDragging = false;
1039
1064
  this.dragStarted = false;
1040
1065
  this.isStoppingDrag = false; // 重置停止标志
1066
+ this.stoppedByIframe = false; // 重置 iframe 停止标志
1041
1067
  // 如果拖动后没有吸附到侧边,启动自动吸附定时器
1042
- if (!this.isAttachedToSide && this.sideAttach && this.autoAttachDelay > 0) {
1068
+ // 但如果是因为 iframe 而停止拖动,不启动自动吸附
1069
+ if (!wasStoppedByIframe && !this.isAttachedToSide && this.sideAttach && this.autoAttachDelay > 0) {
1043
1070
  this.startAutoAttachTimer();
1044
1071
  }
1045
1072
  if (wasClick) {
@@ -1107,30 +1134,8 @@ class IconManager {
1107
1134
  console.log(`[IconManager] Active detection: Updated iframe cache (${this.cachedIframes.length} iframes)`);
1108
1135
  }
1109
1136
  }
1110
- // 检查最后记录的鼠标位置是否在任何 iframe
1111
- for (const { rect, element } of this.cachedIframes) {
1112
- if (clientX >= rect.left &&
1113
- clientX <= rect.right &&
1114
- clientY >= rect.top &&
1115
- clientY <= rect.bottom) {
1116
- // 鼠标在 iframe 上,立即停止拖动
1117
- if (this.debug) {
1118
- console.log('[IconManager] Active detection: Mouse over iframe detected, stopping drag immediately', {
1119
- mousePosition: { x: clientX, y: clientY },
1120
- iframeRect: {
1121
- left: rect.left,
1122
- top: rect.top,
1123
- right: rect.right,
1124
- bottom: rect.bottom
1125
- },
1126
- iframeSrc: element.src || 'no src',
1127
- timeSinceLastMove
1128
- });
1129
- }
1130
- this.stopDrag();
1131
- return;
1132
- }
1133
- }
1137
+ // 移除 iframe 检测停止拖动的逻辑,让拖动可以在 iframe 上顺畅进行
1138
+ // 不再因为检测到 iframe 而停止拖动,保持拖动的流畅性
1134
1139
  }
1135
1140
  // 继续检测
1136
1141
  this.activeDetectionRafId = requestAnimationFrame(checkMousePosition);
@@ -1483,6 +1488,7 @@ class IconManager {
1483
1488
  justifyContent: 'center',
1484
1489
  zIndex: '1000001',
1485
1490
  boxShadow: '0 -1px 4px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.15)',
1491
+ pointerEvents: 'auto', // 子元素启用指针事件,让 badge 可以接收事件
1486
1492
  ...(count === 0 && text === '' && {
1487
1493
  width: '12px',
1488
1494
  height: '12px',
@@ -1974,10 +1980,23 @@ class IframeManager {
1974
1980
  }
1975
1981
  // 创建新的消息处理器并保存引用
1976
1982
  this.messageHandler = (event) => {
1977
- // 验证消息来源(可选的安全检查)
1978
- if (!this.config.src || event.origin === new URL(this.config.src).origin) {
1979
- this.handleIframeMessage(event.data);
1983
+ // 关键:过滤掉自己通过 dispatchEvent 发送的消息,避免无限循环
1984
+ // 自己发送的消息 source window,而 iframe 发送的消息 source 是 iframe.contentWindow
1985
+ if (event.source === window) {
1986
+ if (this.debug) {
1987
+ console.log('[IframeManager] Ignoring self-broadcasted message to prevent infinite loop:', event.data);
1988
+ }
1989
+ return;
1990
+ }
1991
+ // 不验证来源,直接处理所有消息(确保消息能够被接收)
1992
+ if (this.debug) {
1993
+ console.log('[IframeManager] Message received:', {
1994
+ data: event.data,
1995
+ origin: event.origin,
1996
+ source: event.source
1997
+ });
1980
1998
  }
1999
+ this.handleIframeMessage(event.data);
1981
2000
  };
1982
2001
  window.addEventListener('message', this.messageHandler, false);
1983
2002
  }
@@ -1986,7 +2005,7 @@ class IframeManager {
1986
2005
  */
1987
2006
  handleIframeMessage(data) {
1988
2007
  if (this.debug) {
1989
- console.log('Message from iframe:', data);
2008
+ console.log('[IframeManager] Message from iframe received:', data);
1990
2009
  }
1991
2010
  // 判断data是字符串还是对象,兼容两种格式
1992
2011
  let messageType;
@@ -1998,10 +2017,13 @@ class IframeManager {
1998
2017
  }
1999
2018
  else {
2000
2019
  if (this.debug) {
2001
- console.log('Unknown message format:', data);
2020
+ console.log('[IframeManager] Unknown message format:', data);
2002
2021
  }
2003
2022
  return;
2004
2023
  }
2024
+ if (this.debug) {
2025
+ console.log('[IframeManager] Parsed message type:', messageType);
2026
+ }
2005
2027
  // 根据消息类型处理不同的操作
2006
2028
  switch (messageType) {
2007
2029
  case 'iframe_ready':
@@ -2034,11 +2056,22 @@ class IframeManager {
2034
2056
  }
2035
2057
  break;
2036
2058
  case 'goto-login':
2037
- // 登录跳转消息 - 通过 window.postMessage 抛出给外层
2059
+ // 登录跳转消息 - 广播给调用 SDK 的 Vue 页面
2060
+ if (this.debug) {
2061
+ console.log('Received goto-login message, broadcasting to Vue page');
2062
+ }
2063
+ this.broadcastMessageToPage(data);
2064
+ if (this.config.onMessage) {
2065
+ this.config.onMessage(messageType, data);
2066
+ }
2067
+ break;
2068
+ case 'DCR_DEPOSIT':
2069
+ // 存款跳转消息 - 广播给调用 SDK 的 Vue 页面
2070
+ // payload.path 是动态的,例如: "?depositType=0&walletId=69382"
2038
2071
  if (this.debug) {
2039
- console.log('Received goto-login message, forwarding to parent window');
2072
+ console.log('Received DCR_DEPOSIT message, broadcasting to Vue page:', data);
2040
2073
  }
2041
- this.forwardMessageToParent(data);
2074
+ this.broadcastMessageToPage(data);
2042
2075
  if (this.config.onMessage) {
2043
2076
  this.config.onMessage(messageType, data);
2044
2077
  }
@@ -2046,11 +2079,11 @@ class IframeManager {
2046
2079
  default:
2047
2080
  // 处理动态消息:gotoActivityDetailById:xxx
2048
2081
  if (typeof messageType === 'string' && messageType.startsWith('gotoActivityDetailById:')) {
2049
- // 活动详情跳转消息 - 通过 window.postMessage 抛出给外层
2082
+ // 活动详情跳转消息 - 广播给调用 SDK 的 Vue 页面
2050
2083
  if (this.debug) {
2051
- console.log('Received gotoActivityDetailById message, forwarding to parent window:', messageType);
2084
+ console.log('Received gotoActivityDetailById message, broadcasting to Vue page:', messageType);
2052
2085
  }
2053
- this.forwardMessageToParent(data);
2086
+ this.broadcastMessageToPage(data);
2054
2087
  if (this.config.onMessage) {
2055
2088
  this.config.onMessage(messageType, data);
2056
2089
  }
@@ -2065,31 +2098,42 @@ class IframeManager {
2065
2098
  }
2066
2099
  }
2067
2100
  /**
2068
- * 将消息转发给父窗口(通过 window.postMessage)
2069
- * 这样 Vue 页面中的监听器也能收到消息
2101
+ * 向调用 SDK 的地方(Vue 页面)广播消息
2102
+ * 通过 window.dispatchEvent 触发事件,让 Vue 页面中的 window.addEventListener('message') 可以收到
2103
+ *
2104
+ * 重要说明:
2105
+ * 1. window.dispatchEvent 创建的事件会在同一窗口内触发所有监听器,无论 origin 是什么,Vue 页面都能收到
2106
+ * 2. MessageEvent 的 origin 属性不能是 '*',必须是有效的 origin 字符串(如 'http://localhost:5173')
2107
+ * 3. 我们使用 window.location.origin 来标识消息来源(当前页面的 origin)
2108
+ * 4. 如果 Vue 页面需要检查 origin,应该检查 window.location.origin 而不是 '*'
2070
2109
  */
2071
- forwardMessageToParent(data) {
2110
+ broadcastMessageToPage(data) {
2072
2111
  try {
2073
- // 方式1:使用 window.postMessage 发送消息(跨窗口通信)
2074
- // 如果存在父窗口且不是当前窗口,发送给父窗口
2075
- if (window.parent && window.parent !== window) {
2076
- window.parent.postMessage(data, '*');
2077
- }
2078
- // 方式2:使用 dispatchEvent 创建 MessageEvent(在同一窗口内触发所有监听器)
2079
- // 这样 Vue 页面中的 window.addEventListener('message') 也能收到
2112
+ // 使用 dispatchEvent 创建 MessageEvent,在同一窗口内触发所有监听器
2113
+ // 这样调用 SDK 的 Vue 页面中的 window.addEventListener('message') 也能收到
2114
+ // 注意:MessageEvent origin 属性不能是 '*',必须是有效的 origin 字符串
2115
+ // 我们使用 window.location.origin 来标识消息来源(当前页面的 origin)
2116
+ // Vue 页面可以收到消息,因为 dispatchEvent 会在同一窗口内触发所有监听器
2080
2117
  const messageEvent = new MessageEvent('message', {
2081
2118
  data: data,
2082
- origin: window.location.origin,
2083
- source: window
2119
+ origin: window.location.origin, // 使用当前页面的 origin,不是 '*'(MessageEvent 不支持 '*')
2120
+ source: window,
2121
+ bubbles: true,
2122
+ cancelable: true
2084
2123
  });
2085
2124
  window.dispatchEvent(messageEvent);
2086
2125
  if (this.debug) {
2087
- console.log('Message forwarded via window.postMessage and dispatchEvent:', data);
2126
+ console.log('[IframeManager] Message broadcasted to Vue page via window.dispatchEvent:', {
2127
+ data: data,
2128
+ origin: window.location.origin,
2129
+ type: 'message',
2130
+ note: 'origin is window.location.origin, not "*" (MessageEvent does not support "*")'
2131
+ });
2088
2132
  }
2089
2133
  }
2090
2134
  catch (error) {
2091
2135
  if (this.debug) {
2092
- console.error('Failed to forward message:', error);
2136
+ console.error('[IframeManager] Failed to broadcast message:', error);
2093
2137
  }
2094
2138
  }
2095
2139
  }
@@ -2926,20 +2970,20 @@ function copyInputValue(node, cloned) {
2926
2970
  }
2927
2971
 
2928
2972
  const pseudoClasses = [
2929
- ":before",
2930
- ":after"
2931
- // ':placeholder', TODO
2973
+ "::before",
2974
+ "::after"
2975
+ // '::placeholder', TODO
2932
2976
  ];
2933
2977
  const scrollbarPseudoClasses = [
2934
- ":-webkit-scrollbar",
2935
- ":-webkit-scrollbar-button",
2936
- // ':-webkit-scrollbar:horizontal', TODO
2937
- ":-webkit-scrollbar-thumb",
2938
- ":-webkit-scrollbar-track",
2939
- ":-webkit-scrollbar-track-piece",
2940
- // ':-webkit-scrollbar:vertical', TODO
2941
- ":-webkit-scrollbar-corner",
2942
- ":-webkit-resizer"
2978
+ "::-webkit-scrollbar",
2979
+ "::-webkit-scrollbar-button",
2980
+ // '::-webkit-scrollbar:horizontal', TODO
2981
+ "::-webkit-scrollbar-thumb",
2982
+ "::-webkit-scrollbar-track",
2983
+ "::-webkit-scrollbar-track-piece",
2984
+ // '::-webkit-scrollbar:vertical', TODO
2985
+ "::-webkit-scrollbar-corner",
2986
+ "::-webkit-resizer"
2943
2987
  ];
2944
2988
  function copyPseudoClass(node, cloned, copyScrollbar, context, addWordToFontFamilies) {
2945
2989
  const { ownerWindow, svgStyleElement, svgStyles, currentNodeStyle } = context;
@@ -2983,7 +3027,7 @@ function copyPseudoClass(node, cloned, copyScrollbar, context, addWordToFontFami
2983
3027
  allClasses = [];
2984
3028
  svgStyles.set(cssText, allClasses);
2985
3029
  }
2986
- allClasses.push(`.${klasses[0]}:${pseudoClass}`);
3030
+ allClasses.push(`.${klasses[0]}${pseudoClass}`);
2987
3031
  }
2988
3032
  pseudoClasses.forEach(copyBy);
2989
3033
  if (copyScrollbar)