From d90b45d7e9e38e34c044b772006004f2a4cb8b8e Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期五, 26 六月 2026 13:51:50 +0800
Subject: [PATCH] 测试完成

---
 src/components/CallButton/index.vue |  584 +++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 517 insertions(+), 67 deletions(-)

diff --git a/src/components/CallButton/index.vue b/src/components/CallButton/index.vue
index 57690ea..83186de 100644
--- a/src/components/CallButton/index.vue
+++ b/src/components/CallButton/index.vue
@@ -1,98 +1,481 @@
 <template>
   <div class="call-container">
-    <!-- 鍙风爜杈撳叆 -->
-    <input
-      v-model="phoneNumber"
-      type="text"
-      placeholder="杈撳叆鐢佃瘽鍙风爜"
-      @keyup.enter="startCall"
-    >
+    <div class="sip-status" :class="sipStatusClass">
+      SIP鐘舵��: {{ sipStatus }}
+      <span v-if="reconnectCount > 0" class="reconnect-info">
+        (閲嶈繛: {{ reconnectCount }}娆�)
+      </span>
+    </div>
+
+    <!-- 鐘舵�佹樉绀� -->
+    <div class="call-status" :class="callStatusClass">
+      {{ callStatusText }}
+    </div>
 
     <!-- 鍛煎彨鎸夐挳 -->
     <button
-      :class="['call-btn', { 'calling': isCalling }]"
+      :class="[
+        'call-btn',
+        {
+          calling: isCalling,
+          registering: isRegistering,
+          reconnecting: isReconnecting,
+        },
+      ]"
       @click="startCall"
+      :disabled="isButtonDisabled"
     >
-      {{ isCalling ? '閫氳瘽涓�...' : '涓�閿懠鍙�' }}
+      <i v-if="isRegistering || isReconnecting" class="el-icon-loading"></i>
+      {{ callButtonText }}
+    </button>
+
+    <!-- 鎵嬪姩閲嶈繛鎸夐挳 -->
+    <button
+      v-if="showManualReconnect"
+      class="reconnect-btn"
+      @click="manualReconnect"
+      :disabled="isRegistering"
+    >
+      鎵嬪姩閲嶈繛
     </button>
 
     <!-- 鎸傛柇鎸夐挳 -->
-    <button
-      v-if="isCalling"
-      class="end-call-btn"
-      @click="endCall"
-    >
-      鎸傛柇
-    </button>
+    <button v-if="isCalling" class="end-call-btn" @click="endCall">鎸傛柇</button>
 
     <!-- 闊抽鍏冪礌锛堥殣钘忥級 -->
     <audio id="remoteAudio" autoplay></audio>
-
-    <!-- 鐘舵�佹樉绀� -->
-    <div class="call-status">
-      {{ callStatus }}
-    </div>
   </div>
 </template>
 
 <script>
-import sipService from '@/utils/sipService'
+import sipService from "@/utils/sipService";
+import { CallsetState, CallgetList } from "@/api/AiCentre/index";
 
 export default {
+  props: {
+    phoneNumber: {
+      type: String,
+      default: "",
+    },
+  },
   data() {
+    const randomNum = Math.floor(Math.random() * 20) + 1000;
     return {
-      phoneNumber: '',
       isCalling: false,
-      callStatus: '鍑嗗灏辩华',
+      isRegistering: true,
+      isReconnecting: false, // 娣诲姞閲嶈繛涓姸鎬�
+      randomNum: randomNum,
+      randomID: null,
+      orgname: localStorage.getItem("orgname"),
+      callStatus: "idle",
+      sipStatus: "鏈繛鎺�",
+      sipStatusClass: "status-disconnected",
+      reconnectCount: 0, // 閲嶈繛娆℃暟
+      lastActivityTime: null, // 鏈�鍚庢椿鍔ㄦ椂闂�
+      heartbeatTimer: null, // 蹇冭烦瀹氭椂鍣�
+      reconnectTimer: null, // 閲嶈繛瀹氭椂鍣�
+      maxReconnectAttempts: 5, // 鏈�澶ч噸杩炲皾璇曟鏁�
+      reconnectDelay: 5000, // 閲嶈繛寤惰繜(ms)
       sipConfig: {
-        wsUrl: 'wss://192.168.100.6:7443',
-        sipUri: '1000@192.168.100.6',
-        password: 'Smartor@2023',
-        displayName: 'Web 灏忛緳',
-        realm: '192.168.100.6:8090'
+        wsUrl: "",
+        sipUri: "",
+        password: "Smartor@2023",
+        displayName: "Web 灏忛緳",
+      },
+    };
+  },
+  computed: {
+    callStatusText() {
+      const statusMap = {
+        idle: "鍑嗗灏辩华",
+        calling: "鍛煎彨涓�...",
+        connected: "閫氳瘽涓�",
+        ended: "閫氳瘽缁撴潫",
+      };
+      return statusMap[this.callStatus];
+    },
+    countdownText() {
+      if (this.sipStatus !== "宸叉敞鍐�") return "";
+      const { canCall, reason } = sipService.canMakeCall();
+      if (!canCall && reason.includes("绛夊緟")) {
+        return reason;
       }
+      return "";
+    },
+    isButtonDisabled() {
+      return (
+        this.isCalling ||
+        this.sipStatus !== "宸叉敞鍐�" ||
+        this.isRegistering ||
+        this.isReconnecting
+      );
+    },
+    callButtonText() {
+      if (this.isRegistering) return "娉ㄥ唽涓�...";
+      if (this.isReconnecting) return "閲嶈繛涓�...";
+      return this.isCalling ? "閫氳瘽涓�..." : "涓�閿懠鍙�";
+    },
+    callStatusClass() {
+      return `status-${this.callStatus}`;
+    },
+    showManualReconnect() {
+      return (
+        !this.isCalling &&
+        !this.isRegistering &&
+        this.sipStatus !== "宸叉敞鍐�" &&
+        this.sipStatus !== "杩炴帴涓�"
+      );
+    },
+  },
+  created() {
+    if (
+      this.orgname == "绗竴浜烘皯鍖婚櫌婀栨花闄㈠尯" ||
+      this.orgname == "绗竴浜烘皯鍖婚櫌鍚村北闄㈠尯"
+    ) {
+      this.sipConfig.password = "heskj@1234";
+    } else {
+      this.sipConfig.password = "Smartor@2023";
     }
   },
-  mounted() {
-    // 鍒濆鍖朣IP杩炴帴
-    sipService.init(this.sipConfig)
-  },
-  beforeDestroy() {
-    // 缁勪欢閿�姣佹椂缁撴潫閫氳瘽
-    this.endCall()
+
+  async mounted() {
+    const orgName = localStorage.getItem("orgname");
+    if (orgName == "鏅畞鐣叉棌鑷不鍘夸汉姘戝尰闄�") {
+      return;
+    }
+    await this.CallgetList();
+    this.isRegistering = true;
+    this.initSipService();
+    this.setupHeartbeat();
   },
   methods: {
-    // 寮�濮嬪懠鍙�
-    async startCall() {
-      if (!this.phoneNumber) {
-        this.callStatus = '璇疯緭鍏ョ數璇濆彿鐮�'
-        return
-      }
+    async initSipService() {
       try {
-        this.isCalling = true
-        this.callStatus = '鍛煎彨涓�...'
-        // 璋冪敤SIP鏈嶅姟
-        sipService.makeCall(this.phoneNumber)
+        // 鍒濆鍖杝ipService
+        sipService.init(this.sipConfig);
 
-        this.callStatus = '閫氳瘽宸插缓绔�'
+        // 璁剧疆鐘舵�佸洖璋�
+        sipService.onStatusChange = (status) => {
+          this.sipStatus = status.text;
+          this.sipStatusClass = `status-${status.type}`;
+
+          // 澶勭悊鍚勭鐘舵��
+          if (status.type === "registered") {
+            this.handleRegistered();
+          } else if (
+            status.type === "failed" ||
+            status.type === "disconnected"
+          ) {
+            this.handleDisconnected();
+          } else if (status.type === "connecting") {
+            this.handleConnecting();
+          }
+        };
+
+        // 鐩戝惉閫氳瘽鐘舵�佸彉鍖�
+        sipService.onCallStatusChange = (status) => {
+          this.callStatus = status.type;
+          this.isCalling =
+            status.type === "calling" || status.type === "connected";
+          this.updateLastActivityTime(); // 閫氳瘽鐘舵�佸彉鍖栨椂鏇存柊娲诲姩鏃堕棿
+
+          this.$emit("call-status-change", status);
+        };
+
+        // 璁剧疆瓒呮椂澶勭悊
+        this.setupRegistrationTimeout();
       } catch (error) {
-        console.error('鍛煎彨澶辫触:', error)
-        this.callStatus = `鍛煎彨澶辫触: ${error.message}`
-        this.isCalling = false
+        console.error("SIP鏈嶅姟鍒濆鍖栧け璐�:", error);
+        this.handleDisconnected();
       }
     },
 
-    // 缁撴潫閫氳瘽
+    handleRegistered() {
+      console.log("SIP娉ㄥ唽鎴愬姛");
+      this.isRegistering = false;
+      this.isReconnecting = false;
+      this.reconnectCount = 0; // 閲嶇疆閲嶈繛璁℃暟
+      this.updateLastActivityTime();
+      this.startCallsetState();
+
+      // 娓呴櫎閲嶈繛瀹氭椂鍣�
+      if (this.reconnectTimer) {
+        clearTimeout(this.reconnectTimer);
+        this.reconnectTimer = null;
+      }
+    },
+
+    handleDisconnected() {
+      console.log("SIP杩炴帴鏂紑");
+      this.isRegistering = false;
+      this.overCallsetState();
+
+      // 濡傛灉涓嶆槸閫氳瘽涓柇寮�锛屽皾璇曢噸杩�
+      if (!this.isCalling) {
+        this.scheduleReconnect();
+      }
+    },
+
+    handleConnecting() {
+      this.isReconnecting = true;
+    },
+
+    setupRegistrationTimeout() {
+      setTimeout(() => {
+        if (this.isRegistering && this.sipStatus !== "宸叉敞鍐�") {
+          this.isRegistering = false;
+          this.$message.warning("SIP娉ㄥ唽瓒呮椂锛屾鍦ㄥ皾璇曢噸杩�...");
+          this.scheduleReconnect();
+        }
+      }, 10000);
+    },
+
+    // 璁剧疆蹇冭烦妫�娴�
+    setupHeartbeat() {
+      this.heartbeatTimer = setInterval(() => {
+        this.checkConnection();
+      }, 60000); // 姣�30绉掓鏌ヤ竴娆¤繛鎺�
+    },
+
+    // 妫�鏌ヨ繛鎺ョ姸鎬�
+    async checkConnection() {
+      // 濡傛灉姝e湪娉ㄥ唽銆侀噸杩炴垨閫氳瘽涓紝涓嶆鏌�
+      if (this.isRegistering || this.isReconnecting || this.isCalling) {
+        return;
+      }
+
+      // 妫�鏌IP杩炴帴鐘舵��
+      if (sipService && sipService.ua) {
+        const isConnected = sipService.ua.isConnected();
+        const isRegistered = sipService.ua.isRegistered();
+
+        if (!isConnected || !isRegistered) {
+          console.log("蹇冭烦妫�娴�: 杩炴帴寮傚父锛屽皾璇曢噸杩�");
+          await this.reconnectSip();
+        } else {
+          console.log("蹇冭烦妫�娴�: 杩炴帴姝e父");
+          this.updateLastActivityTime();
+        }
+      } else {
+        console.log("蹇冭烦妫�娴�: UA涓嶅瓨鍦紝灏濊瘯閲嶆柊鍒濆鍖�");
+        await this.reconnectSip();
+      }
+    },
+
+    // 璁″垝閲嶈繛
+    scheduleReconnect() {
+      if (this.reconnectTimer || this.isReconnecting) {
+        return;
+      }
+
+      if (this.reconnectCount >= this.maxReconnectAttempts) {
+        console.log("杈惧埌鏈�澶ч噸杩炴鏁帮紝鍋滄閲嶈繛");
+        this.$message.error("SIP杩炴帴澶辫触锛岃鍒锋柊椤甸潰閲嶈瘯");
+        return;
+      }
+
+      this.reconnectCount++;
+      const delay = Math.min(
+        this.reconnectDelay * Math.pow(1.5, this.reconnectCount - 1),
+        30000
+      );
+
+      console.log(`璁″垝鍦�${delay}ms鍚庨噸杩烇紝绗�${this.reconnectCount}娆″皾璇昤);
+
+      this.reconnectTimer = setTimeout(() => {
+        this.reconnectSip();
+      }, delay);
+    },
+
+    // 閲嶆柊杩炴帴SIP
+    async reconnectSip() {
+      if (this.isReconnecting || this.isRegistering) {
+        return;
+      }
+
+      console.log("寮�濮嬮噸杩濻IP鏈嶅姟...");
+      this.isReconnecting = true;
+
+      try {
+        // 娓呯悊鐜版湁杩炴帴
+        this.cleanupSipConnection();
+
+        // 绛夊緟涓�娈垫椂闂�
+        await new Promise((resolve) => setTimeout(resolve, 1000));
+
+        // 閲嶆柊鍒濆鍖�
+        await this.CallgetList(); // 閲嶆柊鑾峰彇鍒嗘満鍙�
+        this.initSipService();
+      } catch (error) {
+        console.error("閲嶈繛澶辫触:", error);
+        this.isReconnecting = false;
+        this.scheduleReconnect(); // 澶辫触鍚庣户缁噸璇�
+      } finally {
+        if (this.reconnectTimer) {
+          clearTimeout(this.reconnectTimer);
+          this.reconnectTimer = null;
+        }
+      }
+    },
+
+    // 娓呯悊SIP杩炴帴
+    cleanupSipConnection() {
+      if (sipService && sipService.ua) {
+        try {
+          sipService.ua.stop();
+          sipService.ua.unregister();
+        } catch (e) {
+          console.warn("娓呯悊SIP杩炴帴鏃跺嚭閿�:", e);
+        }
+      }
+    },
+
+    // 鎵嬪姩閲嶈繛
+    async manualReconnect() {
+      this.reconnectCount = 0; // 閲嶇疆閲嶈繛璁℃暟
+      await this.reconnectSip();
+    },
+
+    // 鏇存柊鏈�鍚庢椿鍔ㄦ椂闂�
+    updateLastActivityTime() {
+      this.lastActivityTime = Date.now();
+    },
+
+    async startCall() {
+      if (!this.phoneNumber) {
+        this.$message.warning("璇疯緭鍏ョ數璇濆彿鐮�");
+        return;
+      }
+
+      try {
+        // 鍏堟鏌ユ槸鍚﹀彲浠ュ懠鍙�
+        const { canCall, reason } = sipService.canMakeCall();
+        if (!canCall) {
+          // 鍙��: 鍙互鏄剧ず鎻愮ず
+        }
+
+        this.callStatus = "calling";
+        this.isCalling = true;
+        console.log("寮�濮嬪懠鍙細", sipService);
+
+        await sipService.makeCall(this.phoneNumber);
+      } catch (error) {
+        console.error("鍛煎彨澶辫触1:", error);
+
+        if (
+          error.message.includes("Canceled") ||
+          error.message.includes("鏈敞鍐�")
+        ) {
+          console.warn("鍛煎彨鍥犻〉闈㈢寮�鎴栨湭娉ㄥ唽鑰屽彇娑堬紝涓嶉噸璇�");
+          this.callStatus = "ended";
+          this.isCalling = false;
+          return;
+        }
+        try {
+          // 灏濊瘯鍔�0鍐嶆鍛煎彨
+          const { canCall, reason } = sipService.canMakeCall();
+          if (!canCall) {
+            // 鍙�夊鐞�
+          }
+          this.callStatus = "calling";
+          this.isCalling = true;
+          console.log("灏濊瘯鍔�0鍐嶆鍛煎彨锛�", sipService);
+
+          await sipService.makeCall("0" + this.phoneNumber);
+        } catch (error) {
+          this.callStatus = "ended";
+          this.isCalling = false;
+          this.$message.warning("褰撳墠鍛煎彨鍗犵嚎涓紝璇风◢鍚庡啀鎷�");
+        }
+      }
+    },
+
+    // 鏌ヨ鍙敤鍒嗘満鍙�
+    async CallgetList() {
+      try {
+        const res = await CallgetList();
+        this.randomNum = res.data[0].tel;
+        this.randomID = res.data[0].id;
+
+        const orgName = localStorage.getItem("orgname");
+        if (orgName == "涓芥按甯備腑鍖婚櫌") {
+          this.sipConfig.sipUri = `${this.randomNum}@192.168.10.124`;
+        } else if (orgName == "榫欐硥甯備汉姘戝尰闄�") {
+          this.sipConfig.sipUri = `${this.randomNum}@10.10.0.220`;
+        } else if (
+          orgName == "绗竴浜烘皯鍖婚櫌婀栨花闄㈠尯" ||
+          orgName == "绗竴浜烘皯鍖婚櫌鍚村北闄㈠尯"
+        ) {
+          this.sipConfig.sipUri = `${this.randomNum}@192.169.129.198`;
+        }
+      } catch (error) {
+        console.error("鑾峰彇鍒嗘満鍙峰け璐�:", error);
+        throw error; // 鎶涘嚭閿欒浠ヤ究涓婂眰澶勭悊
+      }
+    },
+
+    async startCallsetState() {
+      try {
+        await CallsetState({ id: this.randomID, state: 1 });
+        console.log("鍒嗘満鍙风姸鎬佹洿鏂颁负浣跨敤涓�");
+      } catch (error) {
+        console.error("鏇存柊鍒嗘満鍙风姸鎬佸け璐�:", error);
+      }
+    },
+
+    async overCallsetState() {
+      try {
+        if (this.randomID) {
+          await CallsetState({ id: this.randomID, state: 0 });
+          console.log("鍒嗘満鍙风姸鎬佹洿鏂颁负鍙敤");
+        }
+      } catch (error) {
+        console.error("閲婃斁鍒嗘満鍙峰け璐�:", error);
+      }
+    },
+
     endCall() {
-      sipService.endCall()
-      this.isCalling = false
-      this.callStatus = '閫氳瘽宸茬粨鏉�'
+      sipService.endCall();
+      this.callStatus = "ended";
+      this.isCalling = false;
+    },
+
+    cleanupResources() {
+      // 娓呴櫎鎵�鏈夊畾鏃跺櫒
+      if (this.heartbeatTimer) {
+        clearInterval(this.heartbeatTimer);
+        this.heartbeatTimer = null;
+      }
+
+      if (this.reconnectTimer) {
+        clearTimeout(this.reconnectTimer);
+        this.reconnectTimer = null;
+      }
+
+      // 缁撴潫閫氳瘽
+      if (this.isCalling) {
+        sipService.endCall();
+      }
+
+      // 閲婃斁鍒嗘満鍙�
+      this.overCallsetState();
+
+      // 鏂紑 SIP 杩炴帴
+      this.cleanupSipConnection();
+    },
+  },
+  beforeUnmount() {
+    if (this.isCalling) {
+      this.endCall(); // 鍐呴儴璁剧疆浜� isManualEnd
     }
-  }
-}
+    // 鍏朵粬娓呯悊锛堝瀹氭椂鍣級...
+    this.cleanupResources(); // 浣嗘敞鎰忎笉瑕侀噸澶嶆竻鐞嗗畾鏃跺櫒锛屽彲浼樺寲鍒ゆ柇
+  },
+};
 </script>
 
 <style scoped>
+/* 淇濇寔鍘熸湁鏍峰紡涓嶅彉锛屽彧娣诲姞鏂版牱寮� */
 .call-container {
   display: flex;
   flex-direction: column;
@@ -104,27 +487,51 @@
   border-radius: 8px;
 }
 
-input {
-  padding: 8px;
-  border: 1px solid #ccc;
-  border-radius: 4px;
+.reconnect-info {
+  font-size: 12px;
+  color: #666;
+  margin-left: 5px;
 }
 
-.call-btn {
-  padding: 10px;
-  background-color: #4CAF50;
+.reconnect-btn {
+  padding: 8px 12px;
+  background-color: #ff9800;
   color: white;
   border: none;
   border-radius: 4px;
   cursor: pointer;
+  font-size: 12px;
 }
 
-.call-btn:hover {
+.reconnect-btn:hover:not(:disabled) {
+  background-color: #f57c00;
+}
+
+.reconnect-btn:disabled {
+  background-color: #cccccc;
+  cursor: not-allowed;
+}
+
+.call-btn.reconnecting {
+  background-color: #ff9800;
+}
+
+.call-btn:hover:not(:disabled) {
   background-color: #45a049;
 }
 
+.call-btn:disabled {
+  background-color: #cccccc;
+  cursor: not-allowed;
+}
+
 .call-btn.calling {
-  background-color: #2196F3;
+  background-color: #2196f3;
+}
+
+.call-btn.registering,
+.call-btn.reconnecting {
+  position: relative;
 }
 
 .end-call-btn {
@@ -140,9 +547,52 @@
   background-color: #d32f2f;
 }
 
+/* 鐘舵�佹牱寮忎繚鎸佷笉鍙� */
+.sip-status,
 .call-status {
-  margin-top: 10px;
-  font-size: 14px;
+  padding: 8px;
+  margin-bottom: 10px;
+  border-radius: 4px;
+  text-align: center;
+}
+
+.status-disconnected {
+  background-color: #ffebee;
+  color: #c62828;
+}
+
+.status-connecting {
+  background-color: #fff8e1;
+  color: #ff8f00;
+}
+
+.status-registered {
+  background-color: #e8f5e9;
+  color: #2e7d32;
+}
+
+.status-failed {
+  background-color: #ffebee;
+  color: #c62828;
+}
+
+.status-idle {
+  background-color: #f5f5f5;
   color: #666;
 }
+
+.status-calling {
+  background-color: #fff8e1;
+  color: #ff8f00;
+}
+
+.status-connected {
+  background-color: #e8f5e9;
+  color: #2e7d32;
+}
+
+.status-ended {
+  background-color: #ffebee;
+  color: #c62828;
+}
 </style>

--
Gitblit v1.9.3