customer-chat-sdk 1.1.8 → 1.1.9
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 -1
- package/dist/core/IframeManager.d.ts.map +1 -1
- package/dist/customer-sdk.cjs.js +422 -363
- package/dist/customer-sdk.esm.js +423 -363
- package/dist/customer-sdk.min.js +2 -2
- package/dist/index.d.ts +3 -26
- package/dist/index.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/customer-sdk.esm.js
CHANGED
|
@@ -6,20 +6,26 @@ class IconManager {
|
|
|
6
6
|
this.badgeElement = null;
|
|
7
7
|
this.onClickCallback = null;
|
|
8
8
|
this.notificationCallback = null;
|
|
9
|
-
this.isDragging = false;
|
|
10
|
-
this.dragStartX = 0;
|
|
11
|
-
this.dragStartY = 0;
|
|
12
|
-
this.iconStartX = 0;
|
|
13
|
-
this.iconStartY = 0;
|
|
14
|
-
this.dragMoveHandler = null;
|
|
15
|
-
this.dragEndHandler = null;
|
|
16
|
-
this.checkDragHandler = null; // 临时拖动检测监听器
|
|
17
|
-
this.dragStartHandler = null; // 拖动开始事件监听器
|
|
18
|
-
this.touchStartHandler = null; // 触摸开始事件监听器
|
|
19
9
|
this.iconPosition = null; // 图标位置配置
|
|
20
10
|
this.debug = false; // debug 模式标志
|
|
21
11
|
this.isClickEnabled = true; // 是否允许点击(iframe 打开时禁用)
|
|
22
12
|
this.target = null; // 图标传送目标元素(可以是 HTMLElement 或选择器字符串)
|
|
13
|
+
// 拖动相关状态
|
|
14
|
+
this.isDragging = false;
|
|
15
|
+
this.dragStarted = false; // 是否真正开始了拖拽
|
|
16
|
+
this.hasMoved = false; // 是否移动过
|
|
17
|
+
this.dragOffset = { x: 0, y: 0 }; // 拖动偏移量
|
|
18
|
+
this.lastTouchPosition = { x: 0, y: 0 }; // 最后触摸位置
|
|
19
|
+
this.touchStartTime = 0; // 触摸开始时间
|
|
20
|
+
this.clickThreshold = 15; // 点击阈值(像素)
|
|
21
|
+
// 事件处理器引用(用于清理)
|
|
22
|
+
this.onDragHandler = null;
|
|
23
|
+
this.stopDragHandler = null;
|
|
24
|
+
this.startDragHandler = null;
|
|
25
|
+
this.mouseLeaveHandler = null;
|
|
26
|
+
this.pointerLeaveHandler = null;
|
|
27
|
+
this.blurHandler = null;
|
|
28
|
+
this.dragTimeoutId = null; // 拖动超时定时器
|
|
23
29
|
this.iconPosition = position || null;
|
|
24
30
|
this.debug = debug;
|
|
25
31
|
// 保存 target(可以是 HTMLElement 或字符串选择器)
|
|
@@ -120,8 +126,6 @@ class IconManager {
|
|
|
120
126
|
});
|
|
121
127
|
imgContainer.appendChild(iconImg);
|
|
122
128
|
this.iconElement.appendChild(imgContainer);
|
|
123
|
-
// 添加拖动和点击事件
|
|
124
|
-
this.setupDragEvents();
|
|
125
129
|
// 添加到目标元素(如果 target 是字符串,需要重新查找,因为可能在初始化时元素还不存在)
|
|
126
130
|
const targetElement = this.getTargetElement();
|
|
127
131
|
if (targetElement) {
|
|
@@ -134,6 +138,8 @@ class IconManager {
|
|
|
134
138
|
console.warn('Target element not found, icon added to document.body');
|
|
135
139
|
}
|
|
136
140
|
}
|
|
141
|
+
// 设置拖动事件
|
|
142
|
+
this.setupDragEvents();
|
|
137
143
|
if (this.debug) {
|
|
138
144
|
console.log('CustomerSDK icon displayed');
|
|
139
145
|
}
|
|
@@ -142,49 +148,36 @@ class IconManager {
|
|
|
142
148
|
* 强制清理所有拖动事件监听器
|
|
143
149
|
*/
|
|
144
150
|
forceCleanupDragEvents() {
|
|
145
|
-
|
|
146
|
-
if (this.dragMoveHandler) {
|
|
147
|
-
try {
|
|
148
|
-
document.removeEventListener('mousemove', this.dragMoveHandler);
|
|
149
|
-
document.removeEventListener('touchmove', this.dragMoveHandler);
|
|
150
|
-
}
|
|
151
|
-
catch (e) {
|
|
152
|
-
if (this.debug) {
|
|
153
|
-
console.warn('Error removing drag move listeners:', e);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (this.dragEndHandler) {
|
|
158
|
-
try {
|
|
159
|
-
document.removeEventListener('mouseup', this.dragEndHandler);
|
|
160
|
-
document.removeEventListener('touchend', this.dragEndHandler);
|
|
161
|
-
}
|
|
162
|
-
catch (e) {
|
|
163
|
-
if (this.debug) {
|
|
164
|
-
console.warn('Error removing drag end listeners:', e);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
// 重置拖动状态
|
|
169
|
-
this.isDragging = false;
|
|
170
|
-
// 恢复图标样式
|
|
171
|
-
if (this.iconElement) {
|
|
172
|
-
this.iconElement.style.transition = 'transform 0.2s ease';
|
|
173
|
-
this.iconElement.style.cursor = 'pointer';
|
|
174
|
-
}
|
|
151
|
+
this.cleanupDragEvents();
|
|
175
152
|
}
|
|
176
153
|
/**
|
|
177
154
|
* 隐藏悬浮图标
|
|
178
155
|
*/
|
|
179
156
|
hide() {
|
|
180
|
-
//
|
|
181
|
-
this.
|
|
157
|
+
// 清理拖动事件
|
|
158
|
+
this.cleanupDragEvents();
|
|
182
159
|
if (this.iconElement) {
|
|
160
|
+
// 在隐藏前保存当前位置(如果图标已经被拖动过)
|
|
161
|
+
const computedStyle = window.getComputedStyle(this.iconElement);
|
|
162
|
+
const left = computedStyle.left;
|
|
163
|
+
const top = computedStyle.top;
|
|
164
|
+
// 如果 left/top 是有效的像素值(不是 auto),保存到 iconPosition
|
|
165
|
+
if (left !== 'auto' && top !== 'auto' && left !== '' && top !== '') {
|
|
166
|
+
const leftValue = parseFloat(left);
|
|
167
|
+
const topValue = parseFloat(top);
|
|
168
|
+
if (!isNaN(leftValue) && !isNaN(topValue)) {
|
|
169
|
+
this.iconPosition = {
|
|
170
|
+
x: leftValue,
|
|
171
|
+
y: topValue
|
|
172
|
+
};
|
|
173
|
+
if (this.debug) {
|
|
174
|
+
console.log('Icon position saved before hide:', this.iconPosition);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
183
178
|
this.iconElement.remove();
|
|
184
179
|
this.iconElement = null;
|
|
185
180
|
// 注意:不清空 onClickCallback,以便再次显示时能继续使用
|
|
186
|
-
this.dragMoveHandler = null;
|
|
187
|
-
this.dragEndHandler = null;
|
|
188
181
|
if (this.debug) {
|
|
189
182
|
console.log('CustomerSDK icon hidden');
|
|
190
183
|
}
|
|
@@ -215,10 +208,7 @@ class IconManager {
|
|
|
215
208
|
: position.y;
|
|
216
209
|
this.iconElement.style.bottom = 'auto';
|
|
217
210
|
}
|
|
218
|
-
//
|
|
219
|
-
const rect = this.iconElement.getBoundingClientRect();
|
|
220
|
-
this.iconStartX = rect.left;
|
|
221
|
-
this.iconStartY = rect.top;
|
|
211
|
+
// 保存当前位置用于拖动(使用 data-x 和 data-y,由 interact.js 管理)
|
|
222
212
|
}
|
|
223
213
|
}
|
|
224
214
|
/**
|
|
@@ -275,186 +265,310 @@ class IconManager {
|
|
|
275
265
|
}
|
|
276
266
|
}
|
|
277
267
|
/**
|
|
278
|
-
*
|
|
268
|
+
* 设置拖动事件(使用原生事件处理,参考用户提供的代码)
|
|
279
269
|
*/
|
|
280
270
|
setupDragEvents() {
|
|
281
271
|
if (!this.iconElement)
|
|
282
272
|
return;
|
|
283
273
|
// 绑定事件处理器(用于后续清理)
|
|
284
|
-
this.
|
|
285
|
-
this.
|
|
286
|
-
this.
|
|
287
|
-
//
|
|
288
|
-
this.iconElement.addEventListener('mousedown', this.
|
|
289
|
-
this.
|
|
290
|
-
this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
|
|
274
|
+
this.startDragHandler = this.startDrag.bind(this);
|
|
275
|
+
this.onDragHandler = this.onDrag.bind(this);
|
|
276
|
+
this.stopDragHandler = this.stopDrag.bind(this);
|
|
277
|
+
// 添加事件监听器
|
|
278
|
+
this.iconElement.addEventListener('mousedown', this.startDragHandler);
|
|
279
|
+
this.iconElement.addEventListener('touchstart', this.startDragHandler, { passive: false });
|
|
291
280
|
}
|
|
292
281
|
/**
|
|
293
282
|
* 开始拖动
|
|
294
283
|
*/
|
|
295
|
-
|
|
296
|
-
if (!this.iconElement)
|
|
284
|
+
startDrag(e) {
|
|
285
|
+
if (!this.iconElement || !this.isClickEnabled)
|
|
297
286
|
return;
|
|
298
|
-
//
|
|
299
|
-
|
|
287
|
+
// 检查事件目标:如果是 iframe 相关元素,不处理
|
|
288
|
+
const target = e.target;
|
|
289
|
+
if (target && (target.tagName === 'IFRAME' ||
|
|
290
|
+
target.closest('iframe') ||
|
|
291
|
+
target.closest('.customer-sdk-container') ||
|
|
292
|
+
target.closest('.customer-sdk-overlay'))) {
|
|
300
293
|
return;
|
|
301
294
|
}
|
|
302
295
|
e.preventDefault();
|
|
303
296
|
e.stopPropagation();
|
|
297
|
+
// 重置状态
|
|
298
|
+
this.hasMoved = false;
|
|
299
|
+
this.dragStarted = false;
|
|
300
|
+
this.isDragging = false;
|
|
301
|
+
// 记录开始时间和位置
|
|
302
|
+
this.touchStartTime = Date.now();
|
|
304
303
|
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
305
304
|
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
306
|
-
this.
|
|
307
|
-
this.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
// 只有在真正开始拖动时才修改样式,避免点击时图标位置跳动
|
|
315
|
-
// 只在真正开始拖动时添加document事件监听
|
|
316
|
-
// 先添加一个临时的move监听器来检测是否真的在拖动
|
|
317
|
-
const checkDrag = (moveEvent) => {
|
|
318
|
-
// 检测事件目标:如果事件发生在iframe或其他元素上,停止检测拖动
|
|
319
|
-
const target = moveEvent.target;
|
|
320
|
-
if (target && target !== this.iconElement && !this.iconElement?.contains(target)) {
|
|
321
|
-
// 检查是否是iframe相关元素
|
|
322
|
-
const isIframeElement = target.tagName === 'IFRAME' ||
|
|
323
|
-
target.closest('iframe') ||
|
|
324
|
-
target.closest('.customer-sdk-container') ||
|
|
325
|
-
target.closest('.customer-sdk-overlay');
|
|
326
|
-
if (isIframeElement) {
|
|
327
|
-
// 如果事件发生在iframe相关元素上,停止检测并清理监听器
|
|
328
|
-
if (this.checkDragHandler) {
|
|
329
|
-
if ('touches' in moveEvent) {
|
|
330
|
-
document.removeEventListener('touchmove', this.checkDragHandler);
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
document.removeEventListener('mousemove', this.checkDragHandler);
|
|
334
|
-
}
|
|
335
|
-
this.checkDragHandler = null;
|
|
336
|
-
}
|
|
337
|
-
return;
|
|
305
|
+
this.lastTouchPosition.x = clientX;
|
|
306
|
+
this.lastTouchPosition.y = clientY;
|
|
307
|
+
try {
|
|
308
|
+
const iconRect = this.iconElement.getBoundingClientRect();
|
|
309
|
+
const container = this.getTargetElement();
|
|
310
|
+
if (!container) {
|
|
311
|
+
if (this.debug) {
|
|
312
|
+
console.warn('Container not found');
|
|
338
313
|
}
|
|
314
|
+
return;
|
|
339
315
|
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
//
|
|
345
|
-
if (
|
|
346
|
-
this.
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
316
|
+
// 计算拖动偏移量
|
|
317
|
+
this.dragOffset.x = clientX - iconRect.left;
|
|
318
|
+
this.dragOffset.y = clientY - iconRect.top;
|
|
319
|
+
// 注意:不在这里转换位置,只在真正开始拖动时才转换(在 onDrag 中)
|
|
320
|
+
// 添加 document 事件监听器
|
|
321
|
+
if (this.onDragHandler) {
|
|
322
|
+
document.addEventListener('mousemove', this.onDragHandler);
|
|
323
|
+
document.addEventListener('touchmove', this.onDragHandler, { passive: false });
|
|
324
|
+
}
|
|
325
|
+
if (this.stopDragHandler) {
|
|
326
|
+
document.addEventListener('mouseup', this.stopDragHandler);
|
|
327
|
+
document.addEventListener('touchend', this.stopDragHandler);
|
|
328
|
+
}
|
|
329
|
+
// 添加处理 iframe 上事件丢失的机制
|
|
330
|
+
// 1. 监听 mouseleave 和 pointerleave 事件(鼠标离开窗口时停止拖动)
|
|
331
|
+
this.mouseLeaveHandler = (e) => {
|
|
332
|
+
// 只有当鼠标真正离开窗口时才停止拖动
|
|
333
|
+
if (!e.relatedTarget && (e.clientY <= 0 || e.clientX <= 0 ||
|
|
334
|
+
e.clientX >= window.innerWidth || e.clientY >= window.innerHeight)) {
|
|
335
|
+
if (this.debug) {
|
|
336
|
+
console.log('Mouse left window, stopping drag');
|
|
359
337
|
}
|
|
360
|
-
this.
|
|
338
|
+
this.stopDrag();
|
|
361
339
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
340
|
+
};
|
|
341
|
+
this.pointerLeaveHandler = (e) => {
|
|
342
|
+
// 只有当指针真正离开窗口时才停止拖动
|
|
343
|
+
if (!e.relatedTarget && (e.clientY <= 0 || e.clientX <= 0 ||
|
|
344
|
+
e.clientX >= window.innerWidth || e.clientY >= window.innerHeight)) {
|
|
345
|
+
if (this.debug) {
|
|
346
|
+
console.log('Pointer left window, stopping drag');
|
|
367
347
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
348
|
+
this.stopDrag();
|
|
349
|
+
}
|
|
350
|
+
};
|
|
351
|
+
document.addEventListener('mouseleave', this.mouseLeaveHandler);
|
|
352
|
+
document.addEventListener('pointerleave', this.pointerLeaveHandler);
|
|
353
|
+
// 2. 监听 blur 事件(窗口失去焦点时停止拖动)
|
|
354
|
+
this.blurHandler = () => {
|
|
355
|
+
if (this.debug) {
|
|
356
|
+
console.log('Window lost focus, stopping drag');
|
|
357
|
+
}
|
|
358
|
+
this.stopDrag();
|
|
359
|
+
};
|
|
360
|
+
window.addEventListener('blur', this.blurHandler);
|
|
361
|
+
// 3. 添加超时机制(如果一段时间没有收到 mousemove 事件,自动停止拖动)
|
|
362
|
+
// 这可以处理鼠标移动到 iframe 上的情况
|
|
363
|
+
this.dragTimeoutId = window.setTimeout(() => {
|
|
364
|
+
if (this.isDragging) {
|
|
365
|
+
if (this.debug) {
|
|
366
|
+
console.log('Drag timeout, stopping drag (likely mouse moved over iframe)');
|
|
371
367
|
}
|
|
368
|
+
this.stopDrag();
|
|
372
369
|
}
|
|
370
|
+
}, 500); // 500ms 没有移动事件,自动停止拖动(处理鼠标移动到 iframe 上的情况)
|
|
371
|
+
if (this.debug) {
|
|
372
|
+
console.log('Drag start');
|
|
373
373
|
}
|
|
374
|
-
};
|
|
375
|
-
// 保存 checkDrag 引用,以便后续清理
|
|
376
|
-
this.checkDragHandler = checkDrag;
|
|
377
|
-
// 添加临时检测监听器
|
|
378
|
-
if ('touches' in e) {
|
|
379
|
-
document.addEventListener('touchmove', this.checkDragHandler, { passive: false });
|
|
380
|
-
document.addEventListener('touchend', this.dragEndHandler);
|
|
381
374
|
}
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
375
|
+
catch (error) {
|
|
376
|
+
if (this.debug) {
|
|
377
|
+
console.error('Error in startDrag:', error);
|
|
378
|
+
}
|
|
385
379
|
}
|
|
386
380
|
}
|
|
387
381
|
/**
|
|
388
382
|
* 拖动中
|
|
389
383
|
*/
|
|
390
|
-
|
|
391
|
-
if (!this.iconElement
|
|
384
|
+
onDrag(e) {
|
|
385
|
+
if (!this.iconElement)
|
|
392
386
|
return;
|
|
387
|
+
e.preventDefault();
|
|
388
|
+
// 重置超时定时器(每次移动都重置,确保只有真正停止移动时才触发超时)
|
|
389
|
+
if (this.dragTimeoutId !== null) {
|
|
390
|
+
window.clearTimeout(this.dragTimeoutId);
|
|
391
|
+
this.dragTimeoutId = window.setTimeout(() => {
|
|
392
|
+
if (this.isDragging) {
|
|
393
|
+
if (this.debug) {
|
|
394
|
+
console.log('Drag timeout, stopping drag (likely mouse moved over iframe)');
|
|
395
|
+
}
|
|
396
|
+
this.stopDrag();
|
|
397
|
+
}
|
|
398
|
+
}, 500); // 500ms 没有移动事件,自动停止拖动
|
|
399
|
+
}
|
|
393
400
|
const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
|
|
394
401
|
const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
|
|
395
|
-
//
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
402
|
+
// 检查是否有足够的移动距离
|
|
403
|
+
const deltaX = Math.abs(clientX - this.lastTouchPosition.x);
|
|
404
|
+
const deltaY = Math.abs(clientY - this.lastTouchPosition.y);
|
|
405
|
+
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
406
|
+
if (totalMovement > this.clickThreshold) {
|
|
407
|
+
this.hasMoved = true;
|
|
408
|
+
if (!this.dragStarted) {
|
|
409
|
+
// 开始真正的拖拽
|
|
410
|
+
this.dragStarted = true;
|
|
411
|
+
this.isDragging = true;
|
|
412
|
+
// 如果是第一次拖动,需要转换位置(从 right/bottom 到 left/top)
|
|
413
|
+
const computedStyle = window.getComputedStyle(this.iconElement);
|
|
414
|
+
if (computedStyle.right !== 'auto' || computedStyle.bottom !== 'auto') {
|
|
415
|
+
// 获取当前实际位置
|
|
416
|
+
const currentRect = this.iconElement.getBoundingClientRect();
|
|
417
|
+
const container = this.getTargetElement();
|
|
418
|
+
if (container) {
|
|
419
|
+
const containerRect = container.getBoundingClientRect();
|
|
420
|
+
// 计算相对于容器的位置
|
|
421
|
+
const relativeX = currentRect.left - containerRect.left;
|
|
422
|
+
const relativeY = currentRect.top - containerRect.top;
|
|
423
|
+
// 使用相对位置
|
|
424
|
+
this.iconElement.style.left = `${relativeX}px`;
|
|
425
|
+
this.iconElement.style.top = `${relativeY}px`;
|
|
426
|
+
this.iconElement.style.right = 'auto';
|
|
427
|
+
this.iconElement.style.bottom = 'auto';
|
|
428
|
+
// 重新计算偏移量(相对于容器)
|
|
429
|
+
this.dragOffset.x = clientX - currentRect.left;
|
|
430
|
+
this.dragOffset.y = clientY - currentRect.top;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
// 移除过渡动画,使拖动更流畅
|
|
434
|
+
this.iconElement.style.transition = 'none';
|
|
435
|
+
this.iconElement.style.cursor = 'grabbing';
|
|
436
|
+
if (this.debug) {
|
|
437
|
+
console.log('Drag started (moved > threshold)');
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (!this.isDragging) {
|
|
411
442
|
return;
|
|
412
443
|
}
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
444
|
+
try {
|
|
445
|
+
const container = this.getTargetElement();
|
|
446
|
+
if (!container) {
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
const containerRect = container.getBoundingClientRect();
|
|
450
|
+
// 计算新位置
|
|
451
|
+
let newX = clientX - this.dragOffset.x - containerRect.left;
|
|
452
|
+
let newY = clientY - this.dragOffset.y - containerRect.top;
|
|
453
|
+
// 限制在容器内
|
|
454
|
+
const iconWidth = this.iconElement.offsetWidth;
|
|
455
|
+
const iconHeight = this.iconElement.offsetHeight;
|
|
456
|
+
if (container === document.body) {
|
|
457
|
+
// 限制在视口内
|
|
458
|
+
newX = Math.max(0, Math.min(newX, window.innerWidth - iconWidth));
|
|
459
|
+
newY = Math.max(0, Math.min(newY, window.innerHeight - iconHeight));
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
// 限制在容器内
|
|
463
|
+
const containerWidth = containerRect.width;
|
|
464
|
+
const containerHeight = containerRect.height;
|
|
465
|
+
newX = Math.max(0, Math.min(newX, containerWidth - iconWidth));
|
|
466
|
+
newY = Math.max(0, Math.min(newY, containerHeight - iconHeight));
|
|
467
|
+
}
|
|
468
|
+
// 更新位置
|
|
469
|
+
this.iconElement.style.left = `${newX}px`;
|
|
470
|
+
this.iconElement.style.top = `${newY}px`;
|
|
471
|
+
this.iconElement.style.right = 'auto';
|
|
472
|
+
this.iconElement.style.bottom = 'auto';
|
|
473
|
+
// 更新最后位置
|
|
474
|
+
this.lastTouchPosition.x = clientX;
|
|
475
|
+
this.lastTouchPosition.y = clientY;
|
|
476
|
+
}
|
|
477
|
+
catch (error) {
|
|
478
|
+
if (this.debug) {
|
|
479
|
+
console.error('Error in onDrag:', error);
|
|
480
|
+
}
|
|
481
|
+
}
|
|
428
482
|
}
|
|
429
483
|
/**
|
|
430
|
-
*
|
|
484
|
+
* 停止拖动
|
|
431
485
|
*/
|
|
432
|
-
|
|
486
|
+
stopDrag(_e) {
|
|
487
|
+
this.cleanupDragEvents();
|
|
433
488
|
if (!this.iconElement)
|
|
434
489
|
return;
|
|
435
|
-
//
|
|
436
|
-
if (this.checkDragHandler) {
|
|
437
|
-
document.removeEventListener('mousemove', this.checkDragHandler);
|
|
438
|
-
document.removeEventListener('touchmove', this.checkDragHandler);
|
|
439
|
-
this.checkDragHandler = null;
|
|
440
|
-
}
|
|
441
|
-
if (this.dragMoveHandler) {
|
|
442
|
-
document.removeEventListener('mousemove', this.dragMoveHandler);
|
|
443
|
-
document.removeEventListener('touchmove', this.dragMoveHandler);
|
|
444
|
-
}
|
|
445
|
-
if (this.dragEndHandler) {
|
|
446
|
-
document.removeEventListener('mouseup', this.dragEndHandler);
|
|
447
|
-
document.removeEventListener('touchend', this.dragEndHandler);
|
|
448
|
-
}
|
|
449
|
-
// 恢复样式(如果之前被修改过)
|
|
450
|
-
// 注意:如果只是点击(没有拖动),这些样式可能没有被修改,但恢复操作是安全的
|
|
490
|
+
// 恢复样式
|
|
451
491
|
this.iconElement.style.transition = 'transform 0.2s ease';
|
|
452
492
|
this.iconElement.style.cursor = 'pointer';
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
|
|
493
|
+
// 检查是否是点击
|
|
494
|
+
const touchDuration = Date.now() - this.touchStartTime;
|
|
495
|
+
const isValidClick = !this.hasMoved && touchDuration < 1000 && !this.dragStarted;
|
|
496
|
+
if (this.debug) {
|
|
497
|
+
console.log('Drag end', {
|
|
498
|
+
hasMoved: this.hasMoved,
|
|
499
|
+
dragStarted: this.dragStarted,
|
|
500
|
+
touchDuration,
|
|
501
|
+
isValidClick
|
|
502
|
+
});
|
|
503
|
+
}
|
|
504
|
+
// 先保存点击状态,然后重置拖动状态
|
|
505
|
+
const wasClick = isValidClick;
|
|
506
|
+
// 如果真正拖动过,保存当前位置到 iconPosition
|
|
507
|
+
if (this.dragStarted && this.isDragging && this.iconElement) {
|
|
508
|
+
const computedStyle = window.getComputedStyle(this.iconElement);
|
|
509
|
+
const left = computedStyle.left;
|
|
510
|
+
const top = computedStyle.top;
|
|
511
|
+
// 如果 left/top 是有效的像素值,保存到 iconPosition
|
|
512
|
+
if (left !== 'auto' && top !== 'auto') {
|
|
513
|
+
const leftValue = parseFloat(left);
|
|
514
|
+
const topValue = parseFloat(top);
|
|
515
|
+
if (!isNaN(leftValue) && !isNaN(topValue)) {
|
|
516
|
+
this.iconPosition = {
|
|
517
|
+
x: leftValue,
|
|
518
|
+
y: topValue
|
|
519
|
+
};
|
|
520
|
+
if (this.debug) {
|
|
521
|
+
console.log('Icon position saved:', this.iconPosition);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
456
525
|
}
|
|
526
|
+
// 重置状态
|
|
527
|
+
this.hasMoved = false;
|
|
457
528
|
this.isDragging = false;
|
|
529
|
+
this.dragStarted = false;
|
|
530
|
+
if (wasClick) {
|
|
531
|
+
// 是点击,触发点击事件
|
|
532
|
+
// 延迟触发,确保拖动状态已完全重置,并且没有新的拖动开始
|
|
533
|
+
setTimeout(() => {
|
|
534
|
+
// 再次检查,确保没有在延迟期间开始拖动
|
|
535
|
+
if (this.isClickEnabled && !this.dragStarted && !this.hasMoved && !this.isDragging) {
|
|
536
|
+
this.handleClick();
|
|
537
|
+
}
|
|
538
|
+
}, 100); // 增加延迟到 100ms,确保拖动状态完全重置
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* 清理拖动事件
|
|
543
|
+
*/
|
|
544
|
+
cleanupDragEvents() {
|
|
545
|
+
// 清理拖动事件
|
|
546
|
+
if (this.onDragHandler) {
|
|
547
|
+
document.removeEventListener('mousemove', this.onDragHandler);
|
|
548
|
+
document.removeEventListener('touchmove', this.onDragHandler);
|
|
549
|
+
}
|
|
550
|
+
if (this.stopDragHandler) {
|
|
551
|
+
document.removeEventListener('mouseup', this.stopDragHandler);
|
|
552
|
+
document.removeEventListener('touchend', this.stopDragHandler);
|
|
553
|
+
}
|
|
554
|
+
// 清理处理 iframe 事件丢失的监听器
|
|
555
|
+
if (this.mouseLeaveHandler) {
|
|
556
|
+
document.removeEventListener('mouseleave', this.mouseLeaveHandler);
|
|
557
|
+
this.mouseLeaveHandler = null;
|
|
558
|
+
}
|
|
559
|
+
if (this.pointerLeaveHandler) {
|
|
560
|
+
document.removeEventListener('pointerleave', this.pointerLeaveHandler);
|
|
561
|
+
this.pointerLeaveHandler = null;
|
|
562
|
+
}
|
|
563
|
+
if (this.blurHandler) {
|
|
564
|
+
window.removeEventListener('blur', this.blurHandler);
|
|
565
|
+
this.blurHandler = null;
|
|
566
|
+
}
|
|
567
|
+
// 清理超时定时器
|
|
568
|
+
if (this.dragTimeoutId !== null) {
|
|
569
|
+
window.clearTimeout(this.dragTimeoutId);
|
|
570
|
+
this.dragTimeoutId = null;
|
|
571
|
+
}
|
|
458
572
|
}
|
|
459
573
|
/**
|
|
460
574
|
* 处理点击事件
|
|
@@ -474,14 +588,12 @@ class IconManager {
|
|
|
474
588
|
disableClick() {
|
|
475
589
|
this.isClickEnabled = false;
|
|
476
590
|
// 移除拖动事件监听器
|
|
591
|
+
if (this.iconElement && this.startDragHandler) {
|
|
592
|
+
this.iconElement.removeEventListener('mousedown', this.startDragHandler);
|
|
593
|
+
this.iconElement.removeEventListener('touchstart', this.startDragHandler);
|
|
594
|
+
}
|
|
595
|
+
// 禁用所有鼠标事件(包括点击和拖拽)
|
|
477
596
|
if (this.iconElement) {
|
|
478
|
-
if (this.dragStartHandler) {
|
|
479
|
-
this.iconElement.removeEventListener('mousedown', this.dragStartHandler);
|
|
480
|
-
}
|
|
481
|
-
if (this.touchStartHandler) {
|
|
482
|
-
this.iconElement.removeEventListener('touchstart', this.touchStartHandler);
|
|
483
|
-
}
|
|
484
|
-
// 禁用所有鼠标事件(包括点击和拖拽)
|
|
485
597
|
this.iconElement.style.pointerEvents = 'none';
|
|
486
598
|
this.iconElement.style.cursor = 'default';
|
|
487
599
|
}
|
|
@@ -494,14 +606,16 @@ class IconManager {
|
|
|
494
606
|
enableClick() {
|
|
495
607
|
this.isClickEnabled = true;
|
|
496
608
|
// 重新添加拖动事件监听器
|
|
609
|
+
if (this.iconElement && this.startDragHandler) {
|
|
610
|
+
this.iconElement.addEventListener('mousedown', this.startDragHandler);
|
|
611
|
+
this.iconElement.addEventListener('touchstart', this.startDragHandler, { passive: false });
|
|
612
|
+
}
|
|
613
|
+
else if (this.iconElement) {
|
|
614
|
+
// 如果事件处理器不存在,重新设置
|
|
615
|
+
this.setupDragEvents();
|
|
616
|
+
}
|
|
617
|
+
// 恢复鼠标事件
|
|
497
618
|
if (this.iconElement) {
|
|
498
|
-
if (this.dragStartHandler) {
|
|
499
|
-
this.iconElement.addEventListener('mousedown', this.dragStartHandler);
|
|
500
|
-
}
|
|
501
|
-
if (this.touchStartHandler) {
|
|
502
|
-
this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
|
|
503
|
-
}
|
|
504
|
-
// 恢复鼠标事件
|
|
505
619
|
this.iconElement.style.pointerEvents = 'auto';
|
|
506
620
|
this.iconElement.style.cursor = 'pointer';
|
|
507
621
|
}
|
|
@@ -605,7 +719,7 @@ class IframeManager {
|
|
|
605
719
|
this.isOpen = false;
|
|
606
720
|
this.isCreated = false;
|
|
607
721
|
this.debug = false; // debug 模式标志
|
|
608
|
-
this.
|
|
722
|
+
this.targetElement = null; // 目标元素(用于自适应宽度)
|
|
609
723
|
this.config = {
|
|
610
724
|
src: '',
|
|
611
725
|
mode: 'auto', // 默认自动检测设备类型
|
|
@@ -724,14 +838,6 @@ class IframeManager {
|
|
|
724
838
|
*/
|
|
725
839
|
destroy() {
|
|
726
840
|
this.hide();
|
|
727
|
-
// 移除消息监听器(防止内存泄漏)
|
|
728
|
-
if (this.messageHandler) {
|
|
729
|
-
window.removeEventListener('message', this.messageHandler);
|
|
730
|
-
this.messageHandler = null;
|
|
731
|
-
if (this.debug) {
|
|
732
|
-
console.log('Message listener removed');
|
|
733
|
-
}
|
|
734
|
-
}
|
|
735
841
|
// 移除容器
|
|
736
842
|
if (this.containerElement) {
|
|
737
843
|
this.containerElement.remove();
|
|
@@ -814,13 +920,44 @@ class IframeManager {
|
|
|
814
920
|
'allow-pointer-lock', // 允许指针锁定
|
|
815
921
|
'allow-storage-access-by-user-activation' // 允许用户激活的存储访问
|
|
816
922
|
].join(' '));
|
|
923
|
+
// 获取目标元素(如果提供了 target)
|
|
924
|
+
if (this.config.target) {
|
|
925
|
+
this.targetElement = this.getTargetElement(this.config.target);
|
|
926
|
+
}
|
|
817
927
|
// 根据设备类型设置模式
|
|
818
928
|
const actualMode = this.getActualMode();
|
|
819
929
|
const isPC = actualMode === 'popup';
|
|
820
930
|
this.iframeElement.scrolling = 'auto'; // PC和移动端都显示滚动条
|
|
821
|
-
//
|
|
822
|
-
const
|
|
823
|
-
|
|
931
|
+
// 判断是否使用 target 的自适应宽度
|
|
932
|
+
const useTargetWidth = !!this.targetElement && this.targetElement !== document.body;
|
|
933
|
+
// PC模式:如果传了 target,使用 target 的宽度(自适应);否则使用配置的宽度
|
|
934
|
+
// 移动端:如果传了 target,使用 target 的宽度(自适应);否则全屏
|
|
935
|
+
const containerStyles = useTargetWidth ? {
|
|
936
|
+
// 使用 target 的宽度(自适应),PC 和移动端都适用
|
|
937
|
+
// 宽度 100% 自适应 target 的实际宽度(target 本身可能有 max-width 限制)
|
|
938
|
+
width: '100%',
|
|
939
|
+
height: '100%',
|
|
940
|
+
maxWidth: '100%', // 不超过 target 的宽度
|
|
941
|
+
maxHeight: '100%',
|
|
942
|
+
backgroundColor: '#ffffff',
|
|
943
|
+
borderRadius: isPC ? '0' : '12px 12px 0 0',
|
|
944
|
+
boxShadow: isPC ? 'none' : '0 -4px 16px rgba(0, 0, 0, 0.25)',
|
|
945
|
+
border: 'none',
|
|
946
|
+
position: 'absolute', // 相对于 target 元素定位
|
|
947
|
+
zIndex: '999999',
|
|
948
|
+
// 占满 target 元素
|
|
949
|
+
top: '0',
|
|
950
|
+
left: '0',
|
|
951
|
+
bottom: '0',
|
|
952
|
+
right: '0',
|
|
953
|
+
transform: 'none',
|
|
954
|
+
overflow: 'hidden',
|
|
955
|
+
// 初始隐藏的关键样式
|
|
956
|
+
visibility: 'hidden',
|
|
957
|
+
opacity: '0',
|
|
958
|
+
display: 'none'
|
|
959
|
+
} : (isPC ? {
|
|
960
|
+
// PC模式:没有 target,使用配置的宽度,高度100%
|
|
824
961
|
width: `${this.config.width || 450}px`,
|
|
825
962
|
height: '100%',
|
|
826
963
|
maxWidth: '90vw',
|
|
@@ -843,7 +980,7 @@ class IframeManager {
|
|
|
843
980
|
opacity: '0',
|
|
844
981
|
display: 'none'
|
|
845
982
|
} : {
|
|
846
|
-
//
|
|
983
|
+
// 移动端全屏模式(没有 target,强制 100% 宽度和高度)
|
|
847
984
|
width: '100%',
|
|
848
985
|
height: '100%',
|
|
849
986
|
maxWidth: '100%',
|
|
@@ -865,7 +1002,7 @@ class IframeManager {
|
|
|
865
1002
|
visibility: 'hidden',
|
|
866
1003
|
opacity: '0',
|
|
867
1004
|
display: 'none'
|
|
868
|
-
};
|
|
1005
|
+
});
|
|
869
1006
|
Object.assign(this.containerElement.style, containerStyles);
|
|
870
1007
|
// iframe填充整个容器
|
|
871
1008
|
const iframeStyles = {
|
|
@@ -884,8 +1021,30 @@ class IframeManager {
|
|
|
884
1021
|
this.injectMobileStyles();
|
|
885
1022
|
}
|
|
886
1023
|
});
|
|
887
|
-
//
|
|
888
|
-
document.body
|
|
1024
|
+
// 添加到目标元素或 body
|
|
1025
|
+
if (this.targetElement && this.targetElement !== document.body) {
|
|
1026
|
+
// 如果提供了 target,添加到 target 元素内
|
|
1027
|
+
// 需要设置 target 为相对定位,以便 iframe 可以相对于它定位
|
|
1028
|
+
const targetStyle = window.getComputedStyle(this.targetElement);
|
|
1029
|
+
if (targetStyle.position === 'static') {
|
|
1030
|
+
// 如果 target 是静态定位,设置为相对定位
|
|
1031
|
+
this.targetElement.style.position = 'relative';
|
|
1032
|
+
if (this.debug) {
|
|
1033
|
+
console.log('Set target element position to relative for iframe container');
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
this.targetElement.appendChild(this.containerElement);
|
|
1037
|
+
if (this.debug) {
|
|
1038
|
+
console.log('Iframe container added to target element');
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
// 没有 target 或 target 是 body,添加到 body
|
|
1043
|
+
document.body.appendChild(this.containerElement);
|
|
1044
|
+
if (this.debug) {
|
|
1045
|
+
console.log('Iframe container added to document.body');
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
889
1048
|
if (this.debug) {
|
|
890
1049
|
console.log('CustomerSDK container created (hidden, ready for SSE)');
|
|
891
1050
|
}
|
|
@@ -1006,24 +1165,12 @@ class IframeManager {
|
|
|
1006
1165
|
* 设置消息监听
|
|
1007
1166
|
*/
|
|
1008
1167
|
setupMessageListener() {
|
|
1009
|
-
|
|
1010
|
-
if (this.messageHandler) {
|
|
1011
|
-
window.removeEventListener('message', this.messageHandler);
|
|
1012
|
-
if (this.debug) {
|
|
1013
|
-
console.log('Removed old message listener');
|
|
1014
|
-
}
|
|
1015
|
-
}
|
|
1016
|
-
// 创建新的消息处理器并保存引用
|
|
1017
|
-
this.messageHandler = (event) => {
|
|
1168
|
+
window.addEventListener('message', (event) => {
|
|
1018
1169
|
// 验证消息来源(可选的安全检查)
|
|
1019
1170
|
if (!this.config.src || event.origin === new URL(this.config.src).origin) {
|
|
1020
1171
|
this.handleIframeMessage(event.data);
|
|
1021
1172
|
}
|
|
1022
|
-
};
|
|
1023
|
-
window.addEventListener('message', this.messageHandler, false);
|
|
1024
|
-
if (this.debug) {
|
|
1025
|
-
console.log('Message listener setup completed');
|
|
1026
|
-
}
|
|
1173
|
+
}, false);
|
|
1027
1174
|
}
|
|
1028
1175
|
/**
|
|
1029
1176
|
* 处理来自iframe的消息
|
|
@@ -1093,6 +1240,35 @@ class IframeManager {
|
|
|
1093
1240
|
this.containerElement.style.height = `${height}px`;
|
|
1094
1241
|
}
|
|
1095
1242
|
}
|
|
1243
|
+
/**
|
|
1244
|
+
* 获取目标元素(支持字符串选择器或 HTMLElement)
|
|
1245
|
+
*/
|
|
1246
|
+
getTargetElement(target) {
|
|
1247
|
+
if (typeof target === 'string') {
|
|
1248
|
+
const element = document.querySelector(target);
|
|
1249
|
+
if (element) {
|
|
1250
|
+
return element;
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
if (this.debug) {
|
|
1254
|
+
console.warn(`Target element not found: ${target}, falling back to document.body`);
|
|
1255
|
+
}
|
|
1256
|
+
return document.body;
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
if (target instanceof HTMLElement) {
|
|
1260
|
+
if (document.body.contains(target)) {
|
|
1261
|
+
return target;
|
|
1262
|
+
}
|
|
1263
|
+
else {
|
|
1264
|
+
if (this.debug) {
|
|
1265
|
+
console.warn('Target element no longer in DOM, falling back to document.body');
|
|
1266
|
+
}
|
|
1267
|
+
return document.body;
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
return document.body;
|
|
1271
|
+
}
|
|
1096
1272
|
}
|
|
1097
1273
|
|
|
1098
1274
|
function changeJpegDpi(uint8Array, dpi) {
|
|
@@ -20809,40 +20985,24 @@ class CustomerServiceSDK {
|
|
|
20809
20985
|
this.isInitialized = false;
|
|
20810
20986
|
this.initResult = null; // 保存初始化结果
|
|
20811
20987
|
this.debug = false; // debug 模式标志
|
|
20812
|
-
this.lastIconConfig = null; // 保存上一次的图标配置
|
|
20813
20988
|
}
|
|
20814
20989
|
/**
|
|
20815
20990
|
* 初始化 SDK
|
|
20816
20991
|
* @param config SDK配置
|
|
20817
20992
|
* @param options UI选项(可选)
|
|
20818
|
-
* @param forceReinit 是否强制重新初始化(用于更新token等配置)
|
|
20819
20993
|
* @returns 返回初始化信息(包含设备ID等)
|
|
20820
20994
|
*/
|
|
20821
|
-
async init(config, options
|
|
20822
|
-
|
|
20823
|
-
if (this.isInitialized && !forceReinit) {
|
|
20995
|
+
async init(config, options) {
|
|
20996
|
+
if (this.isInitialized) {
|
|
20824
20997
|
if (this.debug) {
|
|
20825
|
-
console.warn('CustomerSDK already initialized
|
|
20998
|
+
console.warn('CustomerSDK already initialized');
|
|
20826
20999
|
}
|
|
21000
|
+
// 如果已经初始化,返回之前保存的初始化信息
|
|
20827
21001
|
if (this.initResult) {
|
|
20828
21002
|
return this.initResult;
|
|
20829
21003
|
}
|
|
20830
21004
|
throw new Error('SDK already initialized but cannot retrieve initialization info');
|
|
20831
21005
|
}
|
|
20832
|
-
// 如果需要强制重新初始化,先清理旧资源
|
|
20833
|
-
if (this.isInitialized && forceReinit) {
|
|
20834
|
-
if (this.debug) {
|
|
20835
|
-
console.log('Force reinitializing SDK...');
|
|
20836
|
-
}
|
|
20837
|
-
// 清理旧的 iframe 和截图管理器
|
|
20838
|
-
this.iframeManager?.destroy();
|
|
20839
|
-
this.screenshotManager?.destroy();
|
|
20840
|
-
this.iframeManager = null;
|
|
20841
|
-
this.screenshotManager = null;
|
|
20842
|
-
// 注意:图标管理器暂时保留,稍后检查配置是否变化再决定是否重新创建
|
|
20843
|
-
// 重置初始化标志
|
|
20844
|
-
this.isInitialized = false;
|
|
20845
|
-
}
|
|
20846
21006
|
this.config = config;
|
|
20847
21007
|
this.debug = config.debug ?? false;
|
|
20848
21008
|
try {
|
|
@@ -20861,52 +21021,20 @@ class CustomerServiceSDK {
|
|
|
20861
21021
|
agent: config.agent,
|
|
20862
21022
|
timestamp: Date.now()
|
|
20863
21023
|
};
|
|
20864
|
-
//
|
|
20865
|
-
|
|
20866
|
-
const
|
|
20867
|
-
|
|
20868
|
-
|
|
20869
|
-
const iconTarget = options?.target !== undefined
|
|
20870
|
-
? options.target
|
|
20871
|
-
: (this.lastIconConfig?.target || undefined);
|
|
20872
|
-
// 检查图标配置是否变化
|
|
20873
|
-
const iconConfigChanged = this.isIconConfigChanged(iconPosition, iconTarget);
|
|
20874
|
-
if (iconConfigChanged) {
|
|
20875
|
-
// 配置变化了,需要重新创建图标管理器
|
|
20876
|
-
if (this.iconManager) {
|
|
20877
|
-
this.iconManager.hide();
|
|
20878
|
-
if (this.debug) {
|
|
20879
|
-
console.log('Icon config changed, recreating icon manager');
|
|
20880
|
-
}
|
|
20881
|
-
}
|
|
20882
|
-
this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
|
|
20883
|
-
await this.iconManager.show();
|
|
20884
|
-
// 保存新的配置
|
|
20885
|
-
this.lastIconConfig = { position: iconPosition, target: iconTarget };
|
|
20886
|
-
}
|
|
20887
|
-
else {
|
|
20888
|
-
// 配置没变化,保留图标管理器(避免闪烁)
|
|
20889
|
-
if (!this.iconManager) {
|
|
20890
|
-
// 如果不存在,创建新的
|
|
20891
|
-
this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
|
|
20892
|
-
await this.iconManager.show();
|
|
20893
|
-
}
|
|
20894
|
-
else {
|
|
20895
|
-
// 已存在,确保显示(可能被隐藏了)
|
|
20896
|
-
await this.iconManager.show();
|
|
20897
|
-
if (this.debug) {
|
|
20898
|
-
console.log('Icon config unchanged, keeping existing icon manager');
|
|
20899
|
-
}
|
|
20900
|
-
}
|
|
20901
|
-
// 更新配置记录
|
|
20902
|
-
this.lastIconConfig = { position: iconPosition, target: iconTarget };
|
|
20903
|
-
}
|
|
21024
|
+
// 创建悬浮图标管理器(支持自定义位置和传送目标)
|
|
21025
|
+
const iconPosition = options?.iconPosition || undefined;
|
|
21026
|
+
const iconTarget = options?.target || undefined;
|
|
21027
|
+
this.iconManager = new IconManager(iconPosition, this.debug, iconTarget);
|
|
21028
|
+
await this.iconManager.show();
|
|
20904
21029
|
// 创建iframe管理器(自动检测设备类型)
|
|
21030
|
+
// 如果提供了 target,iframe 会自适应 target 的宽度(PC 和移动端都适用)
|
|
21031
|
+
// 如果没有 target,PC 模式使用默认宽度,移动端全屏
|
|
20905
21032
|
this.iframeManager = new IframeManager({
|
|
20906
21033
|
src: iframeUrl,
|
|
20907
21034
|
mode: 'auto', // 自动根据设备类型选择模式(PC弹窗,移动端全屏)
|
|
20908
|
-
width: options?.width || 450, // PC模式宽度(像素,默认450px
|
|
20909
|
-
height: options?.height || 600, // PC
|
|
21035
|
+
width: options?.width || 450, // PC模式宽度(像素,默认450px),仅在未提供 target 时使用
|
|
21036
|
+
height: options?.height || 600, // PC模式高度(像素),仅在未提供 target 时使用
|
|
21037
|
+
target: options?.target, // 目标元素(如果提供,iframe 会自适应其宽度)
|
|
20910
21038
|
allowClose: true,
|
|
20911
21039
|
debug: this.debug, // 传递 debug 标志
|
|
20912
21040
|
onMessage: (messageType, _data) => {
|
|
@@ -21111,31 +21239,6 @@ class CustomerServiceSDK {
|
|
|
21111
21239
|
console.log('📸 截图配置已更新:', options);
|
|
21112
21240
|
}
|
|
21113
21241
|
}
|
|
21114
|
-
/**
|
|
21115
|
-
* 更新 token(用于用户登录/退出场景)
|
|
21116
|
-
* 如果已初始化,会重新创建 iframe 并更新 URL
|
|
21117
|
-
* @param token 新的 token(传空字符串或 undefined 表示移除 token)
|
|
21118
|
-
* @param options UI选项(可选,用于重新初始化时的配置)
|
|
21119
|
-
* @returns 返回更新后的初始化信息
|
|
21120
|
-
*/
|
|
21121
|
-
async updateToken(token, options) {
|
|
21122
|
-
if (!this.isInitialized) {
|
|
21123
|
-
throw new Error('SDK not initialized. Call init() first.');
|
|
21124
|
-
}
|
|
21125
|
-
if (!this.config) {
|
|
21126
|
-
throw new Error('SDK config not found');
|
|
21127
|
-
}
|
|
21128
|
-
// 更新配置中的 token
|
|
21129
|
-
const updatedConfig = {
|
|
21130
|
-
...this.config,
|
|
21131
|
-
token: token && token.trim() !== '' ? token : undefined
|
|
21132
|
-
};
|
|
21133
|
-
if (this.debug) {
|
|
21134
|
-
console.log('Updating token:', token ? 'Token provided' : 'Token removed');
|
|
21135
|
-
}
|
|
21136
|
-
// 强制重新初始化以应用新的 token
|
|
21137
|
-
return await this.init(updatedConfig, options, true);
|
|
21138
|
-
}
|
|
21139
21242
|
/**
|
|
21140
21243
|
* 销毁 SDK
|
|
21141
21244
|
*/
|
|
@@ -21148,7 +21251,6 @@ class CustomerServiceSDK {
|
|
|
21148
21251
|
this.screenshotManager = null;
|
|
21149
21252
|
this.config = null;
|
|
21150
21253
|
this.initResult = null;
|
|
21151
|
-
this.lastIconConfig = null;
|
|
21152
21254
|
this.isInitialized = false;
|
|
21153
21255
|
if (this.debug) {
|
|
21154
21256
|
console.log('CustomerSDK destroyed');
|
|
@@ -21193,36 +21295,6 @@ class CustomerServiceSDK {
|
|
|
21193
21295
|
return fallbackId;
|
|
21194
21296
|
}
|
|
21195
21297
|
}
|
|
21196
|
-
/**
|
|
21197
|
-
* 检查图标配置是否变化
|
|
21198
|
-
*/
|
|
21199
|
-
isIconConfigChanged(newPosition, newTarget) {
|
|
21200
|
-
// 如果没有保存的配置,说明是首次初始化,需要创建
|
|
21201
|
-
if (!this.lastIconConfig) {
|
|
21202
|
-
return true;
|
|
21203
|
-
}
|
|
21204
|
-
// 比较位置配置
|
|
21205
|
-
const positionChanged = JSON.stringify(this.lastIconConfig.position) !== JSON.stringify(newPosition);
|
|
21206
|
-
// 比较 target 配置
|
|
21207
|
-
const oldTarget = this.lastIconConfig.target;
|
|
21208
|
-
let targetChanged = false;
|
|
21209
|
-
if (oldTarget !== newTarget) {
|
|
21210
|
-
// 如果引用不同,进一步检查
|
|
21211
|
-
if (typeof oldTarget === 'string' && typeof newTarget === 'string') {
|
|
21212
|
-
// 都是字符串,比较值
|
|
21213
|
-
targetChanged = oldTarget !== newTarget;
|
|
21214
|
-
}
|
|
21215
|
-
else if (oldTarget instanceof HTMLElement && newTarget instanceof HTMLElement) {
|
|
21216
|
-
// 都是 HTMLElement,比较引用(引用不同就是变化了)
|
|
21217
|
-
targetChanged = true;
|
|
21218
|
-
}
|
|
21219
|
-
else {
|
|
21220
|
-
// 类型不同,肯定是变化了
|
|
21221
|
-
targetChanged = true;
|
|
21222
|
-
}
|
|
21223
|
-
}
|
|
21224
|
-
return positionChanged || targetChanged;
|
|
21225
|
-
}
|
|
21226
21298
|
/**
|
|
21227
21299
|
* 构建iframe URL(带用户参数和设备ID)
|
|
21228
21300
|
*/
|
|
@@ -21250,14 +21322,13 @@ let globalSDKInstance = null;
|
|
|
21250
21322
|
* 初始化 Customer SDK
|
|
21251
21323
|
* @param config SDK配置
|
|
21252
21324
|
* @param options UI选项(可选)
|
|
21253
|
-
* @param forceReinit 是否强制重新初始化(用于更新token等配置)
|
|
21254
21325
|
* @returns 返回初始化信息(包含设备ID等)
|
|
21255
21326
|
*/
|
|
21256
|
-
const init = async (config, options
|
|
21327
|
+
const init = async (config, options) => {
|
|
21257
21328
|
if (!globalSDKInstance) {
|
|
21258
21329
|
globalSDKInstance = new CustomerServiceSDK();
|
|
21259
21330
|
}
|
|
21260
|
-
return await globalSDKInstance.init(config, options
|
|
21331
|
+
return await globalSDKInstance.init(config, options);
|
|
21261
21332
|
};
|
|
21262
21333
|
/**
|
|
21263
21334
|
* 获取全局SDK实例
|
|
@@ -21366,16 +21437,6 @@ const updateScreenshotOptions = (options) => {
|
|
|
21366
21437
|
const sdk = getInstance();
|
|
21367
21438
|
sdk.updateScreenshotOptions(options);
|
|
21368
21439
|
};
|
|
21369
|
-
/**
|
|
21370
|
-
* 更新 token(用于用户登录/退出场景)
|
|
21371
|
-
* @param token 新的 token(传空字符串或 undefined 表示移除 token)
|
|
21372
|
-
* @param options UI选项(可选)
|
|
21373
|
-
* @returns 返回更新后的初始化信息
|
|
21374
|
-
*/
|
|
21375
|
-
const updateToken = async (token, options) => {
|
|
21376
|
-
const sdk = getInstance();
|
|
21377
|
-
return await sdk.updateToken(token, options);
|
|
21378
|
-
};
|
|
21379
21440
|
// 默认导出
|
|
21380
21441
|
var index = {
|
|
21381
21442
|
init,
|
|
@@ -21398,8 +21459,7 @@ var index = {
|
|
|
21398
21459
|
enableScreenshot,
|
|
21399
21460
|
getScreenshotState,
|
|
21400
21461
|
updateScreenshotOptions,
|
|
21401
|
-
updateToken,
|
|
21402
21462
|
destroy
|
|
21403
21463
|
};
|
|
21404
21464
|
|
|
21405
|
-
export { CustomerServiceSDK, captureScreenshot, clearNotification, closeChat, index as default, destroy, enableScreenshot, getConnectionStatus, getInitResult, getInstance, getScreenshotState, hideIcon, init, isChatOpen, openChat, sendToIframe, setIconCoordinates, setIconPosition, setIconStyle, setScreenshotTarget, showIcon, showNotification, updateScreenshotOptions
|
|
21465
|
+
export { CustomerServiceSDK, captureScreenshot, clearNotification, closeChat, index as default, destroy, enableScreenshot, getConnectionStatus, getInitResult, getInstance, getScreenshotState, hideIcon, init, isChatOpen, openChat, sendToIframe, setIconCoordinates, setIconPosition, setIconStyle, setScreenshotTarget, showIcon, showNotification, updateScreenshotOptions };
|