| applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue | ●●●●● patch | view | raw | blame | history | |
| uniapps/work-app/src/hooks/useGlobalWS.js | ●●●●● patch | view | raw | blame | history | |
| uniapps/work-app/src/pages/login/index.vue | ●●●●● patch | view | raw | blame | history | |
| uniapps/work-app/src/utils/websocket.js | ●●●●● patch | view | raw | blame | history |
applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue
@@ -1,17 +1,17 @@ <template> <div class="call-container" :class="{'incoming-call': state === 'ringing'} "> <div class="content-wrapper"> <!-- <div class="input-group">--> <!-- <div class="input-item">--> <!-- <span class="input-label">我的 userId:</span>--> <!-- <input v-model="uid" class="input-field" />--> <!-- </div>--> <!-- --> <!-- <div class="input-item">--> <!-- <span class="input-label">对方 userId:</span>--> <!-- <input v-model="peerUid" class="input-field" />--> <!-- </div>--> <!-- </div>--> <!-- <div class="input-group"> <div class="input-item"> <span class="input-label">我的 userId:</span> <input v-model="uid" class="input-field" /> </div> <div class="input-item"> <span class="input-label">对方 userId:</span> <input v-model="peerUid" class="input-field" /> </div> </div>--> <div class="button-group" v-if="false"> <button @click="connectWS" :disabled="connected" class="btn btn-connect">连接WS</button> @@ -27,7 +27,7 @@ <strong v-if="state==='in-call'" class="status-text">通话时长:{{ durationText }}</strong> </div> <div class="button-group"> <button v-if="false" @click="requestCall" :disabled="!connected || state!=='idle'" class="btn btn-call">呼叫</button> <button @click="requestCall" :disabled="!connected || state!=='idle'" 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> @@ -38,16 +38,16 @@ </div> <!-- <div class="tip-message">--> <!-- <small>提示:若 getUserMedia 不可用,请用 https 或 localhost 访问页面。</small>--> <!-- </div>--> <!-- <div class="tip-message"> <small>提示:若 getUserMedia 不可用,请用 https 或 localhost 访问页面。</small> </div>--> <!-- <div class="log-container">--> <!-- <div class="log-box">--> <!-- <div class="log-title">日志信息:</div>--> <!-- <pre class="log-content">{{ logText }}</pre>--> <!-- </div>--> <!-- </div>--> <!-- <div class="log-container"> <div class="log-box"> <div class="log-title">日志信息:</div> <pre class="log-content">{{ logText }}</pre> </div> </div>--> <audio ref="remoteAudio" autoplay class="hidden-audio"></audio> </div> @@ -75,6 +75,16 @@ log('❌ 解析URL参数失败:', String(e)) } } } // 从URL参数或本地存储获取用户ID const getUserIdFromStorage = () => { const userId = localStorage.getItem('userId') || sessionStorage.getItem('userId') if (userId) { log('🔗 从本地存储获取userId:', userId) return userId } return '3' // 默认值 } const uid = ref('3') @@ -318,6 +328,8 @@ /* ---------------- 信令处理 ---------------- */ async function onSignal(msg) { console.log('1cesss',msg); if (!msg) return const t = (msg.type || '').toString() @@ -424,6 +436,9 @@ /* ---------------- 业务操作 ---------------- */ function connectWS() { // 确保获取最新的用户ID uid.value = getUserIdFromStorage() if (ws) ws.close() ws = new WebSocket(WS_BASE + encodeURIComponent(uid.value)) @@ -587,6 +602,21 @@ onMounted(() => { const receiveParameters = route.query.contact console.log('接收参数', receiveParameters) // 解析并使用contact参数 if (receiveParameters) { try { const contact = JSON.parse(decodeURIComponent(receiveParameters)) if (contact.friendId) { peerUid.value = contact.friendId log('🔗 从contact参数获取peerUid:', contact.friendId) log('👤 联系人名称:', contact.friendNickName || '未知联系人') } } catch (e) { log('❌ 解析contact参数失败:', String(e)) } } // 解析URL参数 parseUrlParams() // 自动连接WebSocket @@ -594,11 +624,11 @@ }) // 监听连接状态,连接成功后自动呼叫 watch(() => connected.value, (newVal) => { if (newVal && state.value === 'idle') { requestCall() } }) // watch(() => connected.value, (newVal) => { // if (newVal && state.value === 'idle') { // requestCall() // } // }) onBeforeUnmount(() => { cleanup(false) uniapps/work-app/src/hooks/useGlobalWS.js
@@ -1,21 +1,120 @@ import {useUserStore} from "@/store/index.js"; import {getEnvObj} from "@/utils/index.js"; // import {enterRoom} from "@/utils/voiceCallByTX/index.js"; import { ref, computed, watch } from "vue"; import useAppStore from "../store/modules/app/index.js"; let socketTask = null import websocketService from "@/utils/websocket.js"; export function useGlobalWS() { const userStore = useUserStore(); const appStore = useAppStore(); const userId = computed(() => userStore?.userInfo?.user_id) const access_token = computed(() => userStore?.userInfo?.access_token) const {VITE_APP_WS_API_URL} = getEnvObj() // 设置默认用户ID为3 const defaultUserId = '3'; // WebSocket基础URL const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?userId='; // 消息处理 function messageHandler(payload) { switch (payload.biz_code) { console.log('🌐 全局WebSocket收到消息111111111111111111111:', payload) // 先尝试直接处理消息(适用于mobile-web-view的voiceCallDetail页面的消息格式) const t = (payload.type || '').toString() const bizCode = payload.biz_code || '' console.log('📋 消息分析:type=' + t + ', biz_code=' + bizCode) // 处理语音通话请求 if (t === 'call') { console.log('📞 全局收到来电 call,来自', payload.from) // 构建来电参数 const callParams = { peerUid: payload.from, from: payload.from, type: 'incoming' }; // 转义参数以便在URL中传递 const encodedParams = encodeURIComponent(JSON.stringify(callParams)); // 跳转到mobile-web-view应用中的语音通话页面 const voiceCallUrl = `http://localhost:9527/work-app/#/subPackages/voiceCallDetail/index?params=${encodedParams}`; console.log('🔗 准备导航到:', voiceCallUrl) try { // 优先使用uni-app的导航API(适用于uni-app环境) if (typeof uni !== 'undefined' && uni.navigateTo) { console.log('使用uni-app导航API跳转到语音通话页面'); // 检查是否是跳转到应用内页面 if (voiceCallUrl.includes('/subPackages/voiceCallDetail/')) { // 应用内页面跳转 uni.navigateTo({ url: voiceCallUrl.replace('http://localhost:9527/work-app/#', ''), success: () => { console.log('✅ 应用内导航成功!'); }, fail: (err) => { console.error('❌ 应用内导航失败:', err); // 如果应用内导航失败,尝试使用外部跳转 fallbackToExternalNavigation(); } }); } else { // 外部页面跳转 fallbackToExternalNavigation(); } } // 否则尝试使用window.location跳转(适用于Web环境) else if (typeof window !== 'undefined' && window.location) { console.log('使用window.location跳转到语音通话页面'); window.location.href = voiceCallUrl; } else { console.error('无法跳转到语音通话页面:当前环境不支持导航'); } } catch (error) { console.error('跳转到语音通话页面失败:', error); // 尝试使用外部跳转作为最后的补救措施 fallbackToExternalNavigation(); } // 外部页面跳转的回调函数 function fallbackToExternalNavigation() { console.log('尝试使用外部导航跳转到语音通话页面'); try { // 使用uni-app的外部链接跳转API if (typeof uni !== 'undefined' && uni.navigateToWebview) { // 这是一个示例,具体API可能需要根据uni-app版本和配置调整 uni.navigateToWebview({ url: voiceCallUrl, success: () => { console.log('✅ 外部Webview导航成功!'); }, fail: (err) => { console.error('❌ 外部Webview导航失败:', err); // 如果还是失败,尝试使用浏览器打开 if (typeof plus !== 'undefined' && plus.runtime) { plus.runtime.openURL(voiceCallUrl); } else { console.error('❌ 所有导航方式都失败了'); } } }); } // 或者使用plus.runtime.openURL(适用于App环境) else if (typeof plus !== 'undefined' && plus.runtime) { plus.runtime.openURL(voiceCallUrl); console.log('尝试使用plus.runtime.openURL打开外部链接'); } else { console.error('无法跳转到外部页面:当前环境不支持外部导航'); } } catch (fallbackError) { console.error('外部导航尝试失败:', fallbackError); } } return } // 然后处理biz_code格式的消息 switch (bizCode) { case 'JOB_ISREFRESH': appStore.setJobUpdateKeyAdd() break @@ -34,71 +133,35 @@ // enterRoom(payload, userId.value) break default: // 记录未处理的消息 console.log('未处理的WebSocket消息:', payload) break; } } // 关闭ws function closeWS() { socketTask?.close({ success: () => { console.log('ws关闭连接'); }, }) } // 初始化ws // 初始化WebSocket连接 function initWS() { // 关闭,再连接ws closeWS() if (!access_token.value) return const url = VITE_APP_WS_API_URL + `?x-auth-token=${encodeURI(access_token?.value)}` + `&model_type=3&workspace-id=${userId.value}` // 创建连接 socketTask = uni.connectSocket({ url: url, success: () => { console.log('ws连接成功'); try { // 使用默认用户ID初始化WebSocket连接 websocketService.init(defaultUserId, WS_BASE); console.log('🌐 全局WebSocket初始化成功,默认用户ID:', defaultUserId); // 设置全局消息处理回调 websocketService.setOnMessageCallback(messageHandler); console.log('✅ 全局WebSocket消息处理器已设置'); } catch (error) { console.error('❌ 全局WebSocket初始化失败:', error); } } // 监听用户信息变化,初始化WebSocket watch( () => userStore.userInfo, (newUserInfo) => { console.log('👤 用户信息变化,尝试初始化WebSocket'); initWS(); }, fail: (err) => { console.error('ws连接失败:', err); } }); // 消息监听 socketTask.onMessage((result) => { messageHandler(JSON.parse(result.data)) }) //================================== // 监听连接打开 socketTask.onOpen((res) => { console.log('✅ WebSocket连接已建立') // reconnectAttempts = 0 // 连接成功后重置重连次数 // 可以在这里发送心跳或订阅消息 // startHeartbeat() }) // 监听连接关闭 socketTask.onClose((res) => { console.log(`WebSocket连接关闭,代码: ${res.code}, 原因: ${res.reason}`) // 根据不同的关闭代码处理 if (res.code === 1000) { // 正常关闭 console.log('连接正常关闭') } else if (res.code === 1006) { // 异常关闭 console.log('连接异常关闭,尝试重连...') } else if (res.code === 1011) { // 服务器内部错误 console.log('服务器内部错误(1011),延迟重连...') } else { console.log('其他原因关闭,尝试重连...') } }) // 监听错误 socketTask.onError((err) => { console.error('WebSocket发生错误:', err) }) } // watch(access_token, initWS, {immediate: true}) { immediate: true } ) } uniapps/work-app/src/pages/login/index.vue
@@ -45,6 +45,7 @@ import md5 from "js-md5"; import { loginByUsername } from "@/api/user/index.js"; import { useUserStore } from "@/store/index.js"; import websocketService from "@/utils/websocket.js"; import { HOME_PATH, LOGIN_PATH, removeQueryString } from "@/router"; @@ -134,7 +135,59 @@ userInfo.key, userInfo.code ); console.log('登录成功,用户信息:', res.data.data); userStore.setUserInfo(res.data.data); // 初始化WebSocket连接 try { const wsUrl = 'wss://wrj.shuixiongit.com/ws/chat?userId='; // 设置默认用户ID为3 const defaultUserId = '3'; // 使用默认用户ID初始化WebSocket连接 websocketService.init(defaultUserId, wsUrl); console.log('登录成功,WebSocket已初始化,默认用户ID:', defaultUserId); // 设置来电消息回调 websocketService.setOnMessageCallback((message) => { if (message.type === 'call') { console.log('收到来电,准备跳转到语音通话页面'); // 构建来电参数 const callParams = { peerUid: message.from, from: message.from, type: 'incoming' }; // 转义参数以便在URL中传递 const encodedParams = encodeURIComponent(JSON.stringify(callParams)); try { // 优先使用uni-app的导航API(适用于同应用内跳转) if (typeof uni !== 'undefined' && uni.navigateTo) { console.log('使用uni-app导航API跳转到语音通话页面'); uni.navigateTo({ url: `/subPackages/voiceCallDetail/index?params=${encodedParams}` }); } // 否则使用window.location跳转(适用于Web环境) else if (typeof window !== 'undefined' && window.location) { console.log('使用window.location跳转到语音通话页面'); // 注意:实际部署时需要使用正确的URL const voiceCallUrl = `http://localhost:8080/appPages/voiceCallDetail/index.html?params=${encodedParams}`; window.location.href = voiceCallUrl; } else { console.error('无法跳转到语音通话页面:当前环境不支持导航'); } } catch (error) { console.error('跳转到语音通话页面失败:', error); } } }); } catch (error) { console.error('WebSocket初始化失败:', error); } uni.reLaunch({ url: "/pages/work/index", }); uniapps/work-app/src/utils/websocket.js
New file @@ -0,0 +1,188 @@ // WebSocket服务管理 class WebSocketService { constructor() { this.socketTask = null this.connected = false this.url = '' this.userId = '' this.onMessageCallback = null this.onOpenCallback = null this.onCloseCallback = null this.onErrorCallback = null this.pingTimer = null } // 初始化WebSocket连接 init(userId, url) { if (!url) { console.error('WebSocket初始化失败:缺少url') return } this.userId = userId || '' this.url = url + encodeURIComponent(this.userId) this.connect() } // 建立WebSocket连接 connect() { try { // 关闭现有连接 this.close() // 使用uni-app的WebSocket API this.socketTask = uni.connectSocket({ url: this.url, success: () => { console.log('WebSocket连接已请求') }, fail: (error) => { console.error('WebSocket连接请求失败:', error) if (this.onErrorCallback) { this.onErrorCallback(error) } } }) // 监听连接打开 this.socketTask.onOpen(() => { this.connected = true console.log('WebSocket已连接,userId:', this.userId) this.startPing() if (this.onOpenCallback) { this.onOpenCallback() } }) // 监听连接关闭 this.socketTask.onClose(() => { this.connected = false console.log('WebSocket已关闭') this.stopPing() if (this.onCloseCallback) { this.onCloseCallback() } }) // 监听连接错误 this.socketTask.onError((error) => { console.error('WebSocket错误:', error) this.connected = false this.stopPing() if (this.onErrorCallback) { this.onErrorCallback(error) } }) // 监听消息 this.socketTask.onMessage((res) => { try { const message = JSON.parse(res.data) console.log('收到WebSocket消息:', message) if (this.onMessageCallback) { this.onMessageCallback(message) } } catch (error) { console.error('解析WebSocket消息失败:', error) } }) } catch (error) { console.error('WebSocket连接失败:', error) if (this.onErrorCallback) { this.onErrorCallback(error) } } } // 发送消息 send(type, to, payload) { if (!type || !this.socketTask || !this.connected) { console.error('WebSocket发送失败:连接未建立') return } const message = { type, from: String(this.userId), to: String(to), payload: payload || null, timestamp: Date.now() } this.socketTask.send({ data: JSON.stringify(message), success: () => { console.log('WebSocket消息发送成功:', message) }, fail: (error) => { console.error('WebSocket消息发送失败:', error) } }) } // 开始心跳检测 startPing() { this.stopPing() // 每25秒发送一次心跳 this.pingTimer = setInterval(() => { if (this.connected) { this.send('ping', 'system') } }, 25000) } // 停止心跳检测 stopPing() { if (this.pingTimer) { clearInterval(this.pingTimer) this.pingTimer = null } } // 关闭连接 close() { if (this.socketTask) { this.socketTask.close({ code: 1000, reason: '正常关闭', success: () => { console.log('WebSocket已关闭') }, fail: (error) => { console.error('WebSocket关闭失败:', error) } }) this.socketTask = null } this.stopPing() this.connected = false } // 设置消息回调 setOnMessageCallback(callback) { this.onMessageCallback = callback } // 设置连接打开回调 setOnOpenCallback(callback) { this.onOpenCallback = callback } // 设置连接关闭回调 setOnCloseCallback(callback) { this.onCloseCallback = callback } // 设置错误回调 setOnErrorCallback(callback) { this.onErrorCallback = callback } // 获取连接状态 getConnected() { return this.connected } } // 导出单例实例 export default new WebSocketService()