Przeglądaj źródła

对讲系统弹窗,110一键报警弹窗,sip外拨基础功能实现

fbw 1 tydzień temu
rodzic
commit
b425682b55

+ 18 - 0
src/API/report.js

@@ -7,4 +7,22 @@ export const getMileageList = data =>
     url: 'customPage/page/getList',
     data,
     notShowLoading: true,
+  })
+
+// 获取通用报表信息
+export const getDataList = data =>
+  $http({
+    method: 'POST',
+    url: 'Report/Data/GetList',
+    data,
+    notShowLoading: true,
+  })
+
+// 更新通用报表信息
+export const updateDataList = data =>
+  $http({
+    method: 'POST',
+    url: 'customPage/Page/Update',
+    data,
+    notShowLoading: true,
   })

+ 10 - 0
src/API/sip.js

@@ -0,0 +1,10 @@
+import $http from "@/utils/request";
+
+//获取sip数据
+export const getSipList = (data) =>
+  $http({
+    method: "post",
+    url: "/sip/config/getList",
+    data,
+    notShowLoading: true,
+  });

BIN
src/assets/audio/phone_ringing.mp3


BIN
src/assets/audio/phone_trying.mp3


+ 4 - 0
src/views/basePage/components/dialog/AlarmMapDialog.vue

@@ -253,6 +253,7 @@ export default {
     ...mapGetters(["rtData", "buildInfo"]),
     perimeterAlarmType() {
       let alarmType = this.dialogConfig.dialogMsg.data.alarmType;
+      console.log("周界状态", this.dialogConfig.dialogMsg.data, alarmType);
       switch (alarmType) {
         case 1:
           return "离线";
@@ -270,6 +271,8 @@ export default {
     },
     fireAlarmType() {
       let alarmType = this.dialogConfig.dialogMsg.data.alarmType;
+      console.log("火灾状态", this.dialogConfig.dialogMsg.data, alarmType);
+
       switch (alarmType) {
         case 1:
           return "离线";
@@ -287,6 +290,7 @@ export default {
     },
     jgytAlarmType() {
       let alarmType = this.dialogConfig.dialogMsg.data.alarmType;
+      console.log("激光云台状态", this.dialogConfig.dialogMsg.data, alarmType);
       switch (alarmType) {
         case 1:
           return "离线";

+ 7 - 7
src/views/basePage/mixins/LoadLayer/perimeter.js

@@ -212,23 +212,23 @@ export default {
         let color = '#00FF00'
         switch (data.alarmType) {
           case -1:
-          case 0:
+          case 0: //在线-绿
             color = '#00FF00'
             break
-          case 1:
+          case 1: //离线-亮灰
             color = '#DCDCDC'
             break
-          case 2:
+          case 2: //警告-橙
             color = '#FFA500'
             break
-          case 3:
+          case 3: //报警-红
             color = '#FF0000'
             break
-          case 4:
+          case 4: //撤防-暗灰
             color = '#808080'
             break
-          case 5:
-            color = '#FFF100'
+          case 5: //故障-黄
+            color = '#FFF000'
             break
           default:
             break

+ 1 - 1
src/views/basePage/panels/RiskHint.vue

@@ -145,7 +145,7 @@ export default {
               e.content,
           };
         });
-        console.log("风险报警数据", alarmData, this.hintData1);
+        // console.log("风险报警数据", alarmData, this.hintData1);
       } catch (err) {}
     },
     openRiskDialog(data) {

+ 11 - 0
src/views/components/baseHeader/left.vue

@@ -89,6 +89,9 @@
             <!-- <el-dropdown-item @click.native="test3">
               <span style="display: block">防爆扩音</span>
             </el-dropdown-item> -->
+            <el-dropdown-item @click.native="test4">
+              <span style="display: block">对讲系统</span>
+            </el-dropdown-item>
             <el-dropdown-item @click.native="toAdmin">
               <span style="display: block">管理系统</span>
             </el-dropdown-item>
@@ -285,6 +288,14 @@ export default {
   },
   methods: {
     setShouldAnimation,
+    test4() {
+      console.log("电话对讲");
+      this.$store.dispatch("globalConfig/setVoiceCallDialog", {
+        show: true,
+        dialogMsg: {},
+        type: "All",
+      });
+    },
     test3() {
       console.log("防爆扩音");
       this.getTestList();

+ 382 - 14
src/views/components/dialog/Onekey110Dialog.vue

@@ -11,23 +11,73 @@
         <div class="detail-box">
           <el-row>
             <el-col :span="24">
-              <span class="detail-label">设备名称:{{ deviceInfo.name }}</span>
+              <audio
+                src="@/assets/audio/phone_ringing.mp3"
+                loop
+                hidden="true"
+                ref="phone_ringing"
+              ></audio>
+              <audio
+                src="@/assets/audio/phone_trying.mp3"
+                loop
+                hidden="true"
+                ref="phone_trying"
+              ></audio>
+              <audio autoplay ref="callAudio"></audio>
+              <span class="detail-label">紧急联系:</span
+              ><el-select
+                size="small"
+                v-model="deviceInfo.id"
+                @change="setData"
+              >
+                <el-option
+                  v-for="item in phoneList"
+                  :key="item.id"
+                  :value="item.id"
+                  :label="item.field002"
+                ></el-option>
+              </el-select>
+            </el-col>
+            <el-col :span="24">
+              <!-- <span class="detail-label">设备名称:{{ deviceInfo.name }}</span> -->
             </el-col>
             <el-col :span="24">
               <span class="detail-label"
+                >联系方式:
+                <span style="margin-left: 4px">
+                  {{ deviceInfo.position }}
+                </span>
+              </span>
+              <!-- <span class="detail-label"
                 >所在位置:{{ deviceInfo.position }}</span
-              >
+              > -->
             </el-col>
           </el-row>
-          <div class="btn-box" v-show="oneKey110.type === 'All'">
-            <el-button class="btn-cancel" size="medium" @click="closeDialog"
+          <div
+            class="btn-box"
+            v-show="oneKey110.type === 'All'"
+            v-for="(phone, index) in phones"
+            :key="index"
+            :label="phone.name"
+            :name="phone.name"
+          >
+            <el-button class="btn-cancel" size="smail" @click="closeDialog"
               >关闭</el-button
             >
+            <el-button
+              class="btn-close"
+              type="warning"
+              size="smail"
+              @click="closeCall"
+              :disabled="!phone.enableHungup"
+              >挂断</el-button
+            >
             <el-button
               class="btn-handle"
               type="danger"
-              size="medium"
+              size="smail"
               @click="open"
+              :disabled="!phone.enableCall"
               >110一键报警</el-button
             >
           </div>
@@ -39,6 +89,8 @@
 <script>
 import BaseDragBgDialog from "@/views/components/base/BaseDragBgDialog.vue";
 import { dealAlarm } from "@/API/common";
+import { getDataList } from "@/API/report";
+import { getSipList } from "@/API/sip";
 import { mapGetters } from "vuex";
 export default {
   name: "oneKey110Dialog",
@@ -47,6 +99,12 @@ export default {
     "oneKey110.show": {
       handler(newVal) {
         this.baseDialogConfig.show = newVal;
+        if (newVal) {
+          this.getPhoneDataList();
+          this.init();
+        } else {
+          this.closeCall();
+        }
       },
       // deep: true,
       immediate: true,
@@ -65,15 +123,28 @@ export default {
         zIndex: 10,
       },
       deviceInfo: {
-        name: "门卫室110一键报警设备",
-        position: "门卫室",
+        id: "",
+        name: "",
+        position: "",
       },
+      phoneList: [],
+      // 拨号链接
+      clientId: "",
+      dispalyDialog: false,
+      activeName: "",
+      sips: [],
+      phones: [],
+      pc: null,
+      localStream: null,
     };
   },
   computed: {
     ...mapGetters(["oneKey110"]),
   },
   mounted() {},
+  beforeDestroy() {
+    this.closeCall();
+  },
   methods: {
     closeDialog() {
       this.$store.dispatch("globalConfig/setOneKey110", {
@@ -82,6 +153,38 @@ export default {
         type: "All",
       });
     },
+    setData(data) {
+      console.log(data);
+      this.deviceInfo = this.phoneList
+        .filter((a) => a.id === data)
+        .map((p) => {
+          return {
+            id: p.id,
+            name: p.field002,
+            position: p.field003,
+          };
+        })[0];
+      setTimeout(() => {
+        this.phones[0].no = this.deviceInfo.position;
+      }, 200);
+    },
+    async getPhoneDataList() {
+      try {
+        const params = {
+          sql: "select CAST(id as char) as id,field001,field002,field003 from custom_table_emergency_phones", //紧急联系方式
+        };
+        const res = await getDataList(params);
+        this.phoneList = res.data.content;
+        if (this.phoneList.length > 0) {
+          this.deviceInfo.id = this.phoneList[0].id;
+          this.deviceInfo.name = this.phoneList[0].field002;
+          this.deviceInfo.position = this.phoneList[0].field003;
+        }
+        console.log("入场登记报表sql", params, res);
+      } catch (err) {
+        console.log(err);
+      }
+    },
     async handleDeal() {
       try {
         await dealAlarm({
@@ -94,9 +197,12 @@ export default {
         console.log(err);
       }
     },
+    closeCall() {
+      this.handleHungup(this.phones[0]);
+    },
     open() {
       this.$confirm(
-        this.deviceInfo.name + "即将启动 110报警 ,是否继续?",
+        "即将启动110紧急报警,呼叫" + this.deviceInfo.name + " ,是否继续?",
         "提示",
         {
           confirmButtonText: "确定",
@@ -106,16 +212,17 @@ export default {
         }
       )
         .then(() => {
-          // this.handleDeal()
+          this.phones[0].no = this.deviceInfo.position;
+          this.handleCall(this.phones[0]);
           this.$message({
             type: "success",
-            message: "启动110报警!",
+            message: "启动!",
           });
         })
         .catch(() => {
           this.$message({
             type: "info",
-            message: "报警取消",
+            message: "取消",
           });
         });
     },
@@ -125,6 +232,264 @@ export default {
         this.$refs.dealContent.focus();
       });
     },
+    beforeDialogClose() {
+      console.log("beforeDialogClose");
+      this.dispalyDialog = false;
+    },
+    click() {
+      this.dispalyDialog = true;
+    },
+    handleCall(phone) {
+      if (!phone.no) {
+        this.$message.error("请先输入号码");
+        return;
+      }
+      phone.ws.send(JSON.stringify({ cmd: 1, no: phone.no }));
+    },
+    handleAnswer(phone) {
+      phone.ws.send(JSON.stringify({ cmd: 2 }));
+    },
+    handleHungup(phone) {
+      phone.ws.send(JSON.stringify({ cmd: 3 }));
+      this.$refs.phone_trying.pause();
+      this.$refs.phone_ringing.pause();
+      this.stopPeer();
+    },
+    async startPeer(phone) {
+      if (this.pc) this.pc.close();
+      this.pc = new RTCPeerConnection();
+
+      this.pc.ontrack = (evt) => {
+        console.log("ontrack");
+        this.$refs.callAudio.srcObject = evt.streams[0];
+      };
+
+      this.localStream = await navigator.mediaDevices.getUserMedia({
+        video: false,
+        audio: true,
+      });
+      this.localStream.getTracks().forEach((track) => {
+        console.log("add local track " + track.kind + " to peer connection.");
+        console.log(track);
+        this.pc.addTrack(track, this.localStream);
+      });
+
+      this.pc.onicegatheringstatechange = () => {
+        console.log("onicegatheringstatechange: " + this.pc.iceGatheringState);
+      };
+
+      this.pc.oniceconnectionstatechange = () => {
+        console.log(
+          "oniceconnectionstatechange: " + this.pc.iceConnectionState
+        );
+        if (this.pc.iceConnectionState === "disconnected") {
+          this.handleHungup(phone);
+        }
+      };
+
+      this.pc.onconnectionstatechange = () => {
+        console.log("onconnectionstatechange: " + this.pc.connectionState);
+      };
+
+      this.pc.onsignalingstatechange = () => {
+        console.log("onsignalingstatechange: " + this.pc.signalingState);
+        // if (this.pc.signalingState == 'have-remote-offer') {
+        //   this.pc.createAnswer().then((answer) => {
+        //     this.pc.setLocalDescription(answer)
+        //     console.log('Sending answer SDP.')
+        //     console.log('SDP: ' + answer)
+        //     phone.ws.send(JSON.stringify({ cmd: 5, body: JSON.stringify(answer) }))
+        //   })
+        // }
+      };
+
+      this.pc.onicecandidate = (event) => {
+        if (event.candidate) {
+          console.log("new-ice-candidate:");
+          console.log(event.candidate.candidate);
+          console.log(event.candidate);
+          phone.ws.send(
+            JSON.stringify({ cmd: 4, body: JSON.stringify(event.candidate) })
+          );
+        }
+      };
+    },
+    stopPeer() {
+      if (this.localStream) {
+        this.localStream.getTracks().forEach((track) => {
+          track.stop();
+        });
+        this.localStream = null;
+      }
+      if (this.pc) {
+        this.pc.close();
+        this.pc.ontrack = null;
+        this.pc.onicegatheringstatechange = null;
+        this.pc.oniceconnectionstatechange = null;
+        this.pc.onconnectionstatechange = null;
+        this.pc.onsignalingstatechange = null;
+        this.pc.onicecandidate = null;
+        this.pc = null;
+      }
+    },
+    async init() {
+      this.clientId = Math.random().toString(16).substring(2, 8);
+      try {
+        const res = await getSipList({ enable: true });
+        if (res.code === 20000) {
+          this.sips = res.data.content;
+          if (this.sips) {
+            this.phones = this.sips.map((a) => {
+              const ws = new WebSocket(VUE_APP_BASE_WS() + "/SipOverWebSocket");
+              const obj = {
+                name: a.name,
+                no: "",
+                enableCall: true,
+                enableAnswer: false,
+                enableHungup: false,
+                ws,
+              };
+              const _this = this;
+              ws.onmessage = (evt) => {
+                const resp = JSON.parse(evt.data);
+                console.info(resp);
+                switch (resp.type) {
+                  case "invite":
+                    this.phoneNumber = resp.no;
+                    obj.no = resp.no;
+                    obj.enableCall = false;
+                    obj.enableAnswer = true;
+                    obj.enableHungup = true;
+                    console.log(this.$refs.phone_ringing);
+                    this.$refs.phone_ringing.currentTime = 0;
+                    this.$refs.phone_ringing.play();
+                    break;
+                  case "trying":
+                    this.$refs.phone_trying.currentTime = 0;
+                    this.$refs.phone_trying.play();
+                    break;
+                  case "ringing":
+                    this.$refs.phone_ringing.currentTime = 0;
+                    this.$refs.phone_ringing.play();
+                    break;
+                  case "failed":
+                    this.$message.error(resp.msg);
+                    this.stopPeer();
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    break;
+                  case "answered":
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    if (resp.isSelf) {
+                      obj.enableCall = false;
+                      obj.enableAnswer = false;
+                      obj.enableHungup = true;
+                    } else {
+                      obj.no = "";
+                      obj.enableCall = true;
+                      obj.enableAnswer = false;
+                      obj.enableHungup = false;
+                    }
+                    break;
+                  case "serverCallCancelled":
+                    this.stopPeer();
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    obj.no = "";
+                    obj.enableCall = true;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = false;
+                    break;
+                  case "callHungup":
+                    this.stopPeer();
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    obj.no = "";
+                    obj.enableCall = true;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = false;
+                    if (resp.isSelf) {
+                    } else {
+                    }
+                    break;
+                  case "call":
+                    if (resp.msg) {
+                      this.$message.error(resp.msg);
+                      break;
+                    }
+                    obj.enableCall = false;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = true;
+                    break;
+                  case "answer":
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    if (resp.msg) {
+                      this.$message.error(resp.msg);
+                      break;
+                    }
+                    obj.enableCall = false;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = true;
+                    break;
+                  case "hungup":
+                    this.stopPeer();
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    if (resp.msg) {
+                      this.$message.error(resp.msg);
+                      break;
+                    }
+                    obj.no = "";
+                    obj.enableCall = true;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = false;
+                    break;
+                  case "getOffer":
+                    const offer = JSON.parse(resp.body);
+                    console.log(
+                      "get offer: " + offer.type + " " + offer.sdp + "."
+                    );
+                    this.startPeer(obj).then(() => {
+                      this.pc.setRemoteDescription(
+                        new RTCSessionDescription(offer)
+                      );
+                      this.pc.createAnswer().then((answer) => {
+                        this.pc.setLocalDescription(answer);
+                        console.log("Sending answer SDP.");
+                        console.log("SDP: " + answer);
+                        ws.send(
+                          JSON.stringify({
+                            cmd: 5,
+                            body: JSON.stringify(answer),
+                          })
+                        );
+                      });
+                    });
+                    break;
+                  case "getIcecandidate":
+                    console.log("get icecandidate: ", resp.body);
+                    this.pc.addIceCandidate(JSON.parse(resp.body));
+                    break;
+                }
+              };
+              setTimeout(() => {
+                ws.send(
+                  JSON.stringify({ sipId: a.id, clientId: this.clientId })
+                );
+              }, 500);
+
+              return obj;
+            });
+
+            this.activeName = this.sips[0].name;
+          }
+        }
+      } catch (error) {
+        console.log(error);
+      }
+    },
   },
 };
 </script>
@@ -142,7 +507,7 @@ export default {
     .detail-box {
       .el-row {
         .detail-label {
-          vertical-align: top;
+          vertical-align: center;
           display: inline-block;
           text-align: right;
           padding-right: 0.052083rem /* 10/192 */;
@@ -157,8 +522,8 @@ export default {
         left: 50%;
         transform: translate(-55%, 0);
         .el-button {
-          width: 0.6125rem /* 78/192 */;
-          height: 0.291666rem /* 28/192 */;
+          width: 0.5125rem /* 78/192 */;
+          height: 0.241666rem /* 28/192 */;
           box-sizing: border-box;
           margin-top: 0.015625rem /* 3/192 */;
           color: #fff;
@@ -172,6 +537,9 @@ export default {
         .btn-handle.el-button {
           background: rgba(194, 18, 18, 0.6);
         }
+        .btn-close.el-button {
+          background: rgba(214, 125, 9, 0.842);
+        }
         .btn-cancel.el-button {
           background: rgba(13, 35, 64, 0.1);
         }

+ 326 - 101
src/views/components/dialog/VoiceCallDialog/Dialer.vue

@@ -1,5 +1,18 @@
 <template>
   <div class="dialer-container">
+    <audio
+      src="@/assets/audio/phone_ringing.mp3"
+      loop
+      hidden="true"
+      ref="phone_ringing"
+    ></audio>
+    <audio
+      src="@/assets/audio/phone_trying.mp3"
+      loop
+      hidden="true"
+      ref="phone_trying"
+    ></audio>
+    <audio autoplay ref="callAudio"></audio>
     <!-- 显示输入的电话号码 -->
     <el-input type="text" v-model="currentPhoneNumber" readonly />
     <div class="dialer-buttons">
@@ -13,20 +26,53 @@
       >
         {{ number }}
       </el-button>
-      <audio v-show="false" controls autoplay="autoplay" id="audioCtl"></audio>
     </div>
-    <!-- 语音输入按钮 -->
-    <el-button type="info" @click="startVoiceInput"> 语音输入 </el-button>
-    <!-- 清除输入的电话号码 -->
-    <el-button type="warning" @click="clearNumber">清除</el-button>
-    <!-- 模拟拨号操作 -->
-    <el-button type="success" @click="callNumber">拨号</el-button>
-    <!-- 模拟拨号操作 -->
-    <el-button type="danger" @click="downNumber">挂断</el-button>
+    <div
+      v-for="(phone, index) in phones"
+      :key="index"
+      :label="phone.name"
+      :name="phone.name"
+    >
+      <!-- 拨号 -->
+      <el-button :disabled="!phone.enableCall" @click="handleCall(phone)">
+        拨号
+      </el-button>
+      <el-button
+        type="info"
+        :disabled="!phone.enableCall"
+        @click="clearNumber(1)"
+      >
+        回退
+      </el-button>
+      <!-- 清除输入的电话号码 -->
+      <el-button
+        type="warning"
+        :disabled="!phone.enableCall"
+        @click="clearNumber(2)"
+        >清除</el-button
+      >
+      <!-- 模拟接听操作 -->
+      <el-button
+        type="success"
+        :disabled="!phone.enableAnswer"
+        @click="handleAnswer(phone)"
+        >接听</el-button
+      >
+      <!-- 模拟拨号操作 -->
+      <el-button
+        type="danger"
+        :disabled="!phone.enableHungup"
+        @click="handleHungup(phone)"
+        >挂断</el-button
+      >
+    </div>
   </div>
 </template>
   
-  <script>
+<script>
+import { getSipList } from "@/API/sip";
+import { mapGetters } from "vuex";
+
 export default {
   props: {
     phoneNumber: {
@@ -36,23 +82,43 @@ export default {
   },
   data() {
     return {
-      id: this.generateGuid(),
-      baseUrl: "api/webrtc/",
-      pc: null,
       // 拨号盘上的数字和符号
       numbers: ["1", "2", "3", "4", "5", "6", "7", "8", "9", "*", "0", "#"],
+      // 拨号链接
+      clientId: "",
+      dispalyDialog: false,
+      activeName: "",
+      sips: [],
+      phones: [],
+      pc: null,
+      localStream: null,
     };
   },
-  computed: {
-    getOfferUrl() {
-      return `${this.baseUrl}getoffer?id=${this.id}`;
-    },
-    setAnswerUrl() {
-      return `${this.baseUrl}setanswer?id=${this.id}`;
+  created() {
+    this.init();
+  },
+  watch: {
+    "voiceCallDialog.show": {
+      handler(newVal) {
+        if (newVal && localStorage.getItem("token") !== null) {
+          this.init();
+        }
+      },
+      // deep: true,
+      immediate: true,
     },
-    setIceCandidateUrl() {
-      return `${this.baseUrl}addicecandidate?id=${this.id}`;
+    phoneNumber: {
+      handler(newVal) {
+        if (newVal && newVal.length >= 5) {
+          this.phones[0].no = newVal;
+        }
+      },
+      // deep: true,
+      immediate: true,
     },
+  },
+  computed: {
+    ...mapGetters(["voiceCallDialog"]),
     currentPhoneNumber: {
       get() {
         return this.phoneNumber;
@@ -67,116 +133,275 @@ export default {
     appendNumber(number) {
       this.currentPhoneNumber += number;
     },
-    clearNumber() {
-      this.currentPhoneNumber = "";
-    },
-    callNumber() {
-      if (this.currentPhoneNumber) {
-        alert(`正在拨打号码: ${this.currentPhoneNumber}`);
-        this.start();
-      } else {
-        alert("请输入电话号码");
+    clearNumber(type) {
+      switch (type) {
+        case 1:
+          this.currentPhoneNumber = this.currentPhoneNumber.slice(0, -1);
+          break;
+        case 2:
+          this.currentPhoneNumber = "";
+          break;
+        default:
+          break;
       }
     },
-    downNumber() {
-      this.closePeer();
+    beforeDialogClose() {
+      console.log("beforeDialogClose");
+      this.dispalyDialog = false;
     },
-    startVoiceInput() {
-      const recognition = new (window.SpeechRecognition ||
-        window.webkitSpeechRecognition)();
-      recognition.lang = "zh-CN";
-
-      recognition.onresult = (event) => {
-        const transcript = event.results[0][0].transcript;
-        this.currentPhoneNumber = transcript.replace(/[^0-9*#]/g, "");
-      };
-      recognition.start();
+    click() {
+      this.dispalyDialog = true;
     },
-
-    generateGuid() {
-      return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(
-        /[xy]/g,
-        function (c) {
-          var r = (Math.random() * 16) | 0,
-            v = c == "x" ? r : (r & 0x3) | 0x8;
-          return v.toString(16);
-        }
-      );
+    handleCall(phone) {
+      if (!phone.no) {
+        this.$message.error("请先输入号码");
+        return;
+      }
+      phone.ws.send(JSON.stringify({ cmd: 1, no: phone.no }));
     },
-    async start() {
-      this.closePeer();
+    handleAnswer(phone) {
+      phone.ws.send(JSON.stringify({ cmd: 2 }));
+    },
+    handleHungup(phone) {
+      phone.ws.send(JSON.stringify({ cmd: 3 }));
+      this.$refs.phone_trying.pause();
+      this.$refs.phone_ringing.pause();
+      this.stopPeer();
+    },
+    async startPeer(phone) {
+      if (this.pc) this.pc.close();
+      this.pc = new RTCPeerConnection();
 
-      let videoControl = document.querySelector("#audioCtl");
+      this.pc.ontrack = (evt) => {
+        console.log("ontrack");
+        this.$refs.callAudio.srcObject = evt.streams[0];
+      };
 
-      const localStream = await navigator.mediaDevices.getUserMedia({
+      this.localStream = await navigator.mediaDevices.getUserMedia({
         video: false,
         audio: true,
       });
-      videoControl.srcObject = localStream;
-
-      this.pc = new RTCPeerConnection(null);
-
-      localStream.getTracks().forEach((track) => {
+      this.localStream.getTracks().forEach((track) => {
         console.log("add local track " + track.kind + " to peer connection.");
         console.log(track);
-        this.pc.addTrack(track, localStream);
+        this.pc.addTrack(track, this.localStream);
       });
 
-      this.pc.onicegatheringstatechange = function () {
+      this.pc.onicegatheringstatechange = () => {
         console.log("onicegatheringstatechange: " + this.pc.iceGatheringState);
-      }.bind(this);
+      };
 
-      this.pc.oniceconnectionstatechange = function () {
+      this.pc.oniceconnectionstatechange = () => {
         console.log(
           "oniceconnectionstatechange: " + this.pc.iceConnectionState
         );
-      }.bind(this);
+        if (this.pc.iceConnectionState === "disconnected") {
+          this.handleHungup(phone);
+        }
+      };
+
+      this.pc.onconnectionstatechange = () => {
+        console.log("onconnectionstatechange: " + this.pc.connectionState);
+      };
 
-      this.pc.onsignalingstatechange = function () {
+      this.pc.onsignalingstatechange = () => {
         console.log("onsignalingstatechange: " + this.pc.signalingState);
-      }.bind(this);
+        // if (this.pc.signalingState == 'have-remote-offer') {
+        //   this.pc.createAnswer().then((answer) => {
+        //     this.pc.setLocalDescription(answer)
+        //     console.log('Sending answer SDP.')
+        //     console.log('SDP: ' + answer)
+        //     phone.ws.send(JSON.stringify({ cmd: 5, body: JSON.stringify(answer) }))
+        //   })
+        // }
+      };
 
-      this.pc.onicecandidate = async function (event) {
+      this.pc.onicecandidate = (event) => {
         if (event.candidate) {
           console.log("new-ice-candidate:");
           console.log(event.candidate.candidate);
           console.log(event.candidate);
-          await fetch(this.setIceCandidateUrl, {
-            method: "POST",
-            body: JSON.stringify(event.candidate),
-            headers: { "Content-Type": "application/json" },
-          });
+          phone.ws.send(
+            JSON.stringify({ cmd: 4, body: JSON.stringify(event.candidate) })
+          );
         }
-      }.bind(this);
+      };
+    },
+    stopPeer() {
+      if (this.localStream) {
+        this.localStream.getTracks().forEach((track) => {
+          track.stop();
+        });
+        this.localStream = null;
+      }
+      if (this.pc) {
+        this.pc.close();
+        this.pc.ontrack = null;
+        this.pc.onicegatheringstatechange = null;
+        this.pc.oniceconnectionstatechange = null;
+        this.pc.onconnectionstatechange = null;
+        this.pc.onsignalingstatechange = null;
+        this.pc.onicecandidate = null;
+        this.pc = null;
+      }
+    },
+    async init() {
+      this.clientId = Math.random().toString(16).substring(2, 8);
+      try {
+        const res = await getSipList({ enable: true });
+        if (res.code === 20000) {
+          this.sips = res.data.content;
 
-      let offerResponse = await fetch(this.getOfferUrl);
-      let offer = await offerResponse.json();
-      console.log("got offer: " + offer.type + " " + offer.sdp + ".");
-      await this.pc.setRemoteDescription(offer);
+          if (this.sips) {
+            this.phones = this.sips.map((a) => {
+              const ws = new WebSocket(VUE_APP_BASE_WS() + "/SipOverWebSocket");
+              const obj = {
+                name: a.name,
+                no: "",
+                enableCall: true,
+                enableAnswer: false,
+                enableHungup: false,
+                ws,
+              };
+              const _this = this;
+              ws.onmessage = (evt) => {
+                const resp = JSON.parse(evt.data);
+                console.info(resp);
+                switch (resp.type) {
+                  case "invite":
+                    this.phoneNumber = resp.no;
+                    obj.no = resp.no;
+                    obj.enableCall = false;
+                    obj.enableAnswer = true;
+                    obj.enableHungup = true;
+                    console.log(this.$refs.phone_ringing);
+                    this.$refs.phone_ringing.currentTime = 0;
+                    this.$refs.phone_ringing.play();
+                    break;
+                  case "trying":
+                    this.$refs.phone_trying.currentTime = 0;
+                    this.$refs.phone_trying.play();
+                    break;
+                  case "ringing":
+                    this.$refs.phone_ringing.currentTime = 0;
+                    this.$refs.phone_ringing.play();
+                    break;
+                  case "failed":
+                    this.$message.error(resp.msg);
+                    this.stopPeer();
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    break;
+                  case "answered":
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    if (resp.isSelf) {
+                      obj.enableCall = false;
+                      obj.enableAnswer = false;
+                      obj.enableHungup = true;
+                    } else {
+                      obj.no = "";
+                      obj.enableCall = true;
+                      obj.enableAnswer = false;
+                      obj.enableHungup = false;
+                    }
+                    break;
+                  case "serverCallCancelled":
+                    this.stopPeer();
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    obj.no = "";
+                    obj.enableCall = true;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = false;
+                    break;
+                  case "callHungup":
+                    this.stopPeer();
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    obj.no = "";
+                    obj.enableCall = true;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = false;
+                    if (resp.isSelf) {
+                    } else {
+                    }
+                    break;
+                  case "call":
+                    if (resp.msg) {
+                      this.$message.error(resp.msg);
+                      break;
+                    }
+                    obj.enableCall = false;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = true;
+                    break;
+                  case "answer":
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    if (resp.msg) {
+                      this.$message.error(resp.msg);
+                      break;
+                    }
+                    obj.enableCall = false;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = true;
+                    break;
+                  case "hungup":
+                    this.stopPeer();
+                    this.$refs.phone_trying.pause();
+                    this.$refs.phone_ringing.pause();
+                    if (resp.msg) {
+                      this.$message.error(resp.msg);
+                      break;
+                    }
+                    obj.no = "";
+                    obj.enableCall = true;
+                    obj.enableAnswer = false;
+                    obj.enableHungup = false;
+                    break;
+                  case "getOffer":
+                    const offer = JSON.parse(resp.body);
+                    console.log(
+                      "get offer: " + offer.type + " " + offer.sdp + "."
+                    );
+                    this.startPeer(obj).then(() => {
+                      this.pc.setRemoteDescription(
+                        new RTCSessionDescription(offer)
+                      );
+                      this.pc.createAnswer().then((answer) => {
+                        this.pc.setLocalDescription(answer);
+                        console.log("Sending answer SDP.");
+                        console.log("SDP: " + answer);
+                        ws.send(
+                          JSON.stringify({
+                            cmd: 5,
+                            body: JSON.stringify(answer),
+                          })
+                        );
+                      });
+                    });
+                    break;
+                  case "getIcecandidate":
+                    console.log("get icecandidate: ", resp.body);
+                    this.pc.addIceCandidate(JSON.parse(resp.body));
+                    break;
+                }
+              };
+              setTimeout(() => {
+                ws.send(
+                  JSON.stringify({ sipId: a.id, clientId: this.clientId })
+                );
+              }, 500);
 
-      this.pc
-        .createAnswer()
-        .then(
-          function (answer) {
-            return this.pc.setLocalDescription(answer);
-          }.bind(this)
-        )
-        .then(
-          async function () {
-            console.log("Sending answer SDP.");
-            console.log("SDP: " + this.pc.localDescription.sdp);
-            await fetch(this.setAnswerUrl, {
-              method: "POST",
-              body: JSON.stringify(this.pc.localDescription),
-              headers: { "Content-Type": "application/json" },
+              return obj;
             });
-          }.bind(this)
-        );
-    },
-    closePeer() {
-      if (this.pc != null) {
-        console.log("close peer");
-        this.pc.close();
+
+            this.activeName = this.sips[0].name;
+          }
+        }
+      } catch (error) {
+        console.log(error);
       }
     },
   },

+ 1 - 1
src/views/components/dialog/VoiceCallDialog/index.vue

@@ -81,7 +81,7 @@ export default {
             title: "操作",
             prop: "operation",
             width: "",
-            btns: [{ btnName: "拨号", btnType: "open" }],
+            btns: [{ btnName: "寻呼", btnType: "open" }],
           },
         ],
         tableData: [],

+ 4 - 4
vue.config.js

@@ -17,8 +17,8 @@ module.exports = {
     },
     proxy: {
       '/yapi': {
-        target: 'http://192.168.18.200:18082/prod-api/', // 托克托现场
-        // target: 'https://10.0.0.201:8080/prod-api/', // 托克托现场
+        // target: 'http://192.168.18.200:18082/prod-api/', // 托克托现场
+        target: 'https://10.0.0.201:8080/prod-api/', // 托克托现场
         // target: 'http://172.168.0.62:8080/prod-api/', // 托克托现场
         // target: 'http://192.168.195.136:8080/prod-api/', // 托克托本地虚拟机
 
@@ -29,9 +29,9 @@ module.exports = {
         },
       },
       '/model': {
-        // target: 'https://10.0.0.201:8081/model/', //托克托模型现场服务器
+        target: 'https://10.0.0.201:8081/model/', //托克托模型现场服务器
         // target: 'http://172.168.0.62:8081/model/', //托克托模型现场服务器
-        target: 'http://192.168.195.136:8081/model/', //托克托模型虚拟机
+        // target: 'http://192.168.195.136:8081/model/', //托克托模型虚拟机
         changeOrigin: true,
         pathRewrite: {
           '^/model': '/',