Merge branch 'master' of http://139.196.74.78:10010/r/jagzwxm/ja_web
| | |
| | | ## 环境与配置 |
| | | - Node.js >= 20,pnpm >= 9。 |
| | | - 优先使用根目录工作区脚本,除非必要不要在子目录直接运行脚本。 |
| | | |
| | | # AI Rules / AI行为规范 |
| | | |
| | | - 对低风险修改,AI可自主执行,无需批准。 |
| | | AI may proceed autonomously for low-risk changes. |
| | | - 仅当修改可能影响架构、核心模块、公共接口、数据结构或安全性时,才需要请求批准。 |
| | | Ask for approval only for high-impact changes. |
| | | - 若问题不影响系统正确性,应自行做出合理决策,而不是提问。 |
| | | DO NOT ask questions that do not affect correctness. |
| | | - 优先最小化修改范围。 |
| | | Prefer minimal changes. |
| | | - 未被明确要求时,禁止大规模重构。 |
| | | Avoid large refactors unless requested. |
| | |
| | | } |
| | | } |
| | | |
| | | // 步骤7 接收音频流并且播放 |
| | | pc.ontrack = (e) => { |
| | | log('✅ 收到远端音频流') |
| | | const stream = e.streams[0] |
| | |
| | | pc.getSenders().map(s => (s.track ? s.track.id : null)).filter(Boolean) |
| | | ) |
| | | |
| | | // 步骤6 发送音频流 |
| | | stream.getTracks().forEach(track => { |
| | | if (!existingTrackIds.has(track.id)) { |
| | | pc.addTrack(track, stream) |
| | |
| | | return stream |
| | | } |
| | | |
| | | // 步骤5 |
| | | async function flushCandidates() { |
| | | if (!pc || !pc.remoteDescription) return |
| | | while (pendingCandidates.length) { |
| | |
| | | return |
| | | } |
| | | |
| | | // 步骤2 |
| | | if (t === 'offer') { |
| | | // 被叫收到 offer:只有点击接听后才真正应答(你要求“先点接听”) |
| | | offeredByPeer = true |
| | |
| | | return |
| | | } |
| | | |
| | | // 步骤3 |
| | | if (t === 'answer') { |
| | | log('✅ 收到 answer,通话建立') |
| | | if (!pc) createPC() |
| | | await pc.setRemoteDescription(new RTCSessionDescription(msg.payload?.sdp)) |
| | | // 步骤4 |
| | | await flushCandidates() |
| | | |
| | | state.value = 'in-call' |
| | |
| | | // ✅ 不会重复 addTrack |
| | | await ensureLocalTracksAdded() |
| | | |
| | | // 步骤1 |
| | | const offer = await pc.createOffer() |
| | | await pc.setLocalDescription(offer) |
| | | |
| | |
| | | log('➡️ 已发送 offer 给', peerUid.value) |
| | | } |
| | | |
| | | // 步骤2 |
| | | async function answerOffer(offerSdp, from) { |
| | | if (!offerSdp) { |
| | | log('❌ offer sdp 为空,无法接听') |
| | |
| | | if (ws) ws.close() |
| | | }) |
| | | </script> |
| | | |
| | |
| | | :model="formData" |
| | | :rules="rules" |
| | | :disabled="dialogReadonly" |
| | | label-width="140px" |
| | | label-width="auto" |
| | | > |
| | | <el-row> |
| | | <el-col :span="12"> |
| | |
| | | :model="formData" |
| | | :rules="rules" |
| | | :disabled="dialogReadonly" |
| | | label-width="140px" |
| | | > |
| | | <el-row> |
| | | <el-col :span="12"> |
| | |
| | | <div class="itemBox"> |
| | | <!-- 头像 --> |
| | | <div class="contactAvatar"> |
| | | <image :src="contact.avatar || defaultAvatar" /> |
| | | <image :src="contact.avatar || defaultAvatar" :class="{ 'avatar-disabled': contact.status === '0' }" /> |
| | | </div> |
| | | |
| | | <!-- 联系人信息 --> |
| | |
| | | </div> |
| | | |
| | | <!-- 电话按钮 --> |
| | | <div class="callButton" @click="makeCall(contact)"> |
| | | <div class="callButton" :class="{ 'call-button-disabled': contact.status === '0' }" @click="makeCall(contact)"> |
| | | <image src="@/static/images/voiceVal/phoneVal.svg" /> |
| | | </div> |
| | | </div> |
| | |
| | | } |
| | | // 拨打电话方法 |
| | | const makeCall = (contact) => { |
| | | if (contact.status === '0') { |
| | | return |
| | | } |
| | | console.log('拨打电话', contact) |
| | | // 将contact对象转换为JSON字符串并URL编码 |
| | | const contactStr = encodeURIComponent(JSON.stringify(contact)) |
| | |
| | | |
| | | } |
| | | |
| | | /* 禁用状态样式 */ |
| | | .avatar-disabled { |
| | | filter: grayscale(100%); |
| | | opacity: 0.6; |
| | | } |
| | | |
| | | .call-button-disabled { |
| | | opacity: 0.6; |
| | | pointer-events: none; |
| | | } |
| | | |
| | | .call-button-disabled image { |
| | | filter: grayscale(100%); |
| | | } |
| | | |
| | | .loadingMore { |
| | | display: flex; |
| | | justify-content: center; |
| | |
| | | // 小程序相关接口 |
| | | import { request } from "@/utils/request" |
| | | |
| | | // 轮播图 |
| | | export const getLBList = data => { |
| | | return request({ |
| | | url: `/webservice/miniProgramBannerManager/queryList`, |
| | | method: 'post', |
| | | data, |
| | | }) |
| | | } |
| | | |
| | | // 通知公告列表 noticeType=2 政策制度 1法律法规 |
| | | export const sysNoticePageInfoApi = data => { |
| | | return request({ |
| | |
| | | import docSvg from '@/static/images/doc.png' |
| | | import lbBottomPng from '@/static/images/lb_bottom.png' |
| | | import { sysNoticePageInfoApi, droneFlightTaskApi } from '@/api/index' |
| | | import { flightTaskApi,flightRulesApi,flightModeApi,flightPlanApi,flyActivityStatusApi,flyActivityTakeoffStatusApi,proTypeApi} from '@/api/index' |
| | | import { flightTaskApi,flightRulesApi,flightModeApi,flightPlanApi,flyActivityStatusApi,flyActivityTakeoffStatusApi,proTypeApi,getLBList} from '@/api/index' |
| | | |
| | | import { useAppStore } from "@/store"; |
| | | const appStore = useAppStore() |
| | | |
| | | const lbList = reactive([ |
| | | { |
| | | image: lbPng1, |
| | | title: '法律政策', |
| | | link: '/pages/legalPolicy/index', |
| | | }, |
| | | { |
| | | image: lbPng2, |
| | | title: '法律政策', |
| | | link: '/pages/legalPolicy/index', |
| | | }, |
| | | { |
| | | image: lbPng3, |
| | | title: '法律政策', |
| | | link: '/pages/legalPolicy/index', |
| | | }, |
| | | ]); |
| | | const lbList = ref([]) |
| | | |
| | | // const lbList = reactive([ |
| | | // { |
| | | // image: lbPng1, |
| | | // title: '法律政策', |
| | | // link: '/pages/legalPolicy/index', |
| | | // }, |
| | | // { |
| | | // image: lbPng2, |
| | | // title: '法律政策', |
| | | // link: '/pages/legalPolicy/index', |
| | | // }, |
| | | // { |
| | | // image: lbPng3, |
| | | // title: '法律政策', |
| | | // link: '/pages/legalPolicy/index', |
| | | // }, |
| | | // ]); |
| | | // lbPng 轮播图 5次 |
| | | // for (let i = 0; i < 5; i++) { |
| | | // lbList.push({ |
| | |
| | | title: '政策制度' |
| | | }, |
| | | ]); |
| | | |
| | | // 获取轮播图 |
| | | async function getLBListTest() { |
| | | const res = await getLBList({}) |
| | | console.log(res, '8898') |
| | | lbList.value = res.data.data.map(item => ({ |
| | | ...item, |
| | | image: item.annexUrl, |
| | | title: item.bannerTitle, |
| | | link: item.annexUrl, |
| | | })) |
| | | } |
| | | |
| | | |
| | | const announcementList = ref([]); |
| | |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getLBListTest() |
| | | |
| | | getFlightAirspaceList() |
| | | getSysNoticeList(2) |
| | | getSysNoticeList(1) |