forked from drone/command-center-dashboard

shuishen
2025-04-19 600ab7a279a2ffc1a05e722bf18d4bfe0c1da1ea
Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard
18 files modified
236 ■■■■■ changed files
index.html 2 ●●● patch | view | raw | blame | history
public/favicon.png patch | view | raw | blame | history
src/api/drc.js 5 ●●●●● patch | view | raw | blame | history
src/assets/images/home/useEventOperate/Ellipse_2412.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/eventClosed.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/eventCompleted.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/eventPending.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/eventProcessing.png patch | view | raw | blame | history
src/assets/images/home/useEventOperate/eventWaitAudit.png patch | view | raw | blame | history
src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue 1 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/ControlPanel/ControlComPass/ControlComPass.vue 10 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue 121 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/CurrentTaskDetails.vue 39 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsHead.vue 6 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsLeft.vue 34 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsRight.vue 9 ●●●● patch | view | raw | blame | history
src/hooks/components/DevicePopUpBox.vue 2 ●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue 7 ●●●● patch | view | raw | blame | history
index.html
@@ -11,7 +11,7 @@
  <meta name='apple-mobile-web-app-capable' content='yes'>
  <meta name='apple-mobile-web-app-status-bar-style' content='black'>
  <meta name='format-detection' content='telephone=no'>
  <link rel='icon' href='/favicon.png' />
  <link rel="icon"  href='/favicon.png' />
  <link rel='stylesheet' href='/fonts/font.css'>
  <link rel='stylesheet' href='/iconfont/index.css'>
  <link rel='stylesheet' href='/iconfont/avue/iconfont.css'>
public/favicon.png

src/api/drc.js
@@ -57,9 +57,10 @@
}
// 取消返航
export async function returnHomeCancel(sn) {
export async function returnHomeCancel(data) {
  return request({
    url: `/drone-device-core/dp/home/${sn}/drc/returnHomeCancel`,
    url: `/drone-device-core/dp/home/${data.dock_sn}/drc/returnHomeCancel`,
    method: 'post',
    data,
  })
}
src/assets/images/home/useEventOperate/Ellipse_2412.png

src/assets/images/home/useEventOperate/eventClosed.png

src/assets/images/home/useEventOperate/eventCompleted.png

src/assets/images/home/useEventOperate/eventPending.png

src/assets/images/home/useEventOperate/eventProcessing.png

src/assets/images/home/useEventOperate/eventWaitAudit.png

src/components/CurrentTaskDetails/ControlPanel/BaseControl.vue
@@ -89,6 +89,7 @@
            &:hover {
                cursor: pointer;
                //background: radial-gradient(circle, #5d5c5e 0%, #514f52 50%, #3d3b3d 100%);
                box-shadow: 0 0 20px 5px rgba(0, 0, 0, 0.3);
            }
        }
src/components/CurrentTaskDetails/ControlPanel/ControlComPass/ControlComPass.vue
@@ -8,7 +8,7 @@
        </div>
        <div class="instrument-center">
            <div class="compass-box" :style="{ transform: `rotate(${props?.options?.yawAngle || 0}deg)` }">
            <div class="compass-box" :style="compassStyle">
                <div v-for="(item, index) in str" :key="index" class="scale" :style="{ '--rotate': 30 * index + 'deg' }">
                    <span class="text">{{ item }}</span>
                </div>
@@ -41,10 +41,14 @@
    }
})
const compassStyle = computed(() => {
    return { transform: `rotate(${props?.options?.yawAngle || 0}deg)` }
})
const trueAltitudeStyle = computed(() => {
    const trueAltitude = props?.options?.trueAltitude || 0
    // 将 [-240,240] 映射到 [0%, 100%]
    const percentage = (((trueAltitude + 240) / 480) * 100).toFixed(2)
    // 将 [0,240] 映射到 [0%, 100%]
    const percentage = ((trueAltitude / 240) * 100).toFixed(2)
    return {
        bottom: `${percentage}%`,
    }
src/components/CurrentTaskDetails/ControlPanel/ControlPanel.vue
@@ -34,7 +34,7 @@
            </div>
            <div class="speed">
                <el-icon class="btnIcon" @click="speed = speed + 1">
                <el-icon class="btnIcon" @click="speed = speed === 15 ? 15 : speed + 1">
                    <Plus />
                </el-icon>
                <div>
@@ -42,7 +42,7 @@
                    <br />
                    m/s
                </div>
                <el-icon class="btnIcon" @click="speed = speed - 1">
                <el-icon class="btnIcon" @click="speed = speed === 0 ? 0 : speed - 1">
                    <Minus />
                </el-icon>
            </div>
@@ -84,7 +84,7 @@
            <div class="ptzControlBtnBox">
                <div class="ptzControlBtn b-r">
                    <div
                        v-for="(item, index) in list5"
                        v-for="(item, index) in ptzBtns"
                        :style="item.style"
                        class="ptzControlItem"
                        @mousedown="onMouseDown(item.key)"
@@ -93,7 +93,7 @@
                    <div
                        class="ptzControlItemIcon"
                        v-for="(item, index) in list5"
                        v-for="(item, index) in ptzBtns"
                        :style="{ transform: `rotate(${index * 90}deg)` }"
                    >
                        <el-icon>
@@ -116,7 +116,7 @@
            </div>
            <div class="divider"></div>
            <div v-for="arr in baseInfo" class="info">
            <div v-for="arr in baseInfoList" class="info">
                <div v-for="item in arr" class="infoItem">
                    <div class="infoName">{{ item.name }}</div>
                    <div class="infoValue">{{ item.value + (item.unit || '') }}</div>
@@ -156,8 +156,17 @@
const taskDetails = inject('taskDetails')
const dockSn = inject('dockSn')
const droneSn = inject('droneSn')
const trueAltitude = inject('trueAltitude')
const client_id = inject('client_id')
const deviceTopicInfo = ref({
    pubTopic: '',
    subTopic: '',
})
const flightController = ref(false)
// 控制对象
let manualControl = {}
const isAutoControl = inject('isAutoControl')
const compassOptions = computed(() => {
    return {
@@ -168,7 +177,6 @@
})
let mqttState = null
const client_id = ref('')
const valueTime = ref('00:00:00')
let timer = null
let totalSeconds = 0
@@ -188,31 +196,25 @@
const speed = ref(5)
provide('speed', speed)
const list5 = [
const ptzBtns = [
    { name: '上', key: KeyCode.ARROW_UP, operate: 'up', style: { top: '-70%' } },
    { name: '右', key: KeyCode.ARROW_RIGHT, operate: 'right', style: { left: '70%' } },
    { name: '下', key: KeyCode.ARROW_DOWN, operate: 'down', style: { top: '70%' } },
    { name: '左', key: KeyCode.ARROW_LEFT, operate: 'left', style: { left: '-70%' } },
]
const baseInfo = computed(() => {
    const usedStorage = dock_osd_host.value?.storage?.used || baseInfo.value?.[1]?.[1]?.value || 0
    const zoom_factor = device_osd_host.value?.cameras?.[0]?.zoom_factor || 0
    const usedStorageGB = _.round(usedStorage / 1024 / 1024, 2)
    return [
        [
            { name: '焦距倍数', value: zoom_factor },
            { name: '俯仰角度', value: pitchAngle.value.angle, unit: '°' },
            { name: '横向角度', value: yawAngle.value.angle, unit: '°' },
        ],
        [
            { name: '储存', value: usedStorageGB, unit: 'G' },
            { name: '方向', value: pitchAngle.value.direction },
            { name: '方向', value: yawAngle.value.direction },
        ],
    ]
})
const baseInfoList = ref([
    [
        { name: '焦距倍数', value: 0 },
        { name: '俯仰角度', value: 0, unit: '°' },
        { name: '横向角度', value: 0, unit: '°' },
    ],
    [
        { name: '储存', value: 0, unit: 'G' },
        { name: '方向', value: 0 },
        { name: '方向', value: 0 },
    ],
])
const pitchAngle = computed(() => {
    const { payloads } = device_osd_host?.value || {}
@@ -234,7 +236,6 @@
        direction,
    }
})
const yawAngle = computed(() => {
    let { payloads, attitude_head } = device_osd_host?.value || {}
    const gimbal_yaw = payloads?.[0]?.gimbal_yaw || 0
@@ -255,12 +256,12 @@
    if ((yaw > -2 && yaw < 2) || parseInt(attitude_head) === parseInt(gimbal_yaw)) {
        result = attitude_head < 0 ? 180 + (180 + attitude_head) : attitude_head
    }
    const roundResult = Math.round(result);
    let direction = '';
    const roundResult = Math.round(result)
    let direction = ''
    for (const item of directionMap) {
        if (roundResult >= item.min && roundResult <= item.max) {
            direction = item.value;
            break;
            direction = item.value
            break
        }
    }
    return {
@@ -269,14 +270,25 @@
    }
})
const deviceTopicInfo = ref({
    pubTopic: '',
    subTopic: '',
})
const flightController = ref(false)
// 控制对象
let manualControl = {}
const isAutoControl = inject('isAutoControl')
const baseInfoChange = () => {
    const newUsedStorage = dock_osd_host.value?.storage?.used
    const zoom_factor = device_osd_host.value?.cameras?.[0]?.zoom_factor || 0
    const usedStorageGB = _.round(newUsedStorage / 1024 / 1024, 2)
    baseInfoList.value[0][0].value = _.round(zoom_factor, 0)
    baseInfoList.value[0][1].value = pitchAngle.value.angle
    baseInfoList.value[0][2].value = yawAngle.value.angle
    if (newUsedStorage !== undefined) baseInfoList.value[1][0].value = usedStorageGB
    baseInfoList.value[1][1].value = pitchAngle.value.direction
    baseInfoList.value[1][2].value = yawAngle.value.direction
}
watch(
    wsInfo,
    () => {
        baseInfoChange()
    },
    { immediate: true, deep: true }
)
const timeStart = () => {
    stop() // 避免重复启动
@@ -346,18 +358,20 @@
    })
}
const isBackDock = ref(false)
// 返航
function onBackDock() {
    returnHome(dockSn?.value).then(res => {
        ElMessage.success('返航操作成功')
    })
async function onBackDock() {
    await returnHome(dockSn?.value)
    ElMessage.success('返航操作成功')
    isBackDock.value = true
}
// 取消返航
function cancelBackDock() {
    returnHomeCancel(dockSn?.value).then(res => {
        ElMessage.success('取消返航成功')
    })
async function cancelBackDock() {
    await returnHomeCancel({ dock_sn: dockSn?.value, client_id: client_id.value })
    ElMessage.success('取消返航成功')
    isBackDock.value = false
}
// 创建mqtt连接
@@ -382,11 +396,7 @@
// 返航或取消返航
const returnOrCancelReturn = () => {
    if (device_osd_host?.value?.mode_code === 9) {
        cancelBackDock()
    } else {
        onBackDock()
    }
    isBackDock.value ? cancelBackDock() : onBackDock()
}
// useManualControl里面用的参数
@@ -455,7 +465,7 @@
    right: 0;
    width: 1400px;
    height: 217px;
    background: linear-gradient(196deg, rgba(23, 23, 23, 0.11) 0%, rgba(6, 6, 6, 0.11) 100%);
    background: rgba(31, 31, 31, 0.15);
    backdrop-filter: blur(5px);
    border-radius: 40px 0px 40px 40px;
    display: flex;
@@ -509,10 +519,11 @@
            box-shadow: 2px 4px 6px 0px rgba(0, 13, 26, 0.42);
            border-radius: 8px 8px 8px 8px;
            text-align: center;
            padding: 5px 0;
            padding: 10px 0;
            .btnIcon {
                font-size: 20px;
                cursor: pointer;
            }
        }
    }
@@ -621,7 +632,7 @@
        .divider {
            position: absolute;
            transform: translateX(90px);
            transform: translateX(95px);
            width: 0;
            height: 137px;
            border: 1px solid rgba(255, 255, 255, 0.07);
src/components/CurrentTaskDetails/CurrentTaskDetails.vue
@@ -2,7 +2,7 @@
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2025-04-19 13:13:15
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2025-04-19 14:15:02
 * @LastEditTime: 2025-04-19 15:04:15
 * @FilePath: \command-center-dashboard\src\components\CurrentTaskDetails\CurrentTaskDetails.vue
 * @Description: 
 * 
@@ -30,9 +30,7 @@
                <TaskDetailsHead />
                <TaskDetailsLeft />
            </template>
            <!--    控制面板,里面有方法需要立即执行,不可用v-if        -->
            <!--            <ControlPanel />-->
            <ControlPanel v-show="!isAutoControl" />
            <img alt="" :src="amplifyImg" class="amplify" @click="isMaxMap = !isMaxMap" />
        </div>
@@ -68,7 +66,6 @@
const trueAltitude = ref('') // 真实高度
const isAiLive = ref(false) // 是ai直播
const video_id = ref('') // 直播视频id
const isShow = defineModel('show') // 是否显示当前任务详情
const props = defineProps(['id'])
const currentLiveUrl = ref('') // 当前直播地址
@@ -84,6 +81,20 @@
let { taskDetails, workspace_id, getTaskDetails } = useTaskDetails(getDeviceLiveUrl)
let { wsInfo, removeWS } = useDroneWS(workspace_id) //ws信息,是一个ref对象
provide('wsInfo', wsInfo)
provide('deviceOsdInfo', deviceOsdInfo)
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)
watch(
    wsInfo,
    () => {
@@ -92,19 +103,6 @@
    },
    { deep: true }
)
provide('wsInfo', wsInfo)
provide('isAutoControl', isAutoControl)
provide('lineQuality', lineQuality)
provide('taskDetailsViewer', taskDetailsViewer)
provide('taskDetails', taskDetails)
provide('deviceOsdInfo', deviceOsdInfo)
provide('dockOsdInfo', wsInfo?.value?.dock_osd)
provide('dockSn', dockSn)
provide('droneSn', droneSn)
provide('trueAltitude', trueAltitude)
provide('isAiLive', isAiLive)
provide('video_id', video_id)
const getAiLiveUrl = async () => {
    const res = await getLiveAiLinkApi({
@@ -117,11 +115,14 @@
}
// 获取无人机直播url
async function getDroneLiveUrl() {
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('刷新成功')
}
// 无人机直播画质切换
@@ -220,6 +221,8 @@
        position: absolute;
        left: 340px;
        bottom: 183px;
        width: 22px;
        height: 22px;
    }
}
</style>
src/components/CurrentTaskDetails/TaskDetailsHead.vue
@@ -44,8 +44,8 @@
    { 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: 7, title: '4G信号', value: '-', unit: '' },
    { index: 8, title: 'SDR信号', value: '-', unit: '' },
    { index: 9, title: 'GPS搜星数', value: 0, unit: '' },
    { index: 10, title: 'RTK搜星数', value: 0, unit: '' },
    { index: 11, title: '距离机场', value: 0, unit: 'M' },
@@ -62,7 +62,7 @@
}
function refreshLive() {
    EventBus.emit('CurrentTaskDetails-getDroneLiveUrl')
    EventBus.emit('CurrentTaskDetails-getDroneLiveUrl',true)
}
function getFlightStatisticsFun() {
src/components/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -36,6 +36,7 @@
                :max="cameraParams.camera_type === 'ir' ? 200 : 200"
                @change="sliderChange"
            />
            <div class="cameraZoomText">{{cameraParams.zoom_factor}}X</div>
        </div>
    </div>
    <!-- 广播列表 -->
@@ -87,11 +88,11 @@
<script setup>
import EventBus from '@/event-bus'
import {
    callPhotoAndVideoCmd,
    cameraParamsChangeApi,
    ptzResetModeApi,
    startVoice,
import {
    callPhotoAndVideoCmd,
    cameraParamsChangeApi,
    ptzResetModeApi,
    startVoice,
    stayAwayRiver,
    getVoiceFile,
    playAudio,
@@ -255,7 +256,7 @@
    }
}
const tableList = ref([]);
// 分页相关
// 分页相关
const searchParams = ref({
    sn: droneSn.value,
  name: '',
@@ -363,16 +364,27 @@
    .cameraZoom {
        position: absolute;
        padding: 16px 0;
        padding: 20px 0 15px 0;
        left: 1600px;
        top: -59px;
        width: 112px;
        width: 90px;
        height: 490px;
        background: rgba(64, 64, 64, 0.15);
        background: rgba(0, 0, 0, 0.4);
        backdrop-filter: blur(5rem);
        border-radius: 20px 20px 20px 20px;
        display: flex;
        flex-direction: column;
        align-items: center;
        .el-slider {
            height: 100%;
            flex: 1;
        }
        .cameraZoomText{
            font-family: Segoe UI, Segoe UI;
            font-weight: 400;
            font-size: 22px;
            color: #ffffff;
            margin-top: 20px;
        }
    }
@@ -586,7 +598,7 @@
    bottom: 10px;
        height: 32px;
    display: flex;
        :deep(.number) {
            color: #EDEDED;
        }
src/components/CurrentTaskDetails/TaskDetailsRight.vue
@@ -37,6 +37,10 @@
    () => {
        list.value.forEach(item => {
            item.value = taskDetails?.value?.[item.field] || ''
            if (item.name === '任务频次') {
                const { rep_rule_type = '', rep_rule_val = '' } = taskDetails?.value || {}
                item.value = rep_rule_type + ' -- ' + rep_rule_val
            }
        })
    },
    {
@@ -44,8 +48,6 @@
        deep: true,
    }
)
</script>
<style scoped lang="scss">
@@ -57,6 +59,7 @@
    width: 297px;
    height: 1002px;
    background: rgba(31, 31, 31, 0.15);
    backdrop-filter: blur(0.5rem);
    border-radius: 0px 40px 40px 0px;
    display: flex;
    flex-direction: column;
@@ -107,6 +110,8 @@
            .itemValue {
                font-weight: bold;
                color: #ffffff;
                word-break: break-all;     /* 强制在任意字符断行 */
                white-space: normal;       /* 允许正常换行 */
            }
        }
    }
src/hooks/components/DevicePopUpBox.vue
@@ -55,7 +55,7 @@
  const areaCode = props.data.region_code;
  dataObj.value.region_name = props.data.region_name;
  const res = await getDeviceInfoNum({ areaCode });
  console.log('首页地图弹框',res);
  
  const resJob = await getTotalJobNum({ areaCode });
  dataObj.value.jobNum = resJob.data.data;
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue
@@ -67,7 +67,7 @@
  <DeviceJobDetails
        v-if="isShowDeviceJobDetails"
        v-model:show="isShowDeviceJobDetails"
        :wayLineJodInfoId="wayLineJodInfoId"/>
        :wayLineJodInfoId="rowData.id"/>
</template>
<script setup>
@@ -85,7 +85,6 @@
});
const jobListData = ref([]);
const total = ref(0);
let wayLineJodInfoId = ref('')
let isShowDeviceJobDetails = ref(false);
let isShowCurrentTaskDetails = ref(false);
@@ -113,12 +112,10 @@
let rowData = ref({});
const handleDetail = (row) => {
    if (row.device_sns.length === 1){
        console.log(row)
        rowData.value = row? row : {};
        if (row.status === 2 || row.status === 1){
            rowData.value = row? row : {};
            isShowCurrentTaskDetails.value = true;
        } else{
            wayLineJodInfoId.value = row.id
            isShowDeviceJobDetails.value = true
        }
    }else{