forked from drone/command-center-dashboard

张含笑
2025-04-10 fee18d19a1d961847cd09469a20fc8231c1df46c
Merge remote-tracking branch 'origin/master'
16 files modified
2 files added
1295 ■■■■ changed files
src/api/home/machineNest.js 3 ●●●● patch | view | raw | blame | history
src/api/home/task.js 21 ●●●●● patch | view | raw | blame | history
src/store/modules/home.js 5 ●●●●● patch | view | raw | blame | history
src/views/Home/AINowFly.vue 228 ●●●●● patch | view | raw | blame | history
src/views/Home/Footer.vue 55 ●●●●● patch | view | raw | blame | history
src/views/Home/Home.vue 25 ●●●●● patch | view | raw | blame | history
src/views/Home/HomeLeft/MachineNestList.vue 1 ●●●● patch | view | raw | blame | history
src/views/Home/HomeLeft/OverviewNext.vue 5 ●●●●● patch | view | raw | blame | history
src/views/Home/HomeLeft/components/MachineNestTotal.vue 4 ●●●● patch | view | raw | blame | history
src/views/Home/useMapAggregation/DevicePopUpBox.vue 4 ●●●● patch | view | raw | blame | history
src/views/Home/useMapAggregation/useMapAggregation.js 73 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineLeft/MachineLeft.vue 1 ●●●● patch | view | raw | blame | history
src/views/SignMachineNest/MachineRight/MachineStatus/MachineTableDetails/DeviceJob/DeviceJobDetails/DeviceJobDetailsMap.vue 1 ●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/AddTask.vue 141 ●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails.vue 20 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue 43 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskMap.vue 542 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskTable.vue 123 ●●●● patch | view | raw | blame | history
src/api/home/machineNest.js
@@ -9,13 +9,12 @@
    })
}
// 机巢列表
export const selectDevicePage = ({ nickname, is_execute, ...params }) => {
export const selectDevicePage = ({ nickname, ...params }) => {
    return request({
        url: `/drone-device-core/manage/api/v1/devices/selectDevicePage?type=${params.type}&current=${params.current}&size=${params.size}`,
        method: 'post',
        data: {
            nickname,
            is_execute,
        },
    })
}
src/api/home/task.js
@@ -43,7 +43,7 @@
// 任务列表
export const jobList = data => {
    return request({
        url: '/drone-device-core/wayline/waylineJobInfo/jobList?current=' + data.page + '&size=' + data.limit,
        url: '/drone-device-core/wayline/waylineJobInfo/jobList?current=' + data.page + '&size=' + data.size,
        method: 'post',
        data: data.searchParams,
    })
@@ -86,14 +86,11 @@
}
// 获取可飞行机巢列表
export const getFlyingNestBy = (data) => {
export const getFlyingNestBy = (data,page) => {
    return request({
        url: `/drone-device-core/manage/api/v1/devices/getFlyingNestBy?current=${data.page}&size=${data.limit}`,
        url: `/drone-device-core/manage/api/v1/devices/getFlyingNestBy?current=${page.page}&size=${page.size}`,
        method: 'post',
        data: {
            wayline_id: data.wayline_id,
            type: data.type
        },
        data: data,
    })
}
@@ -106,5 +103,15 @@
    })
}
// 面状航线
export const getWaylineByArea = (data) => {
    return request({
        url: `/drone-device-core/wayline/api/v1/workspaces/getWaylineByArea`,
        method: 'post',
        data,
    })
}
src/store/modules/home.js
@@ -1,17 +1,16 @@
import { set } from 'lodash'
import { EDeviceTypeName } from '@/utils/staticData/enums'
import { getStore, setStore } from '@/utils/store'
const home = {
  state: {
    machineNestDetail: false, // 机巢详情
    machineNestDetail: false, // 机巢长列表
        isEventOverviewDetail: false,//是事件概述详情
        // 用户行政区划中心点
        userAreaPosition: getStore({ name: 'userAreaPosition' }) || {},
        // 用户切换后行政区划中心点
        currentAreaPosition:getStore({ name: 'currentAreaPosition' }) || {},
    singleUavHome: {},
        footActiveIndex: 0,
        isEventOverviewDetail: false,//是事件概述详情
    singleTotal: {}, // 统计单个机巢数据
    // 项目id
    selectedWorkSpaceId: window.localStorage.getItem('bs_workspace_id'),
src/views/Home/AINowFly.vue
New file
@@ -0,0 +1,228 @@
<template>
    <div class="AINowFly">
        <div>智引即飞</div>
        <el-form ref="ruleFormRef" :model="params" :rules="rules" label-width="auto" size="small" status-icon>
            <el-form-item label="任务名称" prop="name">
                <el-input v-model="params.name" />
            </el-form-item>
            <el-form-item label="关联算法" prop="ai_types">
                <el-select v-model="params.ai_types" placeholder="请选择" multiple>
                    <el-option v-for="item in taskAlgorithm" :key="item.id" :label="item.dictValue" :value="item.dictKey" />
                </el-select>
            </el-form-item>
            <el-form-item label="地址">
                <el-input disabled type="text" v-model="di" />
            </el-form-item>
            <!--            todo-->
            <el-form-item label="事件">
                <div>默认拍照</div>
            </el-form-item>
        </el-form>
        <div>可用机巢列表</div>
        <el-table :data="list" @selection-change="handleSelectionChange">
            <el-table-column type="selection" width="55" />
            <el-table-column type="index" label="序号" />
            <el-table-column prop="nickname" label="机巢名称" />
            <el-table-column prop="minute" label="预计飞行时长(分钟)" />
            <el-table-column prop="exe_distance" label="预计飞行距离(m)" />
        </el-table>
        <el-button type="primary" @click="nowFly">迅速起飞</el-button>
    </div>
</template>
<script setup>
import { getMultipleDictionary } from '@/api/system/dictbiz'
import * as Cesium from 'cesium'
import { createTask, getFlyingNestBy } from '@/api/home/task'
import { cartesian3Convert } from '@/utils/cesium/mapUtil'
import { Cartesian3 } from 'cesium'
import uavImg from '@/assets/images/home/useUavHome/uavImg.png'
import ImageTrailMaterial from '@/utils/cesium/ImageTrailMaterial'
import lineImg from '@/assets/images/arrow-right-blue.png'
import _ from 'lodash'
import { ElMessage } from 'element-plus'
import dayjs from 'dayjs'
import { useStore } from 'vuex'
const now = dayjs().format('YYYYMMDDHHmm')
const nowTime = dayjs().format('YYYY-MM-DD HH:mm:ss')
const paramsInit = {
    name: `智引即飞${now}`,
    ai_types: '',
    longitude: '',
    latitude: '',
    dock_sns: [],
    type: 1,
    begin_time: nowTime,
    end_time: nowTime,
}
const params = ref(_.cloneDeep(paramsInit))
const di = computed(() => params.value.longitude + ',' + params.value.latitude)
const rules = ref({
    name: [
        { required: true, message: '请输入任务名称', trigger: 'blur' },
        { min: 3, max: 50, message: '长度在 3 到 20 个字符', trigger: 'blur' },
    ],
    ai_types: [{ required: true, message: '请选择算法', trigger: 'change' }],
})
const ruleFormRef = ref(null)
let handler = null
const taskAlgorithm = ref([])
let viewer
const list = ref([])
let eventPoint = {}
const store = useStore()
const handleSelectionChange = val => {
    params.value.dock_sns = val.map(item => item.device_sn)
}
const nowFly = async () => {
    await ruleFormRef.value.validate()
    if (!params.value.dock_sns.length) return ElMessage.warning('请选择将要飞行的机巢')
    if (!params.value.longitude) return ElMessage.warning('请选择飞行的点')
    createTask(params.value).then(res => {
        ElMessage.success('起飞成功')
        if (params.value.dock_sns.length === 1) {
            store.commit('setSingleUavHome', { device_sn: params.value.dock_sns[0] })
        } else {
            store.commit('setFootActiveIndex', 0)
        }
    })
}
const renderingLine = () => {
    const { longitude, latitude } = eventPoint
    list.value.forEach((item, index) => {
        const start = Cartesian3.fromDegrees(Number(item.longitude), Number(item.latitude))
        const end = Cartesian3.fromDegrees(Number(longitude), Number(latitude))
        const positionsList = [start, end]
        // 线
        viewer.entities.add({
            id: `AINow-line-${index}`,
            polyline: {
                width: 4,
                positions: positionsList,
                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,
            },
        })
        // 点
        viewer.entities.add({
            id: `AINow-point-${index}`,
            position: start,
            label: {
                text: item.nickname,
                font: '12pt Source Han Sans CN',
                fillColor: Cesium.Color.WHITE,
                outlineColor: Cesium.Color.BLACK,
                outlineWidth: 2,
                style: Cesium.LabelStyle.FILL_AND_OUTLINE,
                verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
                pixelOffset: new Cesium.Cartesian2(0, -9),
            },
            billboard: {
                image: new Cesium.ConstantProperty(uavImg),
                width: 24,
                height: 24,
            },
            properties: {
                customData: {
                    data: item,
                },
            },
        })
    })
}
const removeEntities = () => {
    const entitiesIDs = viewer.entities.values.map(i => i.id)
    entitiesIDs.forEach(item => {
        item.includes('AINow-') && viewer.entities.removeById(item)
    })
}
// 单击事件
const singleClickEvent = click => {
    removeEntities()
    const earthPosition = viewer.scene.pickPosition(click.position)
    viewer.entities.add({
        position: earthPosition,
        id: `AINow-event-0`,
        point: {
            pixelSize: 10,
            color: Cesium.Color.RED,
            outlineColor: Cesium.Color.WHITE,
            outlineWidth: 2,
        },
    })
    const { longitude, latitude } = cartesian3Convert(earthPosition, viewer)
    eventPoint = { longitude, latitude }
    params.value = { ...params.value, longitude, latitude }
    getFJList()
}
// 请求字典字段
const requestDictionary = () => {
    getMultipleDictionary('SF').then(res => {
        if (res.code !== 0) {
            taskAlgorithm.value = res.data.data['SF']
        }
    })
}
// 获取飞机列表
const getFJList = async () => {
    params.value.dock_sns = []
    const getListParams = { ...eventPoint, page: 1, limit: 9999, type: 1 }
    const res = await getFlyingNestBy(getListParams)
    list.value = (res.data?.data || []).map(item => ({
        ...item,
        minute: _.round(item.estimated_arrival_time / 60, 0),
    }))
    if (!list.value.length) ElMessage.warning('附近暂无可用无人机')
    renderingLine()
}
const initMap = () => {
    handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas)
    handler.setInputAction(singleClickEvent, Cesium.ScreenSpaceEventType.LEFT_CLICK)
}
const removeAll = () => {
    removeEntities()
    handler?.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
    handler?.destroy()
    handler = null
}
onBeforeUnmount(() => {
    removeAll()
})
onMounted(() => {
    requestDictionary()
    viewer = window.$viewer
    initMap()
})
</script>
<style scoped lang="scss">
.AINowFly {
    position: absolute;
    top: 200px;
    right: 0;
    width: 400px;
    height: 700px;
    background: white;
    color: black;
    font-size: 20px;
}
</style>
src/views/Home/Footer.vue
@@ -1,15 +1,20 @@
<template>
    <div class="footer">
        <img
            v-for="item in list"
            v-for="(item,index) in list.filter(i => !i.disable)"
            :class="item.className"
            :src="item.active ? item.activeImg : item.img"
            alt=""
            @click="imgClick(item)"
            @click="footAction(item,index)"
        />
    </div>
    <div class="intelligent-introduction-flying">
        <img class="orthophoto" src="@/assets/images/intelligent-introduction-flying.png" alt="" />
        <img
            class="orthophoto"
            src="@/assets/images/intelligent-introduction-flying.png"
            alt=""
            @click="footAction(list[5],5)"
        />
    </div>
</template>
@@ -27,6 +32,10 @@
import { useMapAggregation } from '@/views/Home/useMapAggregation/useMapAggregation'
import { useStore } from 'vuex'
import func from '@/utils/func'
const store = useStore()
const footActiveIndex = computed(() => store.state.home.footActiveIndex)
// 机巢聚合
const { init, removeAll } = useMapAggregation('device')
@@ -35,7 +44,7 @@
const list = ref([
    {
        name: 'event1',
        name: 'jc',
        img: img1,
        activeImg: activeImg1,
        active: true,
@@ -44,7 +53,7 @@
        removeAll: removeAll,
    },
    {
        name: 'event2',
        name: 'sj',
        img: img2,
        activeImg: activeImg2,
        active: false,
@@ -52,27 +61,28 @@
        init: eventInit,
        removeAll: eventRemove,
    },
    { name: 'event3', img: img3, activeImg: activeImg3, active: false, className: 'panorama' },
    { name: 'event4', img: img4, activeImg: activeImg4, active: false, className: 'threeD' },
    { name: 'event5', img: img5, activeImg: activeImg5, active: false, className: 'orthophoto' },
    { name: 'qj', img: img3, activeImg: activeImg3, active: false, className: 'panorama' },
    { name: 'sw', img: img4, activeImg: activeImg4, active: false, className: 'threeD' },
    { name: 'zs', img: img5, activeImg: activeImg5, active: false, className: 'orthophoto' },
    { name: 'zyjf', active: false, disable: true },
])
const store = useStore()
const imgClick = (toItem,index) => {
    index !== undefined && store.commit('setFootActiveIndex',index)
    const fromItem = list.value.find(item => item.active)
    if (fromItem.name === toItem.name) return
const footAction = (toItem, index) => {
    const fromItem = list.value.find(item => item.active)  //上一个激活item
    if (fromItem.name === toItem?.name) return  //是重复点击
    index !== undefined && store.commit('setFootActiveIndex', index) //是按钮点击得动作
    fromItem?.removeAll?.()
    nextTick(() => {
        toItem?.init?.()
    })
    list.value = list.value.map(item => ({ ...item, active: item.name === toItem.name }))
    nextTick(() => toItem?.init?.())
    list.value = list.value.map(item => ({ ...item, active: item.name === toItem?.name }))
}
const footActiveIndex = computed(() => store.state.home.footActiveIndex)
watch(footActiveIndex, val => {
    console.log(6666666)
    imgClick(list.value[val])
},{deep:true})
watch(
    footActiveIndex,
    val => {
        footAction(list.value[val])
    },
    { deep: true }
)
// 销毁前钩子
onBeforeUnmount(() => {
@@ -128,6 +138,7 @@
    bottom: 23px;
    img {
        cursor: pointer;
        width: 146px;
        height: 54px;
    }
src/views/Home/Home.vue
@@ -1,15 +1,17 @@
<template>
    <template v-if="singleUavHome?.device_sn">
        <SignMachineNest />
    </template>
    <!--        单个机巢页面-->
    <SignMachineNest v-if="singleUavHome?.device_sn" />
    <!--        事件概述详细信息页面-->
    <EventOverviewDetail v-else-if="isEventOverviewDetail" />
    <AINowFly v-else-if="isAINowFly" />
    <!--            首页默认的页面-->
    <template v-else>
        <EventOverviewDetail v-if="isEventOverviewDetail" />
        <template v-else>
            <SearchBox />
            <HomeLeft />
            <HomeRight />
        </template>
        <HomeLeft />
        <HomeRight />
    </template>
    <SearchBox />
    <RSide />
    <Footer />
</template>
@@ -25,16 +27,19 @@
import { onMounted } from 'vue'
import cesiumOperation from '@/utils/cesium-tsa'
import EventOverviewDetail from '@/views/Home/EventOverviewDetail/EventOverviewDetail.vue'
import AINowFly from '@/views/Home/AINowFly.vue'
const store = useStore()
const { _init, viewerDestory } = cesiumOperation()
const singleUavHome = computed(() => store.state.home.singleUavHome)
const isEventOverviewDetail = computed(() => store.state.home.isEventOverviewDetail)
const isAINowFly = computed(() => store.state.home.footActiveIndex === 5)
onUnmounted(() => {
    store.commit('setSingleUavHome', null)
    console.log('home销毁')
    store.commit('setIsEventOverviewDetail', false)
    store.commit('setFootActiveIndex', 0)
    viewerDestory()
})
src/views/Home/HomeLeft/MachineNestList.vue
@@ -67,7 +67,6 @@
// 搜索数据
const handleSearch = (name) => {
  console.log(name, '哒哒哒');
  searchText.value = name;
  pageParams.value.current = 1;
  tableList.value = [];
src/views/Home/HomeLeft/OverviewNext.vue
@@ -1,6 +1,6 @@
<!-- 机巢概况 -->
<template>
  <common-title :style="{ marginLeft: pxToRem(14) }" @details="detailsFun"></common-title>
  <common-title title="机巢概况" :style="{ marginLeft: pxToRem(14) }" @details="detailsFun"></common-title>
  <div class="overview-next">
    <MachineNestTotal @searchNickName="handleSearch" />
    <div class="table-list">
@@ -56,7 +56,6 @@
const getTableList = () => {
  const params = {
    nickname: searchText.value,
    is_execute: true,
    current: pageParams.value.current,
    size: pageParams.value.size
  };
@@ -114,7 +113,7 @@
  border-radius: 0px 0px 0px 0px;
  opacity: 0.85;
  margin-bottom: 10px;
  .table-list {
    font-family: Source Han Sans CN, Source Han Sans CN;
    margin: 16px;
src/views/Home/HomeLeft/components/MachineNestTotal.vue
@@ -41,8 +41,8 @@
  getDeviceInfoNum().then((res) => {
    if (res.data.code !== 0) return;
    let result = res.data.data;
    listNum.value[0].value = result.ex_num;
    listNum.value[1].value = result.leisure_num;
    listNum.value[0].value = result.leisure_num;
    listNum.value[1].value = result.ex_num;
    listNum.value[2].value = result.offline_num;
    total.value = result.device_num;
  });
src/views/Home/useMapAggregation/DevicePopUpBox.vue
@@ -58,8 +58,8 @@
  const resJob = await getTotalJobNum({ areaCode });
  dataObj.value.jobNum = resJob.data.data;
  dataObj.value.device_num = res.data.data.device_num;
  list.value[0].value = res.data.data.ex_num;
  list.value[1].value = res.data.data.leisure_num;
  list.value[0].value = res.data.data.leisure_num;
  list.value[1].value = res.data.data.ex_num;
  list.value[2].value = res.data.data.offline_num;
  loading.value = false;
});
src/views/Home/useMapAggregation/useMapAggregation.js
@@ -199,17 +199,17 @@
    scalingJudgment.forEach(item => item.show && (item.outline = outlineGJson))
    const [longitude, latitude] = outlineGJson.features[0].properties.centroid
    const height = scalingJudgment[(hierarchy.length - 3) * (-1)].height
    setCenterPosition({longitude, latitude,height})
    setCenterPosition({ longitude, latitude, height })
    flyTo({ longitude, latitude }, 0, height)
  }
  const userAreaPosition = computed(() => store.state.home.userAreaPosition);
  const userAreaPosition = computed(() => store.state.home.userAreaPosition)
  const setCenterPosition = (position) => {
    store.commit('setCurrentAreaPosition', position)
        if (!userAreaPosition.value.longitude){
            store.commit('setUserAreaPosition', position)
        }
    if (!userAreaPosition.value.longitude) {
      store.commit('setUserAreaPosition', position)
    }
  }
@@ -235,7 +235,6 @@
        id: `aggregation-splashed-${index}`,
        position: Cesium.Cartesian3.fromDegrees(item.longitude, item.latitude),
        label: {
          // 随机整数
          text: item.nickname,
          font: '12pt Source Han Sans CN',
          fillColor: Cesium.Color.WHITE,
@@ -262,28 +261,24 @@
  // 渲染轮廓
  const renderOutline = item => {
    item.outline &&
    Cesium.GeoJsonDataSource.load(item.outline).then(dataSource => {
      viewer.dataSources.add(dataSource)
      const entities = dataSource.entities.values
      entities.forEach(entity => {
        // 隐藏多边形填充
        entity.polygon.material = Cesium.Color.TRANSPARENT
        entity.polygon.outline = false // 关闭原生轮廓
        // 创建独立折线作为轮廓
        const positions = entity.polygon.hierarchy.getValue().positions
        viewer.entities.add({
          polyline: {
            positions: positions,
            width: 5, // 直接设置宽度
            material: new Cesium.PolylineGlowMaterialProperty({
              glowPower: 0.5,
              color: Cesium.Color.AQUA,
            }),
            clampToGround: true, // 贴地显示
          },
      Cesium.GeoJsonDataSource.load(item.outline).then(dataSource => {
        const entities = dataSource.entities.values
        entities.forEach((entity, index) => {
          // 创建独立折线作为轮廓
          const positions = entity.polygon.hierarchy.getValue().positions
          viewer.entities.add({
            id: `aggregation-outline-${index}`,
            polyline: {
              positions: positions,
              width: 5, // 直接设置宽度
              material: new Cesium.PolylineGlowMaterialProperty({
                glowPower: 0.5,
                color: Cesium.Color.AQUA,
              }),
            },
          })
        })
      })
    })
  }
  // 聚合机巢
@@ -301,7 +296,7 @@
    // 遍历特征并添加实体
    featuresList.forEach((feature, index) => {
      if (!feature.position) return
      const position = Cesium.Cartesian3.fromDegrees(feature.position[0], feature.position[1],12000)
      const position = Cesium.Cartesian3.fromDegrees(feature.position[0], feature.position[1], 12000)
      viewer.entities.add({
        position: position,
        id: `aggregation-name-${index}`,
@@ -348,10 +343,12 @@
      return arrColor[++index % arrColor.length]
    }
    // 加载新的 GeoJSON 数据
    // 加载边界
    Cesium.GeoJsonDataSource.load(item.gJson).then(dataSource => {
      console.log(dataSource, item.gJson)
      viewer.dataSources.add(dataSource)
      item.dataSource = dataSource // 保存数据源以便后续删除
      item.BJDataSource = dataSource // 保存数据源以便后续删除
      // 获取数据源中的实体
      const entities = dataSource.entities.values
@@ -362,18 +359,14 @@
          alphaPower: 1.3
        })
        entity.polygon.extrudedHeight = (entity.propertyNames.length || 1) * 500
        entity.polygon.extrudedHeight = (entity.properties.childrenNum._value || 1) * 500
        entity.polygon.material = material
        entity.polygon.outline = false // 显示边框
      })
      needFly && viewer.flyTo(dataSource, {
        offset: new Cesium.HeadingPitchRange(
          0, // heading: 0 (朝向不变)
          Cesium.Math.toRadians(-60), // pitch: -90° (垂直向下)
          0 // range: 0 (默认距离)
        ),
        offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-60), 0),
        duration: 0.5,
      })
      needFly = false
@@ -415,7 +408,7 @@
    currentEntity = entity
    const entityPosition = entity?.position?._value
    if (!entityPosition) return
    if (entityPosition){
    if (entityPosition) {
      positionC3 = entityPosition
    }
    removeLabel()
@@ -449,8 +442,12 @@
  // 移除 点 和 gjson 实体
  const removeEntities = () => {
    viewer.dataSources?.removeAll(true)
    // viewer.entities?.removeAll();
    // dataSources移除
    scalingJudgment.forEach(item => {
      item.BJDataSource && viewer.dataSources.remove(item.BJDataSource)
      item.BJDataSource = null
    })
    // entities移除
    const entitiesIDs = viewer.entities.values.map(i => i.id)
    entitiesIDs.forEach(item => {
      item.includes('aggregation-') && viewer.entities.removeById(item)
src/views/SignMachineNest/MachineLeft/MachineLeft.vue
@@ -24,6 +24,7 @@
const goBack = () => {
  store.commit('setSingleUavHome', {});
    store.commit('setFootActiveIndex', 0)
};
</script>
src/views/SignMachineNest/MachineRight/MachineStatus/MachineTableDetails/DeviceJob/DeviceJobDetails/DeviceJobDetailsMap.vue
@@ -9,7 +9,6 @@
import ImageTrailMaterial from '@/utils/cesium/ImageTrailMaterial'
import lineImg from '@/assets/images/arrow-right-blue.png'
import uavImg from '@/assets/images/home/useUavHome/uavImg.png'
import { getCenterPoint } from '@/utils/cesium/mapUtil'
const props = defineProps(['detailsData'])
src/views/TaskManage/TaskIntermediateContent/AddTask.vue
@@ -11,20 +11,21 @@
        <div class="task-contain">
            <div class="left">
                <div class="search">
                    <div class="item">任务名称:<el-input v-model="searchForm.name" placeholder="请输入任务名称"></el-input></div>
                    <div class="item"><span style="color: red;">*</span>任务日期:
                    <div class="item"><span class="required">*</span>任务名称:<el-input v-model="searchForm.name" placeholder="请输入任务名称"></el-input></div>
                    <div class="item"><span class="required">*</span>任务日期:
                        <el-date-picker
                            v-model="searchForm.begin_time"
                            v-model="taskData"
                            format="YYYY-MM-DD"
                            value-format="YYYY-MM-DD"
                            :disabled-date="disabledDate"
                        />
                    </div>
                    <div class="item">任务时间:
                        <!-- <el-time-picker
                        <el-time-picker
                            v-model="searchForm.execute_time_arr"
                            placeholder="选择时间"
                            format="HH:mm"
                            value-format="HH:mm"/> -->
                            value-format="HH:mm"/>
                    </div>
                    <div class="item">选择航线:
                        <el-select
@@ -46,16 +47,28 @@
                </div>
                <div class="lines">
                    <div class="wayline-type">
                        <el-radio-group v-model="searchForm.wayline_type">
                            <el-radio label="1">单点航线</el-radio>
                            <el-radio label="2">智能规划选区</el-radio>
                        <el-radio-group v-model="waylineType" @change="radioChange">
                            <el-radio :value="1" label="单点航线"></el-radio>
                            <el-radio :value="2" label="智能规划选区"></el-radio>
                        </el-radio-group>
                    </div>
                    <TaskMap class="wayline-map" :wayLineFile="wayLineFile"/>
                    <TaskMap class="wayline-map"
                        :wayLineFile="wayLineFile"
                        :checkedTableData="checkedTableData"
                        :waylineTypeTest="waylineType"
                        @clickPosition="clickSignPosition"
                        @saveWayline="savePlanerWayline"
                    />
                </div>
            </div>
            <div class="right">
                <TaskTable :waylineId="waylineId" :waylineType="waylineType" @update:selected="handleSelected" />
                <TaskTable ref="taskTableRef"
                    :waylineId="waylineId"
                    :waylineType="waylineType"
                    :singlePoint="singlePoint"
                    :planarPoints="planarPoints"
                    @update:selected="handleSelected"
                />
                <div class="btn">
                    <el-button type="primary" @click="cancel">取消</el-button>
                    <el-button type="primary" @click="submitClick">发布</el-button>
@@ -73,14 +86,18 @@
import TaskMap from './TaskMap.vue';
import TaskTable from './TaskTable.vue';
const emit = defineEmits(['refresh']);
const rangDate = ref([]);
// 航线ID
let waylineId = ref('');
const waylineId = ref('');
// 航线文件
let wayLineFile = ref('');
const wayLineFile = ref('');
// 航线类型
let waylineType = ref(0);
const waylineType = ref(3);
// 添加子组件引用
const taskTableRef = ref(null);
const taskData = ref('');
const searchForm = reactive({
    name: '',
    ai_types: [],
@@ -91,8 +108,16 @@
    remark: '',
    type: waylineType.value,
    dock_sns: [],
    longitude: '',
    latitude: '',
    polygon: [],
});
const isShowAddTask = defineModel('show');
// 禁用当天之前的日期
const disabledDate = (time) => {
  return time.getTime() < Date.now() - 8.64e7; // 86400000 = 24 * 60 * 60 * 1000
};
// 获取航线列表数据
const routeOptions = ref([]);
@@ -102,32 +127,98 @@
    routeOptions.value = res.data.data;
  }
};
// 关联算法
const algorithmChange = (val) => {
  searchForm.ai_types = val;
};
// 切换航线类型
const radioChange = (val) => {
    // 清空选中航线值
    searchForm.file_id = '';
    // 航线ID也清空
    waylineId.value = '';
    wayLineFile.value = '';
  waylineType.value = val;
    searchForm.type = val;
    // 清空列表数据
    nextTick(() => {
    if (taskTableRef.value) {
      taskTableRef.value.clearTableData();
    }
  });
};
// 获取航线文件
const getWayLineFile = async (val) => {
    searchForm.type = 0;
    waylineType.value = 0;
    waylineId.value = val;
    const currentRoute = routeOptions.value.find(item => item.wayline_id === val);
  wayLineFile.value = currentRoute.object_key;
};
// 获取选中机场列表数据,并且发布
let checkedTableData = ref([]);
const handleSelected = (val) => {
  searchForm.dock_sns = val.map(item => item.device_sn);
    checkedTableData.value = val;
};
const emit = defineEmits(['refresh']);
// 地图点击事件 表格重新刷新数据
let singlePoint = ref({});
const clickSignPosition = (val) => {
  singlePoint.value = val;
    searchForm.longitude = val.longitude;
    searchForm.latitude = val.latitude;
};
// 保存面状航线
let planarPoints = ref([]);
const savePlanerWayline = (val) => {
  planarPoints.value = val;
    const polygonArray = val.map(point => [point.longitude, point.latitude]);
    searchForm.polygon = polygonArray;
};
// 提交
const submitClick = () => {
    if (!searchForm.begin_time) {
    if (!taskData.value) {
    ElMessage({
      message: '请选择任务日期',
      type: 'warning'
    });
    return;
  }
    searchForm.end_time = `${searchForm.begin_time} 23:59:59`;
    searchForm.begin_time = `${searchForm.begin_time} 00:00:00`;
    if (!searchForm.name) {
    ElMessage({
      message: '请输入任务名称',
      type: 'warning'
    });
    return;
  }
    // 检查任务时间
  if (searchForm.execute_time_arr) {
    const now = new Date();
    const today = now.toDateString();
    const selectedDate = new Date(taskData.value).toDateString();
    if (today === selectedDate) {
      const [hours, minutes] = searchForm.execute_time_arr.split(':');
      const selectedTime = new Date();
      selectedTime.setHours(parseInt(hours), parseInt(minutes));
      if (selectedTime < now) {
        ElMessage({
          message: '任务时间不能小于当前时间',
          type: 'warning'
        });
        return;
      }
    }
  }
    searchForm.begin_time = `${taskData.value} 00:00:00`;
  searchForm.end_time = `${taskData.value} 23:59:59`;
    
    createTask(searchForm).then((res) => {
        if (res.data.code === 0) {
@@ -152,7 +243,9 @@
  rangDate.value = [];
  waylineId.value = '';
  wayLineFile.value = '';
    taskData.value = '';
};
onMounted(() => {
  getRouteList();
})
@@ -171,7 +264,7 @@
.task-contain {
    display: flex;
    .left {
        width: 70%;
        width: 68%;
        .search {
            display: grid;
      grid-template-columns: repeat(3, 1fr);
@@ -179,6 +272,7 @@
      gap: 30px; // 增加间距使布局更合理
      margin-bottom: 8px; // 添加底部间距
            .item {
                position: relative;
        display: flex;
        align-items: center;
        :deep(.el-input),
@@ -191,11 +285,18 @@
                :deep(.el-date-editor.el-input__wrapper) {
          width: 200px;  // 调整日期选择器宽度
        }
                .required {
                    color: #f56c6c;
                    margin-left: 4px;
                    position: absolute;
                    left: -10px;
                    top: 8px;
                }
      }
        }
    }
    .right {
        width: 30%;
        width: 32%;
        display: flex;
    flex-direction: column;
    justify-content: space-between;
src/views/TaskManage/TaskIntermediateContent/CurrentTaskDetails.vue
New file
@@ -0,0 +1,20 @@
<!--当前任务详情-->
<template>
  <el-dialog
        modal-class="current-task-details"
        v-model="isShowCurrentTaskDetails"
        title="当前任务详情"
        :width="pxToRem(1500)"
        :close-on-click-modal="false"
        :destroy-on-close="true">
        <div class=""></div>
  </el-dialog>
</template>
<script setup>
const isShowCurrentTaskDetails = defineModel('show');
</script>
<style lang="scss" scoped>
.current-task-details {}
</style>
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue
@@ -5,7 +5,7 @@
    <el-table :data="jobListData" style="width: 100%" height="calc(100vh - 180px)">
      <el-table-column label="序号" width="60">
        <template #default="scope">
          {{ (jobListParams.page - 1) * jobListParams.limit + scope.$index + 1 }}
          {{ (jobListParams.current - 1) * jobListParams.size + scope.$index + 1 }}
        </template>
      </el-table-column>
      <el-table-column prop="job_info_num" label="任务编号" />
@@ -13,7 +13,7 @@
      <el-table-column prop="dept_name" label="所属部门"  />
      <el-table-column prop="device_names" label="所属机巢"  />
      <el-table-column prop="ai_type_str" label="关联算法"  />
      <el-table-column prop="status" label="任务状态" >
      <el-table-column label="任务状态" >
        <template #default="scope">
          {{ scope.row.status ? getStatusText(scope.row.status) : ''  }}
        </template>
@@ -30,8 +30,8 @@
    </el-table>
    <div class="pagination">
      <el-pagination
        v-model:current-page="jobListParams.page"
        v-model:page-size="jobListParams.limit"
        v-model:current-page="jobListParams.current"
        v-model:page-size="jobListParams.size"
        :page-sizes="[10, 20, 30, 50]"
        layout="total, sizes, prev, pager, next"
        :total="total"
@@ -40,17 +40,21 @@
      />
    </div>
  </div>
  <!-- 添加任务 -->
  <AddTask v-model:show="isShowAddTask" @refresh="searchClick"/>
  <!-- 当前任务详情 -->
  <CurrentTaskDetails v-model:show="isShowCurrentTaskDetails"/>
</template>
<script setup>
import SearchBox from '../SearchBox.vue';
import AddTask from './AddTask.vue';
import CurrentTaskDetails from './CurrentTaskDetails.vue';
import { jobList } from '@/api/home/task';
const jobListParams = reactive({
  page: 1,
  limit: 10,
  current: 1,
  size: 10,
  searchParams:{}
});
const jobListData = ref([]);
@@ -73,42 +77,31 @@
    4: '已取消',
    5: '执行失败'
  };
  return statusMap[status] || '未知';
  return statusMap[status] || '-';
};
// 状态标签类型
const getStatusType = (status) => {
  const typeMap = {
    1: 'info',
    2: 'warning',
    3: 'success',
    4: '',
    5: 'danger'
  };
  return typeMap[status] || '';
};
// 查看详情
// 查看当前任务详情
let isShowCurrentTaskDetails = ref(false);
const handleDetail = (row) => {
  console.log('查看详情', row);
  isShowCurrentTaskDetails.value = true;
};
// 分页大小改变
const handleSizeChange = (val) => {
  jobListParams.limit = val;
  jobListParams.size = val;
  getJobList();
};
// 页码改变
const handleCurrentChange = (val) => {
  jobListParams.page = val;
  jobListParams.current = val;
  getJobList();
};
// 传参查询条件
const searchClick = (params) => {
  jobListParams.page = 1;
  jobListParams.limit = 10;
  jobListParams.current = 1;
  jobListParams.size = 10;
  jobListParams.searchParams = params;
  getJobList();
};
src/views/TaskManage/TaskIntermediateContent/TaskMap.vue
@@ -1,5 +1,5 @@
<template>
    <div id="taskMap" />
    <div id="taskMap"></div>
</template>
<script setup>
import * as Cesium from 'cesium'
@@ -12,9 +12,29 @@
import EndPointicon from '@/assets/images/EndPointicon.png';
import uavImg from '@/assets/images/home/useUavHome/uavImg.png'
import { getCenterPoint } from '@/utils/cesium/mapUtil'
import { getWaylineByArea } from '@/api/home/task';
import { useStore } from 'vuex';
const props = defineProps(['wayLineFile'])
const store = useStore();
const userAreaPosition = computed(() => store.state.home.userAreaPosition);
// 声明事件
const emit = defineEmits(['clickPosition', 'saveWayline']);
const props = defineProps({
    wayLineFile: {
    type: String,
    default: ''
  },
  checkedTableData: {
    type: Array,
    default: () => []
  },
    waylineTypeTest: {
    type: Number,
    default: 3
  }
})
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}',
@@ -26,8 +46,18 @@
    maximumLevel: 18,
    tilingScheme: new AmapMercatorTilingScheme(),
    credit: 'amap_SL',
})
let viewer = null
});
let viewer = null;
let currentEntity = null;
let connectLines = []; // 存储连接线实体
let polygonPoints = []; // 存储多边形的点
let polygonEntity = null; // 存储多边形实体
let existingEntity = null; // 后端返回数据生成得面状线
// 添加变量跟踪当前菜单
let currentMenu = null;
const init = () => {
    viewer = new Viewer('taskMap', {
        terrain: Terrain.fromWorldTerrain(),
@@ -46,22 +76,214 @@
    })
    viewer.imageryLayers.addImageryProvider(imageryProvider_ammapSL)
    viewer.scene.morphTo2D(0);
    //设置默认点
    const { longitude = 115.763819, latitude = 28.787374, height = 10 } = userAreaPosition.value || {};
    viewer.camera.setView({
        destination: Cartesian3.fromDegrees(longitude, latitude, height),
    });
}
// 渲染线和点
// 单点左键点击事件
const singlePointLeftClick = () => {
  viewer.screenSpaceEventHandler.setInputAction((click) => {
        if (props.waylineTypeTest !== 1) return
    const cartesian = viewer.camera.pickEllipsoid(
      click.position,
      viewer.scene.globe.ellipsoid
    );
    if (cartesian) {
      // 清除之前的实体
      viewer.entities.removeAll();
      // 添加新点
      const point = viewer.entities.add({
        position: cartesian,
        point: {
          pixelSize: 10,
          color: Cesium.Color.WHITE
        }
      });
      // 转换坐标
      const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
      const longitude = Cesium.Math.toDegrees(cartographic.longitude);
      const latitude = Cesium.Math.toDegrees(cartographic.latitude);
      // 更新当前实体引用
      currentEntity = point;
      // 发送坐标
      emit('clickPosition', { longitude, latitude });
    }
  }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
};
// 智能规划航线
const intelligentPlanning = () => {
    // 添加点击事件监听
  viewer.screenSpaceEventHandler.setInputAction((click) => {
        if (props.waylineTypeTest !== 2) return
        const cartesian = viewer.camera.pickEllipsoid(
            click.position,
            viewer.scene.globe.ellipsoid
        );
        if (cartesian) {
            // 添加新点
            const point = viewer.entities.add({
                position: cartesian,
                point: {
                    pixelSize: 10,
                    color: Cesium.Color.WHITE
                }
            });
            // 存储点位
            polygonPoints.push(cartesian);
            // 当点击超过2个点时绘制多边形
            if (polygonPoints.length > 2) {
                // 移除旧的多边形
                if (polygonEntity) {
                    viewer.entities.remove(polygonEntity);
                }
                // 创建新的多边形
                polygonEntity = viewer.entities.add({
                    polygon: {
                        hierarchy: new Cesium.PolygonHierarchy(polygonPoints),
                        // material: new Cesium.Color.fromBytes(212, 46, 32, 100),
                        material: new Cesium.Color(0, 0.5, 1, 0.3), // 蓝色
                        outline: true,
                        // outlineColor: new Cesium.Color.fromBytes(212, 46, 32, 255),
                        outlineColor: new Cesium.Color(0, 0.5, 1, 1), // 蓝
                        outlineWidth: 2,
                        height: 0, // Set explicit height
                        heightReference: Cesium.HeightReference.NONE // Disable terrain clamping
                    }
                });
            }
            // 转换坐标并发送
            const cartographic = Cesium.Cartographic.fromCartesian(cartesian);
            const longitude = Cesium.Math.toDegrees(cartographic.longitude);
            const latitude = Cesium.Math.toDegrees(cartographic.latitude);
            // emit('clickPosition', cartographic);
        }
    }, Cesium.ScreenSpaceEventType.LEFT_CLICK);
    // 修改右键点击事件,添加菜单
    viewer.screenSpaceEventHandler.setInputAction((movement) => {
        if (props.waylineTypeTest !== 2) return
        if (polygonPoints.length > 2) {
            // 清除之前的菜单
            if (currentMenu) {
                document.body.querySelectorAll('.context-menu').forEach(menu => menu.remove());
            }
            const menuContainer = document.createElement('div');
            menuContainer.className = 'context-menu';
            // 获取地图容器
            const mapContainer = document.getElementById('taskMap');
            // 使用鼠标右键点击的实际位置
            menuContainer.style.position = 'absolute';
            menuContainer.style.left = `${movement.position.x}px`;
            menuContainer.style.top = `${movement.position.y}px`;
            menuContainer.style.zIndex = '1000';
            menuContainer.innerHTML = `
                <div class="menu-item" id="saveWayline">保存航线</div>
                <div class="menu-item" id="cancelDraw">取消绘制</div>
            `;
            mapContainer.appendChild(menuContainer);
            currentMenu = menuContainer;
            // 添加全局点击事件监听
            const handleClickOutside = (e) => {
                if (!menuContainer) return;
                const isClickInside = menuContainer.contains(e.target);
                if (!isClickInside) {
                    menuContainer.remove();
                    document.removeEventListener('mousedown', handleClickOutside);
                }
            };
            // 延迟添加事件监听,避免右键点击立即触发
            setTimeout(() => {
                document.addEventListener('mousedown', handleClickOutside);
            }, 100);
            // 菜单按钮点击事件
            document.getElementById('saveWayline').onclick = () => {
                const coordinates = polygonPoints.map(point => {
                    const cartographic = Cesium.Cartographic.fromCartesian(point);
                    return {
                        longitude: Cesium.Math.toDegrees(cartographic.longitude),
                        latitude: Cesium.Math.toDegrees(cartographic.latitude)
                    };
                });
                emit('saveWayline', coordinates);
                saveWaylineByArea(coordinates);
                mapContainer.removeChild(menuContainer);
            };
            document.getElementById('cancelDraw').onclick = () => {
                polygonPoints = [];
                viewer.entities.removeAll();
                mapContainer.removeChild(menuContainer);
            };
        }
    }, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
};
// 保存航线并且获取线
const saveWaylineByArea = (dataValue) => {
    const polygonArray = dataValue.map(point => [point.longitude, point.latitude]);
    getWaylineByArea({ type: 2, polygon: polygonArray }).then(res => {
        if (res.data.code !== 0) retrun;
        drawResultWayline(res.data.data);
    });
}
// 绘制后端生成得面状线
const drawResultWayline = (dataValue) => {
    // 先检查并删除已存在的航线
  existingEntity = viewer.entities.getById('result_wayline');
  if (existingEntity) {
    viewer.entities.remove(existingEntity);
  }
    const cartesian3List = ref([]);
    dataValue.forEach((lnglat) => {
        const cartesian3 = Cesium.Cartesian3.fromDegrees(
            Number(lnglat.x),
            Number(lnglat.y),
            Number(100), // 默认100
        )
        cartesian3List.value.push(cartesian3)
    });
    const setting = {
        id: 'result_wayline',
        polyline: {
            width: 2,
            positions: cartesian3List.value,
            material: Cesium.Color.CHARTREUSE,
        },
    }
    existingEntity = viewer?.entities.add({
        polyline: setting.polyline,
        id: setting.id,
    })
};
// 选中航线时调用 渲染线和点 type = 0
const renderingLine = lineObj => {
    const positions = lineObj.Placemark.map(item => {
        const [lon, lat] = item.Point.coordinates.split(',')
        return Cartesian3.fromDegrees(Number(lon), Number(lat))
    })
    // viewer.entities.add({
    //     position: positions[0],
    //     billboard: {
    //         image: new Cesium.ConstantProperty(uavImg),
    //         width: 24,
    //         height: 24,
    //     },
    // });
    viewer.entities.add({
        polyline: {
            width: 4,
@@ -75,24 +297,26 @@
            clampToGround: false,
        },
    })
  console.log('positions',positions)
  positions.forEach((point, index) => {
    let setting = {};
    if (index === 0) {
      //TODO
    } else if (index === 1) {
      setting = {
        position: point,
        id: `point_${index}`,
        billboard: {
          image: Startingpointicon,
          outlineWidth: 0,
          width: 20,
          height: 20,
          scale: 1.0,
        },
      }
    } else if (index === positions.length - 1) {
    // if (index === 0) {
    //   //TODO
    // }
        // else if (index === 1) {
    //   setting = {
    //     position: point,
    //     id: `point_${index}`,
    //     billboard: {
    //       image: Startingpointicon,
    //       outlineWidth: 0,
    //       width: 20,
    //       height: 20,
    //       scale: 1.0,
    //     },
    //   }
    // } else
        if (index === positions.length - 1) {
      setting = {
        position: point,
        id: `point_${index}`,
@@ -109,7 +333,7 @@
        position: point,
        id: `point_${index}`,
        label: {
          text: `${index}`,
          text: `${index+1}`,
          font: 'bold 14px serif',
          style: Cesium.LabelStyle.FILL,
          verticalOrigin: Cesium.VerticalOrigin.CENTER, // 垂直居中
@@ -144,25 +368,11 @@
    })
}
// const drawLine = () => {
//   console.log('尽量么');
//     // props.wayLineFile.way_lines.forEach(item => {
//   // let prexUrl = ref(import.meta.env.VITE_APP_AIRLINE_URL+ props.wayLineFile);
//   let prexUrl2 = ref('https://wrj.shuixiongit.com/minio/cloud-bucket/wayline/20241224/wayline_1735032173222.kmz');
//         analyzeKmzFile(`${prexUrl2.value}?_t=${new Date().getTime()}`).then(async res => {
//             const templateXML = await res.fileInfoObj['wpmz/template.kml']
//             const templateXMLJSON = XMLToJSON(templateXML)?.['Document']
//             const templateXMLObj = removeTextKey(templateXMLJSON.Folder)
//             renderingLine(templateXMLObj)
//         })
//     // })
// }
// 异步解析kmz文件
const analysis = async url => {
    return new Promise(async resolve => {
        const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`)
        const res = await analyzeKmzFile(`${url}?_t=${new Date().getTime()}`);
        const templateXML = await res.fileInfoObj['wpmz/template.kml']
        const templateXMLJSON = XMLToJSON(templateXML)?.['Document']
        const templateXMLObj = removeTextKey(templateXMLJSON.Folder)
@@ -174,37 +384,239 @@
const drawLine = async () => {
  let prexUrl = ref(import.meta.env.VITE_APP_AIRLINE_URL+ props.wayLineFile);
    const res = await analysis(prexUrl.value);
    if (!res.Placemark.length) return
  renderingLine(res);
    const points = res.Placemark.map(item => item.Point.coordinates.split(','))
    flyToPoints(points)
}
watch(() => props.wayLineFile, async (newValue, oldValue) => {
    if(newValue){
    await removeMap();
    await init();
// 选择航线时,根据选择机巢连线
const selectLineFile = (newVal) => {
    // 清除之前的实体
    // viewer.entities.removeAll();
    // 重新绘制航线
    // if (props.wayLineFile) {
    //   drawLine();
    // }
    // 添加选中点和连接线
    const positions = newVal.map(item => {
        const position = Cartesian3.fromDegrees(Number(item.longitude), Number(item.latitude));
        // 添加机巢点
        viewer.entities.add({
            position: position,
            billboard: {
                image: new Cesium.ConstantProperty(uavImg),
                width: 24,
                height: 24,
            },
        });
        return position;
    });
    // 添加连接线
    if (positions.length > 1) {
        viewer.entities.add({
            polyline: {
                positions: positions,
                width: 2,
                material: new Cesium.PolylineDashMaterialProperty({
                    color: Cesium.Color.RED,
                    dashLength: 8.0
                })
            }
        });
    }
    // 飞到中心点
    const lngLatArr = newVal.map(item => [item.longitude, item.latitude]);
    flyToPoints(lngLatArr);
};
// 单个点生成选择多个机巢生成航线
const singlePointLines = (newVal) => {
    // 清除之前的连接线
    connectLines.forEach(line => viewer.entities.remove(line));
    connectLines = [];
    if (currentEntity) {
        // 获取当前点的位置
        const currentPosition = currentEntity.position.getValue();
        // 为每个选中的机巢创建点和连接线
        newVal.forEach(item => {
            // 创建机巢点
            const nestPosition = Cartesian3.fromDegrees(
                Number(item.longitude),
                Number(item.latitude)
            );
            viewer.entities.add({
                position: nestPosition,
                billboard: {
                    image: new Cesium.ConstantProperty(uavImg),
                    width: 24,
                    height: 24,
                },
            });
            // 创建连接线
            const line = viewer.entities.add({
                polyline: {
                    positions: [currentPosition, nestPosition],
                    width: 2,
                    material: new Cesium.PolylineDashMaterialProperty({
                        color: Cesium.Color.RED,
                        dashLength: 8.0
                    })
                }
            });
            connectLines.push(line);
        });
        // 飞到所有点的中心位置
        const lngLatArr = newVal.map(item => [item.longitude, item.latitude]);
        flyToPoints(lngLatArr);
    }
};
// 智慧规划航线-面状航线
const planarPointsLines = (newVal) => {
  // 如果存在面状航线
  if (existingEntity) {
    const waylinePositions = existingEntity.polyline.positions.getValue();
    newVal.forEach(item => {
      // 创建机巢点
      const nestPosition = Cartesian3.fromDegrees(
        Number(item.longitude),
        Number(item.latitude)
      );
      // 添加机巢图标
      viewer.entities.add({
        position: nestPosition,
        billboard: {
          image: new Cesium.ConstantProperty(uavImg),
          width: 24,
          height: 24,
        },
      });
      // 找到最近的航线点并连线
      let minDistance = Number.MAX_VALUE;
      let closestPosition = null;
      waylinePositions.forEach(waylinePos => {
        const distance = Cartesian3.distance(nestPosition, waylinePos);
        if (distance < minDistance) {
          minDistance = distance;
          closestPosition = waylinePos;
        }
      });
      // 创建连接线
      if (closestPosition) {
        const line = viewer.entities.add({
          polyline: {
            positions: [nestPosition, closestPosition],
            width: 2,
            material: new Cesium.PolylineDashMaterialProperty({
              color: Cesium.Color.CHARTREUSE,
              dashLength: 8.0
            })
          }
        });
        connectLines.push(line);
      }
    });
    // 飞到所有点的中心位置
    const lngLatArr = newVal.map(item => [item.longitude, item.latitude]);
    flyToPoints(lngLatArr);
  }
};
// 监听选择航线文件事件
watch(() => props.wayLineFile, async (newVal, oldValue) => {
    await removeMap();
    if(newVal){
        await drawLine();
    }
})
}, { deep: true });
// 监听表格选中数据变化
watch(() => props.checkedTableData, (newVal) => {
    if (newVal.length > 0 && props.waylineTypeTest === 0) {
        selectLineFile(newVal);
    } else if (newVal.length > 0 && props.waylineTypeTest === 1) {
        singlePointLines(newVal);
    } else if (newVal.length > 0 && props.waylineTypeTest === 2) {
        planarPointsLines(newVal);
    }
}, { deep: true });
// 监听航线类型
watch(() => props.waylineTypeTest, async (newVal) => {
    await removeMap();
    if (newVal === 1) {
        await singlePointLeftClick();
    } else if (newVal === 2) {
        await intelligentPlanning();
    }
}, { deep: true });
const removeEvent = () => {
    // 清除事件监听器
    viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK);
    viewer.screenSpaceEventHandler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK);
};
const removeMap = () => {
    viewer.entities.removeAll()
    viewer.destroy()
  // 清除所有实体
  if (viewer) {
    // 清除连接线
    connectLines.forEach(line => viewer.entities.remove(line));
    connectLines = [];
    // 清除多边形点和实体
    polygonPoints = [];
    if (polygonEntity) {
      viewer.entities.remove(polygonEntity);
    }
    // 清除当前实体和面状航线
    if (currentEntity) {
      viewer.entities.remove(currentEntity);
    }
    if (existingEntity) {
      viewer.entities.remove(existingEntity);
    }
        viewer.entities.removeAll();
    // 重置所有变量
    currentEntity = null;
    polygonEntity = null;
    existingEntity = null;
  }
}
onBeforeUnmount(() => {
    removeMap()
    removeMap();
    removeEvent();
    // 移除所有实体并销毁viewer
    viewer.destroy();
    viewer = null;
})
onMounted(() => {
    nextTick(() => {
        init()
    })
})
});
</script>
<style scoped lang="scss">
#taskMap {
    position: relative;
    height: 100%;
    :deep() {
@@ -232,5 +644,27 @@
            display: none;
        }
    }
:deep(.context-menu) {
    position: absolute;
    background: rgba(0, 21, 41, 0.9);;
    border-radius: 4px;
    padding: 8px 0;
    min-width: 120px;
    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);
        .menu-item {
            padding: 8px 16px;
            color: #fff;
            cursor: pointer;
            transition: all 0.3s;
            font-size: 14px;
            &:hover {
                background: rgba(255, 255, 255, 0.1);
            }
        }
    }
}
</style>
src/views/TaskManage/TaskIntermediateContent/TaskTable.vue
@@ -1,7 +1,9 @@
<template>
  <div class="table-container">
    <el-table :data="tableData" style="width: 100%" height="400" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" />
    <el-table :data="tableData" style="width: 100%" height="400"
      @selection-change="handleSelectionChange"
      @select="handleSelect">
      <el-table-column type="selection" width="55" :selectable="checkSelectable" />
      <el-table-column type="index" label="序号" width="60" />
      <el-table-column prop="nickname" label="机巢名称" />
      <el-table-column prop="estimated_arrival_time" label="预计到达时间" />
@@ -9,8 +11,8 @@
    </el-table>
    <div class="pagination">
      <el-pagination
        v-model:current-page="pageParams.page"
        v-model:page-size="pageParams.limit"
        v-model:current-page="pagingParams.page"
        v-model:page-size="pagingParams.size"
        :page-sizes="[10, 20, 30]"
        layout="total, sizes, prev, pager, next"
        :total="total"
@@ -31,59 +33,138 @@
  },
  waylineType: {
    type: Number,
    default: 0
    default: 3
  },
  singlePoint: {
    type: Object,
    default: () => ({})
  },
  planarPoints: {
    type: Array,
    default: () => ([])
  }
});
// 选中的数据
const selectedRows = ref([]);
// 分页参数
let pageParams = reactive({
let pageParams = ref({
  wayline_id: props.waylineId, 
  type: props.waylineType,
  longitude: props.singlePoint.longitude,
  latitude: props.singlePoint.latitude,
  polygon: [],
});
let pagingParams = ref({
  page: 1,
  limit: 10
  size: 10
});
// 获取可用机巢列表数据
const total = ref(0);
const tableData = ref([]);
const getNestList = async () => {
    tableData.value = [];
  const res = await getFlyingNestBy(pageParams);
  const res = await getFlyingNestBy(pageParams.value, pagingParams.value);
  if (res.data.code === 0) {
    console.log(res.data.data, '哒哒哒');
    tableData.value = res.data.data;
  }
};
// 分页大小改变
const handleSizeChange = (val) => {
  pageParams.limit = val;
  pagingParams.value.size = val;
  getNestList();
};
// 页码改变
const handleCurrentChange = (val) => {
  pageParams.page = val;
  pagingParams.value.page = val;
  getNestList();
};
// 刷新数据
const refreshData = () => {
  tableData.value = [];
  pagingParams.value.page = 1;
  getNestList();
};
const emit = defineEmits(['update:selected']);
const handleSelectionChange = (val) => {
  selectedRows.value = val;
  // 可以通过emit将选中数据传给父组件
  emit('update:selected', val);
  // 如果不是智能规划选区模式,才更新所有选中数据
  if (props.waylineType !== 2) {
    selectedRows.value = val;
    emit('update:selected', val);
  }
};
// 控制表格行是否可选
const checkSelectable = (row) => {
  if (props.waylineType === 2) {
    // 如果已经有选中的行,且当前行未被选中,则不允许选择
    return selectedRows.value.length === 0 || selectedRows.value.some(selected => selected.device_sn === row.device_sn);
  }
  return true;
};
// 处理单行选择
const handleSelect = (selection, row) => {
  if (props.waylineType === 2) {
    // 如果是智能规划选区模式,确保只能选中一行
    if (selection.length > 1) {
      // 保留最后选中的那一行
      const lastSelected = selection[selection.length - 1];
      selectedRows.value = [lastSelected];
      // 触发更新事件
      emit('update:selected', selectedRows.value);
    }
  }
};
// 监听航线ID变化
watch(() => props.waylineId, (newVal) => {
  pageParams.wayline_id = newVal;
  pageParams.value.type = 0;
  if (newVal) {
    pageParams.page = 1;
    getNestList();
    pageParams.value.wayline_id = newVal;
    refreshData();
  } else {
    // 数据为空时,清除列表数据
    tableData.value = [];
  }
}, { immediate: true });
onMounted(() => {
  // getNestList();
}, { deep: true });
// 监听单点返回的数据
watch(() => props.singlePoint, (newVal) => {
  pageParams.value.wayline_id = '';
  pageParams.value.type = 1;
  if (newVal && newVal.latitude && newVal.longitude) {
    pageParams.value.latitude = newVal.latitude;
    pageParams.value.longitude = newVal.longitude;
    refreshData();
  }
}, { deep: true });
// 监听面状航线返回的数据
watch(() => props.planarPoints, (newVal) => {
  pageParams.value.wayline_id = '';
  pageParams.value.type = 2;
  if (newVal && newVal.length > 0) {
    const polygonArray = newVal.map(point => [point.longitude, point.latitude]);
    pageParams.value.polygon = polygonArray;
    refreshData();
  }
}, { deep: true });
// 暴露给父组件的方法
const clearTableData = () => {
  tableData.value = [];
};
defineExpose({
  clearTableData
});
onMounted(() => {});
</script>
<style lang="scss" scoped>