forked from drone/command-center-dashboard

罗广辉
2025-04-18 61e6956be147e1e84e7104f16dd47e9f87f20dfd
feat: 控制台显示一些信息
8 files modified
1 files renamed
287 ■■■■ changed files
src/api/payload.js 2 ●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/ControlPanel/ControlComPass/ControlComPass.vue 6 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue 117 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/CurrentTaskDetails.vue 13 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/RealTimeMap.vue 45 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsHead.vue 86 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsLeft.vue 8 ●●●● patch | view | raw | blame | history
src/hooks/controlDrone/useManualControl.js 9 ●●●●● patch | view | raw | blame | history
src/hooks/controlDrone/useMqtt.js 1 ●●●● patch | view | raw | blame | history
src/api/payload.js
@@ -44,7 +44,7 @@
// 拍照和录像
export async function callPhotoAndVideoCmd(sn, type) {
  return await request({
    url:`/drone-device-core/droneAirport/liveStreamApi/${sn}/payload/photoAndVideoCmd/${type}`,
    url:`${API_PREFIX}/devices/${sn}/payload/photoAndVideoCmd/${type}`,
    method:'get',
  })
}
src/components/CurrentTaskDetails/ControlPanel/ControlComPass/ControlComPass.vue
File was renamed from src/components/CurrentTaskDetails/ControlComPass/ControlComPass.vue
@@ -3,7 +3,7 @@
    <div class="left-img" :data-text="`${attitude_pitch}°`">
      <div class="scaleImg">
        <p class="scale" :style="{ top: 45 + ScaleTop + 'px' }"></p>
        <img src="../../../assets/images/rightmapidentification.png" />
        <img src="../../../../assets/images/rightmapidentification.png" />
      </div>
    </div>
    <div class="instrument-center">
@@ -14,13 +14,13 @@
        </div>
      </div>
      <div class="center-show">
        <img src="../../../assets/images/mapidentification.png" />
        <img src="../../../../assets/images/mapidentification.png" />
      </div>
      <div class="rotat-btn"></div>
    </div>
    <div class="right-img" :data-text="`${height}m`">
      <div class="ident-arrow">
        <img src="../../../assets/images/leftmapidentification.png" />
        <img src="../../../../assets/images/leftmapidentification.png" />
        <div class="arrow-box" :style="{ bottom: realHeight }">
          <div class="arrow"></div>
        </div>
src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue
@@ -126,7 +126,7 @@
    </div>
</template>
<script setup>
import ControlComPass from '../ControlComPass/ControlComPass.vue'
import ControlComPass from '@/components/CurrentTaskDetails/ControlPanel/ControlComPass/ControlComPass.vue'
import { KeyCode, useManualControl } from '@/hooks/controlDrone/useManualControl'
import { droneController, exitController, postDrc, returnHome, returnHomeCancel } from '@/api/drc'
import { ElMessage } from 'element-plus'
@@ -144,12 +144,13 @@
    RefreshLeft,
    RefreshRight,
} from '@element-plus/icons-vue'
import _ from 'lodash'
import BaseControl from '@/components/CurrentTaskDetails/ControlPanel/BaseControl.vue'
import EventBus from '@/event-bus'
import { getPayloadControlApi, ptzControlApi } from '@/api/payload'
const deviceOsdInfo = inject('deviceOsdInfo')
const host = computed(() => deviceOsdInfo?.value?.data?.host || {})
const taskDetails = inject('taskDetails')
const dockSn = inject('dockSn')
const droneSn = inject('droneSn')
@@ -183,18 +184,106 @@
    { name: '左', key: KeyCode.ARROW_LEFT, operate: 'left', style: { left: '-70%' } },
]
const list4 = [
    [
        { name: '焦距倍数', value: '0' },
        { name: '俯仰角度', value: '0.0°' },
        { name: '横向角度', value: '0.0°' },
    ],
    [
        { name: '储存', value: '64.5G' },
        { name: '方向', value: '正北' },
        { name: '方向', value: '正北' },
    ],
]
const list4 = computed(() => {
    const { longitude, latitude, height, payloads } = host?.value || {}
    const { gimbal_pitch } = payloads?.[0] || {} //俯仰角度
    return [
        [
            { name: '焦距倍数', value: '0' },
            { name: '俯仰角度', value: pitchAngle.value.angle },
            { name: '横向角度', value: yawAngle.value.angle },
        ],
        [
            { name: '储存', value: '64.5G' },
            { name: '方向', value: pitchAngle.value.direction },
            { name: '方向', value: yawAngle.value.direction },
        ],
    ]
})
const pitchAngle = computed(() => {
    const { longitude, latitude, height, payloads } = host?.value || {}
    const gimbal_pitch = payloads?.[0]?.gimbal_pitch || 0
    let direction = ''
    if (gimbal_pitch > -2 && gimbal_pitch < 2) {
        direction = '正前'
    } else if (gimbal_pitch >= 2 && gimbal_pitch < 90) {
        direction = '斜上'
    } else if (gimbal_pitch === 90) {
        direction = '正上'
    } else if (gimbal_pitch <= -2 && gimbal_pitch > -90) {
        direction = '斜下'
    } else if (gimbal_pitch === -90 || gimbal_pitch < -90) {
        direction = '正下'
    }
    return {
        angle: _.round(gimbal_pitch || 0, 1) + '°',
        direction,
    }
})
const yawAngle = computed(() => {
    let { longitude, latitude, height, payloads, attitude_head } = host?.value || {}
    const gimbal_pitch = payloads?.[0]?.gimbal_pitch || 0
    const gimbal_yaw = payloads?.[0]?.gimbal_yaw || 0
    attitude_head = attitude_head || 0
    let yaw = ''
    if (gimbal_yaw > 180) {
        yaw = gimbal_yaw
    } else {
        yaw = gimbal_yaw - attitude_head
    }
    let result = 0
    if (yaw < 0) {
        result = yaw + 360
    }
    if (yaw > 0) {
        result = yaw
    }
    if ((yaw > -2 && yaw < 2) || parseInt(attitude_head) === parseInt(gimbal_yaw)) {
        result = attitude_head < 0 ? 180 + (180 + attitude_head) : attitude_head
    }
    let direction = ''
    const roundResult = Math.round(result)
    if (roundResult === 0) {
        direction = '正北'
    } else if (roundResult > 0 && roundResult < 45) {
        direction = '北偏东'
    } else if (roundResult === 45) {
        direction = '东北'
    } else if (roundResult > 45 && roundResult < 90) {
        direction = '北偏东'
    } else if (roundResult === 90) {
        direction = '正东'
    } else if (roundResult > 90 && roundResult < 135) {
        direction = '东偏南'
    } else if (roundResult === 135) {
        direction = '东南'
    } else if (roundResult > 135 && roundResult < 180) {
        direction = '南偏东'
    } else if (roundResult === 180) {
        direction = '正南'
    } else if (roundResult > 180 && roundResult < 225) {
        direction = '南偏西'
    } else if (roundResult === 225) {
        direction = '西南'
    } else if (roundResult > 225 && roundResult < 270) {
        direction = '西偏南'
    } else if (roundResult === 270) {
        direction = '正西'
    } else if (roundResult > 270 && roundResult < 315) {
        direction = '西偏北'
    } else if (roundResult === 315) {
        direction = '西北'
    } else if (roundResult > 315 && roundResult < 360) {
        direction = '北偏西'
    } else if (roundResult === 360) {
        direction = '正北'
    }
    return {
        angle: _.round(result, 1) + '°',
        direction,
    }
})
const deviceTopicInfo = ref({
    pubTopic: '',
src/components/CurrentTaskDetails/CurrentTaskDetails.vue
@@ -52,6 +52,9 @@
provide('isAutoControl', isAutoControl)
provide('lineQuality', lineQuality)
const taskDetailsViewer = ref(null)
provide('taskDetailsViewer', taskDetailsViewer)
let taskDetails = ref({})
const deviceOsdInfo = ref({})
provide('taskDetails', taskDetails)
@@ -119,7 +122,15 @@
        case EBizCode.DeviceOsd: {
            deviceOsdInfo.value = payload
            setCurrentLiveUrl()
            console.log(deviceOsdInfo.value, 'device_osd信息')
            console.log(payload, 'DeviceOsd--信息')
            break
        }
        case EBizCode.GatewayOsd: {
            console.log(payload, 'GatewayOsd--信息')
            break
        }
        case EBizCode.DockOsd: {
            console.log(payload, 'GatewayOsd--信息')
            break
        }
        default:
src/components/CurrentTaskDetails/RealTimeMap.vue
@@ -26,12 +26,14 @@
    tilingScheme: new AmapMercatorTilingScheme(),
    credit: 'amap_SL',
})
let viewer = null
const taskDetailsViewer = inject('taskDetailsViewer')
const taskDetails = inject('taskDetails')
const deviceOsdInfo = inject('deviceOsdInfo')
const initMap = () => {
    viewer = new Viewer('currentTaskMap', {
    taskDetailsViewer.value = new Viewer('currentTaskMap', {
        terrain: Terrain.fromWorldTerrain(),
        infoBox: false, // 禁用沙箱,解决控制台报错
        animation: false, // 左下角的动画仪表盘
@@ -45,17 +47,17 @@
        baseLayer: false,
        fullscreenButton: false,
    })
    const gdLayer = viewer.imageryLayers.addImageryProvider(imageryProvider_ammapSL)
    const gdLayer = taskDetailsViewer?.value.imageryLayers.addImageryProvider(imageryProvider_ammapSL)
    const options = {
        bInvertColor: true,
        bFilterColor: true,
        filterColor: '#4e70a6',
    }
    // 添加蓝色滤镜
    addBlueFilter(options, viewer, gdLayer)
    viewer.scene.morphTo2D(0)
    addBlueFilter(options, taskDetailsViewer?.value, gdLayer)
    taskDetailsViewer?.value.scene.morphTo2D(0)
    //设置默认点
    viewer.camera.setView({
    taskDetailsViewer?.value.camera.setView({
        destination: Cartesian3.fromDegrees(115.763819, 28.787374, 5000),
    })
}
@@ -66,7 +68,7 @@
        return Cartesian3.fromDegrees(Number(lon), Number(lat))
    })
    // 起点
    viewer.entities.add({
    taskDetailsViewer?.value.entities.add({
        position: positions[0],
        billboard: {
            image: new Cesium.ConstantProperty(rwqfdImg),
@@ -75,7 +77,7 @@
        },
    })
    // 终点
    viewer.entities.add({
    taskDetailsViewer?.value.entities.add({
        position: positions[positions.length - 1],
        billboard: {
            image: new Cesium.ConstantProperty(endPointImg),
@@ -85,7 +87,7 @@
        },
    })
    // 路径线
    viewer.entities.add({
    taskDetailsViewer?.value.entities.add({
        polyline: {
            width: 4,
            positions: positions,
@@ -108,29 +110,28 @@
    const waylinesXMLObj = removeTextKey(waylinesXMLJSON.Folder)
    if (!waylinesXMLObj.Placemark.length) return
    const allPoint = waylinesXMLObj.Placemark.map(item => item.Point.coordinates.split(','))
    flyVisual(allPoint, viewer)
    flyVisual(allPoint, taskDetailsViewer?.value)
    drawWayline(waylinesXMLObj)
}
const removeMap = () => {
    viewer.entities.removeAll()
    viewer.destroy()
    taskDetailsViewer?.value.entities.removeAll()
    taskDetailsViewer?.value.destroy()
}
let viewInfoFrustum
// 设置视椎
const setCreateFrustum = () => {
    const deviceInfo = deviceOsdInfo.value?.data?.host
    if (!deviceInfo) return
    const host = deviceOsdInfo.value?.data?.host
    if (!host) return
    viewInfoFrustum?.clear()
    if ([14, 0].includes(deviceInfo.mode_code)) return
    const attitude_head = 180 + deviceInfo.attitude_head
    const gimbal_pitch = 90 - Number(deviceInfo?.payloads[0]?.gimbal_pitch) || 0
    viewInfoFrustum = new CreateFrustum(viewer, {
    if ([14, 0].includes(host.mode_code)) return
    const attitude_head = 180 + host.attitude_head
    const gimbal_pitch = 90 - Number(host?.payloads[0]?.gimbal_pitch) || 0
    viewInfoFrustum = new CreateFrustum(taskDetailsViewer?.value, {
        position: {
            longitude: deviceInfo.longitude,
            latitude: deviceInfo.latitude,
            altitude: deviceInfo.height,
            longitude: host.longitude,
            latitude: host.latitude,
            altitude: host.height,
        },
        width: 30,
        height: 30,
src/components/CurrentTaskDetails/TaskDetailsHead.vue
@@ -8,7 +8,9 @@
            </div>
        </div>
        <div class="controlBtn">
            <el-icon class="refresh"><Refresh /></el-icon>
            <el-icon class="refresh">
                <Refresh />
            </el-icon>
            <div class="switchBtn" @click="switchBtn">
                <div :class="{ open: open }">NO</div>
                <div :class="{ open: !open }">OFF</div>
@@ -19,26 +21,74 @@
<script setup>
import { Refresh } from '@element-plus/icons-vue'
import { getFlightStatistics } from '@/api/home/machineNest'
import { throttle } from 'lodash'
import { getLnglatAltitude } from '@/utils/cesium/mapUtil'
import _ from 'lodash'
const taskDetailsViewer = inject('taskDetailsViewer')
const deviceOsdInfo = inject('deviceOsdInfo')
const dockSn = inject('dockSn')
const open = ref(true)
const singleTotal = ref({})
const host = computed(() => deviceOsdInfo?.value?.data?.host || {})
const infoList = computed(() => {
    const { longitude, latitude, height, payloads } = host?.value || {}
    return [
        { title: '实时真高', value: '0' },
        { title: '绝对高度', value: _.round(height || 0, 1) + 'm' },
        { title: '水平速度', value: '0' },
        { title: '垂直速度', value: '0' },
        { title: '经度', value: _.round(longitude || 0, 2) },
        { title: '纬度', value: _.round(latitude || 0, 2) },
        { title: '4G信号', value: '0' },
        { title: 'SDR信号', value: '0' },
        { title: 'GPS搜星数', value: '0' },
        { title: 'RTK搜星数', value: '0' },
        { title: '距离机场', value: '0' },
        { title: '飞行时长', value: (singleTotal.value?.hour_count || 0) + '小时' },
        { title: '电池电量', value: (host?.value?.battery?.capacity_percent || 0) + '%' },
    ]
})
const switchBtn = () => {
    open.value = !open.value
}
const infoList = [
    { title: '实时真高', value: '0' },
    { title: '绝对高度', value: '0' },
    { title: '水平速度', value: '0' },
    { title: '垂直速度', value: '0' },
    { title: '经度', value: '0' },
    { title: '纬度', value: '0' },
    { title: '4G信号', value: '0' },
    { title: 'SDR信号', value: '0' },
    { title: 'GPS搜星数', value: '0' },
    { title: 'RTK搜星数', value: '0' },
    { title: '距离机场', value: '0' },
    { title: '飞行时长', value: '0' },
    { title: '电池电量', value: '0' },
]
function getFlightStatisticsFun() {
    if (!dockSn.value) return
    getFlightStatistics(dockSn.value).then(res => {
        singleTotal.value = res.data.data
    })
}
function getRealTimeReallyHigh() {
    if (!taskDetailsViewer?.value) return
    const { latitude, longitude, height } = host?.value || {}
    if (!latitude) return
    getLnglatAltitude(longitude, latitude, taskDetailsViewer.value).then(res => {
        const findIndex = infoList.value.findIndex(item => item.title === '实时真高')
        infoList.value[findIndex].value = _.round(height - res?.height, 1) + 'm'
    })
}
const getRealTimeReallyHighThrottle = throttle(getRealTimeReallyHigh, 500, { leading: true, trailing: true })
watch(
    deviceOsdInfo,
    () => {
        getRealTimeReallyHighThrottle()
    },
    { immediate: true }
)
watch(
    dockSn,
    () => {
        getFlightStatisticsFun()
    },
    { immediate: true }
)
</script>
<style scoped lang="scss">
@@ -48,7 +98,7 @@
    z-index: 5;
    width: 100%;
    height: 68px;
    background: rgb(0,0,0,.4); /* 半透明背景 */
    background: rgb(0, 0, 0, 0.4); /* 半透明背景 */
    backdrop-filter: blur(5px);
    padding: 0 31px;
    display: flex;
@@ -98,7 +148,7 @@
        align-items: center;
        justify-content: space-between;
        .refresh{
        .refresh {
            color: white;
            font-size: 30px;
            cursor: pointer;
src/components/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -22,8 +22,8 @@
            <div @click="recordFun">{{ isRecording ? '录像中...' : '录像' }}</div>
        </div>
        <div class="multiCol">
            <div>喊话</div>
            <div>广播</div>
            <div @click="ElMessage.warning('加急开发中...')">喊话</div>
            <div @click="ElMessage.warning('加急开发中...')">广播</div>
        </div>
        <div class="cameraZoom">
@@ -116,7 +116,7 @@
    const msg = isRecording.value ? '停止录像' : '开始录像'
    const emitType = isRecording.value ? 'controlPanel-timeStop' : 'controlPanel-timeStart'
    callPhotoAndVideoCmd(droneSn.value, type).then(res => {
    callPhotoAndVideoCmd(dockSn.value, type).then(res => {
        ElMessage.success(msg)
        EventBus.emit(emitType)
        isRecording.value = !isRecording.value
@@ -126,7 +126,7 @@
// 拍照
function takePictures() {
    // photo拍照,video_start开始录像,video_stop结束录像
    callPhotoAndVideoCmd(droneSn.value, 'photo').then(res => {
    callPhotoAndVideoCmd(dockSn.value, 'photo').then(res => {
        ElMessage.success('拍照成功')
    })
}
src/hooks/controlDrone/useManualControl.js
@@ -38,7 +38,7 @@
export function useManualControl(mqttState,deviceTopicInfo, isCurrentFlightController,paramsRef) {
    let activeCodeKey = null
    let genPortOne = true //是一代机场
    const mqttHooks = useMqtt(mqttState,deviceTopicInfo)
    let mqttHooks = useMqtt(mqttState,deviceTopicInfo)
    let seq = 0
    const keysPressed = [
        {key: 'KeyQ', value: false},
@@ -281,11 +281,10 @@
        { immediate: true }
    )
    onBeforeUnmount(()=>{
        mqttHooks?.d
    })
    onUnmounted(() => {
        deviceTopicInfo.subTopic = ''
        deviceTopicInfo.pubTopic = ''
        mqttHooks = null
        closeKeyboardManualControl()
    })
src/hooks/controlDrone/useMqtt.js
@@ -67,6 +67,7 @@
      // 2.发心跳
      publishDrcPing(deviceTopicInfo.sn)
    } else {
      console.log('清除pingInterval')
      clearInterval(state.heartState.get(deviceTopicInfo.sn)?.pingInterval)
      state.heartState.delete(deviceTopicInfo.sn)
      heartBeatSeq.value = 0