From 1aaa2392eea4694076c6a329a5b88436ceb50194 Mon Sep 17 00:00:00 2001
From: WXL (wul) <wl_5969728@163.com>
Date: 星期三, 17 九月 2025 15:13:48 +0800
Subject: [PATCH] 电话更新

---
 src/api/AiCentre/index.js                         |   28 
 src/utils/sipService-cs.js                        |  201 ++++++++++++
 /dev/null                                         |  253 ---------------
 src/api/AiCentre/phoneCall.js                     |   18 +
 src/views/followvisit/record/detailpage/index.vue |   31 +
 src/utils/sipService.js                           |  317 +++++++++++--------
 src/components/CallButton/index.vue               |   80 ++++
 7 files changed, 512 insertions(+), 416 deletions(-)

diff --git a/src/api/AiCentre/index.js b/src/api/AiCentre/index.js
index 60f658f..9af0b74 100644
--- a/src/api/AiCentre/index.js
+++ b/src/api/AiCentre/index.js
@@ -1,14 +1,14 @@
-export * from './indicator'
-export * from './Problemspeaking'
-export * from './Followup'
-export * from './general'
-export * from './publicity'
-export * from './Qtemplate'
-export * from './questionnaire'
-export * from './SingleTask'
-export * from './external'
-export * from './patientexternal'
-export * from './EChartsdata'
-export * from './satisfactionse'
-export * from './satisfaction'
-
+export * from "./indicator";
+export * from "./Problemspeaking";
+export * from "./Followup";
+export * from "./general";
+export * from "./publicity";
+export * from "./Qtemplate";
+export * from "./questionnaire";
+export * from "./SingleTask";
+export * from "./external";
+export * from "./patientexternal";
+export * from "./EChartsdata";
+export * from "./satisfactionse";
+export * from "./satisfaction";
+export * from "./phoneCall";
diff --git a/src/api/AiCentre/phoneCall.js b/src/api/AiCentre/phoneCall.js
new file mode 100644
index 0000000..84769dc
--- /dev/null
+++ b/src/api/AiCentre/phoneCall.js
@@ -0,0 +1,18 @@
+import request from "@/utils/request";
+
+
+// 鍒犻櫎澶栭儴鎮h�呰〃
+export function CallgetList() {
+  return request({
+    url: "/smartor/ServiceTelInfo/getList",
+    method: "get",
+  });
+}
+// 鏌ヨ澶栭儴鎮h�呰〃
+export function CallsetState(data) {
+  return request({
+    url: "/smartor/ServiceTelInfo/setState",
+    method: "get",
+    params: data,
+  });
+}
diff --git a/src/components/CallButton/index.vue b/src/components/CallButton/index.vue
index 429a327..d3cb005 100644
--- a/src/components/CallButton/index.vue
+++ b/src/components/CallButton/index.vue
@@ -28,6 +28,7 @@
 
 <script>
 import sipService from "@/utils/sipService";
+import { CallsetState, CallgetList } from "@/api/AiCentre/index";
 
 export default {
   props: {
@@ -37,15 +38,17 @@
     },
   },
   data() {
-    const randomNum = Math.floor(Math.random() * 20) + 1000; // 鍐呴儴瀹氫箟
+    const randomNum = Math.floor(Math.random() * 20) + 1000; // 瀹氫箟闅忔満鍒嗘満鍙�
     return {
       isCalling: false,
+      randomNum: randomNum,
+      randomID: null,
       callStatus: "idle", // idle, calling, connected, ended
       sipStatus: "鏈繛鎺�",
       sipStatusClass: "status-disconnected",
       sipConfig: {
         wsUrl: "wss://192.168.100.6:7443",
-        sipUri: `${randomNum}` + "@192.168.100.6",
+        sipUri: "",
         password: "Smartor@2023",
         displayName: "Web 灏忛緳",
         // realm: "9.208.5.18:8090",
@@ -78,13 +81,22 @@
       return this.isCalling ? "閫氳瘽涓�..." : "涓�閿懠鍙�";
     },
   },
-  mounted() {
-    console.log('褰撳墠鍒嗘満鍙�',this.sipConfig);
+  created() {
+    // CallgetList();
+  },
 
+  async mounted() {
+    await this.CallgetList();
     sipService.init(this.sipConfig);
+    // 璁剧疆鐘舵�佸洖璋�
     sipService.onStatusChange = (status) => {
       this.sipStatus = status.text;
       this.sipStatusClass = `status-${status.type}`;
+
+      // 澶勭悊娉ㄥ唽澶辫触鍜屾柇寮�杩炴帴鎯呭喌
+      if (status.type === "failed" || status.type === "disconnected") {
+        this.overCallsetState(); // 閲婃斁鍒嗘満鍙�
+      }
     };
 
     // 鐩戝惉閫氳瘽鐘舵�佸彉鍖�
@@ -115,7 +127,7 @@
         this.isCalling = true;
         console.log("寮�濮嬪懠鍙細", sipService);
 
-        await sipService.makeCall("0"+this.phoneNumber);
+        await sipService.makeCall("0" + this.phoneNumber);
       } catch (error) {
         let registrationTime = Date.now(); // 璁板綍娉ㄩ攢鎴愬姛鏃堕棿
         console.log(registrationTime, "鍛煎彨澶辫触鏃堕棿");
@@ -123,7 +135,7 @@
         // this.callStatus = "ended";
         // this.isCalling = false;
         //this.$message.error(`鍛煎彨澶辫触: ${error.message}`);
-          try {
+        try {
           // 鍏堟鏌ユ槸鍚﹀彲浠ュ懠鍙�
           const { canCall, reason } = sipService.canMakeCall();
           if (!canCall) {
@@ -133,19 +145,69 @@
           this.isCalling = true;
           console.log("寮�濮嬪懠鍙細", sipService);
 
-          await sipService.makeCall("0"+this.phoneNumber);
+          await sipService.makeCall("0" + this.phoneNumber);
         } catch (error) {
-           this.callStatus = "ended";
-           this.isCalling = false;
+          this.callStatus = "ended";
+          this.isCalling = false;
         }
       }
     },
+    // 鏌ヨ鍙敤鍒嗘満鍙�
+    async CallgetList() {
+      try {
+        const res = await CallgetList();
+        this.randomNum = res.data[0].tel;
+        this.randomID = res.data[0].id;
+        // 姝g‘璁剧疆 sipUri
+        this.sipConfig.sipUri = `${this.randomNum}@192.168.100.6`;
+        this.startCallsetState();
+      } catch (error) {
+        console.error("鑾峰彇鍒嗘満鍙峰け璐�:", error);
+        this.updateStatus("failed", "鑾峰彇鍒嗘満鍙峰け璐�");
+      }
+    },
+    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.callStatus = "ended";
       this.isCalling = false;
     },
+    cleanupResources() {
+      // 缁撴潫閫氳瘽
+      if (this.isCalling) {
+        sipService.endCall();
+      }
+
+      // 閲婃斁鍒嗘満鍙�
+      this.overCallsetState();
+
+      // 鏂紑 SIP 杩炴帴
+      if (sipService.ua) {
+        sipService.ua.stop();
+      }
+    },
+  },
+  beforeUnmount() {
+    // 缁勪欢閿�姣佹椂纭繚閲婃斁璧勬簮
+    this.cleanupResources();
   },
 };
 </script>
diff --git a/src/utils/sipService-cs.js b/src/utils/sipService-cs.js
new file mode 100644
index 0000000..7c63647
--- /dev/null
+++ b/src/utils/sipService-cs.js
@@ -0,0 +1,201 @@
+import JsSIP from "jssip";
+
+class SipService {
+  constructor() {
+    this.ua = null;
+    this.currentSession = null;
+    this.onStatusChange = null; // 鐘舵�佸彉鍖栧洖璋�
+    this.onCallStatusChange = null; // 鏂板閫氳瘽鐘舵�佸洖璋�
+  }
+
+  // 鍒濆鍖朣IP瀹㈡埛绔�
+  init(config) {
+    try {
+      this.updateStatus("connecting", "杩炴帴涓�...");
+console.log(config);
+
+      this.ua = new JsSIP.UA({
+        sockets: [new JsSIP.WebSocketInterface(config.wsUrl)],
+        uri: config.sipUri,
+        password: config.password,
+        display_name: config.displayName,
+        iceservers: [],
+        // realm: config.realm,
+        register: true,
+        session_expires: 180,
+        sessionTimersExpires: 300, // 璁剧疆 Session-Expires=120锛堝繀椤� >= Min-SE锛�
+        extraHeaders: [
+          "Min-SE: 120", // 鍙�夛細鏄惧紡鍛婅瘔鏈嶅姟鍣ㄤ綘鏀寔鐨勬渶灏忓��
+        ],
+        register_expires: 300, // 娉ㄥ唽鏈夋晥鏈�(绉�)
+        connection_recovery_min_interval: 2, // 鏈�灏忛噸杩為棿闅�
+        connection_recovery_max_interval: 30, // 鏈�澶ч噸杩為棿闅�
+      });
+
+      this.ua.start();
+
+      // 娉ㄥ唽浜嬩欢鐩戝惉
+      this.ua.on("registered", () => {
+        this.updateStatus("registered", "宸叉敞鍐�");
+      });
+
+      this.ua.on("registrationFailed", (e) => {
+        this.updateStatus("failed", `娉ㄥ唽澶辫触: ${e.cause}`);
+      });
+
+      this.ua.on("disconnected", () => {
+        this.updateStatus("disconnected", "杩炴帴鏂紑");
+      });
+
+      this.ua.on("connected", () => {
+        this.updateStatus("connecting", "閲嶆柊杩炴帴涓�...");
+      });
+
+      // 鐩戝惉鏉ョ數
+      this.ua.on("newRTCSession", (data) => {
+        this.handleIncomingCall(data.session);
+      });
+    } catch (error) {
+      this.updateStatus("failed", `鍒濆鍖栧け璐�: ${error.message}`);
+      console.error("SIP鍒濆鍖栧け璐�:", error);
+    }
+  }
+  handleIncomingCall(session) {
+    if (session.direction === "incoming") {
+      console.log("鏉ョ數:", session.remote_identity.uri.toString());
+      // 鍙互鍦ㄨ繖閲岃Е鍙� UI 閫氱煡
+      if (this.onIncomingCall) {
+        this.onIncomingCall(session);
+      }
+    }
+  }
+
+  // 鏇存柊鐘舵�佸苟閫氱煡UI
+  updateStatus(type, text) {
+    console.log(`SIP鐘舵�佹洿鏂�: ${type} - ${text}`);
+    if (this.onStatusChange) {
+      this.onStatusChange({ type, text });
+    }
+  }
+
+  // 涓�閿嫧鍙� - 澧炲姞娉ㄥ唽鐘舵�佹鏌�
+  makeCall(targetNumber) {
+    if (!this.ua) {
+      throw new Error("SIP瀹㈡埛绔湭鍒濆鍖�");
+    }
+
+    if (!this.ua.isRegistered()) {
+      throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨");
+    }
+
+    const options = {
+      sessionTimers: true,
+      sessionTimersExpires: 300,
+      extraHeaders: [
+        "Min-SE: 120",
+        "Route: <sip:@192.168.100.6>",
+        "Accept: application/sdp",
+        "Supported: replaces, timer",
+        "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS",
+      ],
+      eventHandlers: {
+        progress: (e) => {
+          this.updateCallStatus("calling", "鍛煎彨涓�...");
+        },
+        failed: (e) => {
+          this.updateCallStatus("ended", `鍛煎彨澶辫触: ${e.cause}`);
+        },
+        ended: (e) => {
+          this.updateCallStatus("ended", "閫氳瘽缁撴潫");
+        },
+        confirmed: (e) => {
+          this.updateCallStatus("connected", "閫氳瘽宸叉帴閫�");
+        },
+      },
+      mediaConstraints: {
+        audio: true,
+        video: false,
+      },
+      rtcOfferConstraints: {
+        offerToReceiveAudio: 1,
+        offerToReceiveVideo: 0,
+        mandatory: {
+          OfferToReceiveAudio: true,
+          OfferToReceiveVideo: false,
+        },
+      },
+      pcConfig: {
+        iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
+        iceTransportPolicy: "all",
+        bundlePolicy: "balanced",
+        rtcpMuxPolicy: "require",
+        codecs: {
+          audio: [
+            { name: "PCMU", clockRate: 8000, payloadType: 0 },
+            { name: "PCMA", clockRate: 8000, payloadType: 8 },
+          ],
+          video: [],
+        },
+      },
+    };
+
+    this.currentSession = this.ua.call(
+      `sip:${targetNumber}@192.168.100.6`,
+      options
+    );
+    // 鍦ㄤ細璇濆垱寤哄悗淇敼 SDP
+    this.currentSession.on("peerconnection", (pc) => {
+       this.updateCallStatus('calling', '鍛煎彨涓�...');
+      pc.createOffer = (offerOptions) => {
+        return RTCPeerConnection.prototype.createOffer
+          .call(pc, offerOptions)
+          .then((offer) => {
+            const modifiedSdp = offer.sdp
+              .replace(/c=IN IP4 192\.168\.100\.10/g, "c=IN IP4 192.168.100.6")
+              .replace(/m=audio \d+ RTP\/AVP.*/, "m=audio 7078 RTP/AVP 0 8");
+            return new RTCSessionDescription({
+              type: "offer",
+              sdp: modifiedSdp,
+            });
+          });
+      };
+    });
+    this.currentSession.on('failed', (e) => {
+        this.updateCallStatus('failed', `鍛煎彨澶辫触2: ${e}`);
+      });
+
+      this.currentSession.on('ended', () => {
+        this.updateCallStatus('ended', '閫氳瘽宸茬粨鏉�');
+      });
+
+      this.currentSession.on('confirmed', () => {
+        this.updateCallStatus('connected', '閫氳瘽宸叉帴閫�');
+      });
+    this.setupAudio(this.currentSession);
+  }
+  setupAudio(session) {
+    session.connection.addEventListener("addstream", (e) => {
+      const audioElement = document.getElementById("remoteAudio");
+      if (audioElement) {
+        audioElement.srcObject = e.stream;
+      }
+    });
+  }
+  // 鎸傛柇褰撳墠閫氳瘽
+  endCall() {
+  if (this.currentSession) {
+      this.currentSession.terminate();
+      this.updateCallStatus('ended', '閫氳瘽宸茬粨鏉�');
+      this.currentSession = null;
+    }
+  }
+  // 鏂板鏂规硶锛氭洿鏂伴�氳瘽鐘舵��
+  updateCallStatus(type, text) {
+    console.log(`閫氳瘽鐘舵�佹洿鏂�: ${type} - ${text}`);
+    if (this.onCallStatusChange) {
+      this.onCallStatusChange({ type, text });
+    }
+  }
+}
+
+export default new SipService();
diff --git a/src/utils/sipService-zs.js b/src/utils/sipService-zs.js
deleted file mode 100644
index dbc4a4e..0000000
--- a/src/utils/sipService-zs.js
+++ /dev/null
@@ -1,253 +0,0 @@
-import JsSIP from "jssip";
-
-class SipService {
-  constructor() {
-    this.ua = null;
-    this.currentSession = null;
-    this.onStatusChange = null;
-    this.onCallStatusChange = null;
-    this.onIncomingCall = null;
-    this.isRegistered = false; // 鏂板娉ㄥ唽鐘舵�佹爣蹇�
-    this.registrationTime = null; // 鏂板娉ㄥ唽鎴愬姛鏃堕棿鎴�
-  }
-
-  init(config) {
-    try {
-      this.updateStatus("connecting", "杩炴帴涓�;...");
-
-      this.ua = new JsSIP.UA({
-        sockets: [new JsSIP.WebSocketInterface(config.wsUrl)],
-        uri: config.sipUri,
-        password: config.password,
-        display_name: config.displayName,
-        iceServers: [],
-        register: true,
-        sessionExpires: 1800,
-        minSessionExpires: 90,
-        register_expires: 300,
-      });
-
-      this.ua.start();
-
-      // 浜嬩欢鐩戝惉
-      this.ua.on("registered", () => {
-        this.isRegistered = true;
-        this.registrationTime = Date.now(); // 璁板綍娉ㄥ唽鎴愬姛鏃堕棿
-        console.log(this.registrationTime, "娉ㄥ唽鏃堕棿");
-
-        this.updateStatus("registered", "宸叉敞鍐�");
-      });
-
-      this.ua.on("registrationFailed", (e) => {
-        this.isRegistered = false;
-        this.updateStatus("failed", `娉ㄥ唽澶辫触: ${e.cause}`);
-      });
-
-      this.ua.on("unregistered", () => {
-        this.isRegistered = false;
-        let registrationTime = Date.now(); // 璁板綍娉ㄩ攢鎴愬姛鏃堕棿
-        console.log(registrationTime, "娉ㄩ攢鏃堕棿");
-        this.updateStatus("disconnected", "宸叉敞閿�");
-      });
-      this.ua.on("disconnected", () =>
-        this.updateStatus("disconnected", "杩炴帴鏂紑")
-      );
-      this.ua.on("connected", () =>
-        this.updateStatus("connecting", "閲嶆柊杩炴帴涓�...")
-      );
-      this.ua.on("newRTCSession", (data) =>
-        this.handleIncomingCall(data.session)
-      );
-    } catch (error) {
-      this.updateStatus("failed", `鍒濆鍖栧け璐�: ${error.message}`);
-      console.error("SIP鍒濆鍖栧け璐�:", error);
-      throw error;
-    }
-  }
-  // 鏂板鏂规硶锛氭鏌ユ槸鍚﹀彲浠ュ懠鍙�
-  canMakeCall(minDelay = 2000) {
-    if (!this.isRegistered) {
-      return { canCall: false, reason: "SIP鏈敞鍐岋紝鏃犳硶鍛煎彨" };
-    }
-
-    const now = Date.now();
-    const timeSinceRegistration = now - this.registrationTime;
-
-    if (timeSinceRegistration < minDelay) {
-      const remaining = minDelay - timeSinceRegistration;
-      return {
-        canCall: false,
-        reason: `娉ㄥ唽鎴愬姛锛岃绛夊緟 ${Math.ceil(remaining / 1000)} 绉掑悗鍐嶅懠鍙玚,
-      };
-    }
-
-    return { canCall: true, reason: "" };
-  }
-  makeCall(targetNumber) {
-    const { canCall, reason } = this.canMakeCall();
-    if (!canCall) {
-      return Promise.reject(new Error(reason));
-    }
-    return new Promise((resolve, reject) => {
-      try {
-        if (!this.ua) {
-          throw new Error("SIP瀹㈡埛绔湭鍒濆鍖�");
-        }
-
-        if (!this.ua.isRegistered()) {
-          throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨");
-        }
-
-        const options = {
-          sessionTimers: true, // 鍚敤浼氳瘽璁℃椂鍣�
-          sessionTimersExpires: 150,
-          extraHeaders: ["Accept: application/sdp"],
-          mediaConstraints: { audio: true, video: false },
-          rtcOfferConstraints: {
-            offerToReceiveAudio: true,
-            offerToReceiveVideo: false,
-          },
-          eventHandlers: {
-            progress: () => this.updateCallStatus("calling", "鍛煎彨涓�..."),
-            failed: (e) => {
-              this.handleCallFailure(e, reject);
-            },
-            ended: () => this.updateCallStatus("ended", "閫氳瘽缁撴潫"),
-            confirmed: () => {
-              this.updateCallStatus("connected", "閫氳瘽宸叉帴閫�");
-              resolve();
-            },
-          },
-        };
-
-        this.currentSession = this.ua.call(
-          `sip:${targetNumber}@1192.170.66.107`,
-          options
-        );
-
-        this.setupPeerConnection(this.currentSession);
-        this.setupAudio(this.currentSession);
-      } catch (error) {
-        this.updateCallStatus("failed", `鍛煎彨澶辫触22: ${error.message}`);
-        reject(error);
-      }
-    });
-  }
-
-  setupPeerConnection(session) {
-    session.on("peerconnection", (pc) => {
-      const originalCreateOffer = pc.createOffer.bind(pc);
-
-      pc.createOffer = async (offerOptions) => {
-        try {
-          const offer = await originalCreateOffer(offerOptions);
-          return this.normalizeSDP(offer);
-        } catch (error) {
-          console.error("鍒涘缓Offer澶辫触:", error);
-          throw error;
-        }
-      };
-    });
-  }
-
-  normalizeSDP(offer) {
-    let sdp = offer.sdp;
-
-    // 鏍囧噯鍖朣DP
-    sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
-    sdp = sdp.replace(
-      /m=audio \d+.*\r\n/,
-      "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
-    );
-
-    // 纭繚鍖呭惈鍩烘湰缂栬В鐮佸櫒
-    if (!sdp.includes("PCMU/8000")) sdp += "a=rtpmap:0 PCMU/8000\r\n";
-    if (!sdp.includes("PCMA/8000")) sdp += "a=rtpmap:8 PCMA/8000\r\n";
-
-    // 娣诲姞蹇呰灞炴��
-    sdp += "a=rtcp-mux\r\n";
-    sdp += "a=sendrecv\r\n";
-
-    console.log("鏍囧噯鍖栧悗鐨凷DP:", sdp);
-    return new RTCSessionDescription({
-      type: offer.type,
-      sdp: sdp,
-    });
-  }
-
-  handleCallFailure(e, reject) {
-    if (e.response?.status_code === 422) {
-      const serverMinSE = e.response.headers["Min-SE"]?.[0]?.raw || "鏈煡";
-      console.error(`鏈嶅姟鍣ㄨ姹� Min-SE 鈮� ${serverMinSE}锛屽綋鍓嶈缃�: 120`);
-    }
-    console.error("鍛煎彨澶辫触璇︽儏:", {
-      cause: e.cause,
-      message: e.message,
-      response: e.response && {
-        status: e.response.status_code,
-        reason: e.response.reason_phrase,
-      },
-    });
-
-    let errorMessage = "鍛煎彨澶辫触";
-    switch (e.cause) {
-      case "Incompatible SDP":
-        errorMessage = "濯掍綋鍗忓晢澶辫触锛岃妫�鏌ョ紪瑙g爜鍣ㄩ厤缃�";
-        break;
-      case "488":
-      case "606":
-        errorMessage = "瀵规柟璁惧涓嶆敮鎸佸綋鍓嶅獟浣撻厤缃�";
-        break;
-      case "422":
-        errorMessage = "浼氳瘽鍙傛暟涓嶆弧瓒虫湇鍔″櫒瑕佹眰";
-        break;
-      default:
-        errorMessage = `鍛煎彨澶辫触3: ${e.cause || e.message}`;
-    }
-
-    this.updateCallStatus("failed55", errorMessage);
-    reject(new Error(errorMessage));
-  }
-
-  setupAudio(session) {
-    session.connection.addEventListener("addstream", (e) => {
-      const audioElement = document.getElementById("remoteAudio");
-      if (audioElement) {
-        audioElement.srcObject = e.stream;
-      }
-    });
-  }
-
-  endCall() {
-    if (this.currentSession) {
-      this.currentSession.terminate();
-      this.updateCallStatus("ended", "閫氳瘽宸茬粨鏉�");
-      this.currentSession = null;
-    }
-  }
-
-  updateStatus(type, text) {
-    console.log(`SIP鐘舵�佹洿鏂�: ${type} - ${text}`);
-    if (this.onStatusChange) {
-      this.onStatusChange({ type, text });
-    }
-  }
-
-  updateCallStatus(type, text) {
-    console.log(`閫氳瘽鐘舵�佹洿鏂�: ${type} - ${text}`);
-    if (this.onCallStatusChange) {
-      this.onCallStatusChange({ type, text });
-    }
-  }
-
-  handleIncomingCall(session) {
-    if (session.direction === "incoming") {
-      console.log("鏉ョ數:", session.remote_identity.uri.toString());
-      if (this.onIncomingCall) {
-        this.onIncomingCall(session);
-      }
-    }
-  }
-}
-
-export default new SipService();
diff --git a/src/utils/sipService.js b/src/utils/sipService.js
index 1654ccc..dbc4a4e 100644
--- a/src/utils/sipService.js
+++ b/src/utils/sipService.js
@@ -4,174 +4,211 @@
   constructor() {
     this.ua = null;
     this.currentSession = null;
-    this.onStatusChange = null; // 鐘舵�佸彉鍖栧洖璋�
-    this.onCallStatusChange = null; // 鏂板閫氳瘽鐘舵�佸洖璋�
+    this.onStatusChange = null;
+    this.onCallStatusChange = null;
+    this.onIncomingCall = null;
+    this.isRegistered = false; // 鏂板娉ㄥ唽鐘舵�佹爣蹇�
+    this.registrationTime = null; // 鏂板娉ㄥ唽鎴愬姛鏃堕棿鎴�
   }
 
-  // 鍒濆鍖朣IP瀹㈡埛绔�
   init(config) {
     try {
-      this.updateStatus("connecting", "杩炴帴涓�...");
+      this.updateStatus("connecting", "杩炴帴涓�;...");
 
       this.ua = new JsSIP.UA({
         sockets: [new JsSIP.WebSocketInterface(config.wsUrl)],
         uri: config.sipUri,
         password: config.password,
         display_name: config.displayName,
-        iceservers: [],
-        // realm: config.realm,
+        iceServers: [],
         register: true,
-        session_expires: 180,
-        sessionTimersExpires: 300, // 璁剧疆 Session-Expires=120锛堝繀椤� >= Min-SE锛�
-        extraHeaders: [
-          "Min-SE: 120", // 鍙�夛細鏄惧紡鍛婅瘔鏈嶅姟鍣ㄤ綘鏀寔鐨勬渶灏忓��
-        ],
-        register_expires: 300, // 娉ㄥ唽鏈夋晥鏈�(绉�)
-        connection_recovery_min_interval: 2, // 鏈�灏忛噸杩為棿闅�
-        connection_recovery_max_interval: 30, // 鏈�澶ч噸杩為棿闅�
+        sessionExpires: 1800,
+        minSessionExpires: 90,
+        register_expires: 300,
       });
 
       this.ua.start();
 
-      // 娉ㄥ唽浜嬩欢鐩戝惉
+      // 浜嬩欢鐩戝惉
       this.ua.on("registered", () => {
+        this.isRegistered = true;
+        this.registrationTime = Date.now(); // 璁板綍娉ㄥ唽鎴愬姛鏃堕棿
+        console.log(this.registrationTime, "娉ㄥ唽鏃堕棿");
+
         this.updateStatus("registered", "宸叉敞鍐�");
       });
 
       this.ua.on("registrationFailed", (e) => {
+        this.isRegistered = false;
         this.updateStatus("failed", `娉ㄥ唽澶辫触: ${e.cause}`);
       });
 
-      this.ua.on("disconnected", () => {
-        this.updateStatus("disconnected", "杩炴帴鏂紑");
+      this.ua.on("unregistered", () => {
+        this.isRegistered = false;
+        let registrationTime = Date.now(); // 璁板綍娉ㄩ攢鎴愬姛鏃堕棿
+        console.log(registrationTime, "娉ㄩ攢鏃堕棿");
+        this.updateStatus("disconnected", "宸叉敞閿�");
       });
-
-      this.ua.on("connected", () => {
-        this.updateStatus("connecting", "閲嶆柊杩炴帴涓�...");
-      });
-
-      // 鐩戝惉鏉ョ數
-      this.ua.on("newRTCSession", (data) => {
-        this.handleIncomingCall(data.session);
-      });
+      this.ua.on("disconnected", () =>
+        this.updateStatus("disconnected", "杩炴帴鏂紑")
+      );
+      this.ua.on("connected", () =>
+        this.updateStatus("connecting", "閲嶆柊杩炴帴涓�...")
+      );
+      this.ua.on("newRTCSession", (data) =>
+        this.handleIncomingCall(data.session)
+      );
     } catch (error) {
       this.updateStatus("failed", `鍒濆鍖栧け璐�: ${error.message}`);
       console.error("SIP鍒濆鍖栧け璐�:", error);
+      throw error;
     }
   }
-  handleIncomingCall(session) {
-    if (session.direction === "incoming") {
-      console.log("鏉ョ數:", session.remote_identity.uri.toString());
-      // 鍙互鍦ㄨ繖閲岃Е鍙� UI 閫氱煡
-      if (this.onIncomingCall) {
-        this.onIncomingCall(session);
-      }
+  // 鏂板鏂规硶锛氭鏌ユ槸鍚﹀彲浠ュ懠鍙�
+  canMakeCall(minDelay = 2000) {
+    if (!this.isRegistered) {
+      return { canCall: false, reason: "SIP鏈敞鍐岋紝鏃犳硶鍛煎彨" };
     }
-  }
 
-  // 鏇存柊鐘舵�佸苟閫氱煡UI
-  updateStatus(type, text) {
-    console.log(`SIP鐘舵�佹洿鏂�: ${type} - ${text}`);
-    if (this.onStatusChange) {
-      this.onStatusChange({ type, text });
-    }
-  }
+    const now = Date.now();
+    const timeSinceRegistration = now - this.registrationTime;
 
-  // 涓�閿嫧鍙� - 澧炲姞娉ㄥ唽鐘舵�佹鏌�
+    if (timeSinceRegistration < minDelay) {
+      const remaining = minDelay - timeSinceRegistration;
+      return {
+        canCall: false,
+        reason: `娉ㄥ唽鎴愬姛锛岃绛夊緟 ${Math.ceil(remaining / 1000)} 绉掑悗鍐嶅懠鍙玚,
+      };
+    }
+
+    return { canCall: true, reason: "" };
+  }
   makeCall(targetNumber) {
-    if (!this.ua) {
-      throw new Error("SIP瀹㈡埛绔湭鍒濆鍖�");
+    const { canCall, reason } = this.canMakeCall();
+    if (!canCall) {
+      return Promise.reject(new Error(reason));
     }
+    return new Promise((resolve, reject) => {
+      try {
+        if (!this.ua) {
+          throw new Error("SIP瀹㈡埛绔湭鍒濆鍖�");
+        }
 
-    if (!this.ua.isRegistered()) {
-      throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨");
-    }
+        if (!this.ua.isRegistered()) {
+          throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨");
+        }
 
-    const options = {
-      sessionTimers: true,
-      sessionTimersExpires: 300,
-      extraHeaders: [
-        "Min-SE: 120",
-        "Route: <sip:@192.168.100.6>",
-        "Accept: application/sdp",
-        "Supported: replaces, timer",
-        "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS",
-      ],
-      eventHandlers: {
-        progress: (e) => {
-          this.updateCallStatus("calling", "鍛煎彨涓�...");
-        },
-        failed: (e) => {
-          this.updateCallStatus("ended", `鍛煎彨澶辫触: ${e.cause}`);
-        },
-        ended: (e) => {
-          this.updateCallStatus("ended", "閫氳瘽缁撴潫");
-        },
-        confirmed: (e) => {
-          this.updateCallStatus("connected", "閫氳瘽宸叉帴閫�");
-        },
-      },
-      mediaConstraints: {
-        audio: true,
-        video: false,
-      },
-      rtcOfferConstraints: {
-        offerToReceiveAudio: 1,
-        offerToReceiveVideo: 0,
-        mandatory: {
-          OfferToReceiveAudio: true,
-          OfferToReceiveVideo: false,
-        },
-      },
-      pcConfig: {
-        iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
-        iceTransportPolicy: "all",
-        bundlePolicy: "balanced",
-        rtcpMuxPolicy: "require",
-        codecs: {
-          audio: [
-            { name: "PCMU", clockRate: 8000, payloadType: 0 },
-            { name: "PCMA", clockRate: 8000, payloadType: 8 },
-          ],
-          video: [],
-        },
-      },
-    };
+        const options = {
+          sessionTimers: true, // 鍚敤浼氳瘽璁℃椂鍣�
+          sessionTimersExpires: 150,
+          extraHeaders: ["Accept: application/sdp"],
+          mediaConstraints: { audio: true, video: false },
+          rtcOfferConstraints: {
+            offerToReceiveAudio: true,
+            offerToReceiveVideo: false,
+          },
+          eventHandlers: {
+            progress: () => this.updateCallStatus("calling", "鍛煎彨涓�..."),
+            failed: (e) => {
+              this.handleCallFailure(e, reject);
+            },
+            ended: () => this.updateCallStatus("ended", "閫氳瘽缁撴潫"),
+            confirmed: () => {
+              this.updateCallStatus("connected", "閫氳瘽宸叉帴閫�");
+              resolve();
+            },
+          },
+        };
 
-    this.currentSession = this.ua.call(
-      `sip:${targetNumber}@192.168.100.6`,
-      options
-    );
-    // 鍦ㄤ細璇濆垱寤哄悗淇敼 SDP
-    this.currentSession.on("peerconnection", (pc) => {
-       this.updateCallStatus('calling', '鍛煎彨涓�...');
-      pc.createOffer = (offerOptions) => {
-        return RTCPeerConnection.prototype.createOffer
-          .call(pc, offerOptions)
-          .then((offer) => {
-            const modifiedSdp = offer.sdp
-              .replace(/c=IN IP4 192\.168\.100\.10/g, "c=IN IP4 192.168.100.6")
-              .replace(/m=audio \d+ RTP\/AVP.*/, "m=audio 7078 RTP/AVP 0 8");
-            return new RTCSessionDescription({
-              type: "offer",
-              sdp: modifiedSdp,
-            });
-          });
+        this.currentSession = this.ua.call(
+          `sip:${targetNumber}@1192.170.66.107`,
+          options
+        );
+
+        this.setupPeerConnection(this.currentSession);
+        this.setupAudio(this.currentSession);
+      } catch (error) {
+        this.updateCallStatus("failed", `鍛煎彨澶辫触22: ${error.message}`);
+        reject(error);
+      }
+    });
+  }
+
+  setupPeerConnection(session) {
+    session.on("peerconnection", (pc) => {
+      const originalCreateOffer = pc.createOffer.bind(pc);
+
+      pc.createOffer = async (offerOptions) => {
+        try {
+          const offer = await originalCreateOffer(offerOptions);
+          return this.normalizeSDP(offer);
+        } catch (error) {
+          console.error("鍒涘缓Offer澶辫触:", error);
+          throw error;
+        }
       };
     });
-    this.currentSession.on('failed', (e) => {
-        this.updateCallStatus('failed', `鍛煎彨澶辫触2: ${e}`);
-      });
-
-      this.currentSession.on('ended', () => {
-        this.updateCallStatus('ended', '閫氳瘽宸茬粨鏉�');
-      });
-
-      this.currentSession.on('confirmed', () => {
-        this.updateCallStatus('connected', '閫氳瘽宸叉帴閫�');
-      });
-    this.setupAudio(this.currentSession);
   }
+
+  normalizeSDP(offer) {
+    let sdp = offer.sdp;
+
+    // 鏍囧噯鍖朣DP
+    sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
+    sdp = sdp.replace(
+      /m=audio \d+.*\r\n/,
+      "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n"
+    );
+
+    // 纭繚鍖呭惈鍩烘湰缂栬В鐮佸櫒
+    if (!sdp.includes("PCMU/8000")) sdp += "a=rtpmap:0 PCMU/8000\r\n";
+    if (!sdp.includes("PCMA/8000")) sdp += "a=rtpmap:8 PCMA/8000\r\n";
+
+    // 娣诲姞蹇呰灞炴��
+    sdp += "a=rtcp-mux\r\n";
+    sdp += "a=sendrecv\r\n";
+
+    console.log("鏍囧噯鍖栧悗鐨凷DP:", sdp);
+    return new RTCSessionDescription({
+      type: offer.type,
+      sdp: sdp,
+    });
+  }
+
+  handleCallFailure(e, reject) {
+    if (e.response?.status_code === 422) {
+      const serverMinSE = e.response.headers["Min-SE"]?.[0]?.raw || "鏈煡";
+      console.error(`鏈嶅姟鍣ㄨ姹� Min-SE 鈮� ${serverMinSE}锛屽綋鍓嶈缃�: 120`);
+    }
+    console.error("鍛煎彨澶辫触璇︽儏:", {
+      cause: e.cause,
+      message: e.message,
+      response: e.response && {
+        status: e.response.status_code,
+        reason: e.response.reason_phrase,
+      },
+    });
+
+    let errorMessage = "鍛煎彨澶辫触";
+    switch (e.cause) {
+      case "Incompatible SDP":
+        errorMessage = "濯掍綋鍗忓晢澶辫触锛岃妫�鏌ョ紪瑙g爜鍣ㄩ厤缃�";
+        break;
+      case "488":
+      case "606":
+        errorMessage = "瀵规柟璁惧涓嶆敮鎸佸綋鍓嶅獟浣撻厤缃�";
+        break;
+      case "422":
+        errorMessage = "浼氳瘽鍙傛暟涓嶆弧瓒虫湇鍔″櫒瑕佹眰";
+        break;
+      default:
+        errorMessage = `鍛煎彨澶辫触3: ${e.cause || e.message}`;
+    }
+
+    this.updateCallStatus("failed55", errorMessage);
+    reject(new Error(errorMessage));
+  }
+
   setupAudio(session) {
     session.connection.addEventListener("addstream", (e) => {
       const audioElement = document.getElementById("remoteAudio");
@@ -180,21 +217,37 @@
       }
     });
   }
-  // 鎸傛柇褰撳墠閫氳瘽
+
   endCall() {
-  if (this.currentSession) {
+    if (this.currentSession) {
       this.currentSession.terminate();
-      this.updateCallStatus('ended', '閫氳瘽宸茬粨鏉�');
+      this.updateCallStatus("ended", "閫氳瘽宸茬粨鏉�");
       this.currentSession = null;
     }
   }
-  // 鏂板鏂规硶锛氭洿鏂伴�氳瘽鐘舵��
+
+  updateStatus(type, text) {
+    console.log(`SIP鐘舵�佹洿鏂�: ${type} - ${text}`);
+    if (this.onStatusChange) {
+      this.onStatusChange({ type, text });
+    }
+  }
+
   updateCallStatus(type, text) {
     console.log(`閫氳瘽鐘舵�佹洿鏂�: ${type} - ${text}`);
     if (this.onCallStatusChange) {
       this.onCallStatusChange({ type, text });
     }
   }
+
+  handleIncomingCall(session) {
+    if (session.direction === "incoming") {
+      console.log("鏉ョ數:", session.remote_identity.uri.toString());
+      if (this.onIncomingCall) {
+        this.onIncomingCall(session);
+      }
+    }
+  }
 }
 
 export default new SipService();
diff --git a/src/views/followvisit/record/detailpage/index.vue b/src/views/followvisit/record/detailpage/index.vue
index d6164c8..cc8990c 100644
--- a/src/views/followvisit/record/detailpage/index.vue
+++ b/src/views/followvisit/record/detailpage/index.vue
@@ -1779,7 +1779,7 @@
       // 杩欓噷璋冪敤浣犵殑鐭俊鍙戦�� API
       // 鍋囪 API 涓� sendMsg锛屽弬鏁板彲鑳介渶瑕佹牴鎹疄闄呮儏鍐佃皟鏁�
       sendMsg({
-        phone:  this.userform.telcode, // 纭繚鐢佃瘽鍙风爜瀛楁姝g‘
+        phone: this.userform.telcode, // 纭繚鐢佃瘽鍙风爜瀛楁姝g‘
         content: this.smsContent,
       })
         .then((res) => {
@@ -2152,6 +2152,16 @@
       this.getTaskservelist();
     },
   },
+  // deactivated() {
+  //   console.log(11);
+  // },
+  beforeRouteLeave(to, from, next) {
+    this.$refs.callButton.cleanupResources();
+    next(); // 纭繚璋冪敤 nex
+  },
+  // beforeRouteUpdate() {
+  //   console.log(33);
+  // },
 };
 </script>
 
@@ -2554,7 +2564,8 @@
   @media screen and (max-width: 1200px), (min-resolution: 1.1dppx) {
     flex-direction: column;
 
-    .call-action, .manual-action {
+    .call-action,
+    .manual-action {
       width: 100% !important;
     }
   }
@@ -2659,7 +2670,8 @@
     margin: 10px;
     padding: 15px;
 
-    .topic-dev, .scriptTopic-dev {
+    .topic-dev,
+    .scriptTopic-dev {
       margin-bottom: 15px;
     }
   }
@@ -2692,7 +2704,8 @@
 
 /* 涓虹Щ鍔ㄨ澶囦紭鍖栨粴鍔ㄤ綋楠� */
 @media screen and (max-width: 768px) {
-  .Followuserinfo, .Followuserinfos {
+  .Followuserinfo,
+  .Followuserinfos {
     padding: 15px;
     margin: 5px;
   }
@@ -2705,18 +2718,20 @@
 
 /* 缂╂斁妫�娴嬫牱寮� */
 @media screen and (min-resolution: 1.1dppx),
-       screen and (-webkit-min-device-pixel-ratio: 1.1),
-       screen and (max-width: 1200px) {
+  screen and (-webkit-min-device-pixel-ratio: 1.1),
+  screen and (max-width: 1200px) {
   .action-container {
     flex-direction: column;
   }
 
-  .call-action, .manual-action {
+  .call-action,
+  .manual-action {
     width: 100%;
   }
 
   /* 璋冩暣鍐呴儴鍏冪礌闂磋窛 */
-  .call-container, .Followuserinfos {
+  .call-container,
+  .Followuserinfos {
     margin-bottom: 20px;
   }
 }

--
Gitblit v1.9.3