<!--
|
* @Author: shuishen 1109946754@qq.com
|
* @Date: 2025-04-19 13:13:15
|
* @LastEditors: shuishen 1109946754@qq.com
|
* @LastEditTime: 2025-04-19 15:07:04
|
* @FilePath: \command-center-dashboard\src\components\CurrentTaskDetails\CurrentTaskDetails.vue
|
* @Description:
|
*
|
* Copyright (c) 2025 by shuishen, All Rights Reserved.
|
-->
|
<!--当前任务详情-->
|
<template>
|
<el-dialog
|
modal-class="current-task-details"
|
v-model="isShow"
|
title="当前任务详情"
|
:width="pxToRem(1500)"
|
:close-on-click-modal="false"
|
:destroy-on-close="true"
|
>
|
<div class="content-container" v-if="isShow">
|
<!-- 视频直播 -->
|
<div :class="`${isMaxMap ? 'minBox' : 'maxBox'} centerPoint`">
|
<LiveVideo :videoUrl="currentLiveUrl" :controls="false" />
|
</div>
|
<!-- 展示地图 -->
|
<RealTimeMap :class="`${isMaxMap ? 'maxBox' : 'minBox'}`" />
|
<TaskDetailsRight v-if="isAutoControl" />
|
<template v-else>
|
<TaskDetailsHead />
|
<TaskDetailsLeft />
|
</template>
|
<!-- 控制面板,里面有方法需要立即执行,不可用v-if -->
|
<ControlPanel v-show="!isAutoControl" />
|
<img alt="" :src="amplifyImg" class="amplify" @click="isMaxMap = !isMaxMap" />
|
</div>
|
</el-dialog>
|
</template>
|
|
<script setup>
|
import { pxToRem } from '@/utils/rem'
|
import LiveVideo from '@/components/LiveVideo.vue'
|
import { liveStart } from '@/api/home/machineNest'
|
import RealTimeMap from '@/components/CurrentTaskDetails/RealTimeMap.vue'
|
import ControlPanel from '@/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue'
|
import TaskDetailsHead from '@/components/CurrentTaskDetails/TaskDetailsHead.vue'
|
import TaskDetailsLeft from '@/components/CurrentTaskDetails/TaskDetailsLeft.vue'
|
import TaskDetailsRight from '@/components/CurrentTaskDetails/TaskDetailsRight.vue'
|
import amplifyImg from '@/assets/images/taskManagement/taskIntermediateContent/amplifyImg.png'
|
import { ElMessage } from 'element-plus'
|
import EventBus from '@/event-bus'
|
import { updateDroneQualityApi } from '@/api/drc'
|
import { getLiveAiLinkApi, getLiveCapacityApi } from '@/api/payload'
|
import { CURRENT_CONFIG } from '@/utils/http/config'
|
import { useDroneWS } from '@/hooks/useDroneWS'
|
import { useTaskDetails } from '@/hooks/useTaskDetails/useTaskDetails'
|
|
const isAutoControl = ref(true) //是否自动控制
|
const lineQuality = ref(1) //1流畅,2标清
|
const taskDetailsViewer = ref(null) //地图实例
|
const dockSn = computed(() => taskDetails?.value?.device_sns?.[0]) //机巢sn
|
const droneSn = computed(() => wsInfo.value?.device_osd?.data?.sn) //无人机sn
|
const trueAltitude = ref('') // 真实高度
|
const isAiLive = ref(false) // 是ai直播
|
const video_id = ref('') // 直播视频id
|
const isShow = defineModel('show') // 是否显示当前任务详情
|
const props = defineProps(['id'])
|
const currentLiveUrl = ref('') // 当前直播地址
|
const isTakeOff = ref(false) // 是在飞行中
|
const isMaxMap = ref(false) //是大地图
|
const client_id = ref('') //mqtt id
|
const hasIr = ref(false) //有红外能力
|
let once = true //第一次触发
|
const isBackDock = ref(false)
|
let { taskDetails, workspace_id, getTaskDetails:initTaskDetails } = useTaskDetails(()=> getDeviceLiveUrl())
|
let { wsInfo } = useDroneWS(workspace_id) //ws信息,是一个ref对象
|
|
provide('wsInfo', wsInfo)
|
provide('isBackDock', isBackDock)
|
provide('workspace_id', workspace_id)
|
provide('dockOsdInfo', wsInfo?.value?.dock_osd)
|
provide('dockSn', dockSn)
|
provide('droneSn', droneSn)
|
provide('isAutoControl', isAutoControl)
|
provide('lineQuality', lineQuality)
|
provide('taskDetailsViewer', taskDetailsViewer)
|
provide('taskDetails', taskDetails)
|
provide('trueAltitude', trueAltitude)
|
provide('isAiLive', isAiLive)
|
provide('video_id', video_id)
|
provide('client_id', client_id)
|
provide('hasIr', hasIr)
|
|
// 获取机巢直播
|
const getDeviceLiveUrl = async () => {
|
const res = await liveStart(dockSn.value, 2)
|
currentLiveUrl.value = res.data.data.rtcs_url
|
}
|
|
//获取是否有红外功能
|
async function getLiveCapacity() {
|
if (!once) return
|
once = false
|
const res = await getLiveCapacityApi({ sn: droneSn.value })
|
res?.data?.data?.forEach(item => {
|
item?.cameras_list?.forEach(item1 => {
|
item1?.videos_list?.forEach(item2 => {
|
item2?.switch_video_types?.forEach(item3 => {
|
if (item3 === 'ir') {
|
hasIr.value = true
|
}
|
})
|
})
|
})
|
})
|
}
|
|
// set Ai直播
|
const getAiLiveUrl = async () => {
|
const res = await getLiveAiLinkApi({
|
original_stream_url: `${CURRENT_CONFIG.rtmpURL}${video_id.value.replace(/\//g, '-')}`,
|
video_id: video_id.value,
|
})
|
currentLiveUrl.value = res.data.data.rtcs_url
|
ElMessage.success('开启成功')
|
isAiLive.value = true
|
}
|
|
// 获取无人机直播url
|
async function getDroneLiveUrl(reset = false) {
|
currentLiveUrl.value = ''
|
await nextTick()
|
const res = await liveStart(droneSn.value, lineQuality.value)
|
currentLiveUrl.value = res.data.data.rtcs_url
|
video_id.value = res.data.data.video_id
|
isAiLive.value = false
|
reset && ElMessage.success('刷新成功')
|
}
|
|
// 无人机直播画质切换
|
const changeLineQuality = async () => {
|
await updateDroneQualityApi({ video_id: video_id.value, video_quality: lineQuality.value })
|
ElMessage.success('切换画质成功')
|
}
|
|
// 设置当前直播地址
|
const setCurrentLiveUrl = async () => {
|
const deviceInfo = wsInfo.value?.device_osd?.data?.host
|
if (!deviceInfo) return
|
const currentIsTakeOff = ![14, 0].includes(deviceInfo?.mode_code)
|
// 如果还是之前的状态,不切换
|
if (isTakeOff.value === currentIsTakeOff) return
|
isTakeOff.value = currentIsTakeOff
|
isTakeOff.value ? await getDroneLiveUrl() : await getDeviceLiveUrl()
|
}
|
|
watch(() => wsInfo.value?.device_osd, getLiveCapacity)
|
watch(wsInfo, setCurrentLiveUrl, { deep: true })
|
|
onMounted(() => {
|
initTaskDetails(props?.id)
|
EventBus.on('CurrentTaskDetails-timeStop', changeLineQuality)
|
EventBus.on('CurrentTaskDetails-getAiLiveUrl', getAiLiveUrl)
|
EventBus.on('CurrentTaskDetails-getDroneLiveUrl', getDroneLiveUrl)
|
})
|
|
onBeforeUnmount(() => {
|
EventBus.off('CurrentTaskDetails-timeStop', changeLineQuality)
|
EventBus.off('CurrentTaskDetails-getAiLiveUrl', getAiLiveUrl)
|
EventBus.off('CurrentTaskDetails-getDroneLiveUrl', getDroneLiveUrl)
|
})
|
</script>
|
|
<style lang="scss">
|
.current-task-details {
|
display: flex;
|
justify-content: space-between;
|
|
.el-dialog {
|
border-radius: 40px;
|
position: relative;
|
margin-top: 25px;
|
width: 1782px;
|
height: 1002px;
|
padding: 0;
|
|
.el-dialog__body {
|
width: 100%;
|
height: 100%;
|
}
|
|
.el-dialog__header {
|
height: 0;
|
overflow: hidden;
|
padding: 0;
|
|
.el-dialog__headerbtn {
|
position: absolute;
|
right: -30px;
|
top: -30px;
|
|
.el-dialog__close {
|
font-size: 30px;
|
}
|
}
|
}
|
}
|
}
|
</style>
|
|
<style lang="scss" scoped>
|
.content-container {
|
height: 100%;
|
width: 100%;
|
border-radius: 4rem;
|
overflow: hidden;
|
|
.centerPoint {
|
&:before {
|
content: '+';
|
font-size: 30px;
|
color: white;
|
position: absolute;
|
left: 50%;
|
top: 50%;
|
transform: translate(-50%, -50%);
|
pointer-events: none;
|
font-weight: bold;
|
text-shadow: -1px -1px 0 black, 1px -1px 0 black, -1px 1px 0 black, 1px 1px 0 black; /* 四方向描边 */
|
}
|
}
|
|
.maxBox {
|
width: 100%;
|
height: 100%;
|
}
|
|
.minBox {
|
position: absolute;
|
width: 376px;
|
height: 212px;
|
left: 0;
|
bottom: 0;
|
border-radius: 0px 20px 0px 40px;
|
border: 1px solid #62a1ff;
|
overflow: hidden;
|
}
|
|
.amplify {
|
cursor: pointer;
|
position: absolute;
|
left: 340px;
|
bottom: 183px;
|
width: 22px;
|
height: 22px;
|
}
|
}
|
</style>
|