customer-chat-sdk 1.1.15 → 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.
- package/dist/core/IconManager.d.ts +1 -0
- package/dist/core/IconManager.d.ts.map +1 -1
- package/dist/core/IframeManager.d.ts +11 -0
- package/dist/core/IframeManager.d.ts.map +1 -1
- package/dist/customer-sdk.cjs.js +202 -105
- package/dist/customer-sdk.esm.js +202 -105
- package/dist/customer-sdk.min.js +2 -2
- package/package.json +3 -2
package/dist/customer-sdk.esm.js
CHANGED
|
@@ -18,6 +18,7 @@ class IconManager {
|
|
|
18
18
|
this.lastTouchPosition = { x: 0, y: 0 }; // 最后触摸位置
|
|
19
19
|
this.touchStartTime = 0; // 触摸开始时间
|
|
20
20
|
this.clickThreshold = 15; // 点击阈值(像素)
|
|
21
|
+
this.stoppedByIframe = false; // 是否因为 iframe 而停止拖动(用于避免触发磁性吸附)
|
|
21
22
|
// 性能优化:缓存容器信息,避免频繁查询 DOM
|
|
22
23
|
this.cachedContainer = null;
|
|
23
24
|
this.cachedContainerRect = null;
|
|
@@ -80,6 +81,7 @@ class IconManager {
|
|
|
80
81
|
this.iconElement = document.createElement('div');
|
|
81
82
|
this.iconElement.className = 'customer-sdk-icon';
|
|
82
83
|
// 直接设置样式 - 图标容器
|
|
84
|
+
// 关键优化:使用 pointer-events: none 让事件穿透到 iframe,子元素使用 pointer-events: auto 保持可点击
|
|
83
85
|
const defaultStyle = {
|
|
84
86
|
position: 'absolute',
|
|
85
87
|
width: '30px',
|
|
@@ -96,7 +98,8 @@ class IconManager {
|
|
|
96
98
|
transition: 'transform 0.2s ease',
|
|
97
99
|
border: 'none',
|
|
98
100
|
outline: 'none',
|
|
99
|
-
overflow: 'visible' // 允许红点显示在图标外部
|
|
101
|
+
overflow: 'visible', // 允许红点显示在图标外部
|
|
102
|
+
pointerEvents: 'none' // 让事件穿透到 iframe,只有子元素可以接收事件
|
|
100
103
|
};
|
|
101
104
|
// 如果指定了位置,使用left/top;否则使用默认的bottom/right
|
|
102
105
|
if (this.iconPosition) {
|
|
@@ -194,7 +197,8 @@ class IconManager {
|
|
|
194
197
|
height: '100%',
|
|
195
198
|
borderRadius: '50%',
|
|
196
199
|
overflow: 'hidden', // 限制图片溢出圆形边界
|
|
197
|
-
position: 'relative'
|
|
200
|
+
position: 'relative',
|
|
201
|
+
pointerEvents: 'auto' // 子元素启用指针事件,让 icon 可以点击和拖动
|
|
198
202
|
});
|
|
199
203
|
imgContainer.appendChild(iconImg);
|
|
200
204
|
this.iconElement.appendChild(imgContainer);
|
|
@@ -597,8 +601,38 @@ class IconManager {
|
|
|
597
601
|
target.closest('.customer-sdk-overlay'))) {
|
|
598
602
|
return;
|
|
599
603
|
}
|
|
604
|
+
// 关键优化:检查触摸点是否真的在 icon 的圆形区域内
|
|
605
|
+
// 如果不在,让事件穿透到 iframe,不触发拖动
|
|
606
|
+
if (this.iconElement) {
|
|
607
|
+
const iconRect = this.iconElement.getBoundingClientRect();
|
|
608
|
+
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
609
|
+
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
610
|
+
// 计算触摸点相对于 icon 中心的位置
|
|
611
|
+
const iconCenterX = iconRect.left + iconRect.width / 2;
|
|
612
|
+
const iconCenterY = iconRect.top + iconRect.height / 2;
|
|
613
|
+
const iconRadius = Math.min(iconRect.width, iconRect.height) / 2;
|
|
614
|
+
const distanceFromCenter = Math.sqrt(Math.pow(clientX - iconCenterX, 2) + Math.pow(clientY - iconCenterY, 2));
|
|
615
|
+
// 如果触摸点在 icon 圆形区域外(留一点边距,比如 10px),不处理拖动
|
|
616
|
+
// 这样可以让 iframe 内的滑动正常工作
|
|
617
|
+
// 但边距不能太大,否则无法拖动
|
|
618
|
+
if (distanceFromCenter > iconRadius + 10) {
|
|
619
|
+
if (this.debug) {
|
|
620
|
+
console.log('[IconManager] Touch point outside icon circle, allowing event to pass through', {
|
|
621
|
+
distance: distanceFromCenter,
|
|
622
|
+
radius: iconRadius,
|
|
623
|
+
touchPoint: { x: clientX, y: clientY },
|
|
624
|
+
iconCenter: { x: iconCenterX, y: iconCenterY }
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
}
|
|
600
630
|
e.preventDefault();
|
|
601
631
|
e.stopPropagation();
|
|
632
|
+
// 拖动开始时,临时启用 pointer-events 确保拖动事件能正常接收
|
|
633
|
+
if (this.iconElement) {
|
|
634
|
+
this.iconElement.style.pointerEvents = 'auto';
|
|
635
|
+
}
|
|
602
636
|
// 重置状态
|
|
603
637
|
this.hasMoved = false;
|
|
604
638
|
this.dragStarted = false;
|
|
@@ -778,29 +812,8 @@ class IconManager {
|
|
|
778
812
|
});
|
|
779
813
|
}
|
|
780
814
|
}
|
|
781
|
-
//
|
|
782
|
-
|
|
783
|
-
if (clientX >= rect.left &&
|
|
784
|
-
clientX <= rect.right &&
|
|
785
|
-
clientY >= rect.top &&
|
|
786
|
-
clientY <= rect.bottom) {
|
|
787
|
-
// 鼠标在 iframe 上,立即停止拖动
|
|
788
|
-
if (this.debug) {
|
|
789
|
-
console.log('[IconManager] Mouse over iframe detected, stopping drag immediately', {
|
|
790
|
-
mousePosition: { x: clientX, y: clientY },
|
|
791
|
-
iframeRect: {
|
|
792
|
-
left: rect.left,
|
|
793
|
-
top: rect.top,
|
|
794
|
-
right: rect.right,
|
|
795
|
-
bottom: rect.bottom
|
|
796
|
-
},
|
|
797
|
-
iframeSrc: element.src || 'no src'
|
|
798
|
-
});
|
|
799
|
-
}
|
|
800
|
-
this.stopDrag();
|
|
801
|
-
return;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
815
|
+
// 移除 iframe 检测停止拖动的逻辑,让拖动可以在 iframe 上顺畅进行
|
|
816
|
+
// 不再因为检测到 iframe 而停止拖动,保持拖动的流畅性
|
|
804
817
|
}
|
|
805
818
|
// 更新最后记录的鼠标位置和时间戳(用于主动检测)
|
|
806
819
|
const now = Date.now();
|
|
@@ -910,19 +923,18 @@ class IconManager {
|
|
|
910
923
|
// 计算新位置
|
|
911
924
|
let newX = clientX - this.dragOffset.x - containerRect.left;
|
|
912
925
|
let newY = clientY - this.dragOffset.y - containerRect.top;
|
|
913
|
-
//
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
}
|
|
926
|
+
// 允许拖动到容器外(允许负值),但限制在合理范围内(避免拖得太远)
|
|
927
|
+
// 允许拖动到容器外,最多隐藏到只剩一小部分可见
|
|
928
|
+
const minX = -iconWidth + 5; // 允许向左拖动,最多隐藏到只剩 5px
|
|
929
|
+
const maxX = container === document.body
|
|
930
|
+
? window.innerWidth - 5 // 允许向右拖动,最多隐藏到只剩 5px
|
|
931
|
+
: containerRect.width + iconWidth - 5; // 允许超出容器,最多隐藏到只剩 5px
|
|
932
|
+
const minY = -iconHeight + 5; // 允许向上拖动,最多隐藏到只剩 5px
|
|
933
|
+
const maxY = container === document.body
|
|
934
|
+
? window.innerHeight - 5 // 允许向下拖动,最多隐藏到只剩 5px
|
|
935
|
+
: containerRect.height + iconHeight - 5; // 允许超出容器,最多隐藏到只剩 5px
|
|
936
|
+
newX = Math.max(minX, Math.min(newX, maxX));
|
|
937
|
+
newY = Math.max(minY, Math.min(newY, maxY));
|
|
926
938
|
// 性能优化:使用 requestAnimationFrame 节流位置更新
|
|
927
939
|
this.pendingPosition.x = newX;
|
|
928
940
|
this.pendingPosition.y = newY;
|
|
@@ -989,6 +1001,8 @@ class IconManager {
|
|
|
989
1001
|
// 恢复样式
|
|
990
1002
|
this.iconElement.style.transition = 'transform 0.2s ease';
|
|
991
1003
|
this.iconElement.style.cursor = 'pointer';
|
|
1004
|
+
// 拖动结束时,恢复 pointer-events: none 让事件穿透到 iframe
|
|
1005
|
+
this.iconElement.style.pointerEvents = 'none';
|
|
992
1006
|
// 检查是否是点击
|
|
993
1007
|
const touchDuration = Date.now() - this.touchStartTime;
|
|
994
1008
|
const isValidClick = !this.hasMoved && touchDuration < 1000 && !this.dragStarted;
|
|
@@ -1005,26 +1019,37 @@ class IconManager {
|
|
|
1005
1019
|
}
|
|
1006
1020
|
// 先保存点击状态,然后重置拖动状态
|
|
1007
1021
|
const wasClick = isValidClick;
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1010
|
-
|
|
1022
|
+
// 保存 stoppedByIframe 状态,用于后续判断是否启动自动吸附
|
|
1023
|
+
const wasStoppedByIframe = this.stoppedByIframe;
|
|
1024
|
+
// 如果是因为 iframe 而停止拖动,不执行磁性吸附和自动吸附
|
|
1025
|
+
// 这样可以避免拖动到 iframe 上时 icon 缩回去
|
|
1026
|
+
if (this.stoppedByIframe) {
|
|
1027
|
+
if (this.debug) {
|
|
1028
|
+
console.log('[IconManager] Drag stopped by iframe, skipping magnetic snap and auto attach');
|
|
1029
|
+
}
|
|
1011
1030
|
}
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
//
|
|
1018
|
-
if (
|
|
1019
|
-
const
|
|
1020
|
-
const
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
if (
|
|
1027
|
-
|
|
1031
|
+
else {
|
|
1032
|
+
// 如果真正拖动过,执行磁性吸附
|
|
1033
|
+
if (this.dragStarted && this.magnetic && this.iconElement) {
|
|
1034
|
+
this.magneticSnap();
|
|
1035
|
+
}
|
|
1036
|
+
// 如果真正拖动过,保存当前位置到 iconPosition
|
|
1037
|
+
if (this.dragStarted && this.isDragging && this.iconElement) {
|
|
1038
|
+
const computedStyle = window.getComputedStyle(this.iconElement);
|
|
1039
|
+
const left = computedStyle.left;
|
|
1040
|
+
const top = computedStyle.top;
|
|
1041
|
+
// 如果 left/top 是有效的像素值,保存到 iconPosition
|
|
1042
|
+
if (left !== 'auto' && top !== 'auto') {
|
|
1043
|
+
const leftValue = parseFloat(left);
|
|
1044
|
+
const topValue = parseFloat(top);
|
|
1045
|
+
if (!isNaN(leftValue) && !isNaN(topValue)) {
|
|
1046
|
+
this.iconPosition = {
|
|
1047
|
+
x: leftValue,
|
|
1048
|
+
y: topValue
|
|
1049
|
+
};
|
|
1050
|
+
if (this.debug) {
|
|
1051
|
+
console.log('Icon position saved:', this.iconPosition);
|
|
1052
|
+
}
|
|
1028
1053
|
}
|
|
1029
1054
|
}
|
|
1030
1055
|
}
|
|
@@ -1034,8 +1059,10 @@ class IconManager {
|
|
|
1034
1059
|
this.isDragging = false;
|
|
1035
1060
|
this.dragStarted = false;
|
|
1036
1061
|
this.isStoppingDrag = false; // 重置停止标志
|
|
1062
|
+
this.stoppedByIframe = false; // 重置 iframe 停止标志
|
|
1037
1063
|
// 如果拖动后没有吸附到侧边,启动自动吸附定时器
|
|
1038
|
-
|
|
1064
|
+
// 但如果是因为 iframe 而停止拖动,不启动自动吸附
|
|
1065
|
+
if (!wasStoppedByIframe && !this.isAttachedToSide && this.sideAttach && this.autoAttachDelay > 0) {
|
|
1039
1066
|
this.startAutoAttachTimer();
|
|
1040
1067
|
}
|
|
1041
1068
|
if (wasClick) {
|
|
@@ -1103,30 +1130,8 @@ class IconManager {
|
|
|
1103
1130
|
console.log(`[IconManager] Active detection: Updated iframe cache (${this.cachedIframes.length} iframes)`);
|
|
1104
1131
|
}
|
|
1105
1132
|
}
|
|
1106
|
-
//
|
|
1107
|
-
|
|
1108
|
-
if (clientX >= rect.left &&
|
|
1109
|
-
clientX <= rect.right &&
|
|
1110
|
-
clientY >= rect.top &&
|
|
1111
|
-
clientY <= rect.bottom) {
|
|
1112
|
-
// 鼠标在 iframe 上,立即停止拖动
|
|
1113
|
-
if (this.debug) {
|
|
1114
|
-
console.log('[IconManager] Active detection: Mouse over iframe detected, stopping drag immediately', {
|
|
1115
|
-
mousePosition: { x: clientX, y: clientY },
|
|
1116
|
-
iframeRect: {
|
|
1117
|
-
left: rect.left,
|
|
1118
|
-
top: rect.top,
|
|
1119
|
-
right: rect.right,
|
|
1120
|
-
bottom: rect.bottom
|
|
1121
|
-
},
|
|
1122
|
-
iframeSrc: element.src || 'no src',
|
|
1123
|
-
timeSinceLastMove
|
|
1124
|
-
});
|
|
1125
|
-
}
|
|
1126
|
-
this.stopDrag();
|
|
1127
|
-
return;
|
|
1128
|
-
}
|
|
1129
|
-
}
|
|
1133
|
+
// 移除 iframe 检测停止拖动的逻辑,让拖动可以在 iframe 上顺畅进行
|
|
1134
|
+
// 不再因为检测到 iframe 而停止拖动,保持拖动的流畅性
|
|
1130
1135
|
}
|
|
1131
1136
|
// 继续检测
|
|
1132
1137
|
this.activeDetectionRafId = requestAnimationFrame(checkMousePosition);
|
|
@@ -1479,6 +1484,7 @@ class IconManager {
|
|
|
1479
1484
|
justifyContent: 'center',
|
|
1480
1485
|
zIndex: '1000001',
|
|
1481
1486
|
boxShadow: '0 -1px 4px rgba(0, 0, 0, 0.3), 0 2px 8px rgba(0, 0, 0, 0.15)',
|
|
1487
|
+
pointerEvents: 'auto', // 子元素启用指针事件,让 badge 可以接收事件
|
|
1482
1488
|
...(count === 0 && text === '' && {
|
|
1483
1489
|
width: '12px',
|
|
1484
1490
|
height: '12px',
|
|
@@ -1739,7 +1745,7 @@ class IframeManager {
|
|
|
1739
1745
|
maxWidth: '100%', // 不超过 target 的宽度
|
|
1740
1746
|
maxHeight: '100%',
|
|
1741
1747
|
backgroundColor: '#ffffff',
|
|
1742
|
-
borderRadius: isPC ? '0' : '
|
|
1748
|
+
borderRadius: isPC ? '0' : '0',
|
|
1743
1749
|
boxShadow: isPC ? 'none' : '0 -4px 16px rgba(0, 0, 0, 0.25)',
|
|
1744
1750
|
border: 'none',
|
|
1745
1751
|
position: 'absolute', // 相对于 target 元素定位
|
|
@@ -1785,7 +1791,7 @@ class IframeManager {
|
|
|
1785
1791
|
maxWidth: '100%',
|
|
1786
1792
|
maxHeight: '100%',
|
|
1787
1793
|
backgroundColor: '#ffffff',
|
|
1788
|
-
borderRadius: '
|
|
1794
|
+
borderRadius: '0',
|
|
1789
1795
|
boxShadow: '0 -4px 16px rgba(0, 0, 0, 0.25)',
|
|
1790
1796
|
border: 'none',
|
|
1791
1797
|
position: 'fixed',
|
|
@@ -1970,10 +1976,23 @@ class IframeManager {
|
|
|
1970
1976
|
}
|
|
1971
1977
|
// 创建新的消息处理器并保存引用
|
|
1972
1978
|
this.messageHandler = (event) => {
|
|
1973
|
-
//
|
|
1974
|
-
|
|
1975
|
-
|
|
1979
|
+
// 关键:过滤掉自己通过 dispatchEvent 发送的消息,避免无限循环
|
|
1980
|
+
// 自己发送的消息 source 是 window,而 iframe 发送的消息 source 是 iframe.contentWindow
|
|
1981
|
+
if (event.source === window) {
|
|
1982
|
+
if (this.debug) {
|
|
1983
|
+
console.log('[IframeManager] Ignoring self-broadcasted message to prevent infinite loop:', event.data);
|
|
1984
|
+
}
|
|
1985
|
+
return;
|
|
1976
1986
|
}
|
|
1987
|
+
// 不验证来源,直接处理所有消息(确保消息能够被接收)
|
|
1988
|
+
if (this.debug) {
|
|
1989
|
+
console.log('[IframeManager] Message received:', {
|
|
1990
|
+
data: event.data,
|
|
1991
|
+
origin: event.origin,
|
|
1992
|
+
source: event.source
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
this.handleIframeMessage(event.data);
|
|
1977
1996
|
};
|
|
1978
1997
|
window.addEventListener('message', this.messageHandler, false);
|
|
1979
1998
|
}
|
|
@@ -1982,7 +2001,7 @@ class IframeManager {
|
|
|
1982
2001
|
*/
|
|
1983
2002
|
handleIframeMessage(data) {
|
|
1984
2003
|
if (this.debug) {
|
|
1985
|
-
console.log('Message from iframe:', data);
|
|
2004
|
+
console.log('[IframeManager] Message from iframe received:', data);
|
|
1986
2005
|
}
|
|
1987
2006
|
// 判断data是字符串还是对象,兼容两种格式
|
|
1988
2007
|
let messageType;
|
|
@@ -1994,10 +2013,13 @@ class IframeManager {
|
|
|
1994
2013
|
}
|
|
1995
2014
|
else {
|
|
1996
2015
|
if (this.debug) {
|
|
1997
|
-
console.log('Unknown message format:', data);
|
|
2016
|
+
console.log('[IframeManager] Unknown message format:', data);
|
|
1998
2017
|
}
|
|
1999
2018
|
return;
|
|
2000
2019
|
}
|
|
2020
|
+
if (this.debug) {
|
|
2021
|
+
console.log('[IframeManager] Parsed message type:', messageType);
|
|
2022
|
+
}
|
|
2001
2023
|
// 根据消息类型处理不同的操作
|
|
2002
2024
|
switch (messageType) {
|
|
2003
2025
|
case 'iframe_ready':
|
|
@@ -2029,11 +2051,86 @@ class IframeManager {
|
|
|
2029
2051
|
this.config.onMessage(messageType, data);
|
|
2030
2052
|
}
|
|
2031
2053
|
break;
|
|
2032
|
-
|
|
2033
|
-
//
|
|
2054
|
+
case 'goto-login':
|
|
2055
|
+
// 登录跳转消息 - 广播给调用 SDK 的 Vue 页面
|
|
2034
2056
|
if (this.debug) {
|
|
2035
|
-
console.log('
|
|
2057
|
+
console.log('Received goto-login message, broadcasting to Vue page');
|
|
2058
|
+
}
|
|
2059
|
+
this.broadcastMessageToPage(data);
|
|
2060
|
+
if (this.config.onMessage) {
|
|
2061
|
+
this.config.onMessage(messageType, data);
|
|
2062
|
+
}
|
|
2063
|
+
break;
|
|
2064
|
+
case 'DCR_DEPOSIT':
|
|
2065
|
+
// 存款跳转消息 - 广播给调用 SDK 的 Vue 页面
|
|
2066
|
+
// payload.path 是动态的,例如: "?depositType=0&walletId=69382"
|
|
2067
|
+
if (this.debug) {
|
|
2068
|
+
console.log('Received DCR_DEPOSIT message, broadcasting to Vue page:', data);
|
|
2069
|
+
}
|
|
2070
|
+
this.broadcastMessageToPage(data);
|
|
2071
|
+
if (this.config.onMessage) {
|
|
2072
|
+
this.config.onMessage(messageType, data);
|
|
2073
|
+
}
|
|
2074
|
+
break;
|
|
2075
|
+
default:
|
|
2076
|
+
// 处理动态消息:gotoActivityDetailById:xxx
|
|
2077
|
+
if (typeof messageType === 'string' && messageType.startsWith('gotoActivityDetailById:')) {
|
|
2078
|
+
// 活动详情跳转消息 - 广播给调用 SDK 的 Vue 页面
|
|
2079
|
+
if (this.debug) {
|
|
2080
|
+
console.log('Received gotoActivityDetailById message, broadcasting to Vue page:', messageType);
|
|
2081
|
+
}
|
|
2082
|
+
this.broadcastMessageToPage(data);
|
|
2083
|
+
if (this.config.onMessage) {
|
|
2084
|
+
this.config.onMessage(messageType, data);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
else {
|
|
2088
|
+
// 其他自定义消息处理
|
|
2089
|
+
if (this.debug) {
|
|
2090
|
+
console.log('Custom message:', data);
|
|
2091
|
+
}
|
|
2036
2092
|
}
|
|
2093
|
+
break;
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* 向调用 SDK 的地方(Vue 页面)广播消息
|
|
2098
|
+
* 通过 window.dispatchEvent 触发事件,让 Vue 页面中的 window.addEventListener('message') 可以收到
|
|
2099
|
+
*
|
|
2100
|
+
* 重要说明:
|
|
2101
|
+
* 1. window.dispatchEvent 创建的事件会在同一窗口内触发所有监听器,无论 origin 是什么,Vue 页面都能收到
|
|
2102
|
+
* 2. MessageEvent 的 origin 属性不能是 '*',必须是有效的 origin 字符串(如 'http://localhost:5173')
|
|
2103
|
+
* 3. 我们使用 window.location.origin 来标识消息来源(当前页面的 origin)
|
|
2104
|
+
* 4. 如果 Vue 页面需要检查 origin,应该检查 window.location.origin 而不是 '*'
|
|
2105
|
+
*/
|
|
2106
|
+
broadcastMessageToPage(data) {
|
|
2107
|
+
try {
|
|
2108
|
+
// 使用 dispatchEvent 创建 MessageEvent,在同一窗口内触发所有监听器
|
|
2109
|
+
// 这样调用 SDK 的 Vue 页面中的 window.addEventListener('message') 也能收到
|
|
2110
|
+
// 注意:MessageEvent 的 origin 属性不能是 '*',必须是有效的 origin 字符串
|
|
2111
|
+
// 我们使用 window.location.origin 来标识消息来源(当前页面的 origin)
|
|
2112
|
+
// Vue 页面可以收到消息,因为 dispatchEvent 会在同一窗口内触发所有监听器
|
|
2113
|
+
const messageEvent = new MessageEvent('message', {
|
|
2114
|
+
data: data,
|
|
2115
|
+
origin: window.location.origin, // 使用当前页面的 origin,不是 '*'(MessageEvent 不支持 '*')
|
|
2116
|
+
source: window,
|
|
2117
|
+
bubbles: true,
|
|
2118
|
+
cancelable: true
|
|
2119
|
+
});
|
|
2120
|
+
window.dispatchEvent(messageEvent);
|
|
2121
|
+
if (this.debug) {
|
|
2122
|
+
console.log('[IframeManager] Message broadcasted to Vue page via window.dispatchEvent:', {
|
|
2123
|
+
data: data,
|
|
2124
|
+
origin: window.location.origin,
|
|
2125
|
+
type: 'message',
|
|
2126
|
+
note: 'origin is window.location.origin, not "*" (MessageEvent does not support "*")'
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
}
|
|
2130
|
+
catch (error) {
|
|
2131
|
+
if (this.debug) {
|
|
2132
|
+
console.error('[IframeManager] Failed to broadcast message:', error);
|
|
2133
|
+
}
|
|
2037
2134
|
}
|
|
2038
2135
|
}
|
|
2039
2136
|
/**
|
|
@@ -2869,20 +2966,20 @@ function copyInputValue(node, cloned) {
|
|
|
2869
2966
|
}
|
|
2870
2967
|
|
|
2871
2968
|
const pseudoClasses = [
|
|
2872
|
-
"
|
|
2873
|
-
"
|
|
2874
|
-
// '
|
|
2969
|
+
"::before",
|
|
2970
|
+
"::after"
|
|
2971
|
+
// '::placeholder', TODO
|
|
2875
2972
|
];
|
|
2876
2973
|
const scrollbarPseudoClasses = [
|
|
2877
|
-
"
|
|
2878
|
-
"
|
|
2879
|
-
// '
|
|
2880
|
-
"
|
|
2881
|
-
"
|
|
2882
|
-
"
|
|
2883
|
-
// '
|
|
2884
|
-
"
|
|
2885
|
-
"
|
|
2974
|
+
"::-webkit-scrollbar",
|
|
2975
|
+
"::-webkit-scrollbar-button",
|
|
2976
|
+
// '::-webkit-scrollbar:horizontal', TODO
|
|
2977
|
+
"::-webkit-scrollbar-thumb",
|
|
2978
|
+
"::-webkit-scrollbar-track",
|
|
2979
|
+
"::-webkit-scrollbar-track-piece",
|
|
2980
|
+
// '::-webkit-scrollbar:vertical', TODO
|
|
2981
|
+
"::-webkit-scrollbar-corner",
|
|
2982
|
+
"::-webkit-resizer"
|
|
2886
2983
|
];
|
|
2887
2984
|
function copyPseudoClass(node, cloned, copyScrollbar, context, addWordToFontFamilies) {
|
|
2888
2985
|
const { ownerWindow, svgStyleElement, svgStyles, currentNodeStyle } = context;
|
|
@@ -2926,7 +3023,7 @@ function copyPseudoClass(node, cloned, copyScrollbar, context, addWordToFontFami
|
|
|
2926
3023
|
allClasses = [];
|
|
2927
3024
|
svgStyles.set(cssText, allClasses);
|
|
2928
3025
|
}
|
|
2929
|
-
allClasses.push(`.${klasses[0]}
|
|
3026
|
+
allClasses.push(`.${klasses[0]}${pseudoClass}`);
|
|
2930
3027
|
}
|
|
2931
3028
|
pseudoClasses.forEach(copyBy);
|
|
2932
3029
|
if (copyScrollbar)
|