吴树波 2 недель назад
Родитель
Сommit
81a8dd0cef

+ 76 - 22
src/components/FloatingSoftPhone/index.vue

@@ -278,6 +278,13 @@ import ccPhoneBarSocket from '@/assets/callCenterPhoneBarSdk/ccPhoneBarSocket.js
 import { EventList, VideoLevels, AgentStatusEnum } from '@/assets/callCenterPhoneBarSdk/constants.js';
 import { myCallUser, getToolbarBasicParam } from '@/api/aiSipCall/aiSipCallUser.js';
 import { syncByUuid } from '@/api/aiSipCall/aiSipCallOutboundCdr.js';
+import {
+  beginCCPhoneBarInit,
+  finishCCPhoneBarInitFailed,
+  finishCCPhoneBarInitSuccess,
+  getConnectedSharedCCPhoneBar,
+  incrementSharedCCPhoneBarRef
+} from '@/utils/ccPhoneBarShared';
 
 // IPCC 和 JsSIP 默认配置已从 softPhone.js 统一导入,此处仅作别名引用
 const IPCC_CONFIG = IPCC_DEFAULTS;
@@ -420,6 +427,8 @@ export default {
       // ccPhoneBar 自动重连相关
       _ccReconnectTimer: null,
       _ccReconnectAttempts: 0,
+      _ccLoginConflict: false,
+      _initCCPromise: null,
       _isDestroying: false,
       _ccEventsBoundFor: null,
 
@@ -975,16 +984,49 @@ export default {
 
     // ==================== 呼叫中心集成 ====================
     async initCCAndStart() {
-      this._isDestroying = false;
-      this.ccSocketConnected = false;
-      this.ccSocketFailed = false;
-      this.isCallingReady = false;
+      if (this._ccLoginConflict) {
+        this.showStatus('账号已在其他窗口登录,请关闭多余软电话后点击重新连接', 'error');
+        return;
+      }
+      if (this._initCCPromise) {
+        return this._initCCPromise;
+      }
+
+      const shared = getConnectedSharedCCPhoneBar();
+      if (shared) {
+        this._isDestroying = false;
+        if (this.ccPhoneBar !== shared) {
+          this.ccPhoneBar = shared;
+          this._isSharedCCPhoneBar = true;
+          incrementSharedCCPhoneBarRef();
+        }
+        this.ccSocketConnected = true;
+        this.ccSocketFailed = false;
+        this._bindCCEvents();
+        if (!this.phone || !this.isRegistered) {
+          await this.startPhone();
+        }
+        return;
+      }
+
+      this._initCCPromise = (async () => {
+        this._isDestroying = false;
+        this.ccSocketFailed = false;
+        this.isCallingReady = false;
+        try {
+          await this.ensureCCSocketConnect();
+          await this.startPhone();
+        } catch (err) {
+          this.ccSocketFailed = true;
+          this.showStatus(`初始化失败: ${err.message}`, 'error');
+          throw err;
+        }
+      })();
+
       try {
-        await this.ensureCCSocketConnect();
-        await this.startPhone();
-      } catch (err) {
-        this.ccSocketFailed = true;
-        this.showStatus(`初始化失败: ${err.message}`, 'error');
+        await this._initCCPromise;
+      } finally {
+        this._initCCPromise = null;
       }
     },
     ensureCCSocketConnect() {
@@ -999,23 +1041,23 @@ export default {
     },
     async _doConnectCCSocket() {
       try {
-        // 优先复用已连接的共享 ccPhoneBar 实例,避免同一分机号重复登录导致 status:201 冲突
-        const shared = window.__sharedCCPhoneBar;
-        if (shared && typeof shared.getIsConnected === 'function' && shared.getIsConnected()) {
+        const shared = getConnectedSharedCCPhoneBar();
+        if (shared) {
           console.log('[FloatingSoftPhone] 检测到已连接的共享 ccPhoneBar,直接复用');
           this.ccPhoneBar = shared;
           this._isSharedCCPhoneBar = true;
           this.ccSocketConnected = true;
           this.ccSocketFailed = false;
-          this.isCallingReady = true;  // 复用时 IPCC 已连接且坐席已置忙,直接就绪
-          // 增加引用计数,防止创建者在其他组件仍使用时断开连接
-          window.__sharedCCPhoneBarRefCount = (window.__sharedCCPhoneBarRefCount || 0) + 1;
+          this.isCallingReady = true;
+          incrementSharedCCPhoneBarRef();
           this._bindCCEvents();
           if (this.ccConnectingResolve) this.ccConnectingResolve();
           this.ccConnectingPromise = null;
           return;
         }
 
+        beginCCPhoneBarInit();
+
         const extRes = await myCallUser();
         if (extRes.code !== 200 || !extRes.data || !extRes.data.extNum) {
           throw new Error('未查询到分机号信息');
@@ -1065,6 +1107,7 @@ export default {
           if (originalResolve) originalResolve();
         };
       } catch (err) {
+        finishCCPhoneBarInitFailed(err);
         if (this.ccConnectingReject) this.ccConnectingReject(err);
         this.ccConnectingPromise = null;
         throw err;
@@ -1074,26 +1117,35 @@ export default {
       if (this._ccEventsBoundFor === this.ccPhoneBar) return;
       this._ccEventsBoundFor = this.ccPhoneBar;
       this.ccPhoneBar.on(EventList.WS_CONNECTED, () => {
-        // 注册为共享实例,供其他组件复用
+        finishCCPhoneBarInitSuccess(this.ccPhoneBar);
         window.__sharedCCPhoneBar = this.ccPhoneBar;
-        window.__sharedCCPhoneBarRefCount = 1;  // 创建者初始引用计数为1
+        window.__sharedCCPhoneBarRefCount = 1;
         this.ccSocketConnected = true;
         this.ccSocketFailed = false;
-        // 重置重连计数
+        this._ccLoginConflict = false;
         this._ccReconnectAttempts = 0;
         if (this.ccConnectingResolve) this.ccConnectingResolve();
         this.ccConnectingPromise = null;
-        // 通知其他组件共享实例已更新(如 aiSipCallManualOutbound)
         this.$root.$emit('cc-phonebar-reconnected', this.ccPhoneBar);
         console.log('[FloatingSoftPhone] ccPhoneBar 连接成功,已注册为共享实例');
       });
+      this.ccPhoneBar.on(EventList.USER_LOGIN_ON_OTHER_DEVICE, (msg) => {
+        console.warn('[FloatingSoftPhone] 检测到重复登录 status:201', msg);
+        this._ccLoginConflict = true;
+        this._ccReconnectAttempts = 999;
+        if (this._ccReconnectTimer) {
+          clearTimeout(this._ccReconnectTimer);
+          this._ccReconnectTimer = null;
+        }
+        finishCCPhoneBarInitFailed(new Error('user_logined_on_other_device'));
+        this.showStatus('软电话已在其他窗口登录,请关闭多余页面后重新连接', 'error');
+      });
       this.ccPhoneBar.on(EventList.WS_DISCONNECTED, () => {
         console.log('[FloatingSoftPhone] ccPhoneBar WebSocket 断开');
         this.ccSocketConnected = false;
         this.isCallingReady = false;
         if (this.phone && this.isRegistered) this.showStatus('连接断开', 'warn');
-        // 非主动销毁时自动重连
-        if (!this._isDestroying) {
+        if (!this._isDestroying && !this._ccLoginConflict) {
           this._scheduleCCReconnect();
         }
       });
@@ -1195,6 +1247,7 @@ export default {
      * 使用指数退避策略,最多重试 5 次,避免无限重连消耗资源
      */
     _scheduleCCReconnect() {
+      if (this._ccLoginConflict) return;
       if (this._ccReconnectTimer) clearTimeout(this._ccReconnectTimer);
       const maxAttempts = 5;
       if (this._ccReconnectAttempts >= maxAttempts) {
@@ -1214,8 +1267,8 @@ export default {
             window.__sharedCCPhoneBar = null;
             window.__sharedCCPhoneBarRefCount = 0;
           }
-          // 清理旧的 ccPhoneBar 引用
           if (this.ccPhoneBar) {
+            try { this.ccPhoneBar.disconnect(); } catch (e) { /* ignore */ }
             this.ccPhoneBar = null;
           }
           this.ccConnectingPromise = null;
@@ -1663,6 +1716,7 @@ export default {
     },
     async resetReconnectState() {
       this.showStatus('正在重置...', 'info');
+      this._ccLoginConflict = false;
       // 清除自动重连定时器和计数
       if (this._ccReconnectTimer) { clearTimeout(this._ccReconnectTimer); this._ccReconnectTimer = null; }
       this._ccReconnectAttempts = 0;

+ 71 - 0
src/utils/ccPhoneBarShared.js

@@ -0,0 +1,71 @@
+/**
+ * ccPhoneBar 全局单例协调(避免同一分机重复登录 status:201)
+ */
+
+export function getConnectedSharedCCPhoneBar() {
+  const shared = window.__sharedCCPhoneBar;
+  if (shared && typeof shared.getIsConnected === 'function' && shared.getIsConnected()) {
+    return shared;
+  }
+  return null;
+}
+
+export function beginCCPhoneBarInit() {
+  if (!window.__ccPhoneBarInitPromise) {
+    window.__ccPhoneBarInitPromise = new Promise((resolve, reject) => {
+      window.__ccPhoneBarInitResolve = resolve;
+      window.__ccPhoneBarInitReject = reject;
+    });
+  }
+  return window.__ccPhoneBarInitPromise;
+}
+
+export function finishCCPhoneBarInitSuccess(phoneBar) {
+  window.__sharedCCPhoneBar = phoneBar;
+  if (typeof window.__ccPhoneBarInitResolve === 'function') {
+    window.__ccPhoneBarInitResolve(phoneBar);
+  }
+  clearCCPhoneBarInitPromise();
+}
+
+export function finishCCPhoneBarInitFailed(error) {
+  if (typeof window.__ccPhoneBarInitReject === 'function') {
+    window.__ccPhoneBarInitReject(error);
+  }
+  clearCCPhoneBarInitPromise();
+}
+
+export function clearCCPhoneBarInitPromise() {
+  window.__ccPhoneBarInitPromise = null;
+  window.__ccPhoneBarInitResolve = null;
+  window.__ccPhoneBarInitReject = null;
+}
+
+/**
+ * 等待 FloatingSoftPhone / softPhone 完成 ccPhoneBar 连接
+ */
+export function waitForSharedCCPhoneBar(timeoutMs = 25000) {
+  const connected = getConnectedSharedCCPhoneBar();
+  if (connected) {
+    return Promise.resolve(connected);
+  }
+  if (!window.__ccPhoneBarInitPromise) {
+    return Promise.resolve(null);
+  }
+  return Promise.race([
+    window.__ccPhoneBarInitPromise,
+    new Promise((_, reject) => {
+      setTimeout(() => reject(new Error('等待软电话连接超时')), timeoutMs);
+    })
+  ]);
+}
+
+export function incrementSharedCCPhoneBarRef() {
+  window.__sharedCCPhoneBarRefCount = (window.__sharedCCPhoneBarRefCount || 0) + 1;
+}
+
+export function decrementSharedCCPhoneBarRef() {
+  if (window.__sharedCCPhoneBarRefCount > 0) {
+    window.__sharedCCPhoneBarRefCount--;
+  }
+}

+ 21 - 15
src/views/aiSipCall/aiSipCallManualOutbound.vue

@@ -489,6 +489,11 @@ import {
     DefaultConfig
 } from '@/assets/callCenterPhoneBarSdk/constants.js';
 import ccPhoneBarSocket from '@/assets/callCenterPhoneBarSdk/ccPhoneBarSocket.js';
+import {
+  getConnectedSharedCCPhoneBar,
+  incrementSharedCCPhoneBarRef,
+  waitForSharedCCPhoneBar
+} from '@/utils/ccPhoneBarShared';
 import {myCallUser,getToolbarBasicParam} from "../../api/aiSipCall/aiSipCallUser";
 import {addCustcallrecord, getCustCommunicationInfo, syncByUuid} from "../../api/aiSipCall/aiSipCallOutboundCdr";
 import { getCustomerDetails, updateCustomer } from "@/api/crm/customer";
@@ -774,12 +779,10 @@ export default {
          */
         onCCPhoneBarReconnected(newPhoneBar) {
             console.log('[电话工具条] 检测到共享 ccPhoneBar 重连,更新引用并重新注册事件');
-            // 清理旧 phoneBar 上的事件回调
             this._cleanupPhoneBarListeners();
-            // 更新引用
             this.phoneBar = newPhoneBar;
             this._isSharedPhoneBar = true;
-            // 重新注册事件监听
+            incrementSharedCCPhoneBarRef();
             this.setupEventListeners();
             // 同步 UI 状态
             this.onlineButtonText = '签出';
@@ -803,27 +806,30 @@ export default {
 
         /**
          * 初始化电话工具条
-         * 优先复用已连接的共享 ccPhoneBar 实例,避免同一分机号重复登录导致 status:201 冲突
+         * 优先等待 FloatingSoftPhone 的共享 ccPhoneBar,避免重复登录 status:201
          */
-        initPhoneBar() {
-            // 检查是否已有共享的 ccPhoneBar 实例(由 FloatingSoftPhone 或 softPhone 注册)
-            const shared = window.__sharedCCPhoneBar;
+        async initPhoneBar() {
+            let shared = getConnectedSharedCCPhoneBar();
+            if (!shared) {
+                try {
+                    shared = await waitForSharedCCPhoneBar(25000);
+                } catch (err) {
+                    console.warn('[电话工具条] 等待共享 ccPhoneBar 超时', err);
+                }
+            }
             if (shared && typeof shared.getIsConnected === 'function' && shared.getIsConnected()) {
-                console.log('[电话工具条] 检测到已连接的共享 ccPhoneBar,直接复用,避免 status:201 冲突');
+                console.log('[电话工具条] 复用共享 ccPhoneBar,避免 status:201 冲突');
                 this.phoneBar = shared;
                 this._isSharedPhoneBar = true;
-                // 增加引用计数,防止创建者在其他组件仍使用时断开连接
-                window.__sharedCCPhoneBarRefCount = (window.__sharedCCPhoneBarRefCount || 0) + 1;
-                // 复用实例时直接同步连接状态,无需重新走连接流程
+                incrementSharedCCPhoneBarRef();
                 this.onlineButtonText = '签出';
                 this.callStatus = '已签入';
                 this.agentStatus = '忙碌';
+                this.loginTime = new Date().toLocaleTimeString();
                 return;
             }
-            // 没有可复用的实例,创建新的
-            this._isSharedPhoneBar = false;
-            this.phoneBar = new ccPhoneBarSocket();
-            this.loadExtNumAndInit();
+
+            this.$message.warning('软电话正在连接,请稍候;若长时间未就绪,请刷新页面');
         },
 
         loadExtNumAndInit() {