customer-chat-sdk 1.0.19 → 1.0.21
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/README.md +157 -29
- package/dist/core/IconManager.d.ts +11 -2
- package/dist/core/IconManager.d.ts.map +1 -1
- package/dist/core/ScreenshotManager.d.ts +167 -0
- package/dist/core/ScreenshotManager.d.ts.map +1 -0
- package/dist/customer-sdk.cjs.js +873 -10
- package/dist/customer-sdk.esm.js +870 -11
- package/dist/customer-sdk.min.js +1 -1
- package/dist/index.d.ts +69 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/types/index.d.ts +30 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +3 -2
package/dist/customer-sdk.cjs.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
var html2canvas = require('html2canvas');
|
|
6
|
+
|
|
5
7
|
// 直接使用base64字符串,避免打包后路径问题
|
|
6
8
|
const iconImage = 'data:image/png;base64,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';
|
|
7
9
|
class IconManager {
|
|
8
|
-
constructor() {
|
|
10
|
+
constructor(position) {
|
|
9
11
|
this.iconElement = null;
|
|
10
12
|
this.badgeElement = null;
|
|
11
13
|
this.onClickCallback = null;
|
|
@@ -17,6 +19,8 @@ class IconManager {
|
|
|
17
19
|
this.iconStartY = 0;
|
|
18
20
|
this.dragMoveHandler = null;
|
|
19
21
|
this.dragEndHandler = null;
|
|
22
|
+
this.iconPosition = null; // 图标位置配置
|
|
23
|
+
this.iconPosition = position || null;
|
|
20
24
|
}
|
|
21
25
|
/**
|
|
22
26
|
* 显示悬浮图标
|
|
@@ -29,7 +33,7 @@ class IconManager {
|
|
|
29
33
|
this.iconElement = document.createElement('div');
|
|
30
34
|
this.iconElement.className = 'customer-sdk-icon';
|
|
31
35
|
// 直接设置样式 - 图标容器
|
|
32
|
-
|
|
36
|
+
const defaultStyle = {
|
|
33
37
|
position: 'fixed',
|
|
34
38
|
width: '30px',
|
|
35
39
|
height: '30px',
|
|
@@ -45,10 +49,35 @@ class IconManager {
|
|
|
45
49
|
transition: 'transform 0.2s ease',
|
|
46
50
|
border: 'none',
|
|
47
51
|
outline: 'none',
|
|
48
|
-
bottom: '80px',
|
|
49
|
-
right: '20px',
|
|
50
52
|
overflow: 'visible' // 允许红点显示在图标外部
|
|
51
|
-
}
|
|
53
|
+
};
|
|
54
|
+
// 如果指定了位置,使用left/top;否则使用默认的bottom/right
|
|
55
|
+
if (this.iconPosition) {
|
|
56
|
+
if (this.iconPosition.x !== undefined) {
|
|
57
|
+
defaultStyle.left = typeof this.iconPosition.x === 'number'
|
|
58
|
+
? `${this.iconPosition.x}px`
|
|
59
|
+
: this.iconPosition.x;
|
|
60
|
+
defaultStyle.right = 'auto';
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
defaultStyle.right = '20px';
|
|
64
|
+
}
|
|
65
|
+
if (this.iconPosition.y !== undefined) {
|
|
66
|
+
defaultStyle.top = typeof this.iconPosition.y === 'number'
|
|
67
|
+
? `${this.iconPosition.y}px`
|
|
68
|
+
: this.iconPosition.y;
|
|
69
|
+
defaultStyle.bottom = 'auto';
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
defaultStyle.bottom = '80px';
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
// 默认位置:右下角
|
|
77
|
+
defaultStyle.bottom = '80px';
|
|
78
|
+
defaultStyle.right = '20px';
|
|
79
|
+
}
|
|
80
|
+
Object.assign(this.iconElement.style, defaultStyle);
|
|
52
81
|
// 添加图标图片(直接使用base64字符串,避免打包后路径问题)
|
|
53
82
|
const iconImg = document.createElement('img');
|
|
54
83
|
iconImg.src = iconImage; // iconImage是base64字符串
|
|
@@ -138,10 +167,35 @@ class IconManager {
|
|
|
138
167
|
}
|
|
139
168
|
}
|
|
140
169
|
/**
|
|
141
|
-
*
|
|
170
|
+
* 设置图标位置(兼容旧的position参数)
|
|
142
171
|
*/
|
|
143
172
|
setPosition(position) {
|
|
144
|
-
//
|
|
173
|
+
// 现在固定为右下角,不做处理(保留兼容性)
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 设置图标坐标位置(x, y)
|
|
177
|
+
*/
|
|
178
|
+
setIconPosition(position) {
|
|
179
|
+
this.iconPosition = position;
|
|
180
|
+
if (this.iconElement) {
|
|
181
|
+
// 更新现有图标的位置
|
|
182
|
+
if (position.x !== undefined) {
|
|
183
|
+
this.iconElement.style.left = typeof position.x === 'number'
|
|
184
|
+
? `${position.x}px`
|
|
185
|
+
: position.x;
|
|
186
|
+
this.iconElement.style.right = 'auto';
|
|
187
|
+
}
|
|
188
|
+
if (position.y !== undefined) {
|
|
189
|
+
this.iconElement.style.top = typeof position.y === 'number'
|
|
190
|
+
? `${position.y}px`
|
|
191
|
+
: position.y;
|
|
192
|
+
this.iconElement.style.bottom = 'auto';
|
|
193
|
+
}
|
|
194
|
+
// 保存当前位置用于拖动
|
|
195
|
+
const rect = this.iconElement.getBoundingClientRect();
|
|
196
|
+
this.iconStartX = rect.left;
|
|
197
|
+
this.iconStartY = rect.top;
|
|
198
|
+
}
|
|
145
199
|
}
|
|
146
200
|
/**
|
|
147
201
|
* setStyle(暂时保留接口兼容性)
|
|
@@ -871,6 +925,748 @@ class IframeManager {
|
|
|
871
925
|
}
|
|
872
926
|
}
|
|
873
927
|
|
|
928
|
+
// @ts-ignore - html2canvas may not have type definitions
|
|
929
|
+
/**
|
|
930
|
+
* 截图管理器
|
|
931
|
+
* 负责页面截图、压缩和上传功能
|
|
932
|
+
*/
|
|
933
|
+
class ScreenshotManager {
|
|
934
|
+
constructor(targetElement, options = {}) {
|
|
935
|
+
this.targetElement = null;
|
|
936
|
+
this.isRunning = false;
|
|
937
|
+
this.screenshotCount = 0;
|
|
938
|
+
this.screenshotHistory = [];
|
|
939
|
+
this.lastScreenshotTime = 0;
|
|
940
|
+
this.error = null;
|
|
941
|
+
this.isEnabled = false;
|
|
942
|
+
// 上传相关状态
|
|
943
|
+
this.isUploading = false;
|
|
944
|
+
this.uploadError = null;
|
|
945
|
+
this.uploadProgress = { success: 0, failed: 0 };
|
|
946
|
+
this.currentUploadConfig = null;
|
|
947
|
+
// WebWorker 相关
|
|
948
|
+
this.worker = null;
|
|
949
|
+
this.screenshotTimer = null;
|
|
950
|
+
// PostMessage 监听器
|
|
951
|
+
this.messageHandler = null;
|
|
952
|
+
// 动态轮询间隔(由 iframe 消息控制)
|
|
953
|
+
this.dynamicInterval = null;
|
|
954
|
+
// 过期定时器
|
|
955
|
+
this.expirationTimer = null;
|
|
956
|
+
// 图片代理缓存
|
|
957
|
+
this.imageProxyCache = new Map();
|
|
958
|
+
// 全局错误处理器
|
|
959
|
+
this.globalErrorHandler = null;
|
|
960
|
+
this.globalRejectionHandler = null;
|
|
961
|
+
this.targetElement = targetElement;
|
|
962
|
+
this.options = {
|
|
963
|
+
interval: options.interval ?? 5000,
|
|
964
|
+
quality: options.quality ?? 0.4,
|
|
965
|
+
scale: options.scale ?? 1,
|
|
966
|
+
maxHistory: options.maxHistory ?? 10,
|
|
967
|
+
compress: options.compress ?? false,
|
|
968
|
+
maxWidth: options.maxWidth ?? 1600,
|
|
969
|
+
maxHeight: options.maxHeight ?? 900,
|
|
970
|
+
outputFormat: options.outputFormat ?? 'webp',
|
|
971
|
+
enableCORS: options.enableCORS ?? true,
|
|
972
|
+
proxyUrl: options.proxyUrl ?? '',
|
|
973
|
+
engine: options.engine ?? 'html2canvas',
|
|
974
|
+
corsMode: options.corsMode ?? 'canvas-proxy',
|
|
975
|
+
silentMode: options.silentMode ?? false,
|
|
976
|
+
maxRetries: options.maxRetries ?? 2
|
|
977
|
+
};
|
|
978
|
+
this.setupMessageListener();
|
|
979
|
+
this.setupVisibilityChangeListener();
|
|
980
|
+
}
|
|
981
|
+
/**
|
|
982
|
+
* 设置目标元素
|
|
983
|
+
*/
|
|
984
|
+
setTargetElement(element) {
|
|
985
|
+
this.targetElement = element;
|
|
986
|
+
}
|
|
987
|
+
/**
|
|
988
|
+
* 设置消息监听
|
|
989
|
+
*/
|
|
990
|
+
setupMessageListener() {
|
|
991
|
+
if (this.messageHandler) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
this.messageHandler = (event) => {
|
|
995
|
+
this.handleIframeMessage(event);
|
|
996
|
+
};
|
|
997
|
+
window.addEventListener('message', this.messageHandler);
|
|
998
|
+
}
|
|
999
|
+
/**
|
|
1000
|
+
* 设置页面可见性监听
|
|
1001
|
+
*/
|
|
1002
|
+
setupVisibilityChangeListener() {
|
|
1003
|
+
document.addEventListener('visibilitychange', () => {
|
|
1004
|
+
if (document.hidden) {
|
|
1005
|
+
if (!this.options.silentMode) {
|
|
1006
|
+
console.log('📸 页面隐藏,截图轮询已暂停');
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
else {
|
|
1010
|
+
if (!this.options.silentMode) {
|
|
1011
|
+
console.log('📸 页面显示,截图轮询已恢复');
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
}
|
|
1016
|
+
/**
|
|
1017
|
+
* 处理 iframe postMessage 消息
|
|
1018
|
+
*/
|
|
1019
|
+
handleIframeMessage(event) {
|
|
1020
|
+
try {
|
|
1021
|
+
// 验证消息类型
|
|
1022
|
+
if (!event.data || event.data.type !== 'checkScreenshot') {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
1025
|
+
if (!this.options.silentMode) {
|
|
1026
|
+
console.log('📸 [iframe] 收到消息:', event.data);
|
|
1027
|
+
}
|
|
1028
|
+
// 解析上传配置
|
|
1029
|
+
const config = this.parseUploadConfig(event.data.data);
|
|
1030
|
+
if (!config) {
|
|
1031
|
+
console.error('📸 [iframe] 解析配置失败');
|
|
1032
|
+
this.uploadError = '解析上传配置失败';
|
|
1033
|
+
return;
|
|
1034
|
+
}
|
|
1035
|
+
// 保存当前配置
|
|
1036
|
+
this.currentUploadConfig = config;
|
|
1037
|
+
// 根据 expirationMinutes 判断是否开启截图功能
|
|
1038
|
+
if (config.expirationMinutes > 0) {
|
|
1039
|
+
// 启用截图功能
|
|
1040
|
+
if (!this.isEnabled) {
|
|
1041
|
+
if (!this.options.silentMode) {
|
|
1042
|
+
console.log('📸 [iframe] 启用截图功能');
|
|
1043
|
+
}
|
|
1044
|
+
this.isEnabled = true;
|
|
1045
|
+
}
|
|
1046
|
+
// 设置动态轮询间隔(使用 duration,单位:毫秒)
|
|
1047
|
+
this.dynamicInterval = config.duration || this.options.interval;
|
|
1048
|
+
// 启动或更新截图轮询
|
|
1049
|
+
if (!this.options.silentMode) {
|
|
1050
|
+
console.log(`📸 [iframe] 设置轮询间隔: ${this.dynamicInterval}ms,过期时间: ${config.expirationMinutes}分钟`);
|
|
1051
|
+
}
|
|
1052
|
+
this.startScreenshot(this.dynamicInterval);
|
|
1053
|
+
// 设置过期定时器
|
|
1054
|
+
if (this.expirationTimer) {
|
|
1055
|
+
clearTimeout(this.expirationTimer);
|
|
1056
|
+
this.expirationTimer = null;
|
|
1057
|
+
}
|
|
1058
|
+
this.expirationTimer = setTimeout(() => {
|
|
1059
|
+
if (!this.options.silentMode) {
|
|
1060
|
+
console.log('📸 [iframe] 上传配置已过期,停止截图');
|
|
1061
|
+
}
|
|
1062
|
+
this.stopScreenshot();
|
|
1063
|
+
this.isEnabled = false;
|
|
1064
|
+
this.currentUploadConfig = null;
|
|
1065
|
+
this.expirationTimer = null;
|
|
1066
|
+
}, config.expirationMinutes * 60 * 1000);
|
|
1067
|
+
}
|
|
1068
|
+
else {
|
|
1069
|
+
// 禁用截图功能
|
|
1070
|
+
if (!this.options.silentMode) {
|
|
1071
|
+
console.log('📸 [iframe] expirationMinutes <= 0,禁用截图功能');
|
|
1072
|
+
}
|
|
1073
|
+
this.stopScreenshot();
|
|
1074
|
+
this.isEnabled = false;
|
|
1075
|
+
this.currentUploadConfig = null;
|
|
1076
|
+
if (this.expirationTimer) {
|
|
1077
|
+
clearTimeout(this.expirationTimer);
|
|
1078
|
+
this.expirationTimer = null;
|
|
1079
|
+
}
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
// 获取最新截图并上传
|
|
1083
|
+
const latestScreenshot = this.getLatestScreenshot();
|
|
1084
|
+
if (!latestScreenshot) {
|
|
1085
|
+
if (!this.options.silentMode) {
|
|
1086
|
+
console.warn('📸 [iframe] 没有可用的截图,等待下次截图后上传');
|
|
1087
|
+
}
|
|
1088
|
+
return;
|
|
1089
|
+
}
|
|
1090
|
+
// 执行上传
|
|
1091
|
+
this.isUploading = true;
|
|
1092
|
+
this.uploadError = null;
|
|
1093
|
+
this.uploadScreenshot(latestScreenshot, config)
|
|
1094
|
+
.then((success) => {
|
|
1095
|
+
if (success) {
|
|
1096
|
+
if (!this.options.silentMode) {
|
|
1097
|
+
console.log('📸 [iframe] ✅ 截图上传成功');
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
else {
|
|
1101
|
+
console.error('📸 [iframe] ❌ 截图上传失败');
|
|
1102
|
+
}
|
|
1103
|
+
})
|
|
1104
|
+
.catch((error) => {
|
|
1105
|
+
console.error('📸 [iframe] ❌ 上传异常:', error);
|
|
1106
|
+
this.uploadError = error instanceof Error ? error.message : String(error);
|
|
1107
|
+
})
|
|
1108
|
+
.finally(() => {
|
|
1109
|
+
this.isUploading = false;
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
catch (error) {
|
|
1113
|
+
console.error('📸 [iframe] 处理消息失败:', error);
|
|
1114
|
+
this.uploadError = error instanceof Error ? error.message : String(error);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* 解析上传配置
|
|
1119
|
+
*/
|
|
1120
|
+
parseUploadConfig(data) {
|
|
1121
|
+
try {
|
|
1122
|
+
const configStr = typeof data === 'string' ? data : JSON.stringify(data);
|
|
1123
|
+
const config = JSON.parse(configStr);
|
|
1124
|
+
if (!config.uploadUrl || !config.contentType) {
|
|
1125
|
+
console.error('📸 [上传] 配置缺少必需字段:', config);
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
if (typeof config.duration !== 'number' || config.duration <= 0) {
|
|
1129
|
+
config.duration = this.options.interval;
|
|
1130
|
+
}
|
|
1131
|
+
return config;
|
|
1132
|
+
}
|
|
1133
|
+
catch (error) {
|
|
1134
|
+
console.error('📸 [上传] 解析配置失败:', error, '原始数据:', data);
|
|
1135
|
+
return null;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* 开始轮询截图
|
|
1140
|
+
*/
|
|
1141
|
+
startScreenshot(customInterval) {
|
|
1142
|
+
if (!this.isEnabled) {
|
|
1143
|
+
console.warn('📸 截图功能已禁用,无法启动');
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
const currentInterval = customInterval || this.dynamicInterval || this.options.interval;
|
|
1147
|
+
if (this.isRunning) {
|
|
1148
|
+
if (!this.options.silentMode) {
|
|
1149
|
+
console.log(`📸 更新轮询间隔: ${currentInterval}ms`);
|
|
1150
|
+
}
|
|
1151
|
+
this.stopScreenshot();
|
|
1152
|
+
}
|
|
1153
|
+
if (!this.options.silentMode) {
|
|
1154
|
+
console.log(`📸 开始轮询截图,间隔: ${currentInterval}ms`);
|
|
1155
|
+
}
|
|
1156
|
+
this.isRunning = true;
|
|
1157
|
+
// 创建 WebWorker
|
|
1158
|
+
if (!this.worker && this.options.compress) {
|
|
1159
|
+
this.worker = this.createWorker();
|
|
1160
|
+
}
|
|
1161
|
+
// 设置定时器
|
|
1162
|
+
this.screenshotTimer = setInterval(async () => {
|
|
1163
|
+
if (this.isRunning && this.isEnabled && !document.hidden) {
|
|
1164
|
+
await this.takeScreenshot();
|
|
1165
|
+
}
|
|
1166
|
+
}, currentInterval);
|
|
1167
|
+
// 立即执行一次
|
|
1168
|
+
setTimeout(() => {
|
|
1169
|
+
if (this.isRunning && this.isEnabled) {
|
|
1170
|
+
this.takeScreenshot();
|
|
1171
|
+
}
|
|
1172
|
+
}, 0);
|
|
1173
|
+
}
|
|
1174
|
+
/**
|
|
1175
|
+
* 停止轮询截图
|
|
1176
|
+
*/
|
|
1177
|
+
stopScreenshot() {
|
|
1178
|
+
if (!this.isRunning) {
|
|
1179
|
+
return;
|
|
1180
|
+
}
|
|
1181
|
+
if (!this.options.silentMode) {
|
|
1182
|
+
console.log('📸 停止轮询截图');
|
|
1183
|
+
}
|
|
1184
|
+
this.isRunning = false;
|
|
1185
|
+
if (this.screenshotTimer) {
|
|
1186
|
+
clearInterval(this.screenshotTimer);
|
|
1187
|
+
this.screenshotTimer = null;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
/**
|
|
1191
|
+
* 手动截图一次
|
|
1192
|
+
*/
|
|
1193
|
+
async captureOnce() {
|
|
1194
|
+
if (!this.isEnabled) {
|
|
1195
|
+
console.warn('📸 截图功能已禁用,无法执行截图');
|
|
1196
|
+
return false;
|
|
1197
|
+
}
|
|
1198
|
+
return await this.takeScreenshot();
|
|
1199
|
+
}
|
|
1200
|
+
/**
|
|
1201
|
+
* 执行截图
|
|
1202
|
+
*/
|
|
1203
|
+
async takeScreenshot() {
|
|
1204
|
+
if (!this.targetElement) {
|
|
1205
|
+
console.warn('📸 目标元素不存在');
|
|
1206
|
+
return false;
|
|
1207
|
+
}
|
|
1208
|
+
this.setupGlobalErrorHandlers();
|
|
1209
|
+
try {
|
|
1210
|
+
if (!this.options.silentMode) {
|
|
1211
|
+
console.log(`📸 开始截图 #${this.screenshotCount + 1}...`);
|
|
1212
|
+
}
|
|
1213
|
+
// 等待 CSS 和字体加载完成
|
|
1214
|
+
await Promise.all([
|
|
1215
|
+
this.waitForStylesAndFonts(),
|
|
1216
|
+
this.waitForFonts()
|
|
1217
|
+
]);
|
|
1218
|
+
// 选择截图引擎(仅支持 html2canvas)
|
|
1219
|
+
const selectedEngine = 'html2canvas';
|
|
1220
|
+
if (!this.options.silentMode) {
|
|
1221
|
+
console.log(`📸 使用截图引擎: ${selectedEngine}`);
|
|
1222
|
+
}
|
|
1223
|
+
// 预处理网络图片
|
|
1224
|
+
if (this.options.enableCORS) {
|
|
1225
|
+
await this.preprocessNetworkImages(this.targetElement);
|
|
1226
|
+
await this.waitForImagesToLoad(this.targetElement);
|
|
1227
|
+
}
|
|
1228
|
+
let dataUrl;
|
|
1229
|
+
// 等待一小段时间,确保 DOM 更新完成
|
|
1230
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1231
|
+
// 使用 html2canvas 截图
|
|
1232
|
+
dataUrl = await this.takeScreenshotWithHtml2Canvas(this.targetElement);
|
|
1233
|
+
const timestamp = Date.now();
|
|
1234
|
+
// 更新状态
|
|
1235
|
+
this.screenshotCount++;
|
|
1236
|
+
this.lastScreenshotTime = timestamp;
|
|
1237
|
+
// 管理历史记录
|
|
1238
|
+
if (this.screenshotHistory.length >= this.options.maxHistory) {
|
|
1239
|
+
this.screenshotHistory.shift();
|
|
1240
|
+
}
|
|
1241
|
+
this.screenshotHistory.push(dataUrl);
|
|
1242
|
+
// 打印基本信息
|
|
1243
|
+
const base64Data = dataUrl.split(',')[1];
|
|
1244
|
+
if (!this.options.silentMode) {
|
|
1245
|
+
console.log('📸 截图完成:');
|
|
1246
|
+
console.log(`📸 编号: #${this.screenshotCount}`);
|
|
1247
|
+
console.log(`📸 时间: ${new Date(timestamp).toLocaleTimeString()}`);
|
|
1248
|
+
console.log(`📸 原始大小: ${Math.round(base64Data.length * 0.75 / 1024)} KB`);
|
|
1249
|
+
console.log(`📸 Base64 长度: ${base64Data.length} 字符`);
|
|
1250
|
+
}
|
|
1251
|
+
// 检测移动设备和低端设备
|
|
1252
|
+
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
1253
|
+
const isLowEndDevice = navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4;
|
|
1254
|
+
// 如果启用压缩(仅在非移动设备上启用)
|
|
1255
|
+
const shouldCompress = this.options.compress && !isMobile && !isLowEndDevice;
|
|
1256
|
+
if (shouldCompress && this.worker) {
|
|
1257
|
+
if (!this.options.silentMode) {
|
|
1258
|
+
console.log('📸 发送到 WebWorker 进行压缩...');
|
|
1259
|
+
}
|
|
1260
|
+
this.worker.postMessage({
|
|
1261
|
+
type: 'COMPRESS_IMAGE',
|
|
1262
|
+
data: {
|
|
1263
|
+
dataUrl,
|
|
1264
|
+
maxWidth: this.options.maxWidth,
|
|
1265
|
+
maxHeight: this.options.maxHeight,
|
|
1266
|
+
quality: this.options.quality,
|
|
1267
|
+
outputFormat: this.options.outputFormat,
|
|
1268
|
+
timestamp,
|
|
1269
|
+
count: this.screenshotCount
|
|
1270
|
+
}
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
this.error = null;
|
|
1274
|
+
return true;
|
|
1275
|
+
}
|
|
1276
|
+
catch (err) {
|
|
1277
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1278
|
+
const isCorsError = errorMessage.includes('CORS') ||
|
|
1279
|
+
errorMessage.includes('Access-Control-Allow-Origin') ||
|
|
1280
|
+
errorMessage.includes('cross-origin') ||
|
|
1281
|
+
errorMessage.includes('XMLHttpRequest') ||
|
|
1282
|
+
errorMessage.includes('Status:0');
|
|
1283
|
+
if (!isCorsError) {
|
|
1284
|
+
console.error('📸 截图失败:', err);
|
|
1285
|
+
this.error = errorMessage;
|
|
1286
|
+
}
|
|
1287
|
+
else if (!this.options.silentMode) {
|
|
1288
|
+
console.warn('📸 截图遇到跨域问题(已忽略)');
|
|
1289
|
+
}
|
|
1290
|
+
return false;
|
|
1291
|
+
}
|
|
1292
|
+
finally {
|
|
1293
|
+
this.removeGlobalErrorHandlers();
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
/**
|
|
1297
|
+
* 使用 html2canvas 截图
|
|
1298
|
+
*/
|
|
1299
|
+
async takeScreenshotWithHtml2Canvas(element) {
|
|
1300
|
+
if (!this.options.silentMode) {
|
|
1301
|
+
console.log('📸 使用 html2canvas 引擎截图...');
|
|
1302
|
+
}
|
|
1303
|
+
const { width, height } = this.calculateCompressedSize(element.scrollWidth, element.scrollHeight, this.options.maxWidth, this.options.maxHeight);
|
|
1304
|
+
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
|
|
1305
|
+
const isLowEndDevice = navigator.hardwareConcurrency && navigator.hardwareConcurrency <= 4;
|
|
1306
|
+
const mobileQuality = isMobile || isLowEndDevice ? Math.max(this.options.quality * 0.7, 0.3) : this.options.quality;
|
|
1307
|
+
const mobileWidth = isMobile ? Math.min(width, 1280) : width;
|
|
1308
|
+
const mobileHeight = isMobile ? Math.min(height, 720) : height;
|
|
1309
|
+
const canvas = await html2canvas(element, {
|
|
1310
|
+
useCORS: this.options.enableCORS,
|
|
1311
|
+
allowTaint: true,
|
|
1312
|
+
scale: isMobile ? 0.8 : this.options.scale,
|
|
1313
|
+
backgroundColor: '#ffffff',
|
|
1314
|
+
logging: false,
|
|
1315
|
+
width: mobileWidth,
|
|
1316
|
+
height: mobileHeight,
|
|
1317
|
+
scrollX: 0,
|
|
1318
|
+
scrollY: 0,
|
|
1319
|
+
removeContainer: false,
|
|
1320
|
+
foreignObjectRendering: false,
|
|
1321
|
+
onclone: (clonedDoc) => {
|
|
1322
|
+
const clonedImages = clonedDoc.querySelectorAll('img');
|
|
1323
|
+
clonedImages.forEach((img) => {
|
|
1324
|
+
if (img.src && !img.src.startsWith('data:') && !img.src.startsWith('blob:')) {
|
|
1325
|
+
try {
|
|
1326
|
+
const imgUrl = new URL(img.src, window.location.href);
|
|
1327
|
+
const currentOrigin = window.location.origin;
|
|
1328
|
+
if (this.options.proxyUrl && img.src.includes(this.options.proxyUrl.split('/api/image-proxy')[0])) {
|
|
1329
|
+
return;
|
|
1330
|
+
}
|
|
1331
|
+
if (imgUrl.origin !== currentOrigin) {
|
|
1332
|
+
if (this.options.corsMode === 'canvas-proxy') {
|
|
1333
|
+
const cachedDataUrl = this.imageProxyCache.get(img.src);
|
|
1334
|
+
if (cachedDataUrl) {
|
|
1335
|
+
img.src = cachedDataUrl;
|
|
1336
|
+
}
|
|
1337
|
+
else if (img.crossOrigin) {
|
|
1338
|
+
img.removeAttribute('crossOrigin');
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
else if (!img.crossOrigin) {
|
|
1342
|
+
img.crossOrigin = 'anonymous';
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
catch (e) {
|
|
1347
|
+
// URL 解析失败,跳过
|
|
1348
|
+
}
|
|
1349
|
+
}
|
|
1350
|
+
});
|
|
1351
|
+
},
|
|
1352
|
+
ignoreElements: (element) => {
|
|
1353
|
+
const htmlElement = element;
|
|
1354
|
+
return element.classList.contains('van-popup') ||
|
|
1355
|
+
element.classList.contains('van-overlay') ||
|
|
1356
|
+
element.classList.contains('van-toast') ||
|
|
1357
|
+
element.classList.contains('van-dialog') ||
|
|
1358
|
+
element.classList.contains('van-loading') ||
|
|
1359
|
+
htmlElement.style.display === 'none' ||
|
|
1360
|
+
htmlElement.style.visibility === 'hidden';
|
|
1361
|
+
},
|
|
1362
|
+
imageTimeout: 15000
|
|
1363
|
+
});
|
|
1364
|
+
let mimeType = 'image/jpeg';
|
|
1365
|
+
let finalQuality = mobileQuality;
|
|
1366
|
+
if (this.options.outputFormat === 'webp' && !isMobile) {
|
|
1367
|
+
try {
|
|
1368
|
+
const testCanvas = document.createElement('canvas');
|
|
1369
|
+
testCanvas.width = 1;
|
|
1370
|
+
testCanvas.height = 1;
|
|
1371
|
+
const testDataUrl = testCanvas.toDataURL('image/webp');
|
|
1372
|
+
if (testDataUrl.indexOf('webp') !== -1) {
|
|
1373
|
+
mimeType = 'image/webp';
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
catch {
|
|
1377
|
+
mimeType = 'image/jpeg';
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
else if (this.options.outputFormat === 'png') {
|
|
1381
|
+
mimeType = 'image/png';
|
|
1382
|
+
finalQuality = undefined;
|
|
1383
|
+
}
|
|
1384
|
+
const dataUrl = mimeType === 'image/png'
|
|
1385
|
+
? canvas.toDataURL(mimeType)
|
|
1386
|
+
: canvas.toDataURL(mimeType, finalQuality);
|
|
1387
|
+
return dataUrl;
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* 预处理网络图片
|
|
1391
|
+
*/
|
|
1392
|
+
async preprocessNetworkImages(element) {
|
|
1393
|
+
const images = element.querySelectorAll('img');
|
|
1394
|
+
const networkImages = Array.from(images).filter(img => {
|
|
1395
|
+
const isBlob = img.src.startsWith('blob:');
|
|
1396
|
+
const isData = img.src.startsWith('data:');
|
|
1397
|
+
const isSameOrigin = img.src.startsWith(window.location.origin);
|
|
1398
|
+
return !isBlob && !isData && !isSameOrigin;
|
|
1399
|
+
});
|
|
1400
|
+
if (networkImages.length === 0) {
|
|
1401
|
+
return;
|
|
1402
|
+
}
|
|
1403
|
+
if (!this.options.silentMode) {
|
|
1404
|
+
console.log(`📸 发现 ${networkImages.length} 个跨域图片,开始预加载并缓存...`);
|
|
1405
|
+
}
|
|
1406
|
+
// 简化处理:只缓存已代理的图片
|
|
1407
|
+
// 实际代理逻辑需要根据项目需求实现
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* 等待图片加载完成
|
|
1411
|
+
*/
|
|
1412
|
+
async waitForImagesToLoad(element) {
|
|
1413
|
+
const images = element.querySelectorAll('img');
|
|
1414
|
+
const imagePromises = [];
|
|
1415
|
+
Array.from(images).forEach((img) => {
|
|
1416
|
+
if (img.complete) {
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
imagePromises.push(new Promise((resolve) => {
|
|
1420
|
+
const timeout = setTimeout(() => {
|
|
1421
|
+
resolve();
|
|
1422
|
+
}, 5000);
|
|
1423
|
+
img.onload = () => {
|
|
1424
|
+
clearTimeout(timeout);
|
|
1425
|
+
resolve();
|
|
1426
|
+
};
|
|
1427
|
+
img.onerror = () => {
|
|
1428
|
+
clearTimeout(timeout);
|
|
1429
|
+
resolve();
|
|
1430
|
+
};
|
|
1431
|
+
}));
|
|
1432
|
+
});
|
|
1433
|
+
if (imagePromises.length > 0) {
|
|
1434
|
+
await Promise.all(imagePromises);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* 等待 CSS 和字体加载完成
|
|
1439
|
+
*/
|
|
1440
|
+
async waitForStylesAndFonts() {
|
|
1441
|
+
return new Promise((resolve) => {
|
|
1442
|
+
setTimeout(() => {
|
|
1443
|
+
resolve();
|
|
1444
|
+
}, 100);
|
|
1445
|
+
});
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* 等待字体加载完成
|
|
1449
|
+
*/
|
|
1450
|
+
async waitForFonts() {
|
|
1451
|
+
if (!document.fonts) {
|
|
1452
|
+
return Promise.resolve();
|
|
1453
|
+
}
|
|
1454
|
+
try {
|
|
1455
|
+
await document.fonts.ready;
|
|
1456
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1457
|
+
}
|
|
1458
|
+
catch {
|
|
1459
|
+
// 忽略错误
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* 计算压缩后的尺寸
|
|
1464
|
+
*/
|
|
1465
|
+
calculateCompressedSize(originalWidth, originalHeight, maxW, maxH) {
|
|
1466
|
+
let width = originalWidth;
|
|
1467
|
+
let height = originalHeight;
|
|
1468
|
+
if (width > maxW || height > maxH) {
|
|
1469
|
+
const widthRatio = maxW / width;
|
|
1470
|
+
const heightRatio = maxH / height;
|
|
1471
|
+
const ratio = Math.min(widthRatio, heightRatio);
|
|
1472
|
+
width = Math.round(width * ratio);
|
|
1473
|
+
height = Math.round(height * ratio);
|
|
1474
|
+
}
|
|
1475
|
+
return { width, height };
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* 创建 WebWorker
|
|
1479
|
+
*/
|
|
1480
|
+
createWorker() {
|
|
1481
|
+
if (typeof Worker === 'undefined' || typeof OffscreenCanvas === 'undefined') {
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
try {
|
|
1485
|
+
// 简化的 Worker 代码(实际使用时需要完整实现)
|
|
1486
|
+
const workerCode = `
|
|
1487
|
+
self.onmessage = function(e) {
|
|
1488
|
+
const { type, data } = e.data;
|
|
1489
|
+
if (type === 'COMPRESS_IMAGE') {
|
|
1490
|
+
// 压缩逻辑(简化版)
|
|
1491
|
+
self.postMessage({
|
|
1492
|
+
type: 'SCREENSHOT_RESULT',
|
|
1493
|
+
data: { compressed: { dataUrl: data.dataUrl } }
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
};
|
|
1497
|
+
`;
|
|
1498
|
+
const blob = new Blob([workerCode], { type: 'application/javascript' });
|
|
1499
|
+
const workerUrl = URL.createObjectURL(blob);
|
|
1500
|
+
const newWorker = new Worker(workerUrl);
|
|
1501
|
+
newWorker.onmessage = (e) => {
|
|
1502
|
+
const { type, data } = e.data;
|
|
1503
|
+
if (type === 'SCREENSHOT_RESULT' && data?.compressed) {
|
|
1504
|
+
if (this.screenshotHistory.length > 0) {
|
|
1505
|
+
this.screenshotHistory[this.screenshotHistory.length - 1] = data.compressed.dataUrl;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
};
|
|
1509
|
+
newWorker.onerror = (e) => {
|
|
1510
|
+
console.error('📸 WebWorker 错误:', e);
|
|
1511
|
+
};
|
|
1512
|
+
URL.revokeObjectURL(workerUrl);
|
|
1513
|
+
return newWorker;
|
|
1514
|
+
}
|
|
1515
|
+
catch (err) {
|
|
1516
|
+
console.error('📸 创建 WebWorker 失败:', err);
|
|
1517
|
+
return null;
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
/**
|
|
1521
|
+
* 设置全局错误处理器
|
|
1522
|
+
*/
|
|
1523
|
+
setupGlobalErrorHandlers() {
|
|
1524
|
+
this.globalErrorHandler = (event) => {
|
|
1525
|
+
const errorMessage = event.message || event.error?.message || '';
|
|
1526
|
+
const isCorsError = errorMessage.includes('CORS') ||
|
|
1527
|
+
errorMessage.includes('Access-Control-Allow-Origin') ||
|
|
1528
|
+
errorMessage.includes('cross-origin');
|
|
1529
|
+
if (isCorsError) {
|
|
1530
|
+
event.preventDefault();
|
|
1531
|
+
event.stopPropagation();
|
|
1532
|
+
return false;
|
|
1533
|
+
}
|
|
1534
|
+
return true;
|
|
1535
|
+
};
|
|
1536
|
+
this.globalRejectionHandler = (event) => {
|
|
1537
|
+
const errorMessage = event.reason?.message || String(event.reason) || '';
|
|
1538
|
+
const isCorsError = errorMessage.includes('CORS') ||
|
|
1539
|
+
errorMessage.includes('Access-Control-Allow-Origin') ||
|
|
1540
|
+
errorMessage.includes('cross-origin');
|
|
1541
|
+
if (isCorsError) {
|
|
1542
|
+
event.preventDefault();
|
|
1543
|
+
return false;
|
|
1544
|
+
}
|
|
1545
|
+
return true;
|
|
1546
|
+
};
|
|
1547
|
+
window.addEventListener('error', this.globalErrorHandler, true);
|
|
1548
|
+
window.addEventListener('unhandledrejection', this.globalRejectionHandler, true);
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* 移除全局错误处理器
|
|
1552
|
+
*/
|
|
1553
|
+
removeGlobalErrorHandlers() {
|
|
1554
|
+
if (this.globalErrorHandler) {
|
|
1555
|
+
window.removeEventListener('error', this.globalErrorHandler, true);
|
|
1556
|
+
this.globalErrorHandler = null;
|
|
1557
|
+
}
|
|
1558
|
+
if (this.globalRejectionHandler) {
|
|
1559
|
+
window.removeEventListener('unhandledrejection', this.globalRejectionHandler, true);
|
|
1560
|
+
this.globalRejectionHandler = null;
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
/**
|
|
1564
|
+
* 上传截图到 S3
|
|
1565
|
+
*/
|
|
1566
|
+
async uploadScreenshot(dataUrl, config) {
|
|
1567
|
+
try {
|
|
1568
|
+
if (!this.options.silentMode) {
|
|
1569
|
+
console.log('📸 [上传] 开始上传截图...');
|
|
1570
|
+
}
|
|
1571
|
+
const blob = this.dataUrlToBlob(dataUrl, config.contentType);
|
|
1572
|
+
const response = await fetch(config.uploadUrl, {
|
|
1573
|
+
method: 'PUT',
|
|
1574
|
+
body: blob,
|
|
1575
|
+
headers: {
|
|
1576
|
+
'Content-Type': config.contentType
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
if (response.status === 200) {
|
|
1580
|
+
if (!this.options.silentMode) {
|
|
1581
|
+
console.log('📸 [上传] ✅ 上传成功');
|
|
1582
|
+
}
|
|
1583
|
+
this.uploadProgress.success++;
|
|
1584
|
+
return true;
|
|
1585
|
+
}
|
|
1586
|
+
else {
|
|
1587
|
+
const errorText = await response.text().catch(() => '');
|
|
1588
|
+
const errorMsg = `上传失败: HTTP ${response.status} ${response.statusText}${errorText ? ` - ${errorText.substring(0, 200)}` : ''}`;
|
|
1589
|
+
console.error('📸 [上传] ❌', errorMsg);
|
|
1590
|
+
this.uploadError = errorMsg;
|
|
1591
|
+
this.uploadProgress.failed++;
|
|
1592
|
+
return false;
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
catch (error) {
|
|
1596
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
1597
|
+
console.error('📸 [上传] ❌ 上传异常:', errorMsg);
|
|
1598
|
+
this.uploadError = `上传异常: ${errorMsg}`;
|
|
1599
|
+
this.uploadProgress.failed++;
|
|
1600
|
+
return false;
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
/**
|
|
1604
|
+
* 将 base64 data URL 转换为 Blob
|
|
1605
|
+
*/
|
|
1606
|
+
dataUrlToBlob(dataUrl, contentType) {
|
|
1607
|
+
const arr = dataUrl.split(',');
|
|
1608
|
+
const mimeMatch = arr[0].match(/:(.*?);/);
|
|
1609
|
+
const mime = mimeMatch ? mimeMatch[1] : contentType;
|
|
1610
|
+
const bstr = atob(arr[1]);
|
|
1611
|
+
let n = bstr.length;
|
|
1612
|
+
const u8arr = new Uint8Array(n);
|
|
1613
|
+
while (n--) {
|
|
1614
|
+
u8arr[n] = bstr.charCodeAt(n);
|
|
1615
|
+
}
|
|
1616
|
+
return new Blob([u8arr], { type: mime });
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* 获取最新截图
|
|
1620
|
+
*/
|
|
1621
|
+
getLatestScreenshot() {
|
|
1622
|
+
return this.screenshotHistory[this.screenshotHistory.length - 1] || null;
|
|
1623
|
+
}
|
|
1624
|
+
/**
|
|
1625
|
+
* 启用/禁用截图功能
|
|
1626
|
+
*/
|
|
1627
|
+
enable(enabled) {
|
|
1628
|
+
this.isEnabled = enabled;
|
|
1629
|
+
if (!enabled && this.isRunning) {
|
|
1630
|
+
this.stopScreenshot();
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* 清理资源
|
|
1635
|
+
*/
|
|
1636
|
+
destroy() {
|
|
1637
|
+
this.stopScreenshot();
|
|
1638
|
+
if (this.worker) {
|
|
1639
|
+
this.worker.terminate();
|
|
1640
|
+
this.worker = null;
|
|
1641
|
+
}
|
|
1642
|
+
if (this.expirationTimer) {
|
|
1643
|
+
clearTimeout(this.expirationTimer);
|
|
1644
|
+
this.expirationTimer = null;
|
|
1645
|
+
}
|
|
1646
|
+
if (this.messageHandler) {
|
|
1647
|
+
window.removeEventListener('message', this.messageHandler);
|
|
1648
|
+
this.messageHandler = null;
|
|
1649
|
+
}
|
|
1650
|
+
this.removeGlobalErrorHandlers();
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* 获取状态
|
|
1654
|
+
*/
|
|
1655
|
+
getState() {
|
|
1656
|
+
return {
|
|
1657
|
+
isRunning: this.isRunning,
|
|
1658
|
+
screenshotCount: this.screenshotCount,
|
|
1659
|
+
lastScreenshotTime: this.lastScreenshotTime,
|
|
1660
|
+
error: this.error,
|
|
1661
|
+
isEnabled: this.isEnabled,
|
|
1662
|
+
isUploading: this.isUploading,
|
|
1663
|
+
uploadError: this.uploadError,
|
|
1664
|
+
uploadProgress: { ...this.uploadProgress },
|
|
1665
|
+
currentUploadConfig: this.currentUploadConfig
|
|
1666
|
+
};
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
|
|
874
1670
|
/******************************************************************************
|
|
875
1671
|
Copyright (c) Microsoft Corporation.
|
|
876
1672
|
|
|
@@ -4145,6 +4941,7 @@ class CustomerServiceSDK {
|
|
|
4145
4941
|
constructor() {
|
|
4146
4942
|
this.iconManager = null;
|
|
4147
4943
|
this.iframeManager = null;
|
|
4944
|
+
this.screenshotManager = null;
|
|
4148
4945
|
this.config = null;
|
|
4149
4946
|
this.isInitialized = false;
|
|
4150
4947
|
}
|
|
@@ -4165,8 +4962,9 @@ class CustomerServiceSDK {
|
|
|
4165
4962
|
console.log('Device ID:', deviceId);
|
|
4166
4963
|
// 构建iframe URL(带参数)
|
|
4167
4964
|
const iframeUrl = this.buildIframeUrl(config, deviceId);
|
|
4168
|
-
//
|
|
4169
|
-
|
|
4965
|
+
// 创建悬浮图标管理器(支持自定义位置)
|
|
4966
|
+
const iconPosition = options?.iconPosition || undefined;
|
|
4967
|
+
this.iconManager = new IconManager(iconPosition);
|
|
4170
4968
|
await this.iconManager.show();
|
|
4171
4969
|
// 创建iframe管理器(自动检测设备类型)
|
|
4172
4970
|
this.iframeManager = new IframeManager({
|
|
@@ -4175,12 +4973,13 @@ class CustomerServiceSDK {
|
|
|
4175
4973
|
width: 400,
|
|
4176
4974
|
height: 600,
|
|
4177
4975
|
allowClose: true,
|
|
4178
|
-
onMessage: (messageType,
|
|
4976
|
+
onMessage: (messageType, _data) => {
|
|
4179
4977
|
// 处理来自iframe的消息
|
|
4180
4978
|
if (messageType === 'new-message') {
|
|
4181
4979
|
// 显示红点通知(只显示红点,不显示数字)
|
|
4182
4980
|
this.showNotification(0, { pulse: true });
|
|
4183
4981
|
}
|
|
4982
|
+
// checkScreenshot 消息由 ScreenshotManager 处理,不需要在这里处理
|
|
4184
4983
|
},
|
|
4185
4984
|
onClose: () => {
|
|
4186
4985
|
// iframe关闭时,清理图标拖动事件监听器
|
|
@@ -4196,6 +4995,13 @@ class CustomerServiceSDK {
|
|
|
4196
4995
|
this.clearNotification();
|
|
4197
4996
|
this.iframeManager?.show();
|
|
4198
4997
|
});
|
|
4998
|
+
// 初始化截图管理器(如果启用了截图功能)
|
|
4999
|
+
if (config.screenshot) {
|
|
5000
|
+
// 默认截图目标为 document.body,可以通过配置自定义
|
|
5001
|
+
const targetElement = document.body;
|
|
5002
|
+
this.screenshotManager = new ScreenshotManager(targetElement, config.screenshot);
|
|
5003
|
+
console.log('CustomerSDK screenshot manager initialized');
|
|
5004
|
+
}
|
|
4199
5005
|
this.isInitialized = true;
|
|
4200
5006
|
console.log('CustomerSDK initialized successfully (iframe pre-connected to SSE)');
|
|
4201
5007
|
}
|
|
@@ -4219,6 +5025,12 @@ class CustomerServiceSDK {
|
|
|
4219
5025
|
setIconPosition(position) {
|
|
4220
5026
|
this.iconManager?.setPosition(position);
|
|
4221
5027
|
}
|
|
5028
|
+
/**
|
|
5029
|
+
* 设置图标坐标位置(x, y)
|
|
5030
|
+
*/
|
|
5031
|
+
setIconCoordinates(position) {
|
|
5032
|
+
this.iconManager?.setIconPosition(position);
|
|
5033
|
+
}
|
|
4222
5034
|
/**
|
|
4223
5035
|
* 更新图标样式
|
|
4224
5036
|
*/
|
|
@@ -4283,14 +5095,38 @@ class CustomerServiceSDK {
|
|
|
4283
5095
|
clearNotification() {
|
|
4284
5096
|
this.iconManager?.clearNotification();
|
|
4285
5097
|
}
|
|
5098
|
+
/**
|
|
5099
|
+
* 设置截图目标元素
|
|
5100
|
+
*/
|
|
5101
|
+
setScreenshotTarget(element) {
|
|
5102
|
+
this.screenshotManager?.setTargetElement(element);
|
|
5103
|
+
}
|
|
5104
|
+
/**
|
|
5105
|
+
* 手动触发截图
|
|
5106
|
+
*/
|
|
5107
|
+
async captureScreenshot() {
|
|
5108
|
+
if (!this.screenshotManager) {
|
|
5109
|
+
console.warn('截图功能未启用');
|
|
5110
|
+
return false;
|
|
5111
|
+
}
|
|
5112
|
+
return await this.screenshotManager.captureOnce();
|
|
5113
|
+
}
|
|
5114
|
+
/**
|
|
5115
|
+
* 获取截图状态
|
|
5116
|
+
*/
|
|
5117
|
+
getScreenshotState() {
|
|
5118
|
+
return this.screenshotManager?.getState() || null;
|
|
5119
|
+
}
|
|
4286
5120
|
/**
|
|
4287
5121
|
* 销毁 SDK
|
|
4288
5122
|
*/
|
|
4289
5123
|
destroy() {
|
|
4290
5124
|
this.iconManager?.hide();
|
|
4291
5125
|
this.iframeManager?.close();
|
|
5126
|
+
this.screenshotManager?.destroy();
|
|
4292
5127
|
this.iconManager = null;
|
|
4293
5128
|
this.iframeManager = null;
|
|
5129
|
+
this.screenshotManager = null;
|
|
4294
5130
|
this.config = null;
|
|
4295
5131
|
this.isInitialized = false;
|
|
4296
5132
|
console.log('CustomerSDK destroyed');
|
|
@@ -4370,6 +5206,10 @@ const setIconPosition = (position) => {
|
|
|
4370
5206
|
const sdk = getInstance();
|
|
4371
5207
|
sdk.setIconPosition(position);
|
|
4372
5208
|
};
|
|
5209
|
+
const setIconCoordinates = (position) => {
|
|
5210
|
+
const sdk = getInstance();
|
|
5211
|
+
sdk.setIconCoordinates(position);
|
|
5212
|
+
};
|
|
4373
5213
|
const setIconStyle = (style) => {
|
|
4374
5214
|
const sdk = getInstance();
|
|
4375
5215
|
sdk.setIconStyle(style);
|
|
@@ -4419,6 +5259,21 @@ const destroy = () => {
|
|
|
4419
5259
|
sdk.destroy();
|
|
4420
5260
|
globalSDKInstance = null;
|
|
4421
5261
|
};
|
|
5262
|
+
/**
|
|
5263
|
+
* 截图相关API
|
|
5264
|
+
*/
|
|
5265
|
+
const setScreenshotTarget = (element) => {
|
|
5266
|
+
const sdk = getInstance();
|
|
5267
|
+
sdk.setScreenshotTarget(element);
|
|
5268
|
+
};
|
|
5269
|
+
const captureScreenshot = async () => {
|
|
5270
|
+
const sdk = getInstance();
|
|
5271
|
+
return await sdk.captureScreenshot();
|
|
5272
|
+
};
|
|
5273
|
+
const getScreenshotState = () => {
|
|
5274
|
+
const sdk = getInstance();
|
|
5275
|
+
return sdk.getScreenshotState();
|
|
5276
|
+
};
|
|
4422
5277
|
// 默认导出
|
|
4423
5278
|
var index = {
|
|
4424
5279
|
init,
|
|
@@ -4426,6 +5281,7 @@ var index = {
|
|
|
4426
5281
|
showIcon,
|
|
4427
5282
|
hideIcon,
|
|
4428
5283
|
setIconPosition,
|
|
5284
|
+
setIconCoordinates,
|
|
4429
5285
|
setIconStyle,
|
|
4430
5286
|
openChat,
|
|
4431
5287
|
closeChat,
|
|
@@ -4434,22 +5290,29 @@ var index = {
|
|
|
4434
5290
|
getConnectionStatus,
|
|
4435
5291
|
showNotification,
|
|
4436
5292
|
clearNotification,
|
|
5293
|
+
setScreenshotTarget,
|
|
5294
|
+
captureScreenshot,
|
|
5295
|
+
getScreenshotState,
|
|
4437
5296
|
destroy
|
|
4438
5297
|
};
|
|
4439
5298
|
|
|
4440
5299
|
exports.CustomerServiceSDK = CustomerServiceSDK;
|
|
5300
|
+
exports.captureScreenshot = captureScreenshot;
|
|
4441
5301
|
exports.clearNotification = clearNotification;
|
|
4442
5302
|
exports.closeChat = closeChat;
|
|
4443
5303
|
exports.default = index;
|
|
4444
5304
|
exports.destroy = destroy;
|
|
4445
5305
|
exports.getConnectionStatus = getConnectionStatus;
|
|
4446
5306
|
exports.getInstance = getInstance;
|
|
5307
|
+
exports.getScreenshotState = getScreenshotState;
|
|
4447
5308
|
exports.hideIcon = hideIcon;
|
|
4448
5309
|
exports.init = init;
|
|
4449
5310
|
exports.isChatOpen = isChatOpen;
|
|
4450
5311
|
exports.openChat = openChat;
|
|
4451
5312
|
exports.sendToIframe = sendToIframe;
|
|
5313
|
+
exports.setIconCoordinates = setIconCoordinates;
|
|
4452
5314
|
exports.setIconPosition = setIconPosition;
|
|
4453
5315
|
exports.setIconStyle = setIconStyle;
|
|
5316
|
+
exports.setScreenshotTarget = setScreenshotTarget;
|
|
4454
5317
|
exports.showIcon = showIcon;
|
|
4455
5318
|
exports.showNotification = showNotification;
|