<template>
|
<div class="detailsHead">
|
<div class="leftArrow" @click="goBack">
|
<img :src="leftArrowSvg" alt="" />
|
</div>
|
<div class="droneNameBox">
|
<div class="droneName">{{ currentWayLine?.name }}</div>
|
<div class="stateBox" v-if="isCurFlightTask" :style="getColorStyle(droneStatus.text ? droneStatus.mode_code : 0)">
|
{{ droneStatusText }}
|
</div>
|
</div>
|
<div class="infoListBox">
|
<template v-if="isCurFlightTask">
|
<div>
|
<div class="infoValue">
|
<img :src="sjSvg" alt="" />
|
{{ time }}
|
</div>
|
</div>
|
<div v-for="item in infoList.filter(i => !i.hide)">
|
<div class="infoValue" :title="item.tips || item.value" @click="titleClick(item)">
|
<img :src="item.img" alt="" />
|
{{ item.value }}{{ item.unit }}
|
</div>
|
</div>
|
</template>
|
</div>
|
|
<div
|
class="aiRecognition"
|
:class="{ disable: aiTypeDisable }"
|
v-if="taskDetails?.ai_type_str && !aiTypeDisable && isCurFlightTask"
|
>
|
<!-- <div class="aiRecognition" :class="{ disable: aiTypeDisable }">-->
|
<div>AI识别</div>
|
<div class="switchBtn" @click="switchAiBtn">
|
<div :class="{ open: !isAiLive }">OFF</div>
|
<div :class="{ open: isAiLive }" style="position: relative">
|
<el-icon class="loadingOn" v-if="aiLoading">
|
<Loading />
|
</el-icon>
|
<template v-else>ON</template>
|
</div>
|
</div>
|
</div>
|
|
<img class="proportionalSwitch" v-if="liveFullScreen" :src="originalImg" alt="" @click="original" />
|
<img class="proportionalSwitch" v-else :src="fillFullImg" alt="" @click="fillFull" />
|
<img
|
v-if="uniPlatform === 'h5'"
|
class="proportionalSwitch"
|
:src="isFullscreen ? qpsxImg : qpfdImg"
|
alt=""
|
@click="toggleFullscreen"
|
/>
|
<img class="shuaxing" v-if="isTakeOff && isCurFlightTask" :src="sxSvg" alt="" @click="refreshLive" />
|
</div>
|
</template>
|
|
<script setup>
|
import { getFlightStatistics } from '@/api/home/machineNest'
|
import _, { throttle } from 'lodash'
|
import fillFullImg from '@/assets/images/taskManagement/currentTaskDetails/fillFull.svg'
|
import originalImg from '@/assets/images/taskManagement/currentTaskDetails/original.svg'
|
import qpsxImg from '@/assets/images/taskManagement/currentTaskDetails/qpsx.svg'
|
import qpfdImg from '@/assets/images/taskManagement/currentTaskDetails/qpfd.svg'
|
import { fourGQuality, SDRQuality } from '@/const/drc'
|
import EventBus from '@/utils/eventBus'
|
import { routeRecordStartApi, routeRecordStatusApi, routeRecordStopApi, startRecording, stopRecording } from '@/api/drc'
|
import { useRunOnce } from '@/hooks'
|
import getBaseConfig from '@/buildConfig/config'
|
import { droneMode } from '@/const/device'
|
import { closeSendWsApi, openSendWsApi } from '@/api/payload'
|
import sxSvg from '@/appDataSource/inspectionTask/droneConsole/刷新.svg'
|
import sjSvg from '@/appDataSource/inspectionTask/droneConsole/时间.svg'
|
import leftArrowSvg from '@/appDataSource/inspectionTask/droneConsole/leftArrow.svg'
|
import { showToast } from 'vant'
|
import dayjs from 'dayjs'
|
import { exitFullScreen, reqFullScreen } from '@/utils/util'
|
import { Loading } from '@element-plus/icons-vue'
|
|
const { hideRouteRecord } = getBaseConfig()
|
|
const taskDetailsViewer = inject('taskDetailsViewer')
|
const wsInfo = inject('wsInfo')
|
const dockSn = inject('dockSn')
|
const droneSn = inject('droneSn')
|
const taskDetails = inject('taskDetails')
|
const isAiLive = inject('isAiLive')
|
const singleTotal = ref({})
|
const trueAltitude = inject('trueAltitude')
|
const isRouteRecord = inject('isRouteRecord')
|
const droneStatus = inject('droneStatus')
|
const aiLoading = inject('aiLoading')
|
const showKeyRemark = inject('showKeyRemark')
|
const hasAiTypeStr = inject('hasAiTypeStr')
|
const showFunBlock = inject('showFunBlock')
|
const liveFullScreen = inject('liveFullScreen')
|
const delayedRecepAuto = inject('delayedRecepAuto')
|
const isTakeOff = inject('isTakeOff')
|
const infoList = inject('infoList')
|
const recordingId = inject('recordingId')
|
const currentWayLine = inject('currentWayLine')
|
const isCurFlightTask = inject('isCurFlightTask')
|
|
// ai识别按钮禁用
|
const aiTypeDisable = computed(() => {
|
return [0, 1, 2, 9, 10, 11, 12, undefined].includes(droneStatus.value?.mode_code)
|
})
|
const store = useStore()
|
const uniPlatform = computed(() => store.state.common.uniPlatform)
|
const droneStatusText = computed(() => {
|
const val = droneStatus.value.text || '舱内关机'
|
if (val === '手动飞行') {
|
return '暂停'
|
}
|
return val
|
})
|
|
const time = ref('')
|
const updateTime = () => {
|
time.value = dayjs().format('HH:mm')
|
}
|
|
function original() {
|
liveFullScreen.value = false
|
}
|
|
function fillFull() {
|
liveFullScreen.value = true
|
}
|
|
// 控制台显示切换
|
function consoleSwitch() {
|
showFunBlock.value = !showFunBlock?.value
|
}
|
|
const isFullscreen = ref(false)
|
// 切换全屏状态
|
const toggleFullscreen = () => {
|
try {
|
isFullscreen.value ? exitFullScreen() : reqFullScreen()
|
isFullscreen.value = !isFullscreen.value
|
} catch (e) {
|
showToast('您的浏览器不支持全屏')
|
}
|
}
|
|
// ai识别开关
|
const switchAiBtn = () => {
|
delayedRecepAuto()
|
if (aiTypeDisable.value) return
|
if (isAiLive.value) {
|
EventBus.emit('CurrentTaskDetails-getDroneLiveUrl')
|
closeSendWsApi(droneSn.value, { type: 'AI' })
|
} else {
|
EventBus.emit('CurrentTaskDetails-getAiLiveUrl')
|
}
|
}
|
|
// 经纬度复制
|
function titleClick(data) {
|
data.tips &&
|
navigator.clipboard.writeText(data.tips).then(() => {
|
showToast('复制成功')
|
})
|
}
|
|
function goBack() {
|
const transmitData = { data: { type: 'back', taskDetails: JSON.parse(JSON.stringify(taskDetails.value)) } }
|
wx.miniProgram.navigateBack({
|
delta: 1 // 返回的页面数,默认1(返回上一页)
|
})
|
wx.miniProgram.postMessage(transmitData)
|
uni.postMessage(transmitData)
|
}
|
|
// 航线录制
|
async function routeRecordFun() {
|
if (!droneSn.value) return showToast('无人机未起飞,请起飞后再录制')
|
if ([9, 10, 0, 1, 2].includes(droneStatus.value.mode_code)) return showToast('当前状态不允许录制')
|
|
startRecording({
|
sn: dockSn.value,
|
waylineJobId: taskDetails.value.wayline_job_id,
|
name: taskDetails.value?.name,
|
}).then(res => {
|
isRouteRecord.value = true
|
recordingId.value = res.data.data.id
|
showToast('开始航线录制成功')
|
})
|
// ElMessageBox.confirm('是否暂停航线?', '提示', {
|
// confirmButtonText: '确定',
|
// cancelButtonText: '取消',
|
// customClass: 'ztzf-message-box-grey',
|
// type: 'warning',
|
// })
|
// .then(() => {})
|
// .catch(() => {})
|
}
|
|
function getColorStyle(code) {
|
if (code === null || code === undefined) return
|
const row = droneMode?.[code]
|
return {
|
color: row?.color,
|
borderColor: row?.color,
|
// backgroundColor: row?.color?.replace(/\)/, ', 0.2)'),
|
}
|
}
|
|
// 停止航线录制
|
function routeRecordStopFun() {
|
if (!droneSn.value) return
|
isRouteRecord.value = false
|
stopRecording(recordingId.value).then(res => {
|
isRouteRecord.value = false
|
recordingId.value = ''
|
showToast('航线录制结束')
|
})
|
}
|
|
function refreshLive() {
|
EventBus.emit('CurrentTaskDetails-getDroneLiveUrl', true)
|
}
|
|
function shortcutKeys() {
|
showKeyRemark.value = true
|
}
|
|
function getFlightStatisticsFun() {
|
if (!dockSn.value) return
|
getFlightStatistics(dockSn.value).then(res => {
|
singleTotal.value = res.data.data
|
})
|
}
|
|
const getRouteRecordStatus = useRunOnce(() => {
|
routeRecordStatusApi(droneSn.value).then(res => {
|
isRouteRecord.value = res.data.data === 'START_OF_RECORD'
|
})
|
})
|
|
let chargeBatteryVal
|
let beforeRemainTime = 1000 * 60
|
|
function getHeadInfo() {
|
const device_osd_host = wsInfo?.value?.device_osd?.data?.host || {}
|
const dock_osd_host = wsInfo?.value?.dock_osd?.data?.host || {}
|
const chargeBattery = dock_osd_host?.drone_battery_maintenance_info?.batteries[0]?.capacity_percent
|
if (chargeBattery) {
|
chargeBatteryVal = chargeBattery
|
}
|
const { longitude, latitude, height, horizontal_speed, vertical_speed, wind_speed, battery } = device_osd_host
|
const { longitude: dockLon, latitude: dockLat, wireless_link } = dock_osd_host
|
|
const newGPSNum = dock_osd_host?.position_state?.gps_number
|
const newRTKNum = dock_osd_host?.position_state?.rtk_number
|
let newRemainTime = battery?.remain_flight_time
|
const newFourG = wireless_link?.['4g_quality']
|
const newSdr = wireless_link?.['sdr_quality']
|
|
infoList.value[1].value = _.round(height || 0, 1)
|
infoList.value[2].value = _.round(horizontal_speed || 0, 0)
|
infoList.value[3].value = _.round((wind_speed || 0) / 10, 0)
|
if (newFourG !== undefined) infoList.value[4].value = fourGQuality[newFourG]
|
if (newSdr !== undefined) infoList.value[5].value = SDRQuality[newSdr]
|
if (newRemainTime && newRemainTime < beforeRemainTime) {
|
beforeRemainTime = newRemainTime
|
}
|
infoList.value[6].value = battery?.capacity_percent || chargeBatteryVal || '--'
|
if (infoList.value[6].value > 100) infoList.value[6].value = 100
|
}
|
|
const getHeadInfoThrottle = throttle(getHeadInfo, 1000, { leading: true, trailing: true })
|
|
watch(
|
wsInfo,
|
() => {
|
getHeadInfoThrottle()
|
if (wsInfo.value?.device_osd) {
|
getRouteRecordStatus()
|
}
|
},
|
{ immediate: true, deep: true }
|
)
|
|
watch(dockSn, getFlightStatisticsFun, { immediate: true })
|
|
let intervalTime
|
onMounted(() => {
|
intervalTime = setInterval(updateTime, 1000)
|
})
|
onUnmounted(() => {
|
if (intervalTime) {
|
clearInterval(intervalTime)
|
intervalTime = null
|
}
|
})
|
</script>
|
<style lang="scss">
|
.more-popper {
|
border: none !important;
|
border-radius: 10px;
|
background: rgba(0, 0, 0, 0.4) !important;
|
backdrop-filter: blur(5px);
|
|
.el-popper__arrow {
|
display: none;
|
}
|
}
|
</style>
|
|
<style scoped lang="scss">
|
.detailsHead {
|
position: absolute;
|
z-index: 1;
|
top: 0;
|
width: 100%;
|
height: half(34);
|
padding-right: half(15);
|
background: rgba(22, 22, 22, 0.5);
|
backdrop-filter: blur(5px);
|
display: flex;
|
align-items: center;
|
font-size: half(13);
|
color: #ffffff;
|
|
.leftArrow {
|
width: half(30);
|
height: 100%;
|
display: flex;
|
align-items: center;
|
justify-content: center;
|
|
> img {
|
width: half(19);
|
height: half(19);
|
}
|
}
|
|
.shuaxing,
|
.proportionalSwitch {
|
width: half(22);
|
height: half(22);
|
margin-left: half(15);
|
}
|
|
.droneNameBox {
|
color: #ffffff;
|
height: 100%;
|
padding: 0 2px;
|
align-items: center;
|
text-align: center;
|
|
display: flex;
|
|
.droneName {
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
}
|
|
.stateBox {
|
display: flex;
|
justify-content: center;
|
align-items: center;
|
padding: 0 half(6);
|
}
|
}
|
|
.infoListBox {
|
width: 0;
|
flex: 1;
|
display: flex;
|
align-items: center;
|
justify-content: space-evenly;
|
font-family: Segoe UI, Segoe UI;
|
font-weight: 400;
|
|
> div {
|
&.isDegree {
|
width: 115px;
|
}
|
|
.infoValue {
|
white-space: nowrap;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
color: #ffffff;
|
display: flex;
|
align-items: center;
|
|
img {
|
width: half(16);
|
height: half(16);
|
}
|
}
|
|
.infoTitle {
|
color: #d2e8fa;
|
line-height: 15px;
|
}
|
}
|
}
|
|
.aiRecognition {
|
display: flex;
|
align-items: center;
|
font-family: Segoe UI, Segoe UI;
|
font-weight: 400;
|
color: #ffffff;
|
gap: 0 half(4);
|
|
&.disable {
|
cursor: not-allowed;
|
|
* {
|
cursor: not-allowed !important;
|
}
|
}
|
}
|
|
.switchBtn {
|
width: half(70);
|
height: half(20);
|
line-height: half(20);
|
border-radius: half(4);
|
border: 1px solid #ffffff;
|
display: flex;
|
align-items: center;
|
justify-content: space-between;
|
font-family: Segoe UI, Segoe UI;
|
text-align: center;
|
color: #ffffff;
|
overflow: hidden;
|
|
/* 定义旋转动画 */
|
@keyframes rotateAnimation {
|
from {
|
transform: rotate(0deg);
|
}
|
to {
|
transform: rotate(360deg);
|
}
|
}
|
|
.loadingOn {
|
width: 100%;
|
height: 100%;
|
position: absolute;
|
left: 0;
|
top: 0;
|
|
> svg {
|
animation: rotateAnimation 2s linear infinite; /* 添加旋转动画 */
|
}
|
}
|
|
> div {
|
width: 50%;
|
}
|
|
.open {
|
background: #ffffff;
|
color: #242424;
|
}
|
}
|
}
|
</style>
|