customer-chat-sdk 1.1.0 → 1.1.1

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.
@@ -20,8 +20,12 @@ export declare class IconManager {
20
20
  private iconStartY;
21
21
  private dragMoveHandler;
22
22
  private dragEndHandler;
23
+ private checkDragHandler;
24
+ private dragStartHandler;
25
+ private touchStartHandler;
23
26
  private iconPosition;
24
27
  private debug;
28
+ private isClickEnabled;
25
29
  constructor(position?: IconPosition, debug?: boolean);
26
30
  /**
27
31
  * 显示悬浮图标
@@ -83,6 +87,14 @@ export declare class IconManager {
83
87
  * 处理点击事件
84
88
  */
85
89
  private handleClick;
90
+ /**
91
+ * 禁用点击和拖拽(iframe 打开时调用)
92
+ */
93
+ disableClick(): void;
94
+ /**
95
+ * 启用点击和拖拽(iframe 关闭时调用)
96
+ */
97
+ enableClick(): void;
86
98
  /**
87
99
  * 创建消息徽章(简化版)
88
100
  */
@@ -1 +1 @@
1
- {"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,UAAU,YAAY;IACpB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,oBAAoB,CAAqC;IACjE,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,eAAe,CAAsD;IAC7E,OAAO,CAAC,cAAc,CAAsD;IAC5E,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,KAAK,CAAiB;gBAElB,QAAQ,CAAC,EAAE,YAAY,EAAE,KAAK,GAAE,OAAe;IAK3D;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+G3B;;OAEG;IACH,sBAAsB,IAAI,IAAI;IAiC9B;;OAEG;IACH,IAAI,IAAI,IAAI;IAgBZ;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAIhC;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAwB7C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAI1B;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAInD;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IA8BpD;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAUzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAYvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAqEvB;;OAEG;IACH,OAAO,CAAC,cAAc;IA+DtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAwBrB;;OAEG;IACH,OAAO,CAAC,WAAW;IAMnB;;OAEG;IACH,OAAO,CAAC,WAAW;CAmDpB"}
1
+ {"version":3,"file":"IconManager.d.ts","sourceRoot":"","sources":["../../src/core/IconManager.ts"],"names":[],"mappings":"AAGA,UAAU,mBAAmB;IAC3B,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,UAAU,YAAY;IACpB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;IACnB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAA;CACpB;AAED,qBAAa,WAAW;IACtB,OAAO,CAAC,WAAW,CAA2B;IAC9C,OAAO,CAAC,YAAY,CAA2B;IAC/C,OAAO,CAAC,eAAe,CAA4B;IACnD,OAAO,CAAC,oBAAoB,CAAqC;IACjE,OAAO,CAAC,UAAU,CAAQ;IAC1B,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,UAAU,CAAI;IACtB,OAAO,CAAC,eAAe,CAAsD;IAC7E,OAAO,CAAC,cAAc,CAAsD;IAC5E,OAAO,CAAC,gBAAgB,CAAsD;IAC9E,OAAO,CAAC,gBAAgB,CAAsD;IAC9E,OAAO,CAAC,iBAAiB,CAAyC;IAClE,OAAO,CAAC,YAAY,CAA4B;IAChD,OAAO,CAAC,KAAK,CAAiB;IAC9B,OAAO,CAAC,cAAc,CAAgB;gBAE1B,QAAQ,CAAC,EAAE,YAAY,EAAE,KAAK,GAAE,OAAe;IAK3D;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA+G3B;;OAEG;IACH,sBAAsB,IAAI,IAAI;IAiC9B;;OAEG;IACH,IAAI,IAAI,IAAI;IAgBZ;;OAEG;IACH,WAAW,CAAC,QAAQ,EAAE,GAAG,GAAG,IAAI;IAIhC;;OAEG;IACH,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI;IAwB7C;;OAEG;IACH,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,IAAI;IAI1B;;OAEG;IACH,OAAO,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAInC;;OAEG;IACH,cAAc,CAAC,QAAQ,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAInD;;OAEG;IACH,gBAAgB,CAAC,OAAO,EAAE,mBAAmB,GAAG,IAAI;IA8BpD;;OAEG;IACH,iBAAiB,IAAI,IAAI;IAUzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAcvB;;OAEG;IACH,OAAO,CAAC,eAAe;IAuGvB;;OAEG;IACH,OAAO,CAAC,cAAc;IAmDtB;;OAEG;IACH,OAAO,CAAC,aAAa;IA+BrB;;OAEG;IACH,OAAO,CAAC,WAAW;IAUnB;;OAEG;IACH,YAAY,IAAI,IAAI;IAoBpB;;OAEG;IACH,WAAW,IAAI,IAAI;IAiBnB;;OAEG;IACH,OAAO,CAAC,WAAW;CAmDpB"}
@@ -53,7 +53,7 @@ export declare class IframeManager {
53
53
  */
54
54
  sendToIframe(data: any): void;
55
55
  /**
56
- * 创建遮罩层
56
+ * 创建遮罩层(PC模式使用)
57
57
  */
58
58
  private createOverlay;
59
59
  /**
@@ -74,6 +74,7 @@ export declare class IframeManager {
74
74
  private isMobileDevice;
75
75
  /**
76
76
  * 获取当前显示模式
77
+ * PC 模式使用弹窗,移动端使用全屏
77
78
  */
78
79
  private getActualMode;
79
80
  /**
@@ -85,7 +86,7 @@ export declare class IframeManager {
85
86
  */
86
87
  private handleIframeMessage;
87
88
  /**
88
- * 调整iframe大小
89
+ * 调整iframe大小(PC模式支持)
89
90
  */
90
91
  private resizeIframe;
91
92
  }
@@ -1 +1 @@
1
- {"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../src/core/IframeManager.ts"],"names":[],"mappings":"AACA,UAAU,aAAa;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAA;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,KAAK,CAAiB;gBAElB,MAAM,GAAE,aAAkB;IActC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB3B;;OAEG;IACH,IAAI,IAAI,IAAI;IA2DZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAqCZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,IAAI,IAAI;IAef;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAM7B;;OAEG;IACH,OAAO,CAAC,aAAa;IA8BrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAyHpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA8D1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAKtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAoD3B;;OAEG;IACH,OAAO,CAAC,YAAY;CAMrB"}
1
+ {"version":3,"file":"IframeManager.d.ts","sourceRoot":"","sources":["../../src/core/IframeManager.ts"],"names":[],"mappings":"AACA,UAAU,aAAa;IACrB,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE,MAAM,GAAG,YAAY,GAAG,OAAO,CAAA;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,SAAS,CAAC,EAAE,OAAO,CAAA;IACnB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,SAAS,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,IAAI,CAAA;IACpD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAA;IACpB,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,aAAa,CAAiC;IACtD,OAAO,CAAC,cAAc,CAA2B;IACjD,OAAO,CAAC,gBAAgB,CAA2B;IACnD,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,SAAS,CAAQ;IACzB,OAAO,CAAC,KAAK,CAAiB;gBAElB,MAAM,GAAE,aAAkB;IActC;;;OAGG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB3B;;OAEG;IACH,IAAI,IAAI,IAAI;IAsEZ;;OAEG;IACH,IAAI,IAAI,IAAI;IAIZ;;OAEG;IACH,IAAI,IAAI,IAAI;IA0CZ;;OAEG;IACH,KAAK,IAAI,IAAI;IAIb;;OAEG;IACH,OAAO,IAAI,IAAI;IAef;;OAEG;IACH,YAAY,IAAI,OAAO;IAIvB;;OAEG;IACH,YAAY,CAAC,IAAI,EAAE,GAAG,GAAG,IAAI;IAM7B;;OAEG;IACH,OAAO,CAAC,aAAa;IAoDrB;;OAEG;IACH,OAAO,CAAC,YAAY;IAuIpB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IA8D1B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAoCzB;;OAEG;IACH,OAAO,CAAC,cAAc;IAKtB;;;OAGG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAS5B;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAwD3B;;OAEG;IACH,OAAO,CAAC,YAAY;CAMrB"}
@@ -17,8 +17,12 @@ class IconManager {
17
17
  this.iconStartY = 0;
18
18
  this.dragMoveHandler = null;
19
19
  this.dragEndHandler = null;
20
+ this.checkDragHandler = null; // 临时拖动检测监听器
21
+ this.dragStartHandler = null; // 拖动开始事件监听器
22
+ this.touchStartHandler = null; // 触摸开始事件监听器
20
23
  this.iconPosition = null; // 图标位置配置
21
24
  this.debug = false; // debug 模式标志
25
+ this.isClickEnabled = true; // 是否允许点击(iframe 打开时禁用)
22
26
  this.iconPosition = position || null;
23
27
  this.debug = debug;
24
28
  }
@@ -34,7 +38,7 @@ class IconManager {
34
38
  this.iconElement.className = 'customer-sdk-icon';
35
39
  // 直接设置样式 - 图标容器
36
40
  const defaultStyle = {
37
- position: 'fixed',
41
+ position: 'absolute',
38
42
  width: '30px',
39
43
  height: '30px',
40
44
  backgroundColor: 'transparent', // 移除背景色,让图片直接显示
@@ -43,7 +47,7 @@ class IconManager {
43
47
  alignItems: 'center',
44
48
  justifyContent: 'center',
45
49
  cursor: 'pointer',
46
- zIndex: '999999',
50
+ zIndex: '1000002', // 确保图标始终在最上层(遮罩层 999998,iframe 容器 999999)
47
51
  boxShadow: '0 4px 12px rgba(0, 0, 0, 0.15)',
48
52
  userSelect: 'none',
49
53
  transition: 'transform 0.2s ease',
@@ -270,9 +274,11 @@ class IconManager {
270
274
  // 绑定事件处理器(用于后续清理)
271
275
  this.dragMoveHandler = this.handleDragMove.bind(this);
272
276
  this.dragEndHandler = this.handleDragEnd.bind(this);
273
- // 只在图标上监听开始事件
274
- this.iconElement.addEventListener('mousedown', this.handleDragStart.bind(this));
275
- this.iconElement.addEventListener('touchstart', this.handleDragStart.bind(this), { passive: false });
277
+ this.dragStartHandler = this.handleDragStart.bind(this);
278
+ // 只在图标上监听开始事件(保存引用以便后续移除)
279
+ this.iconElement.addEventListener('mousedown', this.dragStartHandler);
280
+ this.touchStartHandler = this.handleDragStart.bind(this);
281
+ this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
276
282
  }
277
283
  /**
278
284
  * 开始拖动
@@ -295,12 +301,33 @@ class IconManager {
295
301
  const rect = this.iconElement.getBoundingClientRect();
296
302
  this.iconStartX = rect.left;
297
303
  this.iconStartY = rect.top;
298
- // 添加拖动样式
299
- this.iconElement.style.transition = 'none';
300
- this.iconElement.style.cursor = 'grabbing';
304
+ // 注意:不要在这里立即移除 transition 和设置 cursor
305
+ // 只有在真正开始拖动时才修改样式,避免点击时图标位置跳动
301
306
  // 只在真正开始拖动时添加document事件监听
302
307
  // 先添加一个临时的move监听器来检测是否真的在拖动
303
308
  const checkDrag = (moveEvent) => {
309
+ // 检测事件目标:如果事件发生在iframe或其他元素上,停止检测拖动
310
+ const target = moveEvent.target;
311
+ if (target && target !== this.iconElement && !this.iconElement?.contains(target)) {
312
+ // 检查是否是iframe相关元素
313
+ const isIframeElement = target.tagName === 'IFRAME' ||
314
+ target.closest('iframe') ||
315
+ target.closest('.customer-sdk-container') ||
316
+ target.closest('.customer-sdk-overlay');
317
+ if (isIframeElement) {
318
+ // 如果事件发生在iframe相关元素上,停止检测并清理监听器
319
+ if (this.checkDragHandler) {
320
+ if ('touches' in moveEvent) {
321
+ document.removeEventListener('touchmove', this.checkDragHandler);
322
+ }
323
+ else {
324
+ document.removeEventListener('mousemove', this.checkDragHandler);
325
+ }
326
+ this.checkDragHandler = null;
327
+ }
328
+ return;
329
+ }
330
+ }
304
331
  const moveX = 'touches' in moveEvent ? moveEvent.touches[0].clientX : moveEvent.clientX;
305
332
  const moveY = 'touches' in moveEvent ? moveEvent.touches[0].clientY : moveEvent.clientY;
306
333
  const deltaX = moveX - this.dragStartX;
@@ -308,12 +335,20 @@ class IconManager {
308
335
  // 如果移动距离超过5px,开始拖动
309
336
  if (Math.abs(deltaX) > 5 || Math.abs(deltaY) > 5) {
310
337
  this.isDragging = true;
311
- // 移除临时监听器
312
- if ('touches' in moveEvent) {
313
- document.removeEventListener('touchmove', checkDrag);
338
+ // 只有在真正开始拖动时才移除 transition 和设置 cursor
339
+ if (this.iconElement) {
340
+ this.iconElement.style.transition = 'none';
341
+ this.iconElement.style.cursor = 'grabbing';
314
342
  }
315
- else {
316
- document.removeEventListener('mousemove', checkDrag);
343
+ // 移除临时监听器
344
+ if (this.checkDragHandler) {
345
+ if ('touches' in moveEvent) {
346
+ document.removeEventListener('touchmove', this.checkDragHandler);
347
+ }
348
+ else {
349
+ document.removeEventListener('mousemove', this.checkDragHandler);
350
+ }
351
+ this.checkDragHandler = null;
317
352
  }
318
353
  // 添加正式的事件监听器
319
354
  if (this.dragMoveHandler && this.dragEndHandler) {
@@ -328,13 +363,15 @@ class IconManager {
328
363
  }
329
364
  }
330
365
  };
366
+ // 保存 checkDrag 引用,以便后续清理
367
+ this.checkDragHandler = checkDrag;
331
368
  // 添加临时检测监听器
332
369
  if ('touches' in e) {
333
- document.addEventListener('touchmove', checkDrag, { passive: false });
370
+ document.addEventListener('touchmove', this.checkDragHandler, { passive: false });
334
371
  document.addEventListener('touchend', this.dragEndHandler);
335
372
  }
336
373
  else {
337
- document.addEventListener('mousemove', checkDrag);
374
+ document.addEventListener('mousemove', this.checkDragHandler);
338
375
  document.addEventListener('mouseup', this.dragEndHandler);
339
376
  }
340
377
  }
@@ -346,20 +383,9 @@ class IconManager {
346
383
  return;
347
384
  const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;
348
385
  const clientY = 'touches' in e ? e.touches[0].clientY : e.clientY;
349
- // 检测事件目标:如果事件发生在iframe或其他元素上,停止拖动
350
- const target = e.target;
351
- if (target && target !== this.iconElement && !this.iconElement.contains(target)) {
352
- // 检查是否是iframe相关元素
353
- const isIframeElement = target.tagName === 'IFRAME' ||
354
- target.closest('iframe') ||
355
- target.closest('.customer-sdk-container') ||
356
- target.closest('.customer-sdk-overlay');
357
- if (isIframeElement) {
358
- // 如果事件发生在iframe相关元素上,停止拖动
359
- this.handleDragEnd();
360
- return;
361
- }
362
- }
386
+ // 注意:拖动过程中不检测 iframe 元素,因为用户可能只是想将图标拖动到 iframe 附近或上方
387
+ // 只有在拖动开始时(checkDrag 阶段)才检测 iframe,防止误判为拖动
388
+ // 一旦开始拖动,就应该允许拖动到任何位置,包括 iframe 上方
363
389
  // 计算从拖动开始位置到当前位置的距离
364
390
  const deltaX = clientX - this.dragStartX;
365
391
  const deltaY = clientY - this.dragStartY;
@@ -394,10 +420,15 @@ class IconManager {
394
420
  /**
395
421
  * 结束拖动
396
422
  */
397
- handleDragEnd(e) {
423
+ handleDragEnd(_e) {
398
424
  if (!this.iconElement)
399
425
  return;
400
- // 清理document上的事件监听器
426
+ // 清理document上的所有事件监听器(包括临时检测监听器)
427
+ if (this.checkDragHandler) {
428
+ document.removeEventListener('mousemove', this.checkDragHandler);
429
+ document.removeEventListener('touchmove', this.checkDragHandler);
430
+ this.checkDragHandler = null;
431
+ }
401
432
  if (this.dragMoveHandler) {
402
433
  document.removeEventListener('mousemove', this.dragMoveHandler);
403
434
  document.removeEventListener('touchmove', this.dragMoveHandler);
@@ -406,6 +437,8 @@ class IconManager {
406
437
  document.removeEventListener('mouseup', this.dragEndHandler);
407
438
  document.removeEventListener('touchend', this.dragEndHandler);
408
439
  }
440
+ // 恢复样式(如果之前被修改过)
441
+ // 注意:如果只是点击(没有拖动),这些样式可能没有被修改,但恢复操作是安全的
409
442
  this.iconElement.style.transition = 'transform 0.2s ease';
410
443
  this.iconElement.style.cursor = 'pointer';
411
444
  // 如果只是点击(没有拖动),触发点击事件
@@ -418,10 +451,52 @@ class IconManager {
418
451
  * 处理点击事件
419
452
  */
420
453
  handleClick() {
454
+ // 如果点击被禁用(iframe 打开时),不执行点击回调
455
+ if (!this.isClickEnabled) {
456
+ return;
457
+ }
421
458
  if (this.onClickCallback) {
422
459
  this.onClickCallback();
423
460
  }
424
461
  }
462
+ /**
463
+ * 禁用点击和拖拽(iframe 打开时调用)
464
+ */
465
+ disableClick() {
466
+ this.isClickEnabled = false;
467
+ // 移除拖动事件监听器
468
+ if (this.iconElement) {
469
+ if (this.dragStartHandler) {
470
+ this.iconElement.removeEventListener('mousedown', this.dragStartHandler);
471
+ }
472
+ if (this.touchStartHandler) {
473
+ this.iconElement.removeEventListener('touchstart', this.touchStartHandler);
474
+ }
475
+ // 禁用所有鼠标事件(包括点击和拖拽)
476
+ this.iconElement.style.pointerEvents = 'none';
477
+ this.iconElement.style.cursor = 'default';
478
+ }
479
+ // 清理可能正在进行的拖动
480
+ this.forceCleanupDragEvents();
481
+ }
482
+ /**
483
+ * 启用点击和拖拽(iframe 关闭时调用)
484
+ */
485
+ enableClick() {
486
+ this.isClickEnabled = true;
487
+ // 重新添加拖动事件监听器
488
+ if (this.iconElement) {
489
+ if (this.dragStartHandler) {
490
+ this.iconElement.addEventListener('mousedown', this.dragStartHandler);
491
+ }
492
+ if (this.touchStartHandler) {
493
+ this.iconElement.addEventListener('touchstart', this.touchStartHandler, { passive: false });
494
+ }
495
+ // 恢复鼠标事件
496
+ this.iconElement.style.pointerEvents = 'auto';
497
+ this.iconElement.style.cursor = 'pointer';
498
+ }
499
+ }
425
500
  /**
426
501
  * 创建消息徽章(简化版)
427
502
  */
@@ -487,7 +562,7 @@ class IframeManager {
487
562
  this.config = {
488
563
  src: '',
489
564
  mode: 'auto', // 默认自动检测设备类型
490
- width: 400,
565
+ width: 450, // PC 模式默认宽度
491
566
  height: 600,
492
567
  allowClose: true,
493
568
  ...config
@@ -527,9 +602,9 @@ class IframeManager {
527
602
  try {
528
603
  const actualMode = this.getActualMode();
529
604
  const isPC = actualMode === 'popup';
530
- // PC模式下创建遮罩层
605
+ // PC模式:创建或显示遮罩层
531
606
  if (isPC) {
532
- this.createOverlay();
607
+ this.createOverlay(); // createOverlay 内部会检查是否已存在
533
608
  }
534
609
  // 显示已创建的容器
535
610
  if (this.containerElement) {
@@ -544,8 +619,18 @@ class IframeManager {
544
619
  opacity: '1',
545
620
  display: 'block'
546
621
  });
547
- // 将容器移到遮罩层内
548
- this.overlayElement?.appendChild(this.containerElement);
622
+ // 关键优化:避免重复移动容器导致 iframe 重新加载
623
+ // 只有当容器不在遮罩层内时才移动,且确保遮罩层在 DOM 中
624
+ if (this.overlayElement) {
625
+ // 如果遮罩层不在 DOM 中,先添加到 DOM
626
+ if (!this.overlayElement.parentNode) {
627
+ document.body.appendChild(this.overlayElement);
628
+ }
629
+ // 只有当容器不在遮罩层内时才移动(避免重复移动导致 iframe 重新加载)
630
+ if (this.containerElement.parentNode !== this.overlayElement) {
631
+ this.overlayElement.appendChild(this.containerElement);
632
+ }
633
+ }
549
634
  }
550
635
  else {
551
636
  // 移动端模式:直接全屏显示,不需要遮罩层
@@ -582,22 +667,27 @@ class IframeManager {
582
667
  if (!this.isOpen) {
583
668
  return;
584
669
  }
585
- // 隐藏容器但保留DOM元素
670
+ // 隐藏容器但保留DOM元素(不移动容器,避免 iframe 重新加载)
586
671
  if (this.containerElement) {
587
672
  Object.assign(this.containerElement.style, {
588
673
  visibility: 'hidden',
589
674
  opacity: '0',
590
675
  display: 'none'
591
676
  });
677
+ // 注意:不移动容器,保持容器在当前位置(遮罩层或 body),避免 iframe 重新加载
592
678
  }
593
- // 移除遮罩层(仅PC模式)
679
+ // 隐藏遮罩层但不移除(仅PC模式,避免重新创建导致 iframe 重新加载)
594
680
  if (this.overlayElement) {
595
- this.overlayElement.remove();
596
- this.overlayElement = null;
681
+ Object.assign(this.overlayElement.style, {
682
+ visibility: 'hidden',
683
+ opacity: '0',
684
+ display: 'none'
685
+ });
686
+ // 不移除遮罩层,下次显示时直接显示即可
597
687
  }
598
688
  // 恢复body滚动(移动端模式)
599
- const actualMode = this.getActualMode();
600
- if (actualMode === 'fullscreen') {
689
+ const actualModeForScroll = this.getActualMode();
690
+ if (actualModeForScroll === 'fullscreen') {
601
691
  this.preventBodyScroll(false);
602
692
  }
603
693
  this.isOpen = false;
@@ -645,9 +735,29 @@ class IframeManager {
645
735
  }
646
736
  }
647
737
  /**
648
- * 创建遮罩层
738
+ * 创建遮罩层(PC模式使用)
649
739
  */
650
740
  createOverlay() {
741
+ // 如果遮罩层已存在,直接显示即可
742
+ if (this.overlayElement && this.overlayElement.parentNode) {
743
+ Object.assign(this.overlayElement.style, {
744
+ visibility: 'visible',
745
+ opacity: '1',
746
+ display: 'flex'
747
+ });
748
+ return;
749
+ }
750
+ // 如果遮罩层存在但不在 DOM 中,重新添加到 DOM
751
+ if (this.overlayElement && !this.overlayElement.parentNode) {
752
+ document.body.appendChild(this.overlayElement);
753
+ Object.assign(this.overlayElement.style, {
754
+ visibility: 'visible',
755
+ opacity: '1',
756
+ display: 'flex'
757
+ });
758
+ return;
759
+ }
760
+ // 创建新的遮罩层
651
761
  this.overlayElement = document.createElement('div');
652
762
  this.overlayElement.className = 'customer-sdk-overlay';
653
763
  Object.assign(this.overlayElement.style, {
@@ -663,7 +773,7 @@ class IframeManager {
663
773
  justifyContent: 'center',
664
774
  cursor: this.config.allowClose ? 'pointer' : 'default'
665
775
  });
666
- // 点击遮罩层关闭
776
+ // 点击遮罩层关闭(只添加一次事件监听器)
667
777
  if (this.config.allowClose) {
668
778
  this.overlayElement.addEventListener('click', (e) => {
669
779
  if (e.target === this.overlayElement) {
@@ -715,45 +825,59 @@ class IframeManager {
715
825
  'allow-pointer-lock', // 允许指针锁定
716
826
  'allow-storage-access-by-user-activation' // 允许用户激活的存储访问
717
827
  ].join(' '));
718
- // 根据设备类型设置滚动行为
828
+ // 根据设备类型设置模式
719
829
  const actualMode = this.getActualMode();
720
830
  const isPC = actualMode === 'popup';
721
831
  this.iframeElement.scrolling = isPC ? 'auto' : 'no'; // PC显示滚动条,移动端禁用
722
- const containerStyles = {
723
- width: isPC ? `${this.config.width}px` : '100%',
724
- height: isPC ? `${this.config.height}px` : '100%',
725
- maxWidth: isPC ? '450px' : '100%',
726
- maxHeight: isPC ? '700px' : '100%',
832
+ // PC 模式:使用配置的宽度和高度
833
+ // 移动端:使用全屏
834
+ const containerStyles = isPC ? {
835
+ // PC 弹窗模式
836
+ width: `${this.config.width || 450}px`,
837
+ height: `${this.config.height || 600}px`,
838
+ maxWidth: '90vw',
839
+ maxHeight: '90vh',
727
840
  backgroundColor: '#ffffff',
728
- borderRadius: isPC ? '12px' : '12px 12px 0 0',
729
- boxShadow: isPC
730
- ? '0 20px 40px rgba(0, 0, 0, 0.15)'
731
- : '0 -4px 16px rgba(0, 0, 0, 0.25)',
841
+ borderRadius: '12px',
842
+ boxShadow: '0 20px 40px rgba(0, 0, 0, 0.15)',
732
843
  border: 'none',
733
844
  position: 'fixed',
734
845
  zIndex: '999999',
735
- // PC模式下的定位
736
- ...(isPC ? {
737
- top: '50%',
738
- left: '50%',
739
- transform: 'translate(-50%, -50%)'
740
- } : {
741
- // 移动端全屏模式 - 确保占满屏幕且不滚动
742
- top: '0',
743
- left: '0',
744
- bottom: '0',
745
- right: '0',
746
- transform: 'none',
747
- overflow: 'hidden', // 防止容器本身滚动
748
- position: 'fixed' // 确保固定定位
749
- }),
846
+ // PC模式:居中显示
847
+ top: '50%',
848
+ left: '50%',
849
+ transform: 'translate(-50%, -50%)',
850
+ overflow: 'hidden',
851
+ // 初始隐藏的关键样式
852
+ visibility: 'hidden',
853
+ opacity: '0',
854
+ display: 'none'
855
+ } : {
856
+ // 移动端全屏模式(强制 100% 宽度和高度)
857
+ width: '100%',
858
+ height: '100%',
859
+ maxWidth: '100%',
860
+ maxHeight: '100%',
861
+ backgroundColor: '#ffffff',
862
+ borderRadius: '12px 12px 0 0',
863
+ boxShadow: '0 -4px 16px rgba(0, 0, 0, 0.25)',
864
+ border: 'none',
865
+ position: 'fixed',
866
+ zIndex: '999999',
867
+ // 全屏模式 - 占满整个屏幕
868
+ top: '0',
869
+ left: '0',
870
+ bottom: '0',
871
+ right: '0',
872
+ transform: 'none',
873
+ overflow: 'hidden',
750
874
  // 初始隐藏的关键样式
751
875
  visibility: 'hidden',
752
876
  opacity: '0',
753
877
  display: 'none'
754
878
  };
755
879
  Object.assign(this.containerElement.style, containerStyles);
756
- // iframe填充整个容器,根据设备类型设置滚动样式
880
+ // iframe填充整个容器
757
881
  const iframeStyles = {
758
882
  width: '100%',
759
883
  height: '100%',
@@ -887,6 +1011,7 @@ class IframeManager {
887
1011
  }
888
1012
  /**
889
1013
  * 获取当前显示模式
1014
+ * PC 模式使用弹窗,移动端使用全屏
890
1015
  */
891
1016
  getActualMode() {
892
1017
  if (this.config.mode === 'auto') {
@@ -939,9 +1064,14 @@ class IframeManager {
939
1064
  break;
940
1065
  case 'resize_iframe':
941
1066
  case 'resize':
942
- if (data.width && data.height) {
1067
+ // PC模式支持 resize,移动端忽略
1068
+ const actualMode = this.getActualMode();
1069
+ if (actualMode === 'popup' && data.width && data.height) {
943
1070
  this.resizeIframe(data.width, data.height);
944
1071
  }
1072
+ else if (this.debug) {
1073
+ console.log('Resize request ignored (fullscreen mode)');
1074
+ }
945
1075
  break;
946
1076
  case 'new-message':
947
1077
  // 新消息通知 - 触发回调让外层处理
@@ -960,12 +1090,12 @@ class IframeManager {
960
1090
  }
961
1091
  }
962
1092
  /**
963
- * 调整iframe大小
1093
+ * 调整iframe大小(PC模式支持)
964
1094
  */
965
1095
  resizeIframe(width, height) {
966
- if (this.iframeElement) {
967
- this.iframeElement.style.width = `${width}px`;
968
- this.iframeElement.style.height = `${height}px`;
1096
+ if (this.containerElement) {
1097
+ this.containerElement.style.width = `${width}px`;
1098
+ this.containerElement.style.height = `${height}px`;
969
1099
  }
970
1100
  }
971
1101
  }
@@ -20703,9 +20833,9 @@ class CustomerServiceSDK {
20703
20833
  // 创建iframe管理器(自动检测设备类型)
20704
20834
  this.iframeManager = new IframeManager({
20705
20835
  src: iframeUrl,
20706
- mode: 'auto', // 自动根据设备类型选择模式
20707
- width: 400,
20708
- height: 600,
20836
+ mode: 'auto', // 自动根据设备类型选择模式(PC弹窗,移动端全屏)
20837
+ width: options?.width || 450, // PC模式宽度(像素,默认450px),移动端不使用
20838
+ height: options?.height || 600, // PC模式高度(像素),移动端不使用(强制全屏)
20709
20839
  allowClose: true,
20710
20840
  debug: this.debug, // 传递 debug 标志
20711
20841
  onMessage: (messageType, _data) => {
@@ -20717,8 +20847,9 @@ class CustomerServiceSDK {
20717
20847
  // checkScreenshot 消息由 ScreenshotManager 处理,不需要在这里处理
20718
20848
  },
20719
20849
  onClose: () => {
20720
- // iframe关闭时,清理图标拖动事件监听器
20850
+ // iframe关闭时,清理图标拖动事件监听器,并重新启用图标点击
20721
20851
  this.iconManager?.forceCleanupDragEvents();
20852
+ this.iconManager?.enableClick();
20722
20853
  },
20723
20854
  ...options
20724
20855
  });
@@ -20729,6 +20860,8 @@ class CustomerServiceSDK {
20729
20860
  // 打开iframe时清除红点通知
20730
20861
  this.clearNotification();
20731
20862
  this.iframeManager?.show();
20863
+ // iframe 打开后,禁用图标点击(防止重复打开)
20864
+ this.iconManager?.disableClick();
20732
20865
  });
20733
20866
  // 初始化截图管理器(如果启用了截图功能)
20734
20867
  if (config.screenshot) {