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