吉安感知网项目-前端
chenyao
2026-02-12 6c3a421bce3cdcd5705cd694938642885db605e4
Merge remote-tracking branch 'origin/master'
6 files modified
285 ■■■■ changed files
applications/drone-command/src/views/audioCall.vue 8 ●●●● patch | view | raw | blame | history
applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue 191 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/App.vue 12 ●●●●● patch | view | raw | blame | history
uniapps/work-app/src/hooks/useGlobalWS.js 9 ●●●● patch | view | raw | blame | history
uniapps/work-app/src/manifest.json 4 ●●● patch | view | raw | blame | history
uniapps/work-app/src/subPackages/voiceCallDetail/index.vue 61 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/audioCall.vue
@@ -36,10 +36,10 @@
import { ref, computed, onBeforeUnmount } from 'vue'
/** ✅ 你的 WS 地址前缀(后面拼 userId) */
const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId='
const WS_BASE = 'ws://218.202.104.82:38201/ws/chat'
const uid = ref('2')
const peerUid = ref('3')
const uid = ref('2021474568497131521')
const peerUid = ref('2021475329842999298')
const connected = ref(false)
const state = ref('idle') // idle | calling | ringing | in-call
@@ -310,7 +310,7 @@
function connectWS() {
    if (ws) ws.close()
    ws = new WebSocket(WS_BASE + encodeURIComponent(uid.value))
    ws = new WebSocket(WS_BASE, "eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoyMDIxNDc0NTY4NDk3MTMxNTIxLCJ1c2VyX2tleSI6ImI4MzJhNzJhLTA5NDMtNGI1ZC04ZDRhLTZiYjlmZTJmM2VmMyIsInVzZXJuYW1lIjoiemhpZmVpIn0.amIbSnRkyof2nAal8U1uPO407fg9JfJk1yKoaAnX6nxbWwG5Yf8VY4Bto0D0EBvUNdoSFB2hbcOv5a80XEDDmA")
    ws.onopen = () => {
        connected.value = true
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 }}
@@ -39,77 +39,14 @@
<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'
// 解析URL参数
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);
            }
            // 如果是来电类型,直接触发来电状态
            if (params.type === 'incoming') {
                log('📞 voiceparams检测到来电,直接进入来电状态');
                incomingFrom = String(params.from);
                peerUid.value = incomingFrom;
                acceptedByMe = false;
                offeredByPeer = false;
                state.value = 'ringing';
                log('📞 收到来电 call,来自', incomingFrom);
                // 开始来电提示(铃声和震动)
                playRingtone();
                startVibration();
            }
        } catch (e) {
            log('❌ 解析voiceparams参数失败:', String(e));
        }
    }
    // 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));
        }
    }
}
// 从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) {
        return userId
    }
    return '3' // 默认值
}
const uid = ref('3')
const peerUid = ref('2')
const uid = ref('2021475329842999298')
const peerUid = ref('2021474568497131521')
// 联系人名称
const contactName = ref('张三')
@@ -150,6 +87,70 @@
function stopTimer() {
    if (timer) clearInterval(timer)
    timer = null
}
// 解析URL参数
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);
            }
            // 如果是来电类型,直接触发来电状态
            if (params.type === 'incoming') {
                log('📞 voiceparams检测到来电,直接进入来电状态');
                incomingFrom = String(params.from);
                peerUid.value = incomingFrom;
                acceptedByMe = false;
                offeredByPeer = false;
                state.value = 'ringing';
                log('📞 收到来电 call,来自', incomingFrom);
                // 开始来电提示(铃声和震动)
                playRingtone();
                startVibration();
            }
        } catch (e) {
            log('❌ 解析voiceparams参数失败:', String(e));
        }
    }
    // 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));
        }
    }
}
// 从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) {
        return userId
    }
    return '2021475329842999298' // 默认值
}
// 心跳
@@ -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
    }
}
@@ -530,13 +553,13 @@
    lastOfferSdp = null
    state.value = 'idle'
    // 清除之前的定时器
    if (endTimer.value) {
        clearTimeout(endTimer.value)
        endTimer.value = null
    }
    // 根据是谁发起的挂断决定不同的行为
    if (sendToPeer) {
        // 自己主动挂断:不提示,不延迟,直接跳转
@@ -552,7 +575,7 @@
        // 对方挂断:显示提示,延迟10秒跳转
        callEnded.value = true
        endMessage.value = '对方已挂断'
        // 延迟5秒后跳转页面
        endTimer.value = setTimeout(() => {
            // 返回上一页(uni-app的语音通话列表页面)
@@ -580,7 +603,7 @@
            if (params.access_token) {
                accessToken.value = params.access_token;
                log('🔗 从params获取access_token:', accessToken.value);
            }
            }
        } catch (e) {
            log('❌ 解析params参数失败:', String(e));
        }
@@ -882,4 +905,4 @@
    height: 220px;
    margin-bottom: 60px;
}
</style>
</style>
uniapps/work-app/src/App.vue
@@ -4,7 +4,7 @@
import { useGlobalWS } from '@/hooks/useGlobalWS.js'
// #ifdef APP-PLUS
import { keepAliveStart, requestIgnoreBatteryOptimization } from '@/uni_modules/keep-app'
import { openDialog, allowFloat,getBatteryCapacity } from '@/uni_modules/lgh-dialog'
import { openDialog, allowFloat, getBatteryCapacity } from '@/uni_modules/lgh-dialog'
// #endif
const appStore = useAppStore()
@@ -24,11 +24,8 @@
// testKeep()
// 开启悬浮窗
allowFloat()
// allowFloat()
// 假设五秒后来电
setTimeout(() => {
    openDialog()
}, 5000)
// #endif
useGlobalWS()
@@ -57,9 +54,9 @@
    // 初始化系统信息
    appStore.initSystemInfo()
    // #ifdef APP-PLUS
    keepAliveStart()
    // keepAliveStart()
    // 请求用户允许忽略电池优化(重要:用于保活)
    setTimeout(requestIgnoreBatteryOptimization, 2000)
    // setTimeout(requestIgnoreBatteryOptimization, 2000)
    // #endif
    if (!userStore.userInfo) {
        //不存在则跳转至登录页
@@ -106,4 +103,3 @@
    background: transparent !important;
}
</style>
uniapps/work-app/src/hooks/useGlobalWS.js
@@ -6,11 +6,14 @@
    const userStore = useUserStore();
  const appStore = useAppStore();
  const callStatus = ref(null)
  // token 存储
  const accessToken = ref('')
  // 设置默认用户ID为3
  const defaultUserId = '8';
  const defaultUserId = '2021475329842999298';
  // WebSocket基础URL
  const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId=';
  // const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId=';
  const WS_BASE = 'ws://218.202.104.82:38201/ws/chat';
    // 消息处理
  function messageHandler(payload) {
@@ -76,6 +79,8 @@
  // 初始化WebSocket连接
  function initWS() {
    const userStore = useUserStore()
    accessToken.value =userStore?.userInfo.access_token
    try {
      websocketService.setOnMessageCallback(messageHandler);
uniapps/work-app/src/manifest.json
@@ -53,12 +53,14 @@
                    "<uses-feature android:name=\"android.hardware.camera\"/>",
                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
                    "<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />",
                    "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />",
                    "<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\" />",
                    "<uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />",
                    "<uses-permission android:name=\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\" />",
                    "<uses-permission android:name=\"android.permission.SYSTEM_ALERT_WINDOW\" />",
                    "<uses-permission android:name=\"android.permission.USE_FULL_SCREEN_INTENT\" />"
                ]
                ],
                "minSdkVersion" : 21
            },
            /* ios打包配置 */
            "ios" : {
uniapps/work-app/src/subPackages/voiceCallDetail/index.vue
@@ -23,42 +23,41 @@
 */
async function requestAndroidMicPermission() {
  try {
    // 使用 uni 的权限 API(需要 HBuilderX 3.4.0+)
    if (uni.requestPermissions) {
      const result = await uni.requestPermissions({
        permissions: ['android.permission.RECORD_AUDIO']
      })
      console.log('权限请求结果:', result)
      if (result[0].granted) {
        console.log('✅ 麦克风权限已授予')
        return true
      } else {
        console.warn('⚠️ 麦克风权限被拒绝')
        // 可以提示用户
        uni.showModal({
          title: '权限提示',
          content: '语音通话需要麦克风权限,请前往设置开启',
          showCancel: false,
          success: () => {
            // 跳转到应用设置
            plus.runtime.openURL(plus.runtime.getProperty('package') + '://settings')
    // 获取麦克风权限
    return await new Promise((resolve) => {
      plus.android.requestPermissions(
        ['android.permission.RECORD_AUDIO'],
        (result) => {
          if (result.granted.length > 0) {
            console.log('麦克风权限已获取')
            resolve(true)
            return
          }
        })
        return false
      }
    }
    // 降级方案:使用原生方法
    return await requestAndroidMicPermissionNative()
          if (result.deniedAlways.length > 0) {
            uni.showModal({
              title: '提示',
              content: '需要麦克风权限才能正常使用语音功能,请前往系统设置开启',
              success(res) {
                if (res.confirm) {
                  plus.runtime.openURL('app-settings:')
                }
              },
            })
          }
          resolve(false)
        },
        (error) => {
          console.error('请求麦克风权限失败', error)
          resolve(false)
        }
      )
    })
  } catch (error) {
    console.error('❌ 请求麦克风权限失败:', error)
    console.error('❌ 请求麦克风权限失败', error)
    return false
  }
}
// #endif
onLoad(async (options) => {