| applications/drone-command/src/assets/images/active-menu-item.png | patch | view | raw | blame | history | |
| applications/drone-command/src/assets/images/menu-item.png | patch | view | raw | blame | history | |
| applications/drone-command/src/page/index/top/index.vue | ●●●●● patch | view | raw | blame | history | |
| applications/drone-command/src/utils/auth.js | ●●●●● patch | view | raw | blame | history | |
| applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue | ●●●●● patch | view | raw | blame | history | |
| applications/task-work-order/src/utils/auth.js | ●●●●● patch | view | raw | blame | history | |
| uniapps/work-app/src/api/voiceCall/index.js | ●●●●● patch | view | raw | blame | history | |
| uniapps/work-app/src/pages/voiceCall/index.vue | ●●●●● patch | view | raw | blame | history | |
| uniapps/work-app/src/subPackages/voiceCallDetail/index.vue | ●●●●● patch | view | raw | blame | history |
applications/drone-command/src/assets/images/active-menu-item.png
applications/drone-command/src/assets/images/menu-item.png
applications/drone-command/src/page/index/top/index.vue
@@ -1,38 +1,49 @@ <template> <div class="avue-top"> <div class="top-bar__title"> <img :src="logoUrl" alt=""> <span>低空飞行监管子系统</span> </div> <header class="header-container"> <div class="content-wrap"> <div class="logo-title-wrap"> <img :src="logoUrl" alt="Logo"></img> <p class="title">低空飞行监管子系统</p> </div> <div class="top-bar__right"> <div class="top-user"> <div class="icon-box"> <img class="gateway" @click="jumpMH" src="@/assets/images/mh.png" alt="进入门户" title="进入门户"> </div> <div class="header-right"> <nav class="nav-menu"> <div v-for="(item, index) in topMenus" :key="index" class="nav-item" :class="{ active: item.active }" @click="handleMenuClick(item)"> <span>{{ item.label }}</span> </div> </nav> <el-dropdown popper-class="command-custom-dropdown"> <span class="el-dropdown-link"> <img class="top-bar__img" :src="userInfo.avatar" alt="" /> </span> <div class="icon-group"> <div class="top-user"> <div class="icon-box"> <img class="gateway" @click="jumpMH" src="@/assets/images/mh.png" alt="进入门户" title="进入门户"> </div> <template #dropdown> <el-dropdown-menu> <!-- <el-dropdown-item> <el-dropdown popper-class="command-custom-dropdown"> <span class="el-dropdown-link"> <img class="top-bar__img" :src="userInfo.avatar" alt="" /> </span> <template #dropdown> <el-dropdown-menu> <!-- <el-dropdown-item> <router-link to="/">{{ $t('navbar.dashboard') }}</router-link> </el-dropdown-item>--> <!-- <el-dropdown-item> <!-- <el-dropdown-item> <router-link to="/info/index">{{ $t('navbar.userinfo') }}</router-link> </el-dropdown-item> --> <el-dropdown-item @click="logout" divided>{{ $t('navbar.logOut') }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> <top-setting></top-setting> <el-dropdown-item @click="logout" divided>{{ $t('navbar.logOut') }} </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> <top-setting></top-setting> </div> </div> </div> </div> </div> </header> </template> <script> @@ -69,6 +80,35 @@ data () { return { logoUrl: logo, activeMenu: 'drone-control', topMenus: [ { key: 'twin-supervision', label: '孪生监管', path: '/flight-supervision/#/brain', }, { key: 'airspace-collaboration', label: '空域协同', path: '/flight-supervision/#/space', }, { key: 'air-traffic', label: '空中交通', path: '/flight-supervision/#/activity', }, { key: 'information-service', label: '信息服务', path: '/flight-supervision/#/infoService', }, { key: 'drone-control', label: '无人机管控', path: '', active: true, }, ], } }, filters: {}, @@ -93,115 +133,189 @@ cancelButtonClass: 'command-message-box-cancel', }).then(() => { this.$store.dispatch('LogOut').then(() => { const {VITE_APP_PARENT_SYSTEM,VITE_APP_ENV} = import.meta.env const isDev = VITE_APP_ENV === 'development' isDev ? this.$router.push({ path: '/login' }) : window.location.replace(`${VITE_APP_PARENT_SYSTEM}/#/login`) const { VITE_APP_PARENT_SYSTEM, VITE_APP_ENV } = import.meta.env const isDev = VITE_APP_ENV === 'development' isDev ? this.$router.push({ path: '/login' }) : window.location.replace(`${VITE_APP_PARENT_SYSTEM}/#/login`) }) }) }, jumpMH () { window.open(`${window.location.origin}/droneWeb/#/gateway`, '_blank') }, handleMenuClick (item) { this.activeMenu = item.key if (!item.path) return window.open(`${window.location.origin}${item.path}`, '_blank') }, }, } </script> <style lang="scss" scoped> .avue-top { display: flex; justify-content: space-between; height: 100%; .header-container { position: relative; height: 110px; background: url('@/assets/images/topContainer/top-bg.png') center / 100% 100% no-repeat !important; background-repeat: no-repeat; background-size: cover; pointer-events: none; } z-index: 2; .top-bar__left { flex: 0 0 auto; } .top-bar__title { margin-top: 16px; margin-left: 30px; flex: 1; display: flex; align-items: center; height: pxToVh(48); img { margin-right: 17px; width: 50px; height: 50px; .bg { position: absolute; top: 0; left: 0; width: 100%; pointer-events: none; } span { width: 353px; height: 43px; font-family: PangMen; font-weight: 400; font-size: 36px; color: #FFFFFF; letter-spacing: 3px; text-shadow: 3px 3px 0px rgba(0, 13, 42, 0.27); text-align: left; font-style: normal; text-transform: none; } } .top-bar__right { margin-right: 28px; padding-top: 16px; flex: 0 0 auto; display: flex !important; align-items: flex-start; height: 100%; pointer-events: auto; box-sizing: border-box; .icon-box { margin-top: 6px; margin-right: 30px; .content-wrap { position: absolute; inset: 0; display: flex; align-items: center; gap: 0 20px; z-index: 1; padding: 0 40px; .gateway { width: 21px; height: 18px; .logo-title-wrap { display: flex; align-items: center; transform: translateY(-10px); img { width: 70px; height: 63px; vertical-align: middle; } .title { margin-left: 16px; margin-top: 0; margin-bottom: 0; width: 353px; height: 43px; font-family: PangMen; font-weight: 400; font-size: 36px; color: #FFFFFF; letter-spacing: 3px; text-shadow: 3px 3px 0px rgba(0, 13, 42, 0.27); text-align: left; font-style: normal; text-transform: none; } } >* { cursor: pointer; .header-right { display: flex; align-items: center; margin-left: auto; margin-top: -25px; pointer-events: auto; .nav-menu { display: flex; margin-right: 30px; gap: 0px; .nav-item { width: 136px; height: 34px; box-sizing: border-box; text-align: center; color: #afafe0; text-decoration: none; font-size: 20px; transition: color 0.3s; padding: 0; background: url('@/assets/images/menu-item.png') no-repeat center / 136px 34px; cursor: pointer; margin: 0 10px; display: flex; align-items: center; justify-content: center; padding-bottom: 10px; line-height: 1; font-family: YouSheBiaoTiHei; &:hover { color: #fff; } &.active { color: #fff; background: url('@/assets/images/active-menu-item.png') no-repeat center / 136px 34px; span { background: linear-gradient(180deg, #fff 22.11%, #ffffff 86.69%); background-size: contain; background-clip: text; -webkit-text-fill-color: transparent; text-shadow: 0px 0px 13px 0px #5da6ef73; } } } } .icon-group { display: flex; align-items: center; margin-right: 20px; gap: 15px; .icon-box { margin-top: 6px; margin-right: 30px; display: flex; align-items: center; gap: 0 20px; .gateway { width: 21px; height: 18px; } >* { cursor: pointer; } } } .top-bar__item { margin-right: 15px; display: inline-block !important; } .top-user { display: flex; align-items: center; } .top-bar__img { margin-top: 5px; width: 20px; height: 20px; border: 2px solid #383874; border-radius: 50%; } .el-dropdown-link { cursor: pointer; color: #606266; } .el-dropdown-link:hover { color: #409EFF; } } } } .top-bar__item { margin-right: 15px; display: inline-block !important; } .top-user { display: flex; align-items: center; } .top-bar__img { margin-top: 5px; width: 20px; height: 20px; border: 2px solid #383874; border-radius: 50%; } .el-dropdown-link { cursor: pointer; color: #606266; } .el-dropdown-link:hover { color: #409EFF; } </style> applications/drone-command/src/utils/auth.js
@@ -10,8 +10,8 @@ */ import Cookies from 'js-cookie'; const TokenKey = 'saber3-access-token'; const RefreshTokenKey = 'saber3-refresh-token'; const TokenKey = 'command-access-token'; const RefreshTokenKey = 'command-refresh-token'; const SessionId = 'JSESSIONID'; const UserId = 'b-user-id'; applications/mobile-web-view/src/appPages/voiceCallDetail/index.vue
@@ -41,8 +41,8 @@ import defaultAvatar from '@/appDataSource/appwork/defaultAvatar.svg' const route = useRoute() /** ✅ 你的 WS 地址前缀(后面拼 userId) */ const WS_BASE = 'wss://wrj.shuixiongit.com/ws/chat?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获取参数 @@ -105,11 +105,11 @@ if (userId) { return userId } return '8' // 默认值 return '3' // 默认值 } const uid = ref('8') const peerUid = ref('7') const uid = ref('3') const peerUid = ref('2') // 联系人名称 const contactName = ref('张三') @@ -121,6 +121,8 @@ const callEnded = ref(false) const endMessage = ref('对方已挂断') const endTimer = ref(null) // token 存储 const accessToken = ref('') let ws = null let pc = null @@ -227,7 +229,17 @@ } try { localStream = await navigator.mediaDevices.getUserMedia({ audio: true }) localStream = await navigator.mediaDevices.getUserMedia({ audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true, // 移动端特定配置 channelCount: 1, sampleRate: 16000, sampleSize: 16 } }) log('✅ 已获取本地麦克风') return localStream } catch (e) { @@ -378,8 +390,10 @@ if (ws) ws.close() ws = new WebSocket(WS_BASE + encodeURIComponent(uid.value)) // 使用从参数中获取的token或默认token const token = accessToken.value // ws = new WebSocket(WS_BASE + encodeURIComponent(uid.value)) ws = new WebSocket(WS_BASE, token) ws.onopen = () => { connected.value = true log('WS 已连接 userId=', uid.value) @@ -558,8 +572,19 @@ onMounted(() => { console.log('收拾收拾',route.query.voiceparams); console.log('接收参数', JSON.parse(decodeURIComponent(receiveParameters.value))) 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)); } } // 解析并使用contact参数 if (receiveParameters.value) { try { applications/task-work-order/src/utils/auth.js
@@ -1,8 +1,9 @@ import Cookies from 'js-cookie'; const TokenKey = 'saber3-access-token'; const RefreshTokenKey = 'saber3-refresh-token'; const TokenKey = 'work-access-token'; const RefreshTokenKey = 'work-refresh-token'; const SessionId = 'JSESSIONID'; const UserId = 'b-user-id'; export function getToken() { uniapps/work-app/src/api/voiceCall/index.js
@@ -2,7 +2,7 @@ export const getPhoneBookListApi = (data) => { return request({ url: '/webservice/jaUserContact/pageInfo', url: '/system/user/listCommunication', method: 'post', data, }) uniapps/work-app/src/pages/voiceCall/index.vue
@@ -20,7 +20,7 @@ <!-- 联系人信息 --> <div class="contactInfo"> <div class="contactName">{{ contact.friendNickName || '未知联系人' }}</div> <div class="contactName">{{ contact.nickName || '未知联系人' }}</div> <div class="contactDept">{{ contact.deptName || '未知部门' }}</div> </div> uniapps/work-app/src/subPackages/voiceCallDetail/index.vue
@@ -15,7 +15,62 @@ const userParams = userStore?.userInfo ? JSON.stringify(userStore.userInfo) : '{}' const sWebViewRef = ref(null); const viewUrl = ref(""); onLoad((options) => { // #ifdef APP-PLUS /** * 请求 Android 麦克风权限 * @returns {Promise<boolean>} 权限是否授予 */ 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 false } } // 降级方案:使用原生方法 return await requestAndroidMicPermissionNative() } catch (error) { console.error('❌ 请求麦克风权限失败:', error) return false } } // #endif onLoad(async (options) => { // #ifdef APP-PLUS // 在加载 WebView 前先请求麦克风权限 console.log('📞 语音通话页面加载,请求麦克风权限') const hasPermission = await requestAndroidMicPermission() if (!hasPermission) { console.warn('⚠️ 未获得麦克风权限,通话功能可能无法使用') } // #endif // 解析传递过来的contact参数 const contact = options.contact ? JSON.parse(decodeURIComponent(options.contact)) : null; // 构建viewUrl,将contact参数拼接到URL中 @@ -23,32 +78,8 @@ // viewUrl.value = `https://192.168.1.157:5176/mobile-web-view/#/webViewWrapper/voiceCallDetail?params=${encodeURIComponent(userParams)}&contact=${contactParam}`; viewUrl.value = getWebViewUrl("/voiceCallDetail", { contact: options.contact ,voiceparams: options.voiceparams}); }); function onPostMessage(data) {} // #ifdef APP-PLUS function requestAndroidMicPermission() { return new Promise((resolve) => { const main = plus.android.runtimeMainActivity() const Build = plus.android.importClass('android.os.Build') if (Build.VERSION.SDK_INT < 23) return resolve(true) const Manifest = plus.android.importClass('android.Manifest') const ActivityCompat = plus.android.importClass('androidx.core.app.ActivityCompat') const permission = Manifest.permission.RECORD_AUDIO ActivityCompat.requestPermissions(main, [permission], 1001) // 简化:延迟检查一次(更严谨可写原生回调插件) setTimeout(() => { const PackageManager = plus.android.importClass('android.content.pm.PackageManager') const granted = ActivityCompat.checkSelfPermission(main, permission) === PackageManager.PERMISSION_GRANTED resolve(granted) }, 800) }) } requestAndroidMicPermission() // #endif </script>