forked from drone/command-center-dashboard

罗广辉
2025-04-18 378fc299c70e004abde6ad805274bc60e1a2db2c
feat: 控制台ai直播功能
6 files modified
170 ■■■■■ changed files
src/api/payload.js 17 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/CurrentTaskDetails.vue 21 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsHead.vue 105 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsLeft.vue 5 ●●●● patch | view | raw | blame | history
src/const/drc.js 3 ●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue 19 ●●●● patch | view | raw | blame | history
src/api/payload.js
@@ -80,4 +80,21 @@
};
// 无人机开启ai
export const getLiveAiLinkApi = (data) => {
  return request({
    url:`${API_PREFIX}/live/streams/start/ai`,
    method: 'post',
    data,
  });
};
// original_stream_url
//   :
//   "rtmp://139.196.74.78/live/1581F6Q8D247200GGSWB-81-0-0-normal-0"
// video_id
//   :
//   "1581F6Q8D247200GGSWB/81-0-0/normal-0"
src/components/CurrentTaskDetails/CurrentTaskDetails.vue
@@ -46,6 +46,8 @@
import { ElMessage } from 'element-plus'
import EventBus from '@/event-bus'
import { updateDroneQualityApi } from '@/api/drc'
import { getLiveAiLinkApi } from '@/api/payload'
import { CURRENT_CONFIG } from '@/utils/http/config'
const isAutoControl = ref(true)
const lineQuality = ref(1) //1流畅,2标清
@@ -65,6 +67,9 @@
provide('dockSn', dockSn)
provide('droneSn', droneSn)
const isAiLive = ref(false)
provide('isAiLive', isAiLive)
const isShow = defineModel('show')
const props = defineProps(['id'])
const currentLiveUrl = ref('')
@@ -72,19 +77,27 @@
const isMaxMap = ref(false)
let droneWebSocket //WS实例
// 机巢直播
const video_id = ref('')
provide('video_id', video_id)
// 获取机巢直播
const getDeviceLiveUrl = async () => {
    const res = await liveStart(dockSn.value, 2)
    currentLiveUrl.value = res.data.data.rtcs_url
}
const video_id = ref('')
const getAiLiveUrl = ()=>{
    const res = getLiveAiLinkApi({ original_stream_url: `${CURRENT_CONFIG.rtmpURL}${video_id}`, video_id })
    currentLiveUrl.value = res.data.data.rtcs_url
    isAiLive.value = true
}
// 获取无人机直播url
async function getDroneLiveUrl() {
    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
}
// 无人机直播画质切换
@@ -169,6 +182,8 @@
onMounted(() => {
    getTaskDetails()
    EventBus.on('CurrentTaskDetails-timeStop', changeLineQuality)
    EventBus.on('CurrentTaskDetails-getAiLiveUrl', getAiLiveUrl)
    EventBus.on('CurrentTaskDetails-getDroneLiveUrl', getDroneLiveUrl)
})
onBeforeUnmount(() => {
@@ -176,6 +191,8 @@
    deviceOsdInfo.value = {}
    droneWebSocket = null
    EventBus.off('CurrentTaskDetails-timeStop', changeLineQuality)
    EventBus.off('CurrentTaskDetails-getAiLiveUrl', getAiLiveUrl)
    EventBus.off('CurrentTaskDetails-getDroneLiveUrl', getDroneLiveUrl)
})
</script>
src/components/CurrentTaskDetails/TaskDetailsHead.vue
@@ -1,19 +1,19 @@
<template>
    <div class="detailsHead">
        <div class="droneName" :title="taskDetails.name">{{taskDetails.name}}</div>
        <div class="droneName" :title="taskDetails.name">{{ taskDetails.name }}</div>
        <div class="infoListBox">
            <div v-for="item in infoList">
                <div class="infoValue">{{ item.value }}</div>
                <div class="infoValue">{{ item.value }}{{ item.unit }}</div>
                <div class="infoTitle">{{ item.title }}</div>
            </div>
        </div>
        <div class="controlBtn">
            <el-icon class="refresh" @click="ElMessage.warning('加急开发中...')">
            <el-icon class="refresh" @click="refreshLive">
                <Refresh />
            </el-icon>
            <div class="switchBtn" @click="switchBtn">
                <div :class="{ open: open }">NO</div>
                <div :class="{ open: !open }">OFF</div>
                <div :class="{ open: !isAiLive }">OFF</div>
                <div :class="{ open: isAiLive }">ON</div>
            </div>
        </div>
    </div>
@@ -24,37 +24,45 @@
import { getFlightStatistics } from '@/api/home/machineNest'
import _, { throttle } from 'lodash'
import { getLnglatAltitude, getLnglatDist } from '@/utils/cesium/mapUtil'
import { networkQuality } from '@/const/drc'
import { fourGQuality, SDRQuality } from '@/const/drc'
import { ElMessage } from 'element-plus'
import EventBus from '@/event-bus'
const taskDetailsViewer = inject('taskDetailsViewer')
const wsInfo = inject('wsInfo')
const dockSn = inject('dockSn')
const taskDetails = inject('taskDetails')
const open = ref(true)
const isAiLive = inject('isAiLive')
const singleTotal = ref({})
// 不要随意更换顺序,有联动
const infoList = ref([
    { title: '实时真高', value: 0 },
    { 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 },
    { index: 0, title: '实时真高', value: 0, unit: 'M' },
    { index: 1, title: '绝对高度', value: 0, unit: 'M' },
    { index: 2, title: '水平速度', value: 0, unit: 'M/s' },
    { index: 3, title: '垂直速度', value: 0, unit: 'M/s' },
    { index: 4, title: '经度', value: 0, unit: '°' },
    { index: 5, title: '纬度', value: 0, unit: '°' },
    { index: 6, title: '风速', value: 0, unit: 'M/s' },
    { index: 7, title: '4G信号', value: 0, unit: '' },
    { index: 8, title: 'SDR信号', value: 0, unit: '' },
    { index: 9, title: 'GPS搜星数', value: 0, unit: '' },
    { index: 10, title: 'RTK搜星数', value: 0, unit: '' },
    { index: 11, title: '距离机场', value: 0, unit: 'M' },
    { index: 12, title: '飞行时长', value: 0, unit: '小时' },
    { index: 13, title: '电池电量', value: 0, unit: '%' },
])
const switchBtn = () => {
    ElMessage.warning('加急开发中...')
    open.value = !open.value
    if (isAiLive.value) {
        EventBus.emit('CurrentTaskDetails-getDroneLiveUrl')
    } else {
        EventBus.emit('CurrentTaskDetails-getAiLiveUrl')
    }
}
function refreshLive(){
    EventBus.emit('CurrentTaskDetails-getAiLiveUrl')
}
function getFlightStatisticsFun() {
@@ -70,8 +78,8 @@
    const { latitude, longitude, height } = device_osd_host
    if (!latitude) return
    getLnglatAltitude(longitude, latitude, taskDetailsViewer.value).then(res => {
        const last =  height - res?.height
        infoList.value[0].value = last ? infoList.value[0].value : _.round(height - res?.height, 1) + 'm'
        const last = height - res?.height
        infoList.value[0].value = last ? infoList.value[0].value : _.round(height - res?.height, 1)
    })
}
@@ -79,29 +87,31 @@
    const device_osd_host = wsInfo?.value?.device_osd?.data?.host || {}
    const dock_osd_host = wsInfo?.value?.dock_osd?.data?.host || {}
    const { longitude, latitude, height, horizontal_speed, vertical_speed, wind_speed, battery } = device_osd_host
    const { longitude: dockLon, latitude: dockLat } = dock_osd_host
    const { longitude: dockLon, latitude: dockLat, wireless_link } = dock_osd_host
    let dist = infoList.value[11].value
    if (longitude && latitude && dockLon && dockLat) {
        dist = _.round(getLnglatDist(longitude, latitude, dockLon, dockLat), 0) + 'M'
        dist = _.round(getLnglatDist(longitude, latitude, dockLon, dockLat), 0)
    }
    const GPSNum = dock_osd_host?.position_state?.gps_number || infoList.value[9].value
    const RTKNum = dock_osd_host?.position_state?.rtk_number || infoList.value[10].value
    infoList.value = [
        { title: '实时真高', value: '0' },
        { title: '绝对高度', value: _.round(height || 0, 1) + 'M' },
        { title: '水平速度', value: _.round(horizontal_speed || 0, 0) + 'M/s' },
        { title: '垂直速度', value: _.round(vertical_speed || 0, 0) + 'M/s' },
        { title: '经度', value: _.round(longitude || 0, 2) },
        { title: '纬度', value: _.round(latitude || 0, 2) },
        { title: '风速', value: _.round(wind_speed || 0, 0) + 'M/s' },
        { title: '4G信号', value: networkQuality?.[dock_osd_host?.quality] || '-' },
        { title: 'SDR信号', value: '0' },
        { title: 'GPS搜星数', value: GPSNum },
        { title: 'RTK搜星数', value: RTKNum },
        { title: '距离机场', value: dist },
        { title: '飞行时长', value: _.round((singleTotal.value?.hour_count || 0),1) + '小时' },
        { title: '电池电量', value: (battery?.capacity_percent || 0) + '%' },
    ]
    const newGPSNum = dock_osd_host?.position_state?.gps_number
    const newRTKNum = dock_osd_host?.position_state?.rtk_number
    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(vertical_speed || 0, 0)
    infoList.value[4].value = _.round(longitude || 0, 2)
    infoList.value[5].value = _.round(latitude || 0, 2)
    infoList.value[6].value = _.round(wind_speed || 0, 0)
    if (newFourG !== undefined) infoList.value[7].value = newFourG
    if (newSdr !== undefined) infoList.value[8].value = newSdr
    if (newGPSNum !== undefined) infoList.value[9].value = newGPSNum
    if (newRTKNum !== undefined) infoList.value[10].value = newRTKNum
    infoList.value[11].value = dist
    infoList.value[12].value = _.round(singleTotal.value?.hour_count || 0, 1)
    infoList.value[13].value = battery?.capacity_percent || 0
    getRealTimeReallyHigh()
}
@@ -150,7 +160,7 @@
        color: #ededed;
        text-align: center;
        line-height: 42px;
        white-space:nowrap;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
        padding: 0 10px;
@@ -166,8 +176,9 @@
        font-weight: 400;
        margin-left: 15px;
        >div{
        > div {
            width: 90px;
            .infoValue {
                font-size: 20px;
                color: #ffffff;
src/components/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -83,7 +83,7 @@
// 修改相机参数
function cameraParamsChange() {
    const { camera_type, zoom_factor} = cameraParams.value
    const { camera_type, zoom_factor } = cameraParams.value
    cameraParamsChangeApi({
        sn: dockSn.value,
        ...cameraParams.value,
@@ -125,6 +125,9 @@
// 拍照
function takePictures() {
    if (isRecording.value) {
        return ElMessage.warning('请先结束录像')
    }
    // photo拍照,video_start开始录像,video_stop结束录像
    callPhotoAndVideoCmd(dockSn.value, 'photo').then(res => {
        ElMessage.success('拍照成功')
src/const/drc.js
@@ -8,4 +8,5 @@
    DELAY_TIME_INFO_PUSH: 'delay_info_push', // 图传链路延时信息上报
}
export const networkQuality = { 0: '无信号', 1: '差', 2: '较差', 3: '一般', 4: '较好', 5: '好' }
export const fourGQuality = { 0: '无信号', 1: '差', 2: '较差', 3: '一般', 4: '较好', 5: '好' }
export const SDRQuality = { 0: '无信号', 1: '差', 2: '较差', 3: '一般', 4: '较好', 5: '好' }
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue
@@ -59,7 +59,15 @@
  <!-- 添加任务 -->
  <AddTask v-model:show="isShowAddTask" @refresh="searchClick"/>
  <!-- 当前任务详情 -->
  <CurrentTaskDetails v-if="isShowCurrentTaskDetails"  v-model:show="isShowCurrentTaskDetails" :id="rowData.id"/>
  <CurrentTaskDetails
        v-if="isShowCurrentTaskDetails"
        v-model:show="isShowCurrentTaskDetails"
        :id="rowData.id"/>
    <!-- 历史人物详情 -->
  <DeviceJobDetails
        v-if="isShowDeviceJobDetails"
        v-model:show="isShowDeviceJobDetails"
        :wayLineJodInfoId="wayLineJodInfoId"/>
</template>
<script setup>
@@ -68,6 +76,7 @@
import CurrentTaskDetails from '@/components/CurrentTaskDetails/CurrentTaskDetails.vue';
import { jobList } from '@/api/home/task';
import { ElMessage } from 'element-plus'
import DeviceJobDetails from '@/components/DeviceJobDetails/DeviceJobDetails.vue'
const jobListParams = reactive({
  current: 1,
@@ -76,6 +85,9 @@
});
const jobListData = ref([]);
const total = ref(0);
let wayLineJodInfoId = ref('')
let isShowDeviceJobDetails = ref(false);
let isShowCurrentTaskDetails = ref(false);
// 获取任务列表
const getJobList = () => {
@@ -98,15 +110,16 @@
};
// 查看当前任务详情 如果是一台机则显示详情 如果是多台机则进入集群调度(暂未开发)
let isShowCurrentTaskDetails = ref(false);
let rowData = ref({});
const handleDetail = (row) => {
    if (row.device_sns.length === 1){
        console.log(row)
        if (row.status === 2 || row.status === 1){
            rowData.value = row? row : {};
            isShowCurrentTaskDetails.value = true;
        } else{
            ElMessage.warning('todo 跳历史任务详情')
            wayLineJodInfoId.value = row.id
            isShowDeviceJobDetails.value = true
        }
    }else{
        ElMessage.warning('即将跳转到集群调度');