| | |
| | | <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> |
| | | |
| | |
| | | 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); |
| | | |
| | | |
| | | // åå§åSIPè¿æ¥ |
| | | |
| | | sipService.init(this.sipConfig); |
| | | sipService.onStatusChange = (status) => { |
| | | this.sipStatus = status.text; |
| | | this.sipStatusClass = `status-${status.type}`; |
| | | }; |
| | | |
| | | // æ ¹æ®ç¶ææ´æ°UIææ§è¡å
¶ä»æä½ |
| | | 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> |
| | | |
| | |
| | | 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; |
| | | } |
| | |
| | | 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() |
¶Ô±ÈÐÂÎļþ |
| | |
| | | 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("æ åååçSDP:", 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 = "åªä½åå失败ï¼è¯·æ£æ¥ç¼è§£ç å¨é
ç½®"; |
| | | 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(); |
| | |
| | | this.ua = null; |
| | | this.currentSession = null; |
| | | this.onStatusChange = null; // ç¶æåååè° |
| | | this.onCallStatusChange = null; // æ°å¢éè¯ç¶æåè° |
| | | } |
| | | |
| | | // åå§åSIP客æ·ç«¯ |
| | |
| | | uri: config.sipUri, |
| | | password: config.password, |
| | | display_name: config.displayName, |
| | | iceservers:[], |
| | | iceservers: [], |
| | | // realm: config.realm, |
| | | register: true, |
| | | session_expires: 180, |
| | |
| | | "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, |
| | |
| | | ); |
| | | // å¨ä¼è¯å建åä¿®æ¹ SDP |
| | | this.currentSession.on("peerconnection", (pc) => { |
| | | this.updateCallStatus('calling', 'å¼å«ä¸...'); |
| | | pc.createOffer = (offerOptions) => { |
| | | return RTCPeerConnection.prototype.createOffer |
| | | .call(pc, offerOptions) |
| | |
| | | }); |
| | | }; |
| | | }); |
| | | 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) { |
| | |
| | | } |
| | | // ææå½åéè¯ |
| | | 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(); |
| | |
| | | ></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> |
| | |
| | | userid: "", |
| | | currentPhoneNumber: "", |
| | | callType: "", // ç¨äºåºåæ¯åªä¸ªçµè¯ |
| | | // å·²ææ°æ®... |
| | | callStatus: "idle", // idle, calling, connected, ended, failed |
| | | isEndingCall: false, |
| | | currentCall: null, // å½åéè¯å¯¹è±¡ |
| | | input: "ä»å¤©èº«ä½è¿ä¸é", |
| | | radio: "2", |
| | | taskname: "", |
| | |
| | | }, |
| | | pickerOptions: { |
| | | disabledDate(time) { |
| | | // ç¦ç¨ä»å¤©åä¹åçæ¥æ |
| | | return time.getTime() < Date.now() - 24 * 60 * 60 * 1000; |
| | | }, |
| | | // ç¦ç¨ä»å¤©åä¹åçæ¥æ |
| | | return time.getTime() < Date.now() - 24 * 60 * 60 * 1000; |
| | | }, |
| | | shortcuts: [ |
| | | { |
| | | text: "ä¸å¤©å", |
| | |
| | | patid: null, |
| | | }; |
| | | }, |
| | | |
| | | computed: { |
| | | callStatusText() { |
| | | const statusMap = { |
| | | idle: "åå¤å¼å«", |
| | | calling: `æ£å¨å¼å« ${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; |
| | |
| | | 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; |
| | | |
| | | // çå¾
ä¸ä¸ä¸ªtickç¡®ä¿å¼å·²æ´æ° |
| | | this.$nextTick(() => { |
| | | this.$refs.callButton.startCall(); |
| | | |
| | | // å¯éï¼æ ¹æ®ä¸åç±»ååä¸åå¤ç |
| | | if (type === "tel") { |
| | | console.log("æ£å¨å¼å«æ£è
æ¬äºº:", phone); |
| | | } else { |
| | | console.log("æ£å¨å¼å«è系人:", 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) => { |
| | |
| | | }) |
| | | .catch(() => { |
| | | if (this.form.serviceType == 13) { |
| | | if (this.visitCount!=1) { |
| | | if (this.visitCount != 1) { |
| | | this.$router.push({ |
| | | path: "/logisticsservice/zbAgain", |
| | | }); |
| | |
| | | }); |
| | | } |
| | | } else if (form.serviceType == 2) { |
| | | if (this.visitCount!=1) { |
| | | if (this.visitCount != 1) { |
| | | this.$router.push({ |
| | | path: "/followvisit/again", |
| | | }); |
| | |
| | | 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; |
| | |
| | | } |
| | | // 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) { |
| | |
| | | { value: "47231022633110211A2101", label: "丽水å¸ä¸å»é¢" }, |
| | | { value: "47246102433112211A2101", label: "ç¼äºå¿ä¸å»å»é¢ " }, |
| | | { value: "47240018433118111A2101", label: "龿³å¸ä¸å»å»é¢ " }, |
| | | { value: "47243006833112611A2101", label: "åºå
å¿ä¸å»å»é¢ " }, |
| | | { value: "47234002X33112111A2101", label: "éç°å¿ä¸å»å»é¢ " }, |
| | | ], |
| | | loginRules: { |
| | | username: [ |