From 2eff945fb9fc0d17b2098b26aba74ab192ec3727 Mon Sep 17 00:00:00 2001
From: WXL <1785969728@qq.com>
Date: 星期三, 06 八月 2025 17:48:54 +0800
Subject: [PATCH] 测试完成

---
 dist (2).zip                                      |    0 
 dist.zip                                          |    0 
 src/store/modules/user.js                         |   12 
 src/views/login.vue                               |    2 
 src/views/followvisit/record/detailpage/index.vue |  172 ++++++++++++--
 src/utils/sipService-xs.js                        |  217 ++++++++++++++++++
 src/utils/sipService.js                           |   42 ++
 src/components/CallButton/index.vue               |  230 ++++++++++++++-----
 8 files changed, 576 insertions(+), 99 deletions(-)

diff --git "a/dist \0502\051.zip" "b/dist \0502\051.zip"
new file mode 100644
index 0000000..ab7d81b
--- /dev/null
+++ "b/dist \0502\051.zip"
Binary files differ
diff --git a/dist.zip b/dist.zip
new file mode 100644
index 0000000..bfff269
--- /dev/null
+++ b/dist.zip
Binary files differ
diff --git a/src/components/CallButton/index.vue b/src/components/CallButton/index.vue
index 51a4995..84a1519 100644
--- a/src/components/CallButton/index.vue
+++ b/src/components/CallButton/index.vue
@@ -3,29 +3,32 @@
     <div class="sip-status" :class="sipStatusClass">
       SIP鐘舵��: {{ sipStatus }}
     </div>
-    <!-- 鍙风爜杈撳叆 -->
-    <input
-      v-model="phoneNumber"
-      type="text"
-      placeholder="杈撳叆鐢佃瘽鍙风爜"
-      @keyup.enter="startCall"
-    />
+
+    <!-- 鐘舵�佹樉绀� -->
+    <div class="call-status" :class="callStatusClass">
+      {{ callStatusText }}
+    </div>
 
     <!-- 鍛煎彨鎸夐挳 -->
-    <button :class="['call-btn', { calling: isCalling }]" @click="startCall">
-      {{ isCalling ? "閫氳瘽涓�..." : "涓�閿懠鍙�" }}
+    <button
+      :class="['call-btn', { calling: isCalling }]"
+      @click="startCall"
+      :disabled="isCalling || sipStatus !== '宸叉敞鍐�'"
+    >
+      {{ callButtonText }}
     </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>
 
@@ -33,82 +36,86 @@
 import sipService from "@/utils/sipService";
 
 export default {
-  data() {
-    return {
-      // phoneNumber: "",
-      isCalling: false,
-      callStatus: "鍑嗗灏辩华",
-      sipStatus: "鏈繛鎺�",
-      sipStatusClass: "status-disconnected",
-      sipConfig: {
-        wsUrl: "wss://9.208.5.18:7443",
-        sipUri: "1000@9.208.5.18",
-        password: "Smartor@2023",
-        displayName: "Web 灏忛緳",
-        // realm: "9.208.5.18:8090",
-      },
-    };
-  },
   props: {
     phoneNumber: {
       type: String,
       default: ''
     }
   },
+  data() {
+    return {
+      isCalling: false,
+      callStatus: 'idle', // idle, calling, connected, ended
+      sipStatus: "鏈繛鎺�",
+      sipStatusClass: "status-disconnected",
+      sipConfig: {
+        wsUrl: "wss://192.168.100.6:7443",
+        sipUri: "1000@192.168.100.6",
+        password: "Smartor@2023",
+        displayName: "Web 灏忛緳",
+      },
+    };
+  },
+  computed: {
+    callStatusText() {
+      const statusMap = {
+        idle: '鍑嗗灏辩华',
+        calling: '鍛煎彨涓�...',
+        connected: '閫氳瘽涓�',
+        ended: '閫氳瘽缁撴潫'
+      };
+      return statusMap[this.callStatus];
+    },
+    callStatusClass() {
+      return `status-${this.callStatus}`;
+    },
+    callButtonText() {
+      return this.isCalling ? "閫氳瘽涓�..." : "涓�閿懠鍙�";
+    }
+  },
   mounted() {
-    // 娴嬭瘯
-    const ws = new WebSocket("wss://9.208.5.18:7443");
-    ws.onopen = () => console.log("WebSocket 杩炴帴鎴愬姛");
-    ws.onerror = (e) => console.error("WebSocket 閿欒:", e);
-
-
-    // 鍒濆鍖朣IP杩炴帴
-
     sipService.init(this.sipConfig);
     sipService.onStatusChange = (status) => {
       this.sipStatus = status.text;
       this.sipStatusClass = `status-${status.type}`;
+    };
 
-      // 鏍规嵁鐘舵�佹洿鏂癠I鎴栨墽琛屽叾浠栨搷浣�
-      if (status.type === "registered") {
-        console.log("SIP娉ㄥ唽鎴愬姛锛屽彲浠ュ紑濮嬪懠鍙�");
-      } else if (status.type === "failed") {
-        console.error("SIP娉ㄥ唽澶辫触");
-      }
+    // 鐩戝惉閫氳瘽鐘舵�佸彉鍖�
+    sipService.onCallStatusChange = (status) => {
+      this.callStatus = status.type;
+      this.isCalling = status.type === 'calling' || status.type === 'connected';
+
+      // 閫氱煡鐖剁粍浠堕�氳瘽鐘舵�佸彉鍖�
+      this.$emit('call-status-change', status);
     };
   },
-  beforeDestroy() {
-    // 缁勪欢閿�姣佹椂缁撴潫閫氳瘽
-    this.endCall();
-  },
   methods: {
-    // 寮�濮嬪懠鍙�
     async startCall() {
       if (!this.phoneNumber) {
-        this.callStatus = "璇疯緭鍏ョ數璇濆彿鐮�";
+        this.$message.error("璇疯緭鍏ョ數璇濆彿鐮�");
         return;
       }
-      try {
-        this.isCalling = true;
-        this.callStatus = "鍛煎彨涓�...";
-        // 璋冪敤SIP鏈嶅姟
-        sipService.makeCall(this.phoneNumber);
 
-        this.callStatus = "閫氳瘽宸插缓绔�";
+      try {
+        this.callStatus = 'calling';
+        this.isCalling = true;
+
+        await sipService.makeCall(this.phoneNumber);
+
       } catch (error) {
         console.error("鍛煎彨澶辫触:", error);
-        this.callStatus = `鍛煎彨澶辫触: ${error.message}`;
+        this.callStatus = 'ended';
         this.isCalling = false;
+        this.$message.error(`鍛煎彨澶辫触: ${error.message}`);
       }
     },
 
-    // 缁撴潫閫氳瘽
     endCall() {
       sipService.endCall();
+      this.callStatus = 'ended';
       this.isCalling = false;
-      this.callStatus = "閫氳瘽宸茬粨鏉�";
-    },
-  },
+    }
+  }
 };
 </script>
 
@@ -138,7 +145,106 @@
   border-radius: 4px;
   cursor: pointer;
 }
+.call-status {
+  padding: 8px;
+  margin: 10px 0;
+  border-radius: 4px;
+  text-align: center;
+}
 
+.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;
+}
+
+/* 鍘熸湁鏍峰紡淇濇寔涓嶅彉 */
+.call-container {
+  display: flex;
+  flex-direction: column;
+  gap: 10px;
+  max-width: 300px;
+  margin: 0 auto;
+  padding: 20px;
+  border: 1px solid #eee;
+  border-radius: 8px;
+}
+
+.call-btn {
+  padding: 10px;
+  background-color: #4caf50;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.call-btn:hover:not(:disabled) {
+  background-color: #45a049;
+}
+
+.call-btn:disabled {
+  background-color: #cccccc;
+  cursor: not-allowed;
+}
+
+.call-btn.calling {
+  background-color: #2196f3;
+}
+
+.end-call-btn {
+  padding: 10px;
+  background-color: #f44336;
+  color: white;
+  border: none;
+  border-radius: 4px;
+  cursor: pointer;
+}
+
+.end-call-btn:hover {
+  background-color: #d32f2f;
+}
+
+.sip-status {
+  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;
+}
 .call-btn:hover {
   background-color: #45a049;
 }
diff --git a/src/store/modules/user.js b/src/store/modules/user.js
index a64959d..36c9b90 100644
--- a/src/store/modules/user.js
+++ b/src/store/modules/user.js
@@ -144,11 +144,19 @@
           localStorage.setItem('deptCode', '01040201');
           }else if (orgid=='47246102433112211A2101') {
           localStorage.setItem('orgname', '缂欎簯鍘夸腑鍖诲尰闄�');
-          localStorage.setItem('ZuHuID', '1400360867068907520');
+          localStorage.setItem('ZuHuID', '1429338802177000002');
           localStorage.setItem('deptCode', '');
           }else if (orgid=='47240018433118111A2101') {
           localStorage.setItem('orgname', '榫欐硥甯備腑鍖诲尰闄�');
-          localStorage.setItem('ZuHuID', '1400360867068907520');
+          localStorage.setItem('ZuHuID', '1429338802177000003');
+          localStorage.setItem('deptCode', '');
+          }else if (orgid=='47243006833112611A2101') {
+          localStorage.setItem('orgname', '搴嗗厓鍘夸腑鍖诲尰闄�');
+          localStorage.setItem('ZuHuID', '1429338802177000004');
+          localStorage.setItem('deptCode', '');
+          }else if (orgid=='47234002X33112111A2101') {
+          localStorage.setItem('orgname', '闈掔敯鍘夸腑鍖诲尰闄�');
+          localStorage.setItem('ZuHuID', '1429338802177000005');
           localStorage.setItem('deptCode', '');
           }
           resolve()
diff --git a/src/utils/sipService-xs.js b/src/utils/sipService-xs.js
new file mode 100644
index 0000000..924ac14
--- /dev/null
+++ b/src/utils/sipService-xs.js
@@ -0,0 +1,217 @@
+import JsSIP from "jssip";
+
+class SipService {
+  constructor() {
+    this.ua = null;
+    this.currentSession = null;
+    this.onStatusChange = null;
+    this.onCallStatusChange = null;
+    this.onIncomingCall = 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: [{ urls: "stun:stun.l.google.com:19302" }],
+        register: true,
+        session_expires: 180,
+        sessionTimersExpires: 300,
+        extraHeaders: ["Min-SE: 120"],
+        register_expires: 300,
+        connection_recovery_min_interval: 2,
+        connection_recovery_max_interval: 30,
+        pcConfig: {
+          iceTransportPolicy: "all",
+          rtcpMuxPolicy: "require",
+          bundlePolicy: "max-bundle"
+        }
+      });
+
+      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);
+      throw error;
+    }
+  }
+
+  makeCall(targetNumber) {
+    return new Promise((resolve, reject) => {
+      try {
+        if (!this.ua) {
+          throw new Error("SIP瀹㈡埛绔湭鍒濆鍖�");
+        }
+
+        if (!this.ua.isRegistered()) {
+          throw new Error("SIP鏈敞鍐岋紝鏃犳硶鍛煎彨");
+        }
+
+        const options = {
+          sessionTimers: false, // 鏆傛椂绂佺敤浠ュ噺灏戝吋瀹规�ч棶棰�
+          extraHeaders: [
+            "Min-SE: 120",
+            "Accept: application/sdp",
+            "Supported: outbound"
+          ],
+          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}@9.208.5.18`,
+          options
+        );
+
+        this.setupPeerConnection(this.currentSession);
+        this.setupAudio(this.currentSession);
+
+      } catch (error) {
+        this.updateCallStatus("failed", `鍛煎彨澶辫触: ${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;
+
+    // 1. 鏍囧噯鍖栬繛鎺ヨ
+    sdp = sdp.replace(/c=IN IP4.*\r\n/, "c=IN IP4 0.0.0.0\r\n");
+
+    // 2. 鏍囧噯鍖栭煶棰戝獟浣撹
+    sdp = sdp.replace(/m=audio \d+.*\r\n/,
+      "m=audio 9 UDP/TLS/RTP/SAVPF 0 8\r\n");
+
+    // 3. 纭繚鍖呭惈鍩烘湰缂栬В鐮佸櫒
+    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";
+    }
+
+    // 4. 娣诲姞蹇呰灞炴��
+    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) {
+    console.error("鍛煎彨澶辫触璇︽儏:", {
+      cause: e.cause,
+      message: e.message,
+      response: e.response && e.response.status_code
+    });
+
+    let errorMessage = "鍛煎彨澶辫触";
+    switch(e.cause) {
+      case "Incompatible SDP":
+        errorMessage = "濯掍綋鍗忓晢澶辫触锛岃妫�鏌ョ紪瑙g爜鍣ㄩ厤缃�";
+        break;
+      case "488":
+      case "606":
+        errorMessage = "瀵规柟璁惧涓嶆敮鎸佸綋鍓嶅獟浣撻厤缃�";
+        break;
+      default:
+        errorMessage = `鍛煎彨澶辫触: ${e.cause || e.message}`;
+    }
+
+    this.updateCallStatus("failed", 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 4b12914..8318c1a 100644
--- a/src/utils/sipService.js
+++ b/src/utils/sipService.js
@@ -5,6 +5,7 @@
     this.ua = null;
     this.currentSession = null;
     this.onStatusChange = null; // 鐘舵�佸彉鍖栧洖璋�
+    this.onCallStatusChange = null; // 鏂板閫氳瘽鐘舵�佸洖璋�
   }
 
   // 鍒濆鍖朣IP瀹㈡埛绔�
@@ -17,7 +18,7 @@
         uri: config.sipUri,
         password: config.password,
         display_name: config.displayName,
-        iceservers:[],
+        iceservers: [],
         // realm: config.realm,
         register: true,
         session_expires: 180,
@@ -97,10 +98,18 @@
         "Allow: INVITE, ACK, BYE, CANCEL, OPTIONS",
       ],
       eventHandlers: {
-        progress: (e) => console.log("鍛煎彨涓�..."),
-        failed: (e) => console.error("鍛煎彨澶辫触:", e),
-        ended: (e) => console.log("閫氳瘽缁撴潫"),
-        confirmed: (e) => console.log("閫氳瘽宸叉帴閫�"),
+        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,
@@ -135,6 +144,7 @@
     );
     // 鍦ㄤ細璇濆垱寤哄悗淇敼 SDP
     this.currentSession.on("peerconnection", (pc) => {
+       this.updateCallStatus('calling', '鍛煎彨涓�...');
       pc.createOffer = (offerOptions) => {
         return RTCPeerConnection.prototype.createOffer
           .call(pc, offerOptions)
@@ -149,6 +159,17 @@
           });
       };
     });
+    this.currentSession.on('failed', (e) => {
+        this.updateCallStatus('failed', `鍛煎彨澶辫触2: ${e.cause}`);
+      });
+
+      this.currentSession.on('ended', () => {
+        this.updateCallStatus('ended', '閫氳瘽宸茬粨鏉�');
+      });
+
+      this.currentSession.on('confirmed', () => {
+        this.updateCallStatus('connected', '閫氳瘽宸叉帴閫�');
+      });
     this.setupAudio(this.currentSession);
   }
   setupAudio(session) {
@@ -161,12 +182,19 @@
   }
   // 鎸傛柇褰撳墠閫氳瘽
   endCall() {
-    if (this.currentSession) {
+  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/views/followvisit/record/detailpage/index.vue b/src/views/followvisit/record/detailpage/index.vue
index fb42e14..ab859d4 100644
--- a/src/views/followvisit/record/detailpage/index.vue
+++ b/src/views/followvisit/record/detailpage/index.vue
@@ -260,13 +260,37 @@
                   ></el-input> </el-form-item
               ></el-col>
             </el-row>
+
             <div style="margin-left: 30px">
               <el-button type="primary" plain @click="Editsingletasksonyic('')"
                 >淇濆瓨鏈嶅姟</el-button
               >
             </div>
           </div>
+          <el-row :gutter="20" v-if="callStatus !== 'idle'">
+            <el-col :span="24">
+              <el-alert
+                :title="callStatusText"
+                :type="callStatusType"
+                :closable="false"
+                show-icon
+              />
+            </el-col>
+          </el-row>
 
+          <!-- 鎸傛柇鎸夐挳锛堜粎鍦ㄩ�氳瘽涓樉绀猴級 -->
+          <el-row :gutter="20" v-if="callStatus === 'connected'">
+            <el-col :span="24" style="text-align: center; margin-top: 10px">
+              <el-button
+                type="danger"
+                icon="el-icon-phone"
+                @click="endCurrentCall"
+                :loading="isEndingCall"
+              >
+                鎸傛柇鐢佃瘽
+              </el-button>
+            </el-col>
+          </el-row>
           <el-form-item label="闅忚璁板綍">
             <el-input type="textarea" v-model="form.remark"></el-input>
           </el-form-item>
@@ -796,6 +820,10 @@
       userid: "",
       currentPhoneNumber: "",
       callType: "", // 鐢ㄤ簬鍖哄垎鏄摢涓數璇�
+      // 宸叉湁鏁版嵁...
+      callStatus: "idle", // idle, calling, connected, ended, failed
+      isEndingCall: false,
+      currentCall: null, // 褰撳墠閫氳瘽瀵硅薄
       input: "浠婂ぉ韬綋杩樹笉閿�",
       radio: "2",
       taskname: "",
@@ -854,9 +882,9 @@
       },
       pickerOptions: {
         disabledDate(time) {
-        // 绂佺敤浠婂ぉ鍙婁箣鍓嶇殑鏃ユ湡
-        return time.getTime() < Date.now() - 24 * 60 * 60 * 1000;
-      },
+          // 绂佺敤浠婂ぉ鍙婁箣鍓嶇殑鏃ユ湡
+          return time.getTime() < Date.now() - 24 * 60 * 60 * 1000;
+        },
         shortcuts: [
           {
             text: "涓冨ぉ鍚�",
@@ -948,7 +976,28 @@
       patid: null,
     };
   },
-
+  computed: {
+    callStatusText() {
+      const statusMap = {
+        idle: "鍑嗗鍛煎彨",
+        calling: `姝e湪鍛煎彨 ${this.currentPhoneNumber}...`,
+        connected: `宸叉帴閫� ${this.currentPhoneNumber}`,
+        ended: "閫氳瘽宸茬粨鏉�",
+        failed: "鍛煎彨澶辫触",
+      };
+      return statusMap[this.callStatus];
+    },
+    callStatusType() {
+      const typeMap = {
+        idle: "info",
+        calling: "warning",
+        connected: "success",
+        ended: "info",
+        failed: "error",
+      };
+      return typeMap[this.callStatus];
+    },
+  },
   created() {
     this.taskid = this.$route.query.taskid;
     this.id = this.$route.query.id;
@@ -1217,28 +1266,95 @@
           console.error("鍙戠敓閿欒锛�", error);
         });
     },
-    // 楠岃瘉鎵嬫満鍙锋牸寮�
-    isValidPhone(phone) {
-      return /^1[3-9]\d{9}$/.test(phone);
-    },
-    // 鍛煎彨澶勭悊
+    // 楠岃瘉鐢佃瘽鍙风爜鏍煎紡骞惰繑鍥為敊璇俊鎭�
+validatePhoneNumber(phone) {
+  if (!phone) {
+    return { isValid: false, message: '璇疯緭鍏ョ數璇濆彿鐮�' };
+  }
+
+  // 鎵嬫満鍙锋鍒�
+  const mobileRegex = /^1[3-9]\d{9}$/;
+
+  // 甯﹀尯鍙风殑鍥哄畾鐢佃瘽锛堝畬鏁存牸寮忥級
+  const landlineFullRegex = /^0\d{2,3}-?\d{7,8}$/;
+
+  // 涓嶅甫鍖哄彿鐨勫浐瀹氱數璇濓紙浠呮湰鍦板彿鐮侊級
+  const landlineLocalRegex = /^\d{7,8}$/;
+
+  if (mobileRegex.test(phone)) {
+    return { isValid: true, type: 'mobile' };
+  } else if (landlineFullRegex.test(phone)) {
+    return { isValid: true, type: 'landline' };
+  } else if (landlineLocalRegex.test(phone)) {
+    return {
+      isValid: false,
+      message: '鏈湴鍙风爜璇锋坊鍔犲尯鍙凤紙濡�028-1234567锛�'
+    };
+  } else {
+    return {
+      isValid: false,
+      message: '璇疯緭鍏ユ纭殑鐢佃瘽鍙风爜锛堟墜鏈哄彿鎴栧甫鍖哄彿鐨勫浐瀹氱數璇濓級'
+    };
+  }
+},
+
+// 浣跨敤绀轰緥
+isValidPhone(phone) {
+  return this.validatePhoneNumber(phone).isValid;
+},
     handleCall(phone, type) {
-      if (this.isValidPhone(phone)) {
-        this.currentPhoneNumber = phone;
-        this.callType = type;
-
-        // 绛夊緟涓嬩竴涓猼ick纭繚鍊煎凡鏇存柊
-        this.$nextTick(() => {
-          this.$refs.callButton.startCall();
-
-          // 鍙�夛細鏍规嵁涓嶅悓绫诲瀷鍋氫笉鍚屽鐞�
-          if (type === "tel") {
-            console.log("姝e湪鍛煎彨鎮h�呮湰浜�:", phone);
-          } else {
-            console.log("姝e湪鍛煎彨鑱旂郴浜�:", phone);
-          }
-        });
+      if (!this.isValidPhone(phone)) {
+        this.$message.error("璇疯緭鍏ユ纭殑鎵嬫満鍙风爜");
+        return;
       }
+
+      this.currentPhoneNumber = phone;
+      this.callType = type;
+      this.callStatus = "calling";
+
+      this.$nextTick(() => {
+        this.$refs.callButton.startCall();
+
+        // 鐩戝惉閫氳瘽鐘舵�佸彉鍖�
+        this.$refs.callButton.$on("call-status-change", (status) => {
+          this.handleCallStatusChange(status);
+        });
+      });
+    },
+
+    // 澶勭悊閫氳瘽鐘舵�佸彉鍖�
+    handleCallStatusChange(status) {
+      console.log(status,'status');
+
+      this.callStatus = status.type;
+
+      if (status.type === "connected") {
+        this.currentCall = {
+          phone: this.currentPhoneNumber,
+          type: this.callType,
+          startTime: new Date(),
+        };
+      } else if (status.type === "ended" || status.type === "failed") {
+        this.currentCall = null;
+      }
+
+      // 鍙互鏍规嵁鐘舵�佹墽琛屽叾浠栨搷浣�
+      if (status.type === "failed") {
+        this.$message.error(`鍛煎彨澶辫触: ${status.text}`);
+      }
+    },
+
+    // 缁撴潫褰撳墠閫氳瘽
+    endCurrentCall() {
+      if (!this.currentCall) return;
+
+      this.isEndingCall = true;
+      this.$refs.callButton.endCall();
+
+      // 3绉掑悗閲嶇疆鐘舵��
+      setTimeout(() => {
+        this.isEndingCall = false;
+      }, 3000);
     },
     yuyingetdetail() {
       this.tableDatatop.forEach((item, index) => {
@@ -1285,7 +1401,7 @@
             })
             .catch(() => {
               if (this.form.serviceType == 13) {
-                if (this.visitCount!=1) {
+                if (this.visitCount != 1) {
                   this.$router.push({
                     path: "/logisticsservice/zbAgain",
                   });
@@ -1295,7 +1411,7 @@
                   });
                 }
               } else if (form.serviceType == 2) {
-                if (this.visitCount!=1) {
+                if (this.visitCount != 1) {
                   this.$router.push({
                     path: "/followvisit/again",
                   });
@@ -1331,7 +1447,7 @@
           this.form = res.rows[0].serviceSubtaskList.find(
             (item) => item.id == this.id
           );
-          console.log(this.form.serviceType,'serviceType');
+          console.log(this.form.serviceType, "serviceType");
 
           this.logsheetlist = res.rows[0].serviceSubtaskList;
           this.templateid = this.logsheetlist[0].templateid;
@@ -1500,7 +1616,7 @@
           }
           // form.id = null;
           form.sendstate = 2;
-          console.log(form.serviceType,'form.serviceType');
+          console.log(form.serviceType, "form.serviceType");
 
           addserviceSubtask(form).then((res) => {
             if (res.code == 200) {
diff --git a/src/views/login.vue b/src/views/login.vue
index fe19af7..68eb677 100644
--- a/src/views/login.vue
+++ b/src/views/login.vue
@@ -119,6 +119,8 @@
         { value: "47231022633110211A2101", label: "涓芥按甯備腑鍖婚櫌" },
         { value: "47246102433112211A2101", label: "缂欎簯鍘夸腑鍖诲尰闄� " },
         { value: "47240018433118111A2101", label: "榫欐硥甯備腑鍖诲尰闄� " },
+        { value: "47243006833112611A2101", label: "搴嗗厓鍘夸腑鍖诲尰闄� " },
+        { value: "47234002X33112111A2101", label: "闈掔敯鍘夸腑鍖诲尰闄� " },
        ],
       loginRules: {
         username: [

--
Gitblit v1.9.3