Merge remote-tracking branch 'origin/master'
7 files modified
2 files added
| | |
| | | <template> |
| | | <div class="avue-top"> |
| | | <div class="top-bar__title"> |
| | | <img :src="logoUrl" alt=""> |
| | | <span>低空飞行监管子系统</span> |
| | | <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="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> |
| | | |
| | | <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> |
| | | </div> |
| | | </div> |
| | | </div> |
| | | </header> |
| | | </template> |
| | | |
| | | <script> |
| | |
| | | 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: {}, |
| | |
| | | }) |
| | | }, |
| | | 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; |
| | | |
| | | .bg { |
| | | position: absolute; |
| | | top: 0; |
| | | left: 0; |
| | | width: 100%; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .top-bar__left { |
| | | flex: 0 0 auto; |
| | | } |
| | | |
| | | .top-bar__title { |
| | | margin-top: 16px; |
| | | margin-left: 30px; |
| | | flex: 1; |
| | | .content-wrap { |
| | | position: absolute; |
| | | inset: 0; |
| | | display: flex; |
| | | align-items: center; |
| | | height: pxToVh(48); |
| | | z-index: 1; |
| | | padding: 0 40px; |
| | | |
| | | .logo-title-wrap { |
| | | display: flex; |
| | | align-items: center; |
| | | transform: translateY(-10px); |
| | | |
| | | img { |
| | | margin-right: 17px; |
| | | width: 50px; |
| | | height: 50px; |
| | | width: 70px; |
| | | height: 63px; |
| | | vertical-align: middle; |
| | | } |
| | | |
| | | span { |
| | | .title { |
| | | margin-left: 16px; |
| | | margin-top: 0; |
| | | margin-bottom: 0; |
| | | |
| | | width: 353px; |
| | | height: 43px; |
| | | font-family: PangMen; |
| | |
| | | } |
| | | } |
| | | |
| | | .top-bar__right { |
| | | margin-right: 28px; |
| | | padding-top: 16px; |
| | | flex: 0 0 auto; |
| | | display: flex !important; |
| | | align-items: flex-start; |
| | | height: 100%; |
| | | .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; |
| | |
| | | .el-dropdown-link:hover { |
| | | color: #409EFF; |
| | | } |
| | | } |
| | | } |
| | | } |
| | | </style> |
| | |
| | | */ |
| | | 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'; |
| | | |
| | |
| | | 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获取参数 |
| | |
| | | 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('张三') |
| | | |
| | |
| | | const callEnded = ref(false) |
| | | const endMessage = ref('对方已挂断') |
| | | const endTimer = ref(null) |
| | | // token 存储 |
| | | const accessToken = ref('') |
| | | |
| | | let ws = null |
| | | let pc = null |
| | |
| | | } |
| | | |
| | | 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) { |
| | |
| | | |
| | | 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) |
| | |
| | | |
| | | 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 { |
| | |
| | | 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() { |
| | |
| | | |
| | | export const getPhoneBookListApi = (data) => { |
| | | return request({ |
| | | url: '/webservice/jaUserContact/pageInfo', |
| | | url: '/system/user/listCommunication', |
| | | method: 'post', |
| | | data, |
| | | }) |
| | |
| | | |
| | | <!-- 联系人信息 --> |
| | | <div class="contactInfo"> |
| | | <div class="contactName">{{ contact.friendNickName || '未知联系人' }}</div> |
| | | <div class="contactName">{{ contact.nickName || '未知联系人' }}</div> |
| | | <div class="contactDept">{{ contact.deptName || '未知部门' }}</div> |
| | | </div> |
| | | |
| | |
| | | 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中 |
| | |
| | | // 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> |
| | | |