customer-chat-sdk 1.1.8 → 1.1.10
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 +23 -15
- package/dist/core/IconManager.d.ts.map +1 -1
- package/dist/core/IconManager.interact.example.d.ts +36 -0
- package/dist/core/IconManager.interact.example.d.ts.map +1 -0
- package/dist/core/IframeManager.d.ts +6 -0
- package/dist/core/IframeManager.d.ts.map +1 -1
- package/dist/customer-sdk.cjs.js +454 -222
- package/dist/customer-sdk.esm.js +454 -222
- package/dist/customer-sdk.min.js +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/customer-sdk.cjs.js
CHANGED
|
@@ -10,20 +10,26 @@ class IconManager {
|
|
|
10
10
|
this.badgeElement = null;
|
|
11
11
|
this.onClickCallback = null;
|
|
12
12
|
this.notificationCallback = null;
|
|
13
|
-
this.isDragging = false;
|
|
14
|
-
this.dragStartX = 0;
|
|
15
|
-
this.dragStartY = 0;
|
|
16
|
-
this.iconStartX = 0;
|
|
17
|
-
this.iconStartY = 0;
|
|
18
|
-
this.dragMoveHandler = null;
|
|
19
|
-
this.dragEndHandler = null;
|
|
20
|
-
this.checkDragHandler = null; // 临时拖动检测监听器
|
|
21
|
-
this.dragStartHandler = null; // 拖动开始事件监听器
|
|
22
|
-
this.touchStartHandler = null; // 触摸开始事件监听器
|
|
23
13
|
this.iconPosition = null; // 图标位置配置
|
|
24
14
|
this.debug = false; // debug 模式标志
|
|
25
15
|
this.isClickEnabled = true; // 是否允许点击(iframe 打开时禁用)
|
|
26
16
|
this.target = null; // 图标传送目标元素(可以是 HTMLElement 或选择器字符串)
|
|
17
|
+
// 拖动相关状态
|
|
18
|
+
this.isDragging = false;
|
|
19
|
+
this.dragStarted = false; // 是否真正开始了拖拽
|
|
20
|
+
this.hasMoved = false; // 是否移动过
|
|
21
|
+
this.dragOffset = { x: 0, y: 0 }; // 拖动偏移量
|
|
22
|
+
this.lastTouchPosition = { x: 0, y: 0 }; // 最后触摸位置
|
|
23
|
+
this.touchStartTime = 0; // 触摸开始时间
|
|
24
|
+
this.clickThreshold = 15; // 点击阈值(像素)
|
|
25
|
+
// 事件处理器引用(用于清理)
|
|
26
|
+
this.onDragHandler = null;
|
|
27
|
+
this.stopDragHandler = null;
|
|
28
|
+
this.startDragHandler = null;
|
|
29
|
+
this.mouseLeaveHandler = null;
|
|
30
|
+
this.pointerLeaveHandler = null;
|
|
31
|
+
this.blurHandler = null;
|
|
32
|
+
this.dragTimeoutId = null; // 拖动超时定时器
|
|
27
33
|
this.iconPosition = position || null;
|
|
28
34
|
this.debug = debug;
|
|
29
35
|
// 保存 target(可以是 HTMLElement 或字符串选择器)
|
|
@@ -124,8 +130,6 @@ class IconManager {
|
|
|
124
130
|
});
|
|
125
131
|
imgContainer.appendChild(iconImg);
|
|
126
132
|
this.iconElement.appendChild(imgContainer);
|
|
127
|
-
// 添加拖动和点击事件
|
|
128
|
-
this.setupDragEvents();
|
|
129
133
|
// 添加到目标元素(如果 target 是字符串,需要重新查找,因为可能在初始化时元素还不存在)
|
|
130
134
|
const targetElement = this.getTargetElement();
|
|
131
135
|
if (targetElement) {
|
|
@@ -138,6 +142,8 @@ class IconManager {
|
|
|
138
142
|
console.warn('Target element not found, icon added to document.body');
|
|
139
143
|
}
|
|
140
144
|
}
|
|
145
|
+
// 设置拖动事件
|
|
146
|
+
this.setupDragEvents();
|
|
141
147
|
if (this.debug) {
|
|
142
148
|
console.log('CustomerSDK icon displayed');
|
|
143
149
|
}
|
|
@@ -146,49 +152,36 @@ class IconManager {
|
|
|
146
152
|
* 强制清理所有拖动事件监听器
|
|
147
153
|
*/
|
|
148
154
|
forceCleanupDragEvents() {
|
|
149
|
-
|
|
150
|
-
if (this.dragMoveHandler) {
|
|
151
|
-
try {
|
|
152
|
-
document.removeEventListener('mousemove', this.dragMoveHandler);
|
|
153
|
-
document.removeEventListener('touchmove', this.dragMoveHandler);
|
|
154
|
-
}
|
|
155
|
-
catch (e) {
|
|
156
|
-
if (this.debug) {
|
|
157
|
-
console.warn('Error removing drag move listeners:', e);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (this.dragEndHandler) {
|
|
162
|
-
try {
|
|
163
|
-
document.removeEventListener('mouseup', this.dragEndHandler);
|
|
164
|
-
document.removeEventListener('touchend', this.dragEndHandler);
|
|
165
|
-
}
|
|
166
|
-
catch (e) {
|
|
167
|
-
if (this.debug) {
|
|
168
|
-
console.warn('Error removing drag end listeners:', e);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
// 重置拖动状态
|
|
173
|
-
this.isDragging = false;
|
|
174
|
-
// 恢复图标样式
|
|
175
|
-
if (this.iconElement) {
|
|
176
|
-
this.iconElement.style.transition = 'transform 0.2s ease';
|
|
177
|
-
this.iconElement.style.cursor = 'pointer';
|
|
178
|
-
}
|
|
155
|
+
this.cleanupDragEvents();
|
|
179
156
|
}
|
|
180
157
|
/**
|
|
181
158
|
* 隐藏悬浮图标
|
|
182
159
|
*/
|
|
183
160
|
hide() {
|
|
184
|
-
//
|
|
185
|
-
this.
|
|
161
|
+
// 清理拖动事件
|
|
162
|
+
this.cleanupDragEvents();
|
|
186
163
|
if (this.iconElement) {
|
|
164
|
+
// 在隐藏前保存当前位置(如果图标已经被拖动过)
|
|
165
|
+
const computedStyle = window.getComputedStyle(this.iconElement);
|
|
166
|
+
const left = computedStyle.left;
|
|
167
|
+
const top = computedStyle.top;
|
|
168
|
+
// 如果 left/top 是有效的像素值(不是 auto),保存到 iconPosition
|
|
169
|
+
if (left !== 'auto' && top !== 'auto' && left !== '' && top !== '') {
|
|
170
|
+
const leftValue = parseFloat(left);
|
|
171
|
+
const topValue = parseFloat(top);
|
|
172
|
+
if (!isNaN(leftValue) && !isNaN(topValue)) {
|
|
173
|
+
this.iconPosition = {
|
|
174
|
+
x: leftValue,
|
|
175
|
+
y: topValue
|
|
176
|
+
};
|
|
177
|
+
if (this.debug) {
|
|
178
|
+
console.log('Icon position saved before hide:', this.iconPosition);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
187
182
|
this.iconElement.remove();
|
|
188
183
|
this.iconElement = null;
|
|
189
184
|
// 注意:不清空 onClickCallback,以便再次显示时能继续使用
|
|
190
|
-
this.dragMoveHandler = null;
|
|
191
|
-
this.dragEndHandler = null;
|
|
192
185
|
if (this.debug) {
|
|
193
186
|
console.log('CustomerSDK icon hidden');
|
|
194
187
|
}
|
|
@@ -219,10 +212,7 @@ class IconManager {
|
|
|
219
212
|
: position.y;
|
|
220
213
|
this.iconElement.style.bottom = 'auto';
|
|
221
214
|
}
|
|
222
|
-
//
|
|
223
|
-
const rect = this.iconElement.getBoundingClientRect();
|
|
224
|
-
this.iconStartX = rect.left;
|
|
225
|
-
this.iconStartY = rect.top;
|
|
215
|
+
// 保存当前位置用于拖动(使用 data-x 和 data-y,由 interact.js 管理)
|
|
226
216
|
}
|
|
227
217
|
}
|
|
228
218
|
/**
|
|
@@ -279,186 +269,310 @@ class IconManager {
|
|
|
279
269
|
}
|
|
280
270
|
}
|
|
281
271
|
/**
|
|
282
|
-
*
|
|
272
|
+
* 设置拖动事件(使用原生事件处理,参考用户提供的代码)
|
|
283
273
|
*/
|
|
284
274
|
setupDragEvents() {
|
|
285
275
|
if (!this.iconElement)
|
|
286
276
|
return;
|
|
287
277
|
// 绑定事件处理器(用于后续清理)
|
|
288
|
-
this.
|
|
289
|
-
this.
|
|
290
|
-
this.
|
|
291
|
-
//
|
|
292
|
-
this.iconElement.addEventListener('mousedown', this.
|
|
293
|
-
this.
|
|
294
|
-
this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
|
|
278
|
+
this.startDragHandler = this.startDrag.bind(this);
|
|
279
|
+
this.onDragHandler = this.onDrag.bind(this);
|
|
280
|
+
this.stopDragHandler = this.stopDrag.bind(this);
|
|
281
|
+
// 添加事件监听器
|
|
282
|
+
this.iconElement.addEventListener('mousedown', this.startDragHandler);
|
|
283
|
+
this.iconElement.addEventListener('touchstart', this.startDragHandler, { passive: false });
|
|
295
284
|
}
|
|
296
285
|
/**
|
|
297
286
|
* 开始拖动
|
|
298
287
|
*/
|
|
299
|
-
|
|
300
|
-
if (!this.iconElement)
|
|
288
|
+
startDrag(e) {
|
|
289
|
+
if (!this.iconElement || !this.isClickEnabled)
|
|
301
290
|
return;
|
|
302
|
-
//
|
|
303
|
-
|
|
291
|
+
// 检查事件目标:如果是 iframe 相关元素,不处理
|
|
292
|
+
const target = e.target;
|
|
293
|
+
if (target && (target.tagName === 'IFRAME' ||
|
|
294
|
+
target.closest('iframe') ||
|
|
295
|
+
target.closest('.customer-sdk-container') ||
|
|
296
|
+
target.closest('.customer-sdk-overlay'))) {
|
|
304
297
|
return;
|
|
305
298
|
}
|
|
306
299
|
e.preventDefault();
|
|
307
300
|
e.stopPropagation();
|
|
301
|
+
// 重置状态
|
|
302
|
+
this.hasMoved = false;
|
|
303
|
+
this.dragStarted = false;
|
|
304
|
+
this.isDragging = false;
|
|
305
|
+
// 记录开始时间和位置
|
|
306
|
+
this.touchStartTime = Date.now();
|
|
308
307
|
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
309
308
|
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
310
|
-
this.
|
|
311
|
-
this.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
// 只有在真正开始拖动时才修改样式,避免点击时图标位置跳动
|
|
319
|
-
// 只在真正开始拖动时添加document事件监听
|
|
320
|
-
// 先添加一个临时的move监听器来检测是否真的在拖动
|
|
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;
|
|
309
|
+
this.lastTouchPosition.x = clientX;
|
|
310
|
+
this.lastTouchPosition.y = clientY;
|
|
311
|
+
try {
|
|
312
|
+
const iconRect = this.iconElement.getBoundingClientRect();
|
|
313
|
+
const container = this.getTargetElement();
|
|
314
|
+
if (!container) {
|
|
315
|
+
if (this.debug) {
|
|
316
|
+
console.warn('Container not found');
|
|
342
317
|
}
|
|
318
|
+
return;
|
|
343
319
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
//
|
|
349
|
-
if (
|
|
350
|
-
this.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
320
|
+
// 计算拖动偏移量
|
|
321
|
+
this.dragOffset.x = clientX - iconRect.left;
|
|
322
|
+
this.dragOffset.y = clientY - iconRect.top;
|
|
323
|
+
// 注意:不在这里转换位置,只在真正开始拖动时才转换(在 onDrag 中)
|
|
324
|
+
// 添加 document 事件监听器
|
|
325
|
+
if (this.onDragHandler) {
|
|
326
|
+
document.addEventListener('mousemove', this.onDragHandler);
|
|
327
|
+
document.addEventListener('touchmove', this.onDragHandler, { passive: false });
|
|
328
|
+
}
|
|
329
|
+
if (this.stopDragHandler) {
|
|
330
|
+
document.addEventListener('mouseup', this.stopDragHandler);
|
|
331
|
+
document.addEventListener('touchend', this.stopDragHandler);
|
|
332
|
+
}
|
|
333
|
+
// 添加处理 iframe 上事件丢失的机制
|
|
334
|
+
// 1. 监听 mouseleave 和 pointerleave 事件(鼠标离开窗口时停止拖动)
|
|
335
|
+
this.mouseLeaveHandler = (e) => {
|
|
336
|
+
// 只有当鼠标真正离开窗口时才停止拖动
|
|
337
|
+
if (!e.relatedTarget && (e.clientY <= 0 || e.clientX <= 0 ||
|
|
338
|
+
e.clientX >= window.innerWidth || e.clientY >= window.innerHeight)) {
|
|
339
|
+
if (this.debug) {
|
|
340
|
+
console.log('Mouse left window, stopping drag');
|
|
363
341
|
}
|
|
364
|
-
this.
|
|
342
|
+
this.stopDrag();
|
|
365
343
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
344
|
+
};
|
|
345
|
+
this.pointerLeaveHandler = (e) => {
|
|
346
|
+
// 只有当指针真正离开窗口时才停止拖动
|
|
347
|
+
if (!e.relatedTarget && (e.clientY <= 0 || e.clientX <= 0 ||
|
|
348
|
+
e.clientX >= window.innerWidth || e.clientY >= window.innerHeight)) {
|
|
349
|
+
if (this.debug) {
|
|
350
|
+
console.log('Pointer left window, stopping drag');
|
|
371
351
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
352
|
+
this.stopDrag();
|
|
353
|
+
}
|
|
354
|
+
};
|
|
355
|
+
document.addEventListener('mouseleave', this.mouseLeaveHandler);
|
|
356
|
+
document.addEventListener('pointerleave', this.pointerLeaveHandler);
|
|
357
|
+
// 2. 监听 blur 事件(窗口失去焦点时停止拖动)
|
|
358
|
+
this.blurHandler = () => {
|
|
359
|
+
if (this.debug) {
|
|
360
|
+
console.log('Window lost focus, stopping drag');
|
|
361
|
+
}
|
|
362
|
+
this.stopDrag();
|
|
363
|
+
};
|
|
364
|
+
window.addEventListener('blur', this.blurHandler);
|
|
365
|
+
// 3. 添加超时机制(如果一段时间没有收到 mousemove 事件,自动停止拖动)
|
|
366
|
+
// 这可以处理鼠标移动到 iframe 上的情况
|
|
367
|
+
this.dragTimeoutId = window.setTimeout(() => {
|
|
368
|
+
if (this.isDragging) {
|
|
369
|
+
if (this.debug) {
|
|
370
|
+
console.log('Drag timeout, stopping drag (likely mouse moved over iframe)');
|
|
375
371
|
}
|
|
372
|
+
this.stopDrag();
|
|
376
373
|
}
|
|
374
|
+
}, 500); // 500ms 没有移动事件,自动停止拖动(处理鼠标移动到 iframe 上的情况)
|
|
375
|
+
if (this.debug) {
|
|
376
|
+
console.log('Drag start');
|
|
377
377
|
}
|
|
378
|
-
};
|
|
379
|
-
// 保存 checkDrag 引用,以便后续清理
|
|
380
|
-
this.checkDragHandler = checkDrag;
|
|
381
|
-
// 添加临时检测监听器
|
|
382
|
-
if ('touches' in e) {
|
|
383
|
-
document.addEventListener('touchmove', this.checkDragHandler, { passive: false });
|
|
384
|
-
document.addEventListener('touchend', this.dragEndHandler);
|
|
385
378
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
379
|
+
catch (error) {
|
|
380
|
+
if (this.debug) {
|
|
381
|
+
console.error('Error in startDrag:', error);
|
|
382
|
+
}
|
|
389
383
|
}
|
|
390
384
|
}
|
|
391
385
|
/**
|
|
392
386
|
* 拖动中
|
|
393
387
|
*/
|
|
394
|
-
|
|
395
|
-
if (!this.iconElement
|
|
388
|
+
onDrag(e) {
|
|
389
|
+
if (!this.iconElement)
|
|
396
390
|
return;
|
|
391
|
+
e.preventDefault();
|
|
392
|
+
// 重置超时定时器(每次移动都重置,确保只有真正停止移动时才触发超时)
|
|
393
|
+
if (this.dragTimeoutId !== null) {
|
|
394
|
+
window.clearTimeout(this.dragTimeoutId);
|
|
395
|
+
this.dragTimeoutId = window.setTimeout(() => {
|
|
396
|
+
if (this.isDragging) {
|
|
397
|
+
if (this.debug) {
|
|
398
|
+
console.log('Drag timeout, stopping drag (likely mouse moved over iframe)');
|
|
399
|
+
}
|
|
400
|
+
this.stopDrag();
|
|
401
|
+
}
|
|
402
|
+
}, 500); // 500ms 没有移动事件,自动停止拖动
|
|
403
|
+
}
|
|
397
404
|
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
398
405
|
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
399
|
-
//
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
406
|
+
// 检查是否有足够的移动距离
|
|
407
|
+
const deltaX = Math.abs(clientX - this.lastTouchPosition.x);
|
|
408
|
+
const deltaY = Math.abs(clientY - this.lastTouchPosition.y);
|
|
409
|
+
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
410
|
+
if (totalMovement > this.clickThreshold) {
|
|
411
|
+
this.hasMoved = true;
|
|
412
|
+
if (!this.dragStarted) {
|
|
413
|
+
// 开始真正的拖拽
|
|
414
|
+
this.dragStarted = true;
|
|
415
|
+
this.isDragging = true;
|
|
416
|
+
// 如果是第一次拖动,需要转换位置(从 right/bottom 到 left/top)
|
|
417
|
+
const computedStyle = window.getComputedStyle(this.iconElement);
|
|
418
|
+
if (computedStyle.right !== 'auto' || computedStyle.bottom !== 'auto') {
|
|
419
|
+
// 获取当前实际位置
|
|
420
|
+
const currentRect = this.iconElement.getBoundingClientRect();
|
|
421
|
+
const container = this.getTargetElement();
|
|
422
|
+
if (container) {
|
|
423
|
+
const containerRect = container.getBoundingClientRect();
|
|
424
|
+
// 计算相对于容器的位置
|
|
425
|
+
const relativeX = currentRect.left - containerRect.left;
|
|
426
|
+
const relativeY = currentRect.top - containerRect.top;
|
|
427
|
+
// 使用相对位置
|
|
428
|
+
this.iconElement.style.left = `${relativeX}px`;
|
|
429
|
+
this.iconElement.style.top = `${relativeY}px`;
|
|
430
|
+
this.iconElement.style.right = 'auto';
|
|
431
|
+
this.iconElement.style.bottom = 'auto';
|
|
432
|
+
// 重新计算偏移量(相对于容器)
|
|
433
|
+
this.dragOffset.x = clientX - currentRect.left;
|
|
434
|
+
this.dragOffset.y = clientY - currentRect.top;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
// 移除过渡动画,使拖动更流畅
|
|
438
|
+
this.iconElement.style.transition = 'none';
|
|
439
|
+
this.iconElement.style.cursor = 'grabbing';
|
|
440
|
+
if (this.debug) {
|
|
441
|
+
console.log('Drag started (moved > threshold)');
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (!this.isDragging) {
|
|
415
446
|
return;
|
|
416
447
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
448
|
+
try {
|
|
449
|
+
const container = this.getTargetElement();
|
|
450
|
+
if (!container) {
|
|
451
|
+
return;
|
|
452
|
+
}
|
|
453
|
+
const containerRect = container.getBoundingClientRect();
|
|
454
|
+
// 计算新位置
|
|
455
|
+
let newX = clientX - this.dragOffset.x - containerRect.left;
|
|
456
|
+
let newY = clientY - this.dragOffset.y - containerRect.top;
|
|
457
|
+
// 限制在容器内
|
|
458
|
+
const iconWidth = this.iconElement.offsetWidth;
|
|
459
|
+
const iconHeight = this.iconElement.offsetHeight;
|
|
460
|
+
if (container === document.body) {
|
|
461
|
+
// 限制在视口内
|
|
462
|
+
newX = Math.max(0, Math.min(newX, window.innerWidth - iconWidth));
|
|
463
|
+
newY = Math.max(0, Math.min(newY, window.innerHeight - iconHeight));
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
// 限制在容器内
|
|
467
|
+
const containerWidth = containerRect.width;
|
|
468
|
+
const containerHeight = containerRect.height;
|
|
469
|
+
newX = Math.max(0, Math.min(newX, containerWidth - iconWidth));
|
|
470
|
+
newY = Math.max(0, Math.min(newY, containerHeight - iconHeight));
|
|
471
|
+
}
|
|
472
|
+
// 更新位置
|
|
473
|
+
this.iconElement.style.left = `${newX}px`;
|
|
474
|
+
this.iconElement.style.top = `${newY}px`;
|
|
475
|
+
this.iconElement.style.right = 'auto';
|
|
476
|
+
this.iconElement.style.bottom = 'auto';
|
|
477
|
+
// 更新最后位置
|
|
478
|
+
this.lastTouchPosition.x = clientX;
|
|
479
|
+
this.lastTouchPosition.y = clientY;
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
if (this.debug) {
|
|
483
|
+
console.error('Error in onDrag:', error);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
432
486
|
}
|
|
433
487
|
/**
|
|
434
|
-
*
|
|
488
|
+
* 停止拖动
|
|
435
489
|
*/
|
|
436
|
-
|
|
490
|
+
stopDrag(_e) {
|
|
491
|
+
this.cleanupDragEvents();
|
|
437
492
|
if (!this.iconElement)
|
|
438
493
|
return;
|
|
439
|
-
//
|
|
440
|
-
if (this.checkDragHandler) {
|
|
441
|
-
document.removeEventListener('mousemove', this.checkDragHandler);
|
|
442
|
-
document.removeEventListener('touchmove', this.checkDragHandler);
|
|
443
|
-
this.checkDragHandler = null;
|
|
444
|
-
}
|
|
445
|
-
if (this.dragMoveHandler) {
|
|
446
|
-
document.removeEventListener('mousemove', this.dragMoveHandler);
|
|
447
|
-
document.removeEventListener('touchmove', this.dragMoveHandler);
|
|
448
|
-
}
|
|
449
|
-
if (this.dragEndHandler) {
|
|
450
|
-
document.removeEventListener('mouseup', this.dragEndHandler);
|
|
451
|
-
document.removeEventListener('touchend', this.dragEndHandler);
|
|
452
|
-
}
|
|
453
|
-
// 恢复样式(如果之前被修改过)
|
|
454
|
-
// 注意:如果只是点击(没有拖动),这些样式可能没有被修改,但恢复操作是安全的
|
|
494
|
+
// 恢复样式
|
|
455
495
|
this.iconElement.style.transition = 'transform 0.2s ease';
|
|
456
496
|
this.iconElement.style.cursor = 'pointer';
|
|
457
|
-
//
|
|
458
|
-
|
|
459
|
-
|
|
497
|
+
// 检查是否是点击
|
|
498
|
+
const touchDuration = Date.now() - this.touchStartTime;
|
|
499
|
+
const isValidClick = !this.hasMoved && touchDuration < 1000 && !this.dragStarted;
|
|
500
|
+
if (this.debug) {
|
|
501
|
+
console.log('Drag end', {
|
|
502
|
+
hasMoved: this.hasMoved,
|
|
503
|
+
dragStarted: this.dragStarted,
|
|
504
|
+
touchDuration,
|
|
505
|
+
isValidClick
|
|
506
|
+
});
|
|
460
507
|
}
|
|
508
|
+
// 先保存点击状态,然后重置拖动状态
|
|
509
|
+
const wasClick = isValidClick;
|
|
510
|
+
// 如果真正拖动过,保存当前位置到 iconPosition
|
|
511
|
+
if (this.dragStarted && this.isDragging && this.iconElement) {
|
|
512
|
+
const computedStyle = window.getComputedStyle(this.iconElement);
|
|
513
|
+
const left = computedStyle.left;
|
|
514
|
+
const top = computedStyle.top;
|
|
515
|
+
// 如果 left/top 是有效的像素值,保存到 iconPosition
|
|
516
|
+
if (left !== 'auto' && top !== 'auto') {
|
|
517
|
+
const leftValue = parseFloat(left);
|
|
518
|
+
const topValue = parseFloat(top);
|
|
519
|
+
if (!isNaN(leftValue) && !isNaN(topValue)) {
|
|
520
|
+
this.iconPosition = {
|
|
521
|
+
x: leftValue,
|
|
522
|
+
y: topValue
|
|
523
|
+
};
|
|
524
|
+
if (this.debug) {
|
|
525
|
+
console.log('Icon position saved:', this.iconPosition);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
// 重置状态
|
|
531
|
+
this.hasMoved = false;
|
|
461
532
|
this.isDragging = false;
|
|
533
|
+
this.dragStarted = false;
|
|
534
|
+
if (wasClick) {
|
|
535
|
+
// 是点击,触发点击事件
|
|
536
|
+
// 延迟触发,确保拖动状态已完全重置,并且没有新的拖动开始
|
|
537
|
+
setTimeout(() => {
|
|
538
|
+
// 再次检查,确保没有在延迟期间开始拖动
|
|
539
|
+
if (this.isClickEnabled && !this.dragStarted && !this.hasMoved && !this.isDragging) {
|
|
540
|
+
this.handleClick();
|
|
541
|
+
}
|
|
542
|
+
}, 100); // 增加延迟到 100ms,确保拖动状态完全重置
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* 清理拖动事件
|
|
547
|
+
*/
|
|
548
|
+
cleanupDragEvents() {
|
|
549
|
+
// 清理拖动事件
|
|
550
|
+
if (this.onDragHandler) {
|
|
551
|
+
document.removeEventListener('mousemove', this.onDragHandler);
|
|
552
|
+
document.removeEventListener('touchmove', this.onDragHandler);
|
|
553
|
+
}
|
|
554
|
+
if (this.stopDragHandler) {
|
|
555
|
+
document.removeEventListener('mouseup', this.stopDragHandler);
|
|
556
|
+
document.removeEventListener('touchend', this.stopDragHandler);
|
|
557
|
+
}
|
|
558
|
+
// 清理处理 iframe 事件丢失的监听器
|
|
559
|
+
if (this.mouseLeaveHandler) {
|
|
560
|
+
document.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
|
561
|
+
this.mouseLeaveHandler = null;
|
|
562
|
+
}
|
|
563
|
+
if (this.pointerLeaveHandler) {
|
|
564
|
+
document.removeEventListener('pointerleave', this.pointerLeaveHandler);
|
|
565
|
+
this.pointerLeaveHandler = null;
|
|
566
|
+
}
|
|
567
|
+
if (this.blurHandler) {
|
|
568
|
+
window.removeEventListener('blur', this.blurHandler);
|
|
569
|
+
this.blurHandler = null;
|
|
570
|
+
}
|
|
571
|
+
// 清理超时定时器
|
|
572
|
+
if (this.dragTimeoutId !== null) {
|
|
573
|
+
window.clearTimeout(this.dragTimeoutId);
|
|
574
|
+
this.dragTimeoutId = null;
|
|
575
|
+
}
|
|
462
576
|
}
|
|
463
577
|
/**
|
|
464
578
|
* 处理点击事件
|
|
@@ -478,14 +592,12 @@ class IconManager {
|
|
|
478
592
|
disableClick() {
|
|
479
593
|
this.isClickEnabled = false;
|
|
480
594
|
// 移除拖动事件监听器
|
|
595
|
+
if (this.iconElement && this.startDragHandler) {
|
|
596
|
+
this.iconElement.removeEventListener('mousedown', this.startDragHandler);
|
|
597
|
+
this.iconElement.removeEventListener('touchstart', this.startDragHandler);
|
|
598
|
+
}
|
|
599
|
+
// 禁用所有鼠标事件(包括点击和拖拽)
|
|
481
600
|
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
601
|
this.iconElement.style.pointerEvents = 'none';
|
|
490
602
|
this.iconElement.style.cursor = 'default';
|
|
491
603
|
}
|
|
@@ -498,14 +610,16 @@ class IconManager {
|
|
|
498
610
|
enableClick() {
|
|
499
611
|
this.isClickEnabled = true;
|
|
500
612
|
// 重新添加拖动事件监听器
|
|
613
|
+
if (this.iconElement && this.startDragHandler) {
|
|
614
|
+
this.iconElement.addEventListener('mousedown', this.startDragHandler);
|
|
615
|
+
this.iconElement.addEventListener('touchstart', this.startDragHandler, { passive: false });
|
|
616
|
+
}
|
|
617
|
+
else if (this.iconElement) {
|
|
618
|
+
// 如果事件处理器不存在,重新设置
|
|
619
|
+
this.setupDragEvents();
|
|
620
|
+
}
|
|
621
|
+
// 恢复鼠标事件
|
|
501
622
|
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
623
|
this.iconElement.style.pointerEvents = 'auto';
|
|
510
624
|
this.iconElement.style.cursor = 'pointer';
|
|
511
625
|
}
|
|
@@ -609,7 +723,8 @@ class IframeManager {
|
|
|
609
723
|
this.isOpen = false;
|
|
610
724
|
this.isCreated = false;
|
|
611
725
|
this.debug = false; // debug 模式标志
|
|
612
|
-
this.
|
|
726
|
+
this.targetElement = null; // 目标元素(用于自适应宽度)
|
|
727
|
+
this.messageHandler = null; // 消息监听器引用(用于清理)
|
|
613
728
|
this.config = {
|
|
614
729
|
src: '',
|
|
615
730
|
mode: 'auto', // 默认自动检测设备类型
|
|
@@ -619,7 +734,8 @@ class IframeManager {
|
|
|
619
734
|
...config
|
|
620
735
|
};
|
|
621
736
|
this.debug = config.debug ?? false;
|
|
622
|
-
|
|
737
|
+
// 不在构造函数中添加消息监听器,延迟到 init() 中添加
|
|
738
|
+
// 这样可以避免创建多个实例时产生多个监听器
|
|
623
739
|
}
|
|
624
740
|
/**
|
|
625
741
|
* 初始化iframe(隐藏状态)
|
|
@@ -627,6 +743,8 @@ class IframeManager {
|
|
|
627
743
|
*/
|
|
628
744
|
async init() {
|
|
629
745
|
try {
|
|
746
|
+
// 设置消息监听器(在 init() 中而不是构造函数中,避免多次创建实例时产生多个监听器)
|
|
747
|
+
this.setupMessageListener();
|
|
630
748
|
// 关键修复:在初始化前,先清理页面上所有旧的容器元素
|
|
631
749
|
// 防止切换模式或多次初始化时产生重复的元素
|
|
632
750
|
this.cleanupOrphanedElements();
|
|
@@ -641,6 +759,11 @@ class IframeManager {
|
|
|
641
759
|
catch (error) {
|
|
642
760
|
// 错误始终输出
|
|
643
761
|
console.error('Failed to initialize iframe:', error);
|
|
762
|
+
// 如果初始化失败,清理消息监听器
|
|
763
|
+
if (this.messageHandler) {
|
|
764
|
+
window.removeEventListener('message', this.messageHandler);
|
|
765
|
+
this.messageHandler = null;
|
|
766
|
+
}
|
|
644
767
|
throw error;
|
|
645
768
|
}
|
|
646
769
|
}
|
|
@@ -732,9 +855,6 @@ class IframeManager {
|
|
|
732
855
|
if (this.messageHandler) {
|
|
733
856
|
window.removeEventListener('message', this.messageHandler);
|
|
734
857
|
this.messageHandler = null;
|
|
735
|
-
if (this.debug) {
|
|
736
|
-
console.log('Message listener removed');
|
|
737
|
-
}
|
|
738
858
|
}
|
|
739
859
|
// 移除容器
|
|
740
860
|
if (this.containerElement) {
|
|
@@ -818,13 +938,44 @@ class IframeManager {
|
|
|
818
938
|
'allow-pointer-lock', // 允许指针锁定
|
|
819
939
|
'allow-storage-access-by-user-activation' // 允许用户激活的存储访问
|
|
820
940
|
].join(' '));
|
|
941
|
+
// 获取目标元素(如果提供了 target)
|
|
942
|
+
if (this.config.target) {
|
|
943
|
+
this.targetElement = this.getTargetElement(this.config.target);
|
|
944
|
+
}
|
|
821
945
|
// 根据设备类型设置模式
|
|
822
946
|
const actualMode = this.getActualMode();
|
|
823
947
|
const isPC = actualMode === 'popup';
|
|
824
948
|
this.iframeElement.scrolling = 'auto'; // PC和移动端都显示滚动条
|
|
825
|
-
//
|
|
826
|
-
const
|
|
827
|
-
|
|
949
|
+
// 判断是否使用 target 的自适应宽度
|
|
950
|
+
const useTargetWidth = !!this.targetElement && this.targetElement !== document.body;
|
|
951
|
+
// PC模式:如果传了 target,使用 target 的宽度(自适应);否则使用配置的宽度
|
|
952
|
+
// 移动端:如果传了 target,使用 target 的宽度(自适应);否则全屏
|
|
953
|
+
const containerStyles = useTargetWidth ? {
|
|
954
|
+
// 使用 target 的宽度(自适应),PC 和移动端都适用
|
|
955
|
+
// 宽度 100% 自适应 target 的实际宽度(target 本身可能有 max-width 限制)
|
|
956
|
+
width: '100%',
|
|
957
|
+
height: '100%',
|
|
958
|
+
maxWidth: '100%', // 不超过 target 的宽度
|
|
959
|
+
maxHeight: '100%',
|
|
960
|
+
backgroundColor: '#ffffff',
|
|
961
|
+
borderRadius: isPC ? '0' : '12px 12px 0 0',
|
|
962
|
+
boxShadow: isPC ? 'none' : '0 -4px 16px rgba(0, 0, 0, 0.25)',
|
|
963
|
+
border: 'none',
|
|
964
|
+
position: 'absolute', // 相对于 target 元素定位
|
|
965
|
+
zIndex: '999999',
|
|
966
|
+
// 占满 target 元素
|
|
967
|
+
top: '0',
|
|
968
|
+
left: '0',
|
|
969
|
+
bottom: '0',
|
|
970
|
+
right: '0',
|
|
971
|
+
transform: 'none',
|
|
972
|
+
overflow: 'hidden',
|
|
973
|
+
// 初始隐藏的关键样式
|
|
974
|
+
visibility: 'hidden',
|
|
975
|
+
opacity: '0',
|
|
976
|
+
display: 'none'
|
|
977
|
+
} : (isPC ? {
|
|
978
|
+
// PC模式:没有 target,使用配置的宽度,高度100%
|
|
828
979
|
width: `${this.config.width || 450}px`,
|
|
829
980
|
height: '100%',
|
|
830
981
|
maxWidth: '90vw',
|
|
@@ -847,7 +998,7 @@ class IframeManager {
|
|
|
847
998
|
opacity: '0',
|
|
848
999
|
display: 'none'
|
|
849
1000
|
} : {
|
|
850
|
-
//
|
|
1001
|
+
// 移动端全屏模式(没有 target,强制 100% 宽度和高度)
|
|
851
1002
|
width: '100%',
|
|
852
1003
|
height: '100%',
|
|
853
1004
|
maxWidth: '100%',
|
|
@@ -869,7 +1020,7 @@ class IframeManager {
|
|
|
869
1020
|
visibility: 'hidden',
|
|
870
1021
|
opacity: '0',
|
|
871
1022
|
display: 'none'
|
|
872
|
-
};
|
|
1023
|
+
});
|
|
873
1024
|
Object.assign(this.containerElement.style, containerStyles);
|
|
874
1025
|
// iframe填充整个容器
|
|
875
1026
|
const iframeStyles = {
|
|
@@ -888,8 +1039,30 @@ class IframeManager {
|
|
|
888
1039
|
this.injectMobileStyles();
|
|
889
1040
|
}
|
|
890
1041
|
});
|
|
891
|
-
//
|
|
892
|
-
document.body
|
|
1042
|
+
// 添加到目标元素或 body
|
|
1043
|
+
if (this.targetElement && this.targetElement !== document.body) {
|
|
1044
|
+
// 如果提供了 target,添加到 target 元素内
|
|
1045
|
+
// 需要设置 target 为相对定位,以便 iframe 可以相对于它定位
|
|
1046
|
+
const targetStyle = window.getComputedStyle(this.targetElement);
|
|
1047
|
+
if (targetStyle.position === 'static') {
|
|
1048
|
+
// 如果 target 是静态定位,设置为相对定位
|
|
1049
|
+
this.targetElement.style.position = 'relative';
|
|
1050
|
+
if (this.debug) {
|
|
1051
|
+
console.log('Set target element position to relative for iframe container');
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
this.targetElement.appendChild(this.containerElement);
|
|
1055
|
+
if (this.debug) {
|
|
1056
|
+
console.log('Iframe container added to target element');
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
else {
|
|
1060
|
+
// 没有 target 或 target 是 body,添加到 body
|
|
1061
|
+
document.body.appendChild(this.containerElement);
|
|
1062
|
+
if (this.debug) {
|
|
1063
|
+
console.log('Iframe container added to document.body');
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
893
1066
|
if (this.debug) {
|
|
894
1067
|
console.log('CustomerSDK container created (hidden, ready for SSE)');
|
|
895
1068
|
}
|
|
@@ -1010,12 +1183,9 @@ class IframeManager {
|
|
|
1010
1183
|
* 设置消息监听
|
|
1011
1184
|
*/
|
|
1012
1185
|
setupMessageListener() {
|
|
1013
|
-
//
|
|
1186
|
+
// 如果已存在,先移除旧的监听器(防止重复添加)
|
|
1014
1187
|
if (this.messageHandler) {
|
|
1015
1188
|
window.removeEventListener('message', this.messageHandler);
|
|
1016
|
-
if (this.debug) {
|
|
1017
|
-
console.log('Removed old message listener');
|
|
1018
|
-
}
|
|
1019
1189
|
}
|
|
1020
1190
|
// 创建新的消息处理器并保存引用
|
|
1021
1191
|
this.messageHandler = (event) => {
|
|
@@ -1025,9 +1195,6 @@ class IframeManager {
|
|
|
1025
1195
|
}
|
|
1026
1196
|
};
|
|
1027
1197
|
window.addEventListener('message', this.messageHandler, false);
|
|
1028
|
-
if (this.debug) {
|
|
1029
|
-
console.log('Message listener setup completed');
|
|
1030
|
-
}
|
|
1031
1198
|
}
|
|
1032
1199
|
/**
|
|
1033
1200
|
* 处理来自iframe的消息
|
|
@@ -1097,6 +1264,35 @@ class IframeManager {
|
|
|
1097
1264
|
this.containerElement.style.height = `${height}px`;
|
|
1098
1265
|
}
|
|
1099
1266
|
}
|
|
1267
|
+
/**
|
|
1268
|
+
* 获取目标元素(支持字符串选择器或 HTMLElement)
|
|
1269
|
+
*/
|
|
1270
|
+
getTargetElement(target) {
|
|
1271
|
+
if (typeof target === 'string') {
|
|
1272
|
+
const element = document.querySelector(target);
|
|
1273
|
+
if (element) {
|
|
1274
|
+
return element;
|
|
1275
|
+
}
|
|
1276
|
+
else {
|
|
1277
|
+
if (this.debug) {
|
|
1278
|
+
console.warn(`Target element not found: ${target}, falling back to document.body`);
|
|
1279
|
+
}
|
|
1280
|
+
return document.body;
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (target instanceof HTMLElement) {
|
|
1284
|
+
if (document.body.contains(target)) {
|
|
1285
|
+
return target;
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
if (this.debug) {
|
|
1289
|
+
console.warn('Target element no longer in DOM, falling back to document.body');
|
|
1290
|
+
}
|
|
1291
|
+
return document.body;
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
return document.body;
|
|
1295
|
+
}
|
|
1100
1296
|
}
|
|
1101
1297
|
|
|
1102
1298
|
function changeJpegDpi(uint8Array, dpi) {
|
|
@@ -20811,6 +21007,7 @@ class CustomerServiceSDK {
|
|
|
20811
21007
|
this.screenshotManager = null;
|
|
20812
21008
|
this.config = null;
|
|
20813
21009
|
this.isInitialized = false;
|
|
21010
|
+
this.isInitializing = false; // 初始化锁,防止并发初始化
|
|
20814
21011
|
this.initResult = null; // 保存初始化结果
|
|
20815
21012
|
this.debug = false; // debug 模式标志
|
|
20816
21013
|
this.lastIconConfig = null; // 保存上一次的图标配置
|
|
@@ -20823,6 +21020,10 @@ class CustomerServiceSDK {
|
|
|
20823
21020
|
* @returns 返回初始化信息(包含设备ID等)
|
|
20824
21021
|
*/
|
|
20825
21022
|
async init(config, options, forceReinit = false) {
|
|
21023
|
+
// 防止并发初始化
|
|
21024
|
+
if (this.isInitializing) {
|
|
21025
|
+
throw new Error('SDK is already initializing. Please wait for the current initialization to complete.');
|
|
21026
|
+
}
|
|
20826
21027
|
// 如果已经初始化且不强制重新初始化,返回之前保存的初始化信息
|
|
20827
21028
|
if (this.isInitialized && !forceReinit) {
|
|
20828
21029
|
if (this.debug) {
|
|
@@ -20833,6 +21034,8 @@ class CustomerServiceSDK {
|
|
|
20833
21034
|
}
|
|
20834
21035
|
throw new Error('SDK already initialized but cannot retrieve initialization info');
|
|
20835
21036
|
}
|
|
21037
|
+
// 设置初始化锁
|
|
21038
|
+
this.isInitializing = true;
|
|
20836
21039
|
// 如果需要强制重新初始化,先清理旧资源
|
|
20837
21040
|
if (this.isInitialized && forceReinit) {
|
|
20838
21041
|
if (this.debug) {
|
|
@@ -20879,6 +21082,7 @@ class CustomerServiceSDK {
|
|
|
20879
21082
|
// 配置变化了,需要重新创建图标管理器
|
|
20880
21083
|
if (this.iconManager) {
|
|
20881
21084
|
this.iconManager.hide();
|
|
21085
|
+
this.iconManager = null; // 先清空引用,避免引用混乱
|
|
20882
21086
|
if (this.debug) {
|
|
20883
21087
|
console.log('Icon config changed, recreating icon manager');
|
|
20884
21088
|
}
|
|
@@ -20906,11 +21110,14 @@ class CustomerServiceSDK {
|
|
|
20906
21110
|
this.lastIconConfig = { position: iconPosition, target: iconTarget };
|
|
20907
21111
|
}
|
|
20908
21112
|
// 创建iframe管理器(自动检测设备类型)
|
|
21113
|
+
// 如果提供了 target,iframe 会自适应 target 的宽度(PC 和移动端都适用)
|
|
21114
|
+
// 如果没有 target,PC 模式使用默认宽度,移动端全屏
|
|
20909
21115
|
this.iframeManager = new IframeManager({
|
|
20910
21116
|
src: iframeUrl,
|
|
20911
21117
|
mode: 'auto', // 自动根据设备类型选择模式(PC弹窗,移动端全屏)
|
|
20912
|
-
width: options?.width || 450, // PC模式宽度(像素,默认450px
|
|
20913
|
-
height: options?.height || 600, // PC
|
|
21118
|
+
width: options?.width || 450, // PC模式宽度(像素,默认450px),仅在未提供 target 时使用
|
|
21119
|
+
height: options?.height || 600, // PC模式高度(像素),仅在未提供 target 时使用
|
|
21120
|
+
target: options?.target, // 目标元素(如果提供,iframe 会自适应其宽度)
|
|
20914
21121
|
allowClose: true,
|
|
20915
21122
|
debug: this.debug, // 传递 debug 标志
|
|
20916
21123
|
onMessage: (messageType, _data) => {
|
|
@@ -20970,8 +21177,33 @@ class CustomerServiceSDK {
|
|
|
20970
21177
|
catch (error) {
|
|
20971
21178
|
// 错误始终输出
|
|
20972
21179
|
console.error('Failed to initialize CustomerSDK:', error);
|
|
21180
|
+
// 清理已创建的资源(防止资源泄漏)
|
|
21181
|
+
try {
|
|
21182
|
+
if (this.iconManager) {
|
|
21183
|
+
this.iconManager.hide();
|
|
21184
|
+
this.iconManager = null;
|
|
21185
|
+
}
|
|
21186
|
+
if (this.iframeManager) {
|
|
21187
|
+
this.iframeManager.destroy();
|
|
21188
|
+
this.iframeManager = null;
|
|
21189
|
+
}
|
|
21190
|
+
if (this.screenshotManager) {
|
|
21191
|
+
this.screenshotManager.destroy();
|
|
21192
|
+
this.screenshotManager = null;
|
|
21193
|
+
}
|
|
21194
|
+
this.isInitialized = false;
|
|
21195
|
+
this.initResult = null;
|
|
21196
|
+
}
|
|
21197
|
+
catch (cleanupError) {
|
|
21198
|
+
// 清理过程中的错误不应该影响原始错误的抛出
|
|
21199
|
+
console.error('Error during cleanup after initialization failure:', cleanupError);
|
|
21200
|
+
}
|
|
20973
21201
|
throw error;
|
|
20974
21202
|
}
|
|
21203
|
+
finally {
|
|
21204
|
+
// 释放初始化锁
|
|
21205
|
+
this.isInitializing = false;
|
|
21206
|
+
}
|
|
20975
21207
|
}
|
|
20976
21208
|
/**
|
|
20977
21209
|
* 显示/隐藏悬浮图标
|
|
@@ -21145,7 +21377,7 @@ class CustomerServiceSDK {
|
|
|
21145
21377
|
*/
|
|
21146
21378
|
destroy() {
|
|
21147
21379
|
this.iconManager?.hide();
|
|
21148
|
-
this.iframeManager?.
|
|
21380
|
+
this.iframeManager?.destroy(); // 使用 destroy 而不是 close,确保完全清理
|
|
21149
21381
|
this.screenshotManager?.destroy();
|
|
21150
21382
|
this.iconManager = null;
|
|
21151
21383
|
this.iframeManager = null;
|