吉安感知网项目-前端
罗广辉
2026-08-13 1d552a3bad95caae4af15322df28e6240b797693
applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue
@@ -8,7 +8,7 @@
         <div class="contactAvatar">
            <img :src="defaultAvatar" alt=""></img>
         </div>
         <!--  联系人名称-->
         <div class="contactName">
            {{ contactName }}
@@ -22,7 +22,7 @@
            <strong v-if="callEnded" class="status-text ended-status">{{ endMessage }}</strong>
         </div>
         <div class="button-group">
            <button v-if="state === 'idle' && !receiveParameters" @click="requestCall" :disabled="!connected" class="btn btn-call">呼叫</button>
            <!-- <button v-if="state === 'idle' && !receiveParameters && !callEnded" @click="requestCall" :disabled="!connected" class="btn btn-call">呼叫</button> -->
            <button v-if="state==='ringing'" @click="acceptCall" class="btn btn-accept incoming-accept">接听</button>
            <button v-if="state==='ringing'" @click="rejectCall" class="btn btn-reject incoming-reject">拒绝</button>
@@ -39,15 +39,17 @@
<script setup>
import { useRoute } from 'vue-router'
import defaultAvatar from '@/appDataSource/appwork/defaultAvatar.svg'
import { showToast } from 'vant'
const route = useRoute()
/** ✅ 你的 WS 地址前缀(后面拼 userId) */
// const WS_BASE = 'ws://218.202.104.82:38201/ws/chat?userId='
const WS_BASE = 'ws://218.202.104.82:38201/ws/chat'
const uid = ref('2011686857615683585')
const peerUid = ref('1')
// const WS_BASE = 'ws://218.202.104.82:38201/ws/chat'
const WS_BASE = 'ws://220.177.172.27:8100/webrtc/ws/chat'
const newUserInfo = ref(null)
const uid = ref('')
const peerUid = ref('')
// 联系人名称
const contactName = ref('张三')
const contactName = ref('')
const connected = ref(false)
const state = ref('idle') // idle | calling | ringing | in-call
@@ -91,17 +93,11 @@
const parseUrlParams = () => {
   // 1. 优先从route.query.voiceparams获取参数
   if (route.query && route.query.voiceparams) {
      console.log('从route.query.voiceparams获取参数:', route.query.voiceparams);
      try {
         // 解码并解析voiceparams参数
         const params = JSON.parse(decodeURIComponent(route.query.voiceparams));
         console.log('解析后的voiceparams:', params);
         if (params.peerUid) {
            peerUid.value = String(params.peerUid);
            log('🔗 从voiceparams获取peerUid:', params.peerUid);
         }
         peerUid.value = String(params.peerUid);
         contactName.value = params.callerName || '未知联系人'
         // 如果是来电类型,直接触发来电状态
         if (params.type === 'incoming') {
            log('📞 voiceparams检测到来电,直接进入来电状态');
@@ -113,9 +109,7 @@
            state.value = 'ringing';
            log('📞 收到来电 call,来自', incomingFrom);
            // 开始来电提示(铃声和震动)
            playRingtone();
            startVibration();
         }
      } catch (e) {
         log('❌ 解析voiceparams参数失败:', String(e));
@@ -123,44 +117,44 @@
   }
   // 2. 同时保留原有的URL参数解析逻辑,作为备选
   const url = new URL(window.location.href);
   const paramsStr = url.searchParams.get('params');
   if (paramsStr) {
      try {
         const params = JSON.parse(decodeURIComponent(paramsStr));
         if (params.peerUid) {
            peerUid.value = String(params.peerUid);
            log('🔗 从URL参数获取peerUid:', params.peerUid);
         }
      } catch (e) {
         log('❌ 解析URL参数失败:', String(e));
      }
   }
   // const url = new URL(window.location.href);
   // const paramsStr = url.searchParams.get('params');
   // if (paramsStr) {
   //    try {
   //       const params = JSON.parse(decodeURIComponent(paramsStr));
   //       if (params.peerUid) {
   //          peerUid.value = String(params.peerUid);
   //          log('🔗 从URL参数获取peerUid:', params.peerUid);
   //       }
   //    } catch (e) {
   //       log('❌ 解析URL参数失败:', String(e));
   //    }
   // }
}
// 从URL参数或本地存储获取用户ID
const getUserIdFromStorage = () => {
   // 从url获取用户id
   const contact = JSON.parse(decodeURIComponent(receiveParameters.value))
   console.log('从url获取用户id',contact?.userId)
   // 从本地获取用户id
   const userId = localStorage.getItem('userId') || sessionStorage.getItem('userId')
   console.log('从本地存储获取userId:', userId)
   if (userId) {
   const userId = '2021474815063486465'
   try {
      const params = JSON.parse(decodeURIComponent(route.query.params));
      newUserInfo.value = params.new_userInfo
      const userIdFromParams = params.new_userInfo?.userId
      console.log('从url获取用户id', userIdFromParams)
      return userIdFromParams || userId
   } catch (error) {
      return userId
   }
   return '2011686857615683585' // 默认值
}
// 心跳
let pingTimer = null
function startPing() {
   stopPing()
   // 每 25 秒发一次 ping(你后端有 PING/PONG)
   pingTimer = setInterval(() => {
      if (connected.value) send('ping', 'system', null)
   }, 25000)
}
// function startPing() {
//    stopPing()
//    // 每 25 秒发一次 ping(你后端有 PING/PONG)
//    pingTimer = setInterval(() => {
//       if (connected.value) send('ping', 'system', null)
//    }, 25000)
// }
function stopPing() {
   if (pingTimer) clearInterval(pingTimer)
@@ -196,7 +190,14 @@
function createPC() {
   pc = new RTCPeerConnection({
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
      iceServers: [{ urls: 'stun:stun.l.google.com:19302' },
         { urls: 'stun:stun.l.google.com:19302' },
         { urls: 'stun:stun1.l.google.com:19302' },
         { urls: 'stun:stun.stunprotocol.org:3478' },
         // 添加多个公共TURN服务器以提高跨网络连接成功率
         { urls: ['turn:freeturn.net:80', 'turn:freeturn.net:443'], username: 'free', credential: 'free' },
         // 备用TURN服务器
         { urls: 'turn:turn.cloudlifter.io:443', username: 'webrtc', credential: 'webrtc' }],
   })
   pc.onicecandidate = (e) => {
@@ -229,21 +230,43 @@
   }
   try {
      localStream = await navigator.mediaDevices.getUserMedia({
         audio: {
            echoCancellation: true,
            noiseSuppression: true,
            autoGainControl: true,
            // 移动端特定配置
            channelCount: 1,
            sampleRate: 16000,
            sampleSize: 16
         }
        })
      log('✅ 已获取本地麦克风')
      // 尝试使用完整约束
      try {
         localStream = await navigator.mediaDevices.getUserMedia({
            audio: {
               echoCancellation: true,
               noiseSuppression: true,
               autoGainControl: true,
               channelCount: 1,
               sampleRate: 16000,
               sampleSize: 16
            }
         })
         log('✅ 已获取本地麦克风(标准约束)')
      } catch (strictError) {
         // 如果标准约束失败,尝试最简单的约束
         log('⚠️ 标准约束失败,尝试简化约束:', String(strictError))
         localStream = await navigator.mediaDevices.getUserMedia({
            audio: true
         })
         log('✅ 已获取本地麦克风(简化约束)')
      }
      return localStream
   } catch (e) {
      log('❌ 获取麦克风失败:', String(e))
      // 提供更详细的错误信息
      if (e.name === 'NotReadableError') {
         log('❌ NotReadableError: 麦克风可能被其他应用占用,或权限未正确配置')
         showToast('麦克风被占用或权限未授予')
      } else if (e.name === 'NotAllowedError') {
         log('❌ NotAllowedError: 用户拒绝了麦克风权限')
         showToast('请授予麦克风权限')
      } else if (e.name === 'NotFoundError') {
         log('❌ NotFoundError: 未找到麦克风设备')
         showToast('未找到麦克风设备')
      }
      throw e
   }
}
@@ -397,7 +420,7 @@
   ws.onopen = () => {
      connected.value = true
      log('WS 已连接 userId=', uid.value)
      startPing()
      // startPing()
   }
   ws.onclose = () => {
@@ -434,6 +457,7 @@
   send('accept', incomingFrom, null)
   log('✅ 已点击接听,发送 accept 给', incomingFrom)
   state.value = 'calling'
   // 如果 offer 已经提前到达(我们暂存了),立刻 answer
@@ -441,6 +465,9 @@
      answerOffer(lastOfferSdp, incomingFrom)
      lastOfferSdp = null
   }
   // 停止
   const transmitData = { data: { sign:'closeBox' } }
   uni.postMessage(transmitData)
}
function rejectCall() {
@@ -449,6 +476,8 @@
   // 你后端没有 reject,用 busy 表示拒绝/不可接听
   send('busy', incomingFrom, null)
   log('❌ 已拒绝来电,发送 busy 给', incomingFrom)
   const transmitData = { data: { type: 'hangupVoice',sign:'closeBox'  } }
uni.postMessage(transmitData)
   incomingFrom = null
   acceptedByMe = false
@@ -500,6 +529,9 @@
}
function hangup() {
   // 停止铃声
//    const transmitData = { data: { type: 'stopVoice' } }
// uni.postMessage(transmitData)
   cleanup(true)
}
@@ -530,13 +562,13 @@
   lastOfferSdp = null
   state.value = 'idle'
   // 清除之前的定时器
   if (endTimer.value) {
      clearTimeout(endTimer.value)
      endTimer.value = null
   }
   // 根据是谁发起的挂断决定不同的行为
   if (sendToPeer) {
      // 自己主动挂断:不提示,不延迟,直接跳转
@@ -548,11 +580,14 @@
      } else {
         log('❌ uni对象不存在,无法返回上一页')
      }
      const transmitData = { data: { type: 'hangupVoice' } }
      uni.postMessage(transmitData)
   } else {
      // 对方挂断:显示提示,延迟10秒跳转
      callEnded.value = true
      endMessage.value = '对方已挂断'
      const transmitData = { data: { type: 'hangupVoice',sign:'closeBox'  } }
      uni.postMessage(transmitData)
      // 延迟5秒后跳转页面
      endTimer.value = setTimeout(() => {
         // 返回上一页(uni-app的语音通话列表页面)
@@ -561,9 +596,10 @@
         } else {
            log('❌ uni对象不存在,无法返回上一页')
         }
         // 重置通话结束状态
         callEnded.value = false
      }, 5000)
      }, 2000)
   }
}
@@ -571,16 +607,15 @@
const receiveParameters = ref(route.query.contact || null);
onMounted(() => {
   console.log('收拾收拾',route.query.voiceparams);
   console.log('接收参数', receiveParameters)
   console.log('对方打给我的',route.query.voiceparams);
   console.log('接收拨打电话:对方参数', receiveParameters)
   if (route.query && route.query.params) {
      try {
         const params = JSON.parse(decodeURIComponent(route.query.params));
         // 获取token
         if (params.access_token) {
            accessToken.value = params.access_token;
            log('🔗 从params获取access_token:', accessToken.value);
         }
         }
      } catch (e) {
         log('❌ 解析params参数失败:', String(e));
      }
@@ -589,11 +624,9 @@
   if (receiveParameters.value) {
      try {
         const contact = JSON.parse(decodeURIComponent(receiveParameters.value))
         if (contact.friendId) {
            peerUid.value = contact.friendId
            contactName.value = contact.friendNickName || '未知联系人'
            log('🔗 从contact参数获取peerUid:', contact.friendId)
            log('👤 联系人名称:', contactName.value)
         if (contact.userId) {
            peerUid.value = contact.userId
            contactName.value = contact.nickName || '未知联系人'
         }
      } catch (e) {
         log('❌ 解析contact参数失败:', String(e))
@@ -628,6 +661,9 @@
   font-family: Arial, sans-serif;
   background-color: #f5f5f5;
   transition: all 0.3s ease;
   display: flex;
   align-items: center;
   justify-content: center;
}
/* 来电时的全屏样式 */
@@ -654,12 +690,11 @@
.content-wrapper {
   display: flex;
   flex-direction: column;
   gap: 20px;
   gap: 30px;
   max-width: 600px;
   margin: 0 auto;
   height: 100vh;
   justify-content: center;
   width: 100%;
   align-items: center;
   padding: 20px;
}
.input-group {
@@ -692,7 +727,7 @@
   gap: 50px;
   flex-wrap: wrap;
   justify-content: center;
   margin-top: 80px;
   margin-top: 40px;
}
.btn {
@@ -774,18 +809,16 @@
   font-size: 18px;
   color: #333;
   text-align: center;
   margin-bottom: 20px;
   margin-bottom: 10px;
}
   /* 联系人名称样式 */
   .contactName {
      font-size: 18px;
      font-size: 20px;
      font-weight: bold;
      color: #333;
      text-align: center;
      margin-top: 10px;
         margin-bottom: 40px;
      margin: 15px 0;
   }
/* 来电状态样式 */
@@ -876,10 +909,9 @@
   }
}
/* 来电时的头像放大效果 */
/* 来电时的头像样式 */
.call-container.incoming-call .contactAvatar {
   width: 220px;
   height: 220px;
   margin-bottom: 60px;
   width: 180px;
   height: 180px;
}
</style>
</style>