forked from drone/command-center-dashboard

chenyao
2025-04-19 bdbc09270be8590d9b5a4837501074dfc338d34b
Merge branch 'master' of http://139.196.74.78:10010/r/drone/command-center-dashboard
23 files modified
3 files added
8489 ■■■■ changed files
index.html 2 ●●● patch | view | raw | blame | history
pnpm-lock.yaml 7190 ●●●● patch | view | raw | blame | history
public/favicon.png patch | view | raw | blame | history
src/api/payload.js 32 ●●●●● 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/ControlPanel.vue 16 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/CurrentTaskDetails.vue 81 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/RealTimeMap.vue 67 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsHead.vue 4 ●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsLeft.vue 40 ●●●●● patch | view | raw | blame | history
src/components/CurrentTaskDetails/TaskDetailsRight.vue 6 ●●●●● patch | view | raw | blame | history
src/hooks/components/DevicePopUpBox.vue 2 ●●● patch | view | raw | blame | history
src/hooks/useTaskDetails/useTaskDetails.js 27 ●●●●● patch | view | raw | blame | history
src/hooks/useTaskViewInfo/useTaskViewInfo.js 81 ●●●●● patch | view | raw | blame | history
src/hooks/useTaskWayline/useTaskWayline.js 103 ●●●●● patch | view | raw | blame | history
src/views/Home/HomeRight/EventOverview.vue 2 ●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineLeft/InspectionRaskDetails.vue 11 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/InspectionRaskList.vue 760 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/AddTask.vue 57 ●●●● 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'>
pnpm-lock.yaml
Diff too large
public/favicon.png

src/api/payload.js
@@ -24,17 +24,17 @@
}
// 获得有效载荷控制
export async function getPayloadControlApi(params) {
  return await request({
    url:`${API_PREFIX}/devices/payload-control/requests`,
    method:'get',
export function getPayloadControlApi(params) {
  return request({
    url: `${API_PREFIX}/devices/payload-control/requests`,
    method: 'get',
    params
  })
}
// 云台控制api
export async function ptzControlApi(key,params) {
  return await request({
export function ptzControlApi(key,params) {
  return request({
    url:`${API_PREFIX}/devices/payload-control/payload/${key}`,
    method:'get',
    params
@@ -42,8 +42,8 @@
}
// 拍照和录像
export async function callPhotoAndVideoCmd(sn, type) {
  return await request({
export function callPhotoAndVideoCmd(sn, type) {
  return request({
    url:`${API_PREFIX}/devices/${sn}/payload/photoAndVideoCmd/${type}`,
    method:'get',
  })
@@ -51,17 +51,25 @@
// 相机参数调整
export async function cameraParamsChangeApi(data) {
  return await request({
export function cameraParamsChangeApi(data) {
  return request({
    url:`${API_PREFIX}/devices/payload-control/payload/zoom/level`,
    method:'post',
    data
  })
}
export function getLiveCapacityApi(workspace_id,params) {
  return request({
    url:`/drone-device-core/manage/api/v1/live/capacity${workspace_id}`,
    method:'get',
    params
  })
}
// 云台重置
export async function ptzResetModeApi(params) {
  return await request({
export function ptzResetModeApi(params) {
  return request({
    url:`${API_PREFIX}/devices/payload-control/payload/reset`,
    method:'get',
    params
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/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>
@@ -181,7 +181,7 @@
let timer = null
let totalSeconds = 0
const workspace_id = computed(() => taskDetails?.value?.workspace_id)
const workspace_id = inject('workspace_id')
const list1 = [
    { key: KeyCode.KEY_Q, text: 'Q', icon: RefreshLeft },
    { key: KeyCode.KEY_W, text: 'W', icon: ArrowUp },
@@ -406,8 +406,7 @@
    speed: speed.value,
}))
watch(
    () => workspace_id.value,
watch(workspace_id,
    async () => {
        if (workspace_id.value && mqttState === null && client_id.value === '') {
            await createConnect()
@@ -465,7 +464,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;
@@ -519,10 +518,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;
            }
        }
    }
@@ -631,7 +631,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
@@ -1,3 +1,13 @@
<!--
 * @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
@@ -10,7 +20,7 @@
    >
        <div class="content-container" v-if="isShow">
            <!-- 视频直播 -->
            <div :class="`${isMaxMap ? 'minBox' : 'maxBox'}`">
            <div :class="`${isMaxMap ? 'minBox' : 'maxBox'} centerPoint`">
                <LiveVideo :videoUrl="currentLiveUrl" :controls="false" />
            </div>
            <!-- 展示地图 -->
@@ -20,9 +30,7 @@
                <TaskDetailsHead />
                <TaskDetailsLeft />
            </template>
            <!--    控制面板,里面有方法需要立即执行,不可用v-if        -->
            <!--            <ControlPanel />-->
            <ControlPanel v-show="!isAutoControl" />
            <img alt="" :src="amplifyImg" class="amplify" @click="isMaxMap = !isMaxMap" />
        </div>
@@ -44,14 +52,14 @@
import { ElMessage } from 'element-plus'
import EventBus from '@/event-bus'
import { updateDroneQualityApi } from '@/api/drc'
import { getLiveAiLinkApi } from '@/api/payload'
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) //地图实例
let taskDetails = ref({}) //任务详情
const deviceOsdInfo = computed(() => wsInfo.value?.device_osd)
const dockSn = computed(() => taskDetails?.value?.device_sns?.[0])
const droneSn = computed(() => deviceOsdInfo?.value?.data?.sn)
@@ -64,9 +72,27 @@
const isTakeOff = ref(false) // 是在飞行中
const isMaxMap = ref(false) //是大地图
const client_id = ref('') //是大地图
const workspace_id = ref('')
// 获取机巢直播
const getDeviceLiveUrl = async () => {
    const res = await liveStart(dockSn.value, 2)
    currentLiveUrl.value = res.data.data.rtcs_url
}
//获取相机能力
async function getLiveCapacity() {
    const res = await getLiveCapacityApi(workspace_id.value,{ sn: dockSn.value})
}
const useTaskDetailsCallBack = () => {
    console.log(workspace_id.value,66666666)
    getDeviceLiveUrl()
}
let { taskDetails, workspace_id, getTaskDetails } = useTaskDetails(useTaskDetailsCallBack)
let { wsInfo, removeWS } = useDroneWS(workspace_id) //ws信息,是一个ref对象
provide('wsInfo', wsInfo)
provide('workspace_id', workspace_id)
provide('deviceOsdInfo', deviceOsdInfo)
provide('dockOsdInfo', wsInfo?.value?.dock_osd)
provide('dockSn', dockSn)
@@ -80,6 +106,14 @@
provide('video_id', video_id)
provide('client_id', client_id)
let once = true
watch(deviceOsdInfo,()=>{
    if (once){
        // getLiveCapacity()
        once =false
    }
})
watch(
    wsInfo,
    () => {
@@ -88,12 +122,6 @@
    },
    { deep: true }
)
// 获取机巢直播
const getDeviceLiveUrl = async () => {
    const res = await liveStart(dockSn.value, 2)
    currentLiveUrl.value = res.data.data.rtcs_url
}
const getAiLiveUrl = async () => {
    const res = await getLiveAiLinkApi({
@@ -132,19 +160,9 @@
    isTakeOff.value = currentIsTakeOff
    isTakeOff.value ? await getDroneLiveUrl() : await getDeviceLiveUrl()
}
// 获取任务详情获取航线文件
const getTaskDetails = () => {
    if (!props.id) ElMessage.warning('请检查是否传入id')
    getJobDetails({ wayLineJobInfoId: props.id }).then(async res => {
        taskDetails.value = res.data.data
        await getDeviceLiveUrl()
        taskDetails.value.workspace_id = taskDetails.value.way_lines[0]?.workspace_id
        workspace_id.value = taskDetails.value.workspace_id
    })
}
onMounted(() => {
    getTaskDetails()
    getTaskDetails(props?.id)
    EventBus.on('CurrentTaskDetails-timeStop', changeLineQuality)
    EventBus.on('CurrentTaskDetails-getAiLiveUrl', getAiLiveUrl)
    EventBus.on('CurrentTaskDetails-getDroneLiveUrl', getDroneLiveUrl)
@@ -201,6 +219,21 @@
    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%;
@@ -222,6 +255,8 @@
        position: absolute;
        left: 340px;
        bottom: 183px;
        width: 22px;
        height: 22px;
    }
}
</style>
src/components/CurrentTaskDetails/RealTimeMap.vue
@@ -5,15 +5,10 @@
import * as Cesium from 'cesium'
import AmapMercatorTilingScheme from '@/utils/cesium/AmapMercatorTilingScheme'
import { Cartesian3, Terrain, Viewer } from 'cesium'
import endPointImg from '@/assets/images/EndPointicon.png'
import { addBlueFilter } from '@/utils/cesium/common'
import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'
import rwqfdImg from '@/assets/images/signMachineNest/rwqfd.png'
import ImageTrailMaterial from '@/utils/cesium/ImageTrailMaterial'
import lineImg from '@/assets/images/arrow-right-blue.png'
import { flyVisual } from '@/utils/cesium/mapUtil'
import CreateFrustum from '@/utils/cesium/frustum/CreateFrustum'
import aircraftGltf from '@/assets/gltf/aircraft.gltf'
import { useTaskWayline } from '@/hooks/useTaskWayline/useTaskWayline'
const imageryProvider_ammapSL = new Cesium.UrlTemplateImageryProvider({
    url: 'https://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}',
@@ -61,59 +56,6 @@
        destination: Cartesian3.fromDegrees(115.763819, 28.787374, 5000),
    })
}
const drawWayline = lineObj => {
    const positions = lineObj.Placemark.map(item => {
        const [lon, lat] = item.Point.coordinates.split(',')
        return Cartesian3.fromDegrees(Number(lon), Number(lat))
    })
    // 起点
    taskDetailsViewer?.value.entities.add({
        position: positions[0],
        billboard: {
            image: new Cesium.ConstantProperty(rwqfdImg),
            width: 70,
            height: 70,
        },
    })
    // 终点
    taskDetailsViewer?.value.entities.add({
        position: positions[positions.length - 1],
        billboard: {
            image: new Cesium.ConstantProperty(endPointImg),
            width: 30,
            height: 30,
            verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 底部对齐
        },
    })
    // 路径线
    taskDetailsViewer?.value.entities.add({
        polyline: {
            width: 4,
            positions: positions,
            material: new ImageTrailMaterial({
                color: { alpha: 1, blue: 1, green: 1, red: 1 },
                speed: 20,
                image: lineImg,
                repeat: { x: Math.floor(40), y: 1 },
            }),
            clampToGround: false,
        },
    })
}
// 解析kmz文件
const parsingFiles = async url => {
    const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`)
    const waylinesXML = await res.fileInfoObj['wpmz/waylines.wpml']
    const waylinesXMLJSON = XMLToJSON(waylinesXML)?.['Document']
    const waylinesXMLObj = removeTextKey(waylinesXMLJSON.Folder)
    if (!waylinesXMLObj.Placemark.length) return
    const allPoint = waylinesXMLObj.Placemark.map(item => item.Point.coordinates.split(','))
    flyVisual(allPoint, taskDetailsViewer?.value)
    drawWayline(waylinesXMLObj)
}
let viewInfoFrustum
// 设置视椎
@@ -165,11 +107,8 @@
    setCreateFrustum()
    setAircraftGltf()
})
watch(taskDetails, () => {
    if (taskDetails.value.way_lines.length) {
        parsingFiles(taskDetails.value.way_lines[0].url)
    }
})
useTaskWayline(taskDetailsViewer, taskDetails)
const removeMap = () => {
    taskDetailsViewer?.value.entities.removeAll()
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' },
src/components/CurrentTaskDetails/TaskDetailsLeft.vue
@@ -21,7 +21,7 @@
            <div @click="takePictures">拍照</div>
            <div @click="recordFun">{{ isRecording ? '录像中...' : '录像' }}</div>
        </div>
        <div class="multiCol">
        <div class="multiCol" v-if="wsInfo?.psdk_widget_values">
            <div @click="shoutFun">{{ isRecordShouting ? '喊话' : '停止喊话' }}</div>
            <div @click="broadcastFun">广播</div>
        </div>
@@ -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,
@@ -101,9 +102,9 @@
import Recorder from 'js-audio-recorder';
import dayjs from 'dayjs'
const wsInfo = inject('wsInfo')
// 初始化喊话
let globalShout = null
const isRecording = ref(false)
const list1 = ref([
@@ -255,7 +256,7 @@
    }
}
const tableList = ref([]);
// 分页相关
// 分页相关
const searchParams = ref({
    sn: droneSn.value,
  name: '',
@@ -345,7 +346,7 @@
    top: 50%;
    transform: translateY(-60%);
    width: 178px;
    height: 416px;
    padding: 20px 0;
    background: rgba(0, 0, 0, 0.5);
    backdrop-filter: blur(5px);
    border-radius: 20px 20px 20px 20px;
@@ -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
@@ -11,7 +11,7 @@
            </div>
        </div>
        <BaseControl v-if="taskDetails.workspace_id" />
        <BaseControl v-if="workspace_id" />
    </div>
</template>
<script setup>
@@ -19,6 +19,7 @@
import BaseControl from '@/components/CurrentTaskDetails/ControlPanel/BaseControl.vue'
const taskDetails = inject('taskDetails')
const workspace_id = inject('workspace_id')
const list = ref([
    { name: '任务编号', value: '', field: 'job_info_num' },
@@ -58,7 +59,8 @@
    top: 0;
    width: 297px;
    height: 1002px;
    background: rgba(31, 31, 31, 0.15);
    background: rgba(31, 31, 31, 0.5);
    backdrop-filter: blur(0.5rem);
    border-radius: 0px 40px 40px 0px;
    display: flex;
    flex-direction: column;
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/hooks/useTaskDetails/useTaskDetails.js
New file
@@ -0,0 +1,27 @@
import { ElMessage } from 'element-plus'
import { getJobDetails } from '@/api/home/task'
export function useTaskDetails (cb) {
  const taskDetails = ref({})
  const workspace_id = ref('')
  const getTaskDetails = async (id) => {
    if (!id) ElMessage.warning('请检查是否传入id')
    try {
      const res = await getJobDetails({ wayLineJobInfoId: id })
      taskDetails.value = res.data.data
      cb && await cb()
      workspace_id.value = taskDetails.value.way_lines[0]?.workspace_id
      return taskDetails.value
    } catch (error) {
      ElMessage.warning('获取任务详情失败')
      throw error
    }
  }
  return {
    taskDetails,
    workspace_id,
    getTaskDetails
  }
}
src/hooks/useTaskViewInfo/useTaskViewInfo.js
New file
@@ -0,0 +1,81 @@
import * as Cesium from 'cesium'
import aircraftGltf from '@/assets/gltf/aircraft.gltf'
import CreateFrustum from '@/utils/cesium/frustum/CreateFrustum'
export function useTaskViewInfo (viewer, wsInfo, removeEntitys) {
  const newViewer = unref(viewer)
  let viewInfoFrustum
  // 设置视椎
  const setCreateFrustum = (host) => {
    if (!host) return
    viewInfoFrustum?.clear()
    const attitude_head = 180 + host.attitude_head
    const gimbal_pitch = 90 - Number(host?.payloads[0]?.gimbal_pitch) || 0
    viewInfoFrustum = new CreateFrustum(newViewer, {
      position: {
        longitude: host.longitude,
        latitude: host.latitude,
        altitude: host.height,
      },
      width: 30,
      height: 30,
      fov: 20.0,
      near: 3.0,
      far: 250.0,
      roll: gimbal_pitch,
      pitch: 0,
      heading: attitude_head,
    })
  }
  function setAircraftGltf () {
    const host = deviceOsdInfo.value?.data?.host
    const aircraftEntity = newViewer.entities.getById('aircraftGltf')
    const position = Cesium.Cartesian3.fromDegrees(host.longitude, host.latitude, host.height)
    if (aircraftEntity) {
      aircraftEntity.position = new Cesium.ConstantPositionProperty(position)
      return
    }
    newViewer.entities.add({
      id: 'aircraftGltf',
      position,
      model: {
        uri: aircraftGltf, // 或 .glb
        scale: 1.0, // 缩放比例
        minimumPixelSize: 64, // 最小像素尺寸(保证模型远处可见)
        maximumScale: 128, // 最大缩放(可选)
      },
    })
  }
  // 视椎加载处理
  const deviceOsdInfo = computed(() => wsInfo.value?.device_osd)
  watch(deviceOsdInfo, () => {
    const host = deviceOsdInfo.value?.data?.host
    if ([14, 0].includes(host.mode_code)) {
      mapEntityRemove()
      return
    }
    setCreateFrustum(host)
    setAircraftGltf()
  })
  const mapEntityRemove = () => {
    viewInfoFrustum?.clear()
    newViewer.entities.removeById('aircraftGltf')
    removeEntitys && removeEntitys()
  }
  onUnmounted(() => {
    mapEntityRemove()
  })
}
src/hooks/useTaskWayline/useTaskWayline.js
New file
@@ -0,0 +1,103 @@
/*
 * @Author: shuishen 1109946754@qq.com
 * @Date: 2025-04-19 14:24:34
 * @LastEditors: shuishen 1109946754@qq.com
 * @LastEditTime: 2025-04-19 15:23:57
 * @FilePath: \command-center-dashboard\src\hooks\useTaskWayline\useTaskWayline.js
 * @Description:
 *
 * Copyright (c) 2025 by shuishen, All Rights Reserved.
 */
import lineImg from '@/assets/images/arrow-right-blue.png'
import rwqfdImg from '@/assets/images/signMachineNest/rwqfd.png'
import endPointImg from '@/assets/images/EndPointicon.png'
import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'
import { flyVisual } from '@/utils/cesium/mapUtil'
import ImageTrailMaterial from '@/utils/cesium/ImageTrailMaterial'
import * as Cesium from 'cesium'
import { Cartesian3 } from 'cesium'
export function useTaskWayline (viewer, taskDetails) {
  const newViewer = unref(viewer)
  // 解析kmz文件
  const parsingFiles = async url => {
    const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`)
    const waylinesXML = await res.fileInfoObj['wpmz/waylines.wpml']
    const waylinesXMLJSON = XMLToJSON(waylinesXML)?.['Document']
    const waylinesXMLObj = removeTextKey(waylinesXMLJSON.Folder)
    if (!waylinesXMLObj.Placemark.length) return
    const allPoint = waylinesXMLObj.Placemark.map(item => item.Point.coordinates.split(','))
    flyVisual(allPoint, newViewer)
    drawWayline(waylinesXMLObj)
  }
  const drawWayline = lineObj => {
    const positions = lineObj.Placemark.map(item => {
      const [lon, lat] = item.Point.coordinates.split(',')
      return Cartesian3.fromDegrees(Number(lon), Number(lat))
    })
    // 起点
    newViewer.entities.add({
      id: 'drone-job-wayline-start',
      position: positions[0],
      billboard: {
        image: new Cesium.ConstantProperty(rwqfdImg),
        width: 70,
        height: 70,
      },
    })
    // 终点
    newViewer.entities.add({
      id: 'drone-job-wayline-end',
      position: positions[positions.length - 1],
      billboard: {
        image: new Cesium.ConstantProperty(endPointImg),
        width: 30,
        height: 30,
        verticalOrigin: Cesium.VerticalOrigin.BOTTOM, // 底部对齐
      },
    })
    // 路径线
    newViewer.entities.add({
      id: 'drone-job-wayline-polyline',
      polyline: {
        width: 4,
        positions: positions,
        material: new ImageTrailMaterial({
          color: { alpha: 1, blue: 1, green: 1, red: 1 },
          speed: 20,
          image: lineImg,
          repeat: { x: Math.floor(40), y: 1 },
        }),
        clampToGround: false,
      },
    })
  }
  watch(taskDetails, () => {
    if (taskDetails.value?.way_lines?.length) {
      parsingFiles(taskDetails.value.way_lines[0].url)
    }
  }, { immediate: true })
  const removeEntitys = () => {
    const entitiesIDs = newViewer?.entities.values.map(i => i.id)
    entitiesIDs.forEach(item => {
      item.includes('drone-job-wayline-') && newViewer?.entities.removeById(item)
    })
  }
  onBeforeUnmount(() => {
    removeEntitys()
  })
  return {
    removeEntitys
  }
}
src/views/Home/HomeRight/EventOverview.vue
@@ -144,7 +144,7 @@
const today = dayjs().format('YYYY-MM-DD')
const timeArr = ref([today, today])
const completionRateSeries = {
    name: '完成率',
    name: '完结率',
    type: 'line',
    itemStyle: {
        color: '#0CEBF7', // 设置颜色
src/views/SignMachineNest/MachineLeft/InspectionRaskDetails.vue
@@ -162,12 +162,13 @@
                color: '#fff',
            },
            data: [
                { value: 0, name: '待审核', itemStyle: { color: '#1860EC' } },
                { value: 0, name: '待处理', itemStyle: { color: '#47D107' } },
                { value: 0, name: '已完成', itemStyle: { color: '#F29509' } },
                { value: 0, name: '待审核', itemStyle: { color: '#8CFEA7' } },
                { value: 0, name: '待处理', itemStyle: { color: '#FF7411' } },
                { value: 0, name: '处理中', itemStyle: { color: '#FFC398' } },
                { value: 0, name: '已完成', itemStyle: { color: '#AFD9FB' } },
                // { value: 0, name: '待分拨', itemStyle: { color: '#E9C81A' } },
                { value: 0, name: '处理中', itemStyle: { color: '#0FC1E8' } },
                { value: 0, name: '已完结', itemStyle: { color: '#FE577F' } },
                { value: 0, name: '已完结', itemStyle: { color: '#11C4FF' } },
            ],
        },
    ],
src/views/SignMachineNest/MachineRight/InspectionRaskList.vue
@@ -1,355 +1,501 @@
<!-- 巡检任务列表 -->
<template>
  <CommonTitle title="巡检任务列表" />
  <div :style="{ marginLeft: pxToRem(14) }">
    <div class="inspection-rask-list">
      <div class="tab-search">
        <div class="tab-btn">
          <div :class="tabIndex===1?'active':''" @click="tabClick(1)">当前任务</div>
          <div :class="tabIndex===2?'active':''" @click="tabClick(2)">历史任务</div>
        </div>
        <div class="search-box">
          <el-input v-model="searchText" placeholder="请输入搜索内容" class="input-with-select">
            <template #append>
              <el-button :icon="Search" @click="searchNickName"/>
            </template>
          </el-input>
        </div>
      </div>
      <div class="table-list" v-if="tableList.length > 0"
        infinite-scroll-distance="6"
        v-infinite-scroll="loadMore"
        :infinite-scroll-disabled="busy"
        infinite-scroll-immediate="true">>
        <div class="item" v-for="(item,index) in tableList">
          <div class="left" @click="taskClick(item)">
            <div class="left-t">
              <span>{{ index+1 }}.</span>{{ item.name }}
              <span class="status" :class="item.status===2?'active':''">
                {{ getStatusText(item.status) }}
              </span>
            </div>
            <div class="left-b">
              <img src="../../../assets/images/signMachineNest/machineRight/date.png" alt="" />{{ item.begin_time }}
              <img src="../../../assets/images/signMachineNest/machineRight/name.png" alt="" />{{ item.creator_name || '' }}
            </div>
          </div>
          <div class="right" v-if="tabIndex===1" @click="reExecute(item.dock_sn)">
            <span>立即返航</span>
            <img src="../../../assets/images/signMachineNest/machineRight/return-fly.png" alt="">
          </div>
          <div class="right" v-else @click="returnImmediately(item.job_id)">
            <span>再次执行</span>
            <img src="../../../assets/images/signMachineNest/machineRight/return-fly.png" alt="">
          </div>
        </div>
      </div>
      <el-empty class="custom-empty" v-else>
        <template #description>
          <span class="custom-text">暂无数据</span>
        </template>
      </el-empty>
    </div>
  </div>
  <!-- 当前任务详情 -->
  <CurrentTaskDetails
        v-if="isShowCurrentTaskDetails"
        v-model:show="isShowCurrentTaskDetails"
        :id="currentInfoId"/>
    <CommonTitle title="巡检任务列表" />
    <div :style="{ marginLeft: pxToRem(14) }">
        <div class="inspection-rask-list">
            <div class="tab-search">
                <div class="tab-btn">
                    <div :class="tabIndex === 1 ? 'active' : ''" @click="tabClick(1)">当前任务</div>
                    <div :class="tabIndex === 2 ? 'active' : ''" @click="tabClick(2)">历史任务</div>
                </div>
                <div class="search-box">
                    <el-input v-model="searchText" placeholder="请输入搜索内容" class="input-with-select">
                        <template #append>
                            <el-button :icon="Search" @click="searchNickName" />
                        </template>
                    </el-input>
                </div>
            </div>
            <div
                class="table-list"
                v-if="tableList.length > 0"
                infinite-scroll-distance="6"
                v-infinite-scroll="loadMore"
                :infinite-scroll-disabled="busy"
                infinite-scroll-immediate="true"
            >
                >
                <div class="item" v-for="(item, index) in tableList">
                    <div class="left" @click="taskClick(item)">
                        <div class="left-t">
                            <span>{{ index + 1 }}.</span>
                            {{ item.name }}
                            <span class="status" :class="item.status === 2 ? 'active' : ''">
                                {{ getStatusText(item.status) }}
                            </span>
                        </div>
                        <div class="left-b">
                            <img src="../../../assets/images/signMachineNest/machineRight/date.png" alt="" />
                            {{ item.begin_time }}
                            <img src="../../../assets/images/signMachineNest/machineRight/name.png" alt="" />
                            {{ item.creator_name || '' }}
                        </div>
                    </div>
                    <div class="right" v-if="tabIndex === 1" @click="reExecute(item.dock_sn)">
                        <span>立即返航</span>
                        <img src="../../../assets/images/signMachineNest/machineRight/return-fly.png" alt="" />
                    </div>
                    <div class="right" v-else @click="returnImmediately(item.job_id)">
                        <span>再次执行</span>
                        <img src="../../../assets/images/signMachineNest/machineRight/return-fly.png" alt="" />
                    </div>
                </div>
            </div>
            <el-empty class="custom-empty" v-else>
                <template #description>
                    <span class="custom-text">暂无数据</span>
                </template>
            </el-empty>
        </div>
    </div>
    <!-- 当前任务详情 -->
    <CurrentTaskDetails v-if="isShowCurrentTaskDetails" v-model:show="isShowCurrentTaskDetails" :id="currentInfoId" />
    <!-- 历史任务详情 -->
  <DeviceJobDetails
    <DeviceJobDetails
        v-if="isShowDeviceJobDetails"
        v-model:show="isShowDeviceJobDetails"
        :wayLineJodInfoId="wayLineJodInfoId"/>
        :wayLineJodInfoId="wayLineJodInfoId"
    />
</template>
<script setup>
import { Search } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import CommonTitle from '@/components/CommonTitle.vue';
import CurrentTaskDetails from '@/components/CurrentTaskDetails/CurrentTaskDetails.vue';
import { Search } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import CommonTitle from '@/components/CommonTitle.vue'
import CurrentTaskDetails from '@/components/CurrentTaskDetails/CurrentTaskDetails.vue'
import DeviceJobDetails from '@/components/DeviceJobDetails/DeviceJobDetails.vue'
import { getBeforeJob, getTodayJob, flyByJobId, returnHome } from '@/api/home';
import { useStore } from 'vuex';
import { getBeforeJob, getTodayJob, flyByJobId, returnHome } from '@/api/home'
import { useStore } from 'vuex'
import { useTaskWayline } from '@/hooks/useTaskWayline/useTaskWayline'
import { useTaskDetails } from '@/hooks/useTaskDetails/useTaskDetails'
import { useTaskViewInfo } from '@/hooks/useTaskViewInfo/useTaskViewInfo'
import { useDroneWS } from '@/hooks/useDroneWS'
let viewer = null
const store = useStore();
let { taskDetails, workspace_id, getTaskDetails } = useTaskDetails()
let { wsInfo, removeWS } = useDroneWS(workspace_id) //ws信息,是一个ref对象
const store = useStore()
// 设备任务详情
let currentInfoId = ref('');
let isShowCurrentTaskDetails = ref(false);
let wayLineJodInfoId = ref('');
let isShowDeviceJobDetails = ref(false);
let currentInfoId = ref('')
let isShowCurrentTaskDetails = ref(false)
let wayLineJodInfoId = ref('')
let isShowDeviceJobDetails = ref(false)
// 单个机巢信息
const singleUavHome = computed(() => store.state.home.singleUavHome);
const singleUavHome = computed(() => store.state.home.singleUavHome)
const isMore = ref(true);
const isMore = ref(true)
// 控制加载状态
const busy = ref(false);
const busy = ref(false)
let searchText = ref('')
const tableList = ref([]);
const tableList = ref([])
// 分页
const pageParams = ref({
  current: 1,
  size: 5,
  total: 0
});
    current: 1,
    size: 5,
    total: 0,
})
// 当前任务和历史任务切换
let tabIndex = ref(1);
const tabClick = (value) => {
  tabIndex.value = value;
  clearData();
};
let tabIndex = ref(1)
const tabClick = value => {
    tabIndex.value = value
    clearData()
}
// 状态文字判断
const getStatusText = (status) => {
  switch (status) {
    case 1:
      return '待执行';
    case 2:
      return '执行中';
    case 3:
      return '完成';
    case 4:
      return '取消';
    case 5:
      return '失败';
    default:
      return '未知';
  }
};
const getStatusText = status => {
    switch (status) {
        case 1:
            return '待执行'
        case 2:
            return '执行中'
        case 3:
            return '完成'
        case 4:
            return '取消'
        case 5:
            return '失败'
        default:
            return '未知'
    }
}
// 获取历史巡检任务列表
const getJobList = async () => {
  const params = {
    area_code: '',
    device_sn: singleUavHome.value.device_sn,
    job_name: searchText.value,
    current: pageParams.value.current,
    size: pageParams.value.size,
  }
  let result = null;
  if (tabIndex.value === 1) {
    result = await getTodayJob(params)
  } else {
    result = await getBeforeJob(params)
  }
  if (result.data.code !== 0) return;
  if (result.data.data.records.length === 0) return (isMore.value = false);
  pageParams.value.current += 1;
  tableList.value = [...tableList.value, ...result.data.data.records];
  busy.value = false;
    const params = {
        area_code: '',
        device_sn: singleUavHome.value.device_sn,
        job_name: searchText.value,
        current: pageParams.value.current,
        size: pageParams.value.size,
    }
    let result = null
    if (tabIndex.value === 1) {
        result = await getTodayJob(params)
    } else {
        result = await getBeforeJob(params)
    }
    if (result.data.code !== 0) return
    if (result.data.data.records.length === 0) return (isMore.value = false)
    pageParams.value.current += 1
    tableList.value = [...tableList.value, ...result.data.data.records]
    busy.value = false
}
// 清除数据
const clearData = () => {
  tableList.value = [];
  pageParams.value.current = 1;
  isMore.value = true;
  getJobList();
};
    tableList.value = []
    pageParams.value.current = 1
    isMore.value = true
    getJobList()
}
// 加载更多数据
const loadMore = async () => {
    busy.value = true;
    if (!isMore.value) return;
    getJobList();
};
    busy.value = true
    if (!isMore.value) return
    getJobList()
}
// 搜索数据
const searchNickName = () => {
  clearData();
};
    clearData()
}
// 立即返航
const returnImmediately = (id) => {
  flyByJobId(id).then(result => {
    if (result.data.code === 0) {
      ElMessage.success('执行成功');
    } else {
      ElMessage.error(result.data.message);
    }
  });
};
const returnImmediately = id => {
    flyByJobId(id).then(result => {
        if (result.data.code === 0) {
            ElMessage.success('执行成功')
        } else {
            ElMessage.error(result.data.message)
        }
    })
}
// 重新执行
const reExecute = (dock_sn) => {
  returnHome(dock_sn).then(result => {
    if (result.data.code === 0) {
      ElMessage.success('返航成功');
    } else {
      ElMessage.error(result.data.message);
    }
  });
};
const reExecute = dock_sn => {
    returnHome(dock_sn).then(result => {
        if (result.data.code === 0) {
            ElMessage.success('返航成功')
        } else {
            ElMessage.error(result.data.message)
        }
    })
}
// 点击当前任务显示当前任务详情
const taskClick = (item) => {
  if (tabIndex.value === 1) {
    // 展示当前任务详情
    currentInfoId.value = item.wayline_job_info_id;
    isShowCurrentTaskDetails.value = true;
  } else {
    // 展示历史任务详情
    wayLineJodInfoId.value = item.wayline_job_info_id;
    isShowDeviceJobDetails.value = true;
  }
};
const taskClick = item => {
    if (tabIndex.value === 1) {
        // 展示当前任务详情
        currentInfoId.value = item.wayline_job_info_id
        isShowCurrentTaskDetails.value = true
    } else {
        // 展示历史任务详情
        wayLineJodInfoId.value = item.wayline_job_info_id
        isShowDeviceJobDetails.value = true
    }
}
onMounted(() => {
  getJobList();
});
onMounted(async () => {
    viewer = window.$viewer
    await getJobList()
    // tableList.value = [
    //     {
    //         id: 11563,
    //         status: 5,
    //         begin_time: '2025/04/19 05:46:25',
    //         end_time: '2025/04/19 05:46:25',
    //         create_time: '2025-04-19T05:46:36.032+00:00',
    //         name: '智引即飞202504191252',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 340,
    //         job_id: '484a7846-60e0-4ec3-b9fb-272a055c6938',
    //     },
    //     {
    //         id: 11559,
    //         status: 5,
    //         begin_time: '2025/04/19 05:39:42',
    //         end_time: '2025/04/19 05:39:42',
    //         create_time: '2025-04-19T05:39:52.022+00:00',
    //         name: '智引即飞202504191252',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 340,
    //         job_id: 'b8c58bfb-4352-47c5-ba33-242eec8df188',
    //     },
    //     {
    //         id: 11558,
    //         status: 3,
    //         begin_time: '2025/04/19 05:39:17',
    //         end_time: '2025/04/19 05:39:17',
    //         create_time: '2025-04-19T05:39:27.407+00:00',
    //         name: '智引即飞202504191252',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 340,
    //         job_id: '839773ce-bd46-4947-a2ef-d859eaea82f6',
    //     },
    //     {
    //         id: 11557,
    //         status: 5,
    //         begin_time: '2025/04/19 04:52:04',
    //         end_time: '2025/04/19 04:52:04',
    //         create_time: '2025-04-19T04:52:14.503+00:00',
    //         name: '智引即飞202504191252',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 340,
    //         job_id: '58366fd4-8361-456f-8ad9-3b881f933620',
    //     },
    //     {
    //         id: 11556,
    //         status: 5,
    //         begin_time: '2025/04/19 03:45:06',
    //         end_time: '2025/04/19 03:45:06',
    //         create_time: '2025-04-19T03:45:16.522+00:00',
    //         name: '智引即飞202504191145',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 338,
    //         job_id: '39c60fb8-e5c4-4474-912a-c3af3f1b9054',
    //     },
    //     {
    //         id: 11555,
    //         status: 5,
    //         begin_time: '2025/04/19 03:27:55',
    //         end_time: '2025/04/19 03:27:55',
    //         create_time: '2025-04-19T03:28:05.196+00:00',
    //         name: '智引即飞202504191127',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 336,
    //         job_id: '78c42532-45e4-4a01-b698-69bca19286ff',
    //     },
    //     {
    //         id: 11554,
    //         status: 5,
    //         begin_time: '2025/04/19 02:58:53',
    //         end_time: '2025/04/19 02:58:53',
    //         create_time: '2025-04-19T02:59:03.602+00:00',
    //         name: '智引即飞202504191058',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 335,
    //         job_id: 'd3b25edc-ec23-4ad6-800a-0f659a72bff3',
    //     },
    //     {
    //         id: 11553,
    //         status: 5,
    //         begin_time: '2025/04/19 02:52:21',
    //         end_time: '2025/04/19 02:52:21',
    //         create_time: '2025-04-19T02:52:31.415+00:00',
    //         name: '智引即飞202504191052',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 334,
    //         job_id: 'e27090f1-e740-4ae4-8d4d-301354eb0bbc',
    //     },
    //     {
    //         id: 11548,
    //         status: 5,
    //         begin_time: '2025/04/19 02:23:06',
    //         end_time: '2025/04/19 02:23:06',
    //         create_time: '2025-04-19T02:23:16.151+00:00',
    //         name: '智引即飞202504191023',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 332,
    //         job_id: '9e1f7283-fe34-4cfd-b468-cd2b34a96d34',
    //     },
    //     {
    //         id: 11547,
    //         status: 3,
    //         begin_time: '2025/04/19 02:18:24',
    //         end_time: '2025/04/19 02:18:24',
    //         create_time: '2025-04-19T02:18:34.590+00:00',
    //         name: '智引即飞202504191018',
    //         event_number: 0,
    //         dock_sn: '7CTDLCR00BQBM2',
    //         wayline_job_info_id: 331,
    //         job_id: '7aa5222c-2f8b-401e-b6ba-d6a550398829',
    //     },
    // ]
    if (tableList.value.length > 0) {
        await getTaskDetails(tableList.value[0].wayline_job_info_id)
    }
})
const { removeEntitys } = useTaskWayline(viewer || window.$viewer, taskDetails)
useTaskViewInfo(viewer || window.$viewer, wsInfo, removeEntitys)
</script>
<style lang="scss" scoped>
  .inspection-rask-list {
    width: 390px;
    height: 325px;
    background: linear-gradient(
      270deg,
      #1f3e7a 0%,
      rgba(31, 62, 122, 0.35) 79%,
      rgba(31, 62, 122, 0) 100%
    );
    opacity: 0.85;
    margin: 2px 0 13 0;
    padding: 11px 27px 0;
    .tab-search {
      width: 358px;
      height: 76px;
      margin-bottom: 12px;
      .tab-btn {
        display: flex;
        justify-content: center;
        margin-bottom: 16px;
        div {
          width: 104px;
          height: 32px;
          background: #0E2042;
          border: 1px solid #8EA3D1;
          font-family: Source Han Sans CN, Source Han Sans CN;
          font-weight: 400;
          font-size: 16px;
          color: #8EA3D1;
          line-height: 32px;
          text-align: center;
          cursor: pointer;
        }
        .active {
          width: 104px;
          height: 32px;
          background: linear-gradient( 180deg, rgba(0,82,248,0.58) 0%, rgba(103,209,251,0.8) 100%);
          color: #fff;
          border: 1px solid rgba(103,209,251,0.8);
        }
      }
      .search-box {
        :deep(.el-input__wrapper) {
          background-color: rgba(0, 112, 255, 0.1);
          background: rgba(0, 15, 34, 0.5);
          box-shadow: 0 0 0 1px #0070ff inset;
        }
.inspection-rask-list {
    width: 390px;
    height: 325px;
    background: linear-gradient(270deg, #1f3e7a 0%, rgba(31, 62, 122, 0.35) 79%, rgba(31, 62, 122, 0) 100%);
    opacity: 0.85;
    margin: 2px 0 13 0;
    padding: 11px 27px 0;
    .tab-search {
        width: 358px;
        height: 76px;
        margin-bottom: 12px;
        .tab-btn {
            display: flex;
            justify-content: center;
            margin-bottom: 16px;
            div {
                width: 104px;
                height: 32px;
                background: #0e2042;
                border: 1px solid #8ea3d1;
                font-family: Source Han Sans CN, Source Han Sans CN;
                font-weight: 400;
                font-size: 16px;
                color: #8ea3d1;
                line-height: 32px;
                text-align: center;
                cursor: pointer;
            }
            .active {
                width: 104px;
                height: 32px;
                background: linear-gradient(180deg, rgba(0, 82, 248, 0.58) 0%, rgba(103, 209, 251, 0.8) 100%);
                color: #fff;
                border: 1px solid rgba(103, 209, 251, 0.8);
            }
        }
        .search-box {
            :deep(.el-input__wrapper) {
                background-color: rgba(0, 112, 255, 0.1);
                background: rgba(0, 15, 34, 0.5);
                box-shadow: 0 0 0 1px #0070ff inset;
            }
        :deep(.el-input-group__append) {
          background: rgba(0, 112, 255, 0.38);
          .el-button {
            background-color: transparent;
            border: 1px solid #0070ff;
            border-left: none;
            color: #fff;
          }
        }
      }
    }
    .table-list {
      width: 358px;
      height: 202px;
      overflow: auto;
      &::-webkit-scrollbar {
        width: 0;
        display: none;
      }
      -ms-overflow-style: none;  /* IE and Edge */
      scrollbar-width: none;  /* Firefox */
      .item {
        width: 100%;
        height: 72px;
        padding: 12px 16px;
        margin-bottom: 8px;
        font-family: Source Han Sans CN, Source Han Sans CN;
        font-weight: 500;
        font-size: 14px;
        color: #fff;
        display: flex;
        justify-content: space-between;
        background: linear-gradient( 90deg, rgba(71,157,255,0) 0%, rgba(71, 157, 255, 0.12) 50%, rgba(71,157,255,0) 100%);
        .left {
          cursor: pointer;
          .left-t {
            height: 24px;
            font-size: 16px;
            margin-bottom: 4px;
            .status {
              text-align: center;
              font-size: 12px;
              display: inline-block;
              width: 48px;
              height: 20px;
              background: rgba(76,166,255,0.08);
              border-radius: 4px 4px 4px 4px;
              border: 1px solid #4CA6FF;
              color: #4CA6FF;
              margin-left: 10px;
            }
            .active {
              border: 1px solid #04F0D1;
              color: #04F0D1;
            }
          }
          .left-b {
            height: 21px;
            line-height: 21px;
            img {
              width: 16px;
              height: 16px;
              margin-right: 2px;
              &:last-child {
                margin-left: 42px;
              }
            }
          }
        }
        .right {
          cursor: pointer;
          position: relative;
          span {
            position: absolute;
            display: inline-block;
            top: 12px;
            left: 12px;
            width: 26px;
            line-height: 12px;
            font-size: 12px;
            font-family: YouSheBiaoTiHei, YouSheBiaoTiHei, serif;
            color: rgb(4,18,44)
          }
          img {
            width: 46px;
            height: 46px
          }
        }
      }
    }
    .custom-empty {
      color: #fff;
      margin: 16px 0;
      padding: 0;
      :deep(.el-empty__image) {
        width: 100px;
        height: 100px;
      }
    }
 }
            :deep(.el-input-group__append) {
                background: rgba(0, 112, 255, 0.38);
                .el-button {
                    background-color: transparent;
                    border: 1px solid #0070ff;
                    border-left: none;
                    color: #fff;
                }
            }
        }
    }
    .table-list {
        width: 358px;
        height: 202px;
        overflow: auto;
        &::-webkit-scrollbar {
            width: 0;
            display: none;
        }
        -ms-overflow-style: none; /* IE and Edge */
        scrollbar-width: none; /* Firefox */
        .item {
            width: 100%;
            height: 72px;
            padding: 12px 16px;
            margin-bottom: 8px;
            font-family: Source Han Sans CN, Source Han Sans CN;
            font-weight: 500;
            font-size: 14px;
            color: #fff;
            display: flex;
            justify-content: space-between;
            background: linear-gradient(
                90deg,
                rgba(71, 157, 255, 0) 0%,
                rgba(71, 157, 255, 0.12) 50%,
                rgba(71, 157, 255, 0) 100%
            );
            .left {
                cursor: pointer;
                .left-t {
                    height: 24px;
                    font-size: 16px;
                    margin-bottom: 4px;
                    .status {
                        text-align: center;
                        font-size: 12px;
                        display: inline-block;
                        width: 48px;
                        height: 20px;
                        background: rgba(76, 166, 255, 0.08);
                        border-radius: 4px 4px 4px 4px;
                        border: 1px solid #4ca6ff;
                        color: #4ca6ff;
                        margin-left: 10px;
                    }
                    .active {
                        border: 1px solid #04f0d1;
                        color: #04f0d1;
                    }
                }
                .left-b {
                    height: 21px;
                    line-height: 21px;
                    img {
                        width: 16px;
                        height: 16px;
                        margin-right: 2px;
                        &:last-child {
                            margin-left: 42px;
                        }
                    }
                }
            }
            .right {
                cursor: pointer;
                position: relative;
                span {
                    position: absolute;
                    display: inline-block;
                    top: 12px;
                    left: 12px;
                    width: 26px;
                    line-height: 12px;
                    font-size: 12px;
                    font-family: YouSheBiaoTiHei, YouSheBiaoTiHei, serif;
                    color: rgb(4, 18, 44);
                }
                img {
                    width: 46px;
                    height: 46px;
                }
            }
        }
    }
    .custom-empty {
        color: #fff;
        margin: 16px 0;
        padding: 0;
        :deep(.el-empty__image) {
            width: 100px;
            height: 100px;
        }
    }
}
</style>
src/views/TaskManage/TaskIntermediateContent/AddTask.vue
@@ -19,6 +19,7 @@
                    </div>
                    <div class="item">
                        <el-date-picker
                            popper-class="custom-date-picker"
                            class="ztzf-date-picker"
                            v-model="taskData"
                            type="daterange"
@@ -31,6 +32,7 @@
                    <div class="item">
                        <div class="itemchild">任务时间:</div>
                        <el-time-picker
                         popper-class="custom-time-picker"
                            class="ztzf-date-picker tasktimer"
                            v-model="timeSlot"
                            placeholder="请选择"
@@ -40,8 +42,9 @@
                    </div>
                    <div class="item">
                        <div class="itemchild">选择航线:</div>
                        <el-select
                            :teleported="false"
                            class="ztzf-select"
                            v-model="searchForm.file_id"
                            @change="getWayLineFile"
@@ -57,12 +60,10 @@
                        </el-select>
                    </div>
                    <div class="item">
                        <div class="itemchild">关联算法:</div>
                        <TaskAlgorithmBusiness  :setWidth="200" :showAlgorithm="true" @algorithmChange="algorithmChange" />
                        <TaskAlgorithmBusiness :setWidth="200" :showAlgorithm="true" @algorithmChange="algorithmChange" />
                    </div>
                    <div class="item">
                        <div class="itemchild">任务描述:</div>
                        <el-input class="ztzf-input" v-model="searchForm.remark" placeholder="请输入任务名称"></el-input>
                    </div>
@@ -84,7 +85,7 @@
                    />
                </div>
            </div>
            <div class="right ">
            <div class="right">
                <TaskTable
                    ref="taskTableRef"
                    :waylineId="waylineId"
@@ -94,9 +95,8 @@
                    @update:selected="handleSelected"
                />
                <div class="btn">
                    <img @click="cancel" style="margin-right:23px" src="@/assets/images/task/cancel.png" alt="">
                    <img @click="submitClick" src="@/assets/images/task/publish.png" alt="">
                    <img @click="cancel" style="margin-right: 23px" src="@/assets/images/task/cancel.png" alt="" />
                    <img @click="submitClick" src="@/assets/images/task/publish.png" alt="" />
                </div>
            </div>
        </div>
@@ -181,7 +181,7 @@
    waylineType.value = 0
    waylineId.value = val
    const currentRoute = routeOptions.value.find(item => item.wayline_id === val)
    wayLineFile.value = currentRoute?.object_key || '';
    wayLineFile.value = currentRoute?.object_key || ''
}
// 获取选中机场列表数据,并且发布
@@ -250,10 +250,10 @@
        })
        return
    }
    searchForm.begin_time = `${taskData.value[0]} 00:00:00`
    searchForm.end_time = `${taskData.value[1]} 23:59:59`
    searchForm.execute_time_arr = timeSlot.value ? [timeSlot.value] : [];
    searchForm.execute_time_arr = timeSlot.value ? [timeSlot.value] : []
    createTask(searchForm).then(res => {
        if (res.data.code === 0) {
            ElMessage.success('任务创建成功')
@@ -294,6 +294,12 @@
        padding: 20px;
    }
}
/* 修改日期单元格背景色 */
.custom-date-picker .el-picker-panel__body {
    background: #012350 !important;
    color: #fff !important;
}
</style>
<style lang="scss" scoped>
@@ -316,14 +322,13 @@
                font-size: 16px;
                color: #ffffff;
                .itemchild {
                width: 68px;
                 white-space: nowrap;
                margin-right: 5px;
                    width: 68px;
                    white-space: nowrap;
                    margin-right: 5px;
                }
                :deep(.el-date-editor.el-input__wrapper) {
                    width: 200px; // 调整日期选择器宽度
                }
            }
        }
    }
@@ -338,17 +343,15 @@
            justify-content: center;
            align-items: center;
            img {
            width: 137px;
            height: 32px;
            cursor: pointer;
                width: 137px;
                height: 32px;
                cursor: pointer;
            }
        }
    }
    :deep(.tasktimer .el-input__wrapper) {
        background: none !important;
        color: #8ac3fd !important;
    }
    :deep(.el-input__inner) {
        color: #8ac3fd !important;
@@ -356,15 +359,15 @@
            color: #8ac3fd !important;
        }
    }
    :deep(.el-radio__inner){
    background: none !important;
    border: 1px solid #1B5D9A !important;
    :deep(.el-radio__inner) {
        background: none !important;
        border: 1px solid #1b5d9a !important;
    }
    :deep(.el-radio__label){
    color: #fff !important;
    :deep(.el-radio__label) {
        color: #fff !important;
    }
    :deep(.el-radio__inner:after ){
    background: #65B5FF !important;
    :deep(.el-radio__inner:after) {
        background: #65b5ff !important;
    }
}
</style>
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{