forked from drone/command-center-dashboard

罗广辉
2025-04-07 a119e5a86c626cc6175cbfdbb236c34270fbe90c
Merge remote-tracking branch 'origin/master'
5 files modified
7 files added
747 ■■■■■ changed files
.env.development 3 ●●●●● patch | view | raw | blame | history
package-lock.json 81 ●●●●● patch | view | raw | blame | history
src/api/home/task.js 31 ●●●●● patch | view | raw | blame | history
src/assets/images/EndPointicon.png patch | view | raw | blame | history
src/assets/images/Startingpointicon.png patch | view | raw | blame | history
src/assets/images/custom-point.png patch | view | raw | blame | history
src/views/TaskManage/SearchBox.vue 46 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/AddTask.vue 194 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue 10 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskMap.vue 236 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/TaskIntermediateContent/TaskTable.vue 86 ●●●●● patch | view | raw | blame | history
src/views/TaskManage/components/TaskAlgorithmBusiness.vue 60 ●●●●● patch | view | raw | blame | history
.env.development
@@ -8,3 +8,6 @@
# ws地址
VITE_APP_WS_API_URL = wss://wrj.shuixiongit.com/drone-wss/api/v1/ws
# 航线文件地址
VITE_APP_AIRLINE_URL = https://wrj.shuixiongit.com/minio/cloud-bucket
package-lock.json
@@ -28,6 +28,7 @@
        "js-base64": "^3.7.4",
        "js-cookie": "^3.0.0",
        "js-md5": "^0.7.3",
        "jszip": "^3.10.1",
        "lodash": "^4.17.21",
        "nprogress": "^0.2.0",
        "postcss-pxtorem": "^6.1.0",
@@ -3367,6 +3368,11 @@
        "node": ">=0.10.0"
      }
    },
    "node_modules/core-util-is": {
      "version": "1.0.3",
      "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
      "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="
    },
    "node_modules/cors": {
      "version": "2.8.5",
      "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
@@ -4878,6 +4884,11 @@
        "node": ">=0.10.0"
      }
    },
    "node_modules/immediate": {
      "version": "3.0.6",
      "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
      "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
    },
    "node_modules/immer": {
      "version": "9.0.21",
      "resolved": "https://registry.npmmirror.com/immer/-/immer-9.0.21.tgz",
@@ -4898,8 +4909,7 @@
    "node_modules/inherits": {
      "version": "2.0.4",
      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
      "dev": true
      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
    },
    "node_modules/inherits-browser": {
      "version": "0.1.0",
@@ -5387,8 +5397,7 @@
    "node_modules/isarray": {
      "version": "1.0.0",
      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
      "dev": true
      "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
    },
    "node_modules/isobject": {
      "version": "2.1.0",
@@ -5462,6 +5471,49 @@
        "graceful-fs": "^4.1.6"
      }
    },
    "node_modules/jszip": {
      "version": "3.10.1",
      "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz",
      "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==",
      "dependencies": {
        "lie": "~3.3.0",
        "pako": "~1.0.2",
        "readable-stream": "~2.3.6",
        "setimmediate": "^1.0.5"
      }
    },
    "node_modules/jszip/node_modules/pako": {
      "version": "1.0.11",
      "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
      "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
    },
    "node_modules/jszip/node_modules/readable-stream": {
      "version": "2.3.8",
      "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
      "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
      "dependencies": {
        "core-util-is": "~1.0.0",
        "inherits": "~2.0.3",
        "isarray": "~1.0.0",
        "process-nextick-args": "~2.0.0",
        "safe-buffer": "~5.1.1",
        "string_decoder": "~1.1.1",
        "util-deprecate": "~1.0.1"
      }
    },
    "node_modules/jszip/node_modules/safe-buffer": {
      "version": "5.1.2",
      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
      "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
    },
    "node_modules/jszip/node_modules/string_decoder": {
      "version": "1.1.1",
      "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
      "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
      "dependencies": {
        "safe-buffer": "~5.1.0"
      }
    },
    "node_modules/kdbush": {
      "version": "4.0.2",
      "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz",
@@ -5485,6 +5537,14 @@
      "version": "2.0.0",
      "resolved": "https://registry.npmjs.org/lerc/-/lerc-2.0.0.tgz",
      "integrity": "sha512-7qo1Mq8ZNmaR4USHHm615nEW2lPeeWJ3bTyoqFbd35DLx0LUH7C6ptt5FDCTAlbIzs3+WKrk5SkJvw8AFDE2hg=="
    },
    "node_modules/lie": {
      "version": "3.3.0",
      "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz",
      "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==",
      "dependencies": {
        "immediate": "~3.0.5"
      }
    },
    "node_modules/loader-utils": {
      "version": "1.4.2",
@@ -6415,6 +6475,11 @@
        "node": ">=6"
      }
    },
    "node_modules/process-nextick-args": {
      "version": "2.0.1",
      "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
      "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
    },
    "node_modules/protobufjs": {
      "version": "7.4.0",
      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
@@ -6970,6 +7035,11 @@
      "engines": {
        "node": ">=0.10.0"
      }
    },
    "node_modules/setimmediate": {
      "version": "1.0.5",
      "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
      "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA=="
    },
    "node_modules/side-channel": {
      "version": "1.1.0",
@@ -8300,8 +8370,7 @@
    "node_modules/util-deprecate": {
      "version": "1.0.2",
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
      "dev": true
      "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
    },
    "node_modules/vary": {
      "version": "1.1.2",
src/api/home/task.js
@@ -76,4 +76,35 @@
    })
}
// 获取航线列表
export const getWaylineList = (params) => {
    return request({
        url: `/drone-device-core/wayline/api/v1/workspaces/getWaylineFileListByArea`,
        method: 'get',
        params
    })
}
// 获取可飞行机巢列表
export const getFlyingNestBy = (data) => {
    return request({
        url: `/drone-device-core/manage/api/v1/devices/getFlyingNestBy?current=${data.page}&size=${data.limit}`,
        method: 'post',
        data: {
            wayline_id: data.wayline_id,
            type: data.type
        },
    })
}
// 机场发布功能
export const createTask = (data) => {
    return request({
        url: `/drone-device-core/wayline/waylineJobInfo/createTask`,
        method: 'post',
        data,
    })
}
src/assets/images/EndPointicon.png
src/assets/images/Startingpointicon.png
src/assets/images/custom-point.png
src/views/TaskManage/SearchBox.vue
@@ -53,9 +53,10 @@
          />
        </el-form-item>
        <el-form-item label="任务算法">
          <el-select v-model="searchForm.ai_type" placeholder="请选择算法" clearable>
          <!-- <el-select v-model="searchForm.ai_type" placeholder="请选择算法" clearable>
            <el-option v-for="item in taskAlgorithm" :key="item.id" :label="item.dictValue" :value="item.dictKey" />
          </el-select>
          </el-select> -->
          <TaskAlgorithmBusiness :showAlgorithm="true" @algorithmChange="algorithmChange"/>
        </el-form-item>
      </div>
      <div class="search-row">
@@ -70,9 +71,10 @@
          </el-select>
        </el-form-item>
        <el-form-item label="任务类型">
          <el-select v-model="searchForm.industry_type" placeholder="请选择类型" clearable>
          <!-- <el-select v-model="searchForm.industry_type" placeholder="请选择类型" clearable>
            <el-option v-for="item in taskBusiness" :key="item.id" :label="item.dictValue" :value="item.dictKey" />
          </el-select>
          </el-select> -->
          <TaskAlgorithmBusiness :showBusiness="true" @businessChange="businessChange"/>
        </el-form-item>
      </div>
    </div>
@@ -80,7 +82,8 @@
  </div>
</template>
<script setup>
import { getDictionary, deptsByAreaCode, getDockInfo } from '@/api/home/common';
import { deptsByAreaCode, getDockInfo } from '@/api/home/common';
import TaskAlgorithmBusiness from './components/TaskAlgorithmBusiness.vue';
const isExpand = ref(false);
const toggleExpand = () => {
@@ -109,7 +112,7 @@
  { label: '执行失败', value: 5 }
];
const emit = defineEmits(['search']);
const emit = defineEmits(['search','addTask']);
const handleSearch = () => {
  emit('search', {
@@ -129,24 +132,29 @@
// 新增任务
const addTask = () => {
  // 跳转新增任务页面
  window.open('/#/task/add');
  emit('addTask');
};
let taskAlgorithm = ref([]);
let taskBusiness = ref([]);
// let taskAlgorithm = ref([]);
// let taskBusiness = ref([]);
// 请求字典字段
const requestDictionary = () => {
  getDictionary('SF,HYLB').then((res) => {
    if (res.code !== 0) {
      // 处理数据
      taskAlgorithm.value = res.data.data['SF'];
      taskBusiness.value = res.data.data['HYLB'];
    }
  });
// const requestDictionary = () => {
//   getDictionary('SF,HYLB').then((res) => {
//     if (res.code !== 0) {
//       // 处理数据
//       taskAlgorithm.value = res.data.data['SF'];
//       taskBusiness.value = res.data.data['HYLB'];
//     }
//   });
// };
const algorithmChange = (val) => {
  searchForm.ai_type = val;
};
const businessChange = (val) => {
  searchForm.industry_type = val;
};
let deptData = ref([]);
// 所属部门信息
@@ -183,7 +191,7 @@
onMounted(() => {
  requestDictionary();
  // requestDictionary();
  getDeptsByAreaCode();
  requestDockInfo();
});
src/views/TaskManage/TaskIntermediateContent/AddTask.vue
New file
@@ -0,0 +1,194 @@
<!-- 机巢列表详情 -->
<template>
    <el-dialog
        modal-class="add-task"
        v-model="isShowAddTask"
        title="新建任务"
        :width="pxToRem(1500)"
        :close-on-click-modal="false"
        :destroy-on-close="true">
        <!-- <el-divider content-position="left">新建任务</el-divider> -->
        <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">任务日期:
                        <el-date-picker
                            v-model="searchForm.begin_time"
                            format="YYYY-MM-DD"
                            value-format="YYYY-MM-DD"
                        />
                    </div>
                    <div class="item">任务时间:
                        <!-- <el-time-picker
                            v-model="searchForm.execute_time_arr"
                            placeholder="选择时间"
                            format="HH:mm"
                            value-format="HH:mm"/> -->
                    </div>
                    <div class="item">选择航线:
                        <el-select
                            v-model="searchForm.file_id"
                            @change="getWayLineFile"
                            placeholder="请选择航线"
                            clearable>
                            <el-option
                                v-for="item in routeOptions"
                                :key="item.wayline_id"
                                :label="item.name"
                                :value="item.wayline_id"/>
                            </el-select>
                        </div>
                        <div class="item">关联算法:
                            <TaskAlgorithmBusiness :showAlgorithm="true" @algorithmChange="algorithmChange"/>
                        </div>
                    <div class="item">任务描述:<el-input v-model="searchForm.remark" placeholder="请输入任务名称"></el-input></div>
                </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>
                    </div>
                    <TaskMap class="wayline-map" :wayLineFile="wayLineFile"/>
                </div>
            </div>
            <div class="right">
                <TaskTable :waylineId="waylineId" :waylineType="waylineType" @update:selected="handleSelected" />
                <div class="btn">
                    <el-button type="primary" @click="cancel">取消</el-button>
                    <el-button type="primary" @click="submitClick">发布</el-button>
                </div>
            </div>
        </div>
    </el-dialog>
</template>
<script setup>
import { pxToRem } from '@/utils/rem';
import { getWaylineList, createTask } from '@/api/home/task';
import TaskAlgorithmBusiness from '../components/TaskAlgorithmBusiness.vue';
import TaskMap from './TaskMap.vue';
import TaskTable from './TaskTable.vue';
const rangDate = ref([]);
// 航线ID
let waylineId = ref('');
// 航线文件
let wayLineFile = ref('');
// 航线类型
let waylineType = ref(0);
const searchForm = reactive({
    name: '',
    ai_type: [],
    file_id: '',
    begin_time: '',
    end_time: '',
    execute_time_arr: '',
    remark: '',
    type: waylineType.value,
    dock_sns: [],
});
const isShowAddTask = defineModel('show');
// 获取航线列表数据
const routeOptions = ref([]);
const getRouteList = async () => {
  const res = await getWaylineList();
  if (res.data.code === 0) {
    routeOptions.value = res.data.data;
  }
};
const algorithmChange = (val) => {
  searchForm.ai_type = val;
};
// 获取航线文件
const getWayLineFile = async (val) => {
    waylineId.value = val;
    const currentRoute = routeOptions.value.find(item => item.wayline_id === val);
  wayLineFile.value = currentRoute.object_key;
};
// 获取选中机场列表数据,并且发布
const handleSelected = (val) => {
  searchForm.dock_sns = val.map(item => item.device_sn);
};
const emit = defineEmits(['refresh']);
// 提交
const submitClick = () => {
    searchForm.end_time = `${searchForm.begin_time} 23:59:59`;
    searchForm.begin_time = `${searchForm.begin_time} 00:00:00`;
    createTask(searchForm).then((res) => {
        console.log(res);
        if (res.code === 0) {
            ElMessage.success('任务创建成功');
            // 关闭当前窗口,刷新任务管理列表
            isShowAddTask.value = false;
            emit('refresh');
        }
    });
};
const cancel = () => {
  isShowAddTask.value = false;
    // 清除搜索数据
    searchForm.name = '';
  searchForm.ai_type = [];
  searchForm.file_id = '';
  searchForm.begin_time = '';
  searchForm.end_time = '';
  searchForm.execute_time_arr = '';
  searchForm.remark = '';
  searchForm.dock_sns = [];
  rangDate.value = [];
  waylineId.value = '';
  wayLineFile.value = '';
};
onMounted(() => {
  getRouteList();
})
</script>
<style lang="scss">
.add-task{
    .el-pagination {
        text-align: left;
        padding: 20px;
    }
}
</style>
<style lang="scss" scoped>
.task-contain {
    display: flex;
    .left {
        width: 70%;
        .search {
            display: grid;
      grid-template-columns: repeat(3, 1fr);
      grid-template-rows: repeat(2, 1fr);
      gap: 30px; // 增加间距使布局更合理
      margin-bottom: 8px; // 添加底部间距
            .item {
        display: flex;
        align-items: center;
        :deep(.el-input),
        :deep(.el-select),
        :deep(.el-date-picker),
        :deep(.el-time-picker) {
          width: 240px;
          margin-left: 10px;
        }
                :deep(.el-date-editor.el-input__wrapper) {
          width: 200px;  // 调整日期选择器宽度
        }
      }
        }
    }
    .right {
        width: 30%;
    }
}
</style>
src/views/TaskManage/TaskIntermediateContent/TaskIntermediateContent.vue
@@ -1,6 +1,6 @@
<!-- 任务统计表格 -->
<template>
  <SearchBox @search="searchClick"></SearchBox>
  <SearchBox @search="searchClick" @addTask="handleAddTask"></SearchBox>
  <div class="task-intermediate-content">
    <el-table :data="jobListData" style="width: 100%" height="calc(100vh - 180px)">
      <el-table-column label="序号" width="60">
@@ -40,10 +40,12 @@
      />
    </div>
  </div>
  <AddTask v-model:show="isShowAddTask" @refresh="searchClick"/>
</template>
<script setup>
import SearchBox from '../SearchBox.vue';
import AddTask from './AddTask.vue';
import { jobList } from '@/api/home/task';
const jobListParams = reactive({
@@ -112,6 +114,12 @@
  getJobList();
};
// 新建任务
const isShowAddTask = ref(false);
const handleAddTask = () => {
  isShowAddTask.value = true;
};
onMounted(() => {
  getJobList();
});
src/views/TaskManage/TaskIntermediateContent/TaskMap.vue
New file
@@ -0,0 +1,236 @@
<template>
    <div id="taskMap" />
</template>
<script setup>
import * as Cesium from 'cesium'
import { Cartesian3, Math as CesiumMath, Terrain, Viewer } from 'cesium'
import AmapMercatorTilingScheme from '@/utils/cesium/AmapMercatorTilingScheme'
import { analyzeKmzFile, removeTextKey, XMLToJSON } from '@/utils/cesium/kmz'
import ImageTrailMaterial from '@/utils/cesium/ImageTrailMaterial'
import lineImg from '@/assets/images/arrow-right-blue.png';
import Startingpointicon from '@/assets/images/Startingpointicon.png';
import EndPointicon from '@/assets/images/EndPointicon.png';
import uavImg from '@/assets/images/home/useUavHome/uavImg.png'
import { getCenterPoint } from '@/utils/cesium/mapUtil'
const props = defineProps(['wayLineFile'])
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}',
    layer: 'tdtVecBasicLayer',
    style: 'default',
    format: 'image/png',
    tileMatrixSetID: 'GoogleMapsCompatible',
    subdomains: ['0', '1', '2', '3', '4', '5', '6', '7'],
    maximumLevel: 18,
    tilingScheme: new AmapMercatorTilingScheme(),
    credit: 'amap_SL',
})
let viewer = null
const init = () => {
    viewer = new Viewer('taskMap', {
        terrain: Terrain.fromWorldTerrain(),
        infoBox: false, // 禁用沙箱,解决控制台报错
        animation: false, // 左下角的动画仪表盘
        baseLayerPicker: false, // 右上角的图层选择按钮
        geocoder: false, // 搜索框
        homeButton: false, // home按钮
        sceneModePicker: false, // 模式切换按钮
        timeline: false, // 底部的时间轴
        navigationHelpButton: false, // 右上角的帮助按钮,
        selectionIndicator: false, // 是否显示选择指示器
        baseLayer: false,
        fullscreenButton: false,
        // sceneMode: Cesium.SceneMode.COLUMBUS_VIEW,
    })
    viewer.imageryLayers.addImageryProvider(imageryProvider_ammapSL)
    viewer.scene.morphTo2D(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,
            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,
        },
    })
  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) {
      setting = {
        position: point,
        id: `point_${index}`,
        billboard: {
          image: EndPointicon,
          outlineWidth: 0,
          width: 20,
          height: 20,
          scale: 1.0,
        },
      }
    } else {
      setting = {
        position: point,
        id: `point_${index}`,
        label: {
          text: `${index}`,
          font: 'bold 14px serif',
          style: Cesium.LabelStyle.FILL,
          verticalOrigin: Cesium.VerticalOrigin.CENTER, // 垂直居中
          horizontalOrigin: Cesium.HorizontalOrigin.CENTER, // 水平居中
          pixelOffset: new Cesium.Cartesian2(0, 0), // 根据需要调整偏移量
          eyeOffset: new Cesium.Cartesian3(0, 0, -10) // 使标签在点的上方
        },
        point: {
          pixelSize: 24,
          color: new Cesium.Color.fromBytes(255, 186, 0, 255),
        },
        offset: new Cesium.Cartesian2(10, 30),
      }
    }
    viewer.entities.add(setting)
  });
}
// 飞到中心点
function flyToPoints(lngLatArr) {
    if (!Array.isArray(lngLatArr) || lngLatArr.length === 0) return
    const positions = lngLatArr.map(([lon, lat]) =>
        Cesium.Cartesian3.fromDegrees(Number(lon), Number(lat))
    )
    // 计算包围盒 BoundingSphere(所有点的外接球)
    const boundingSphere = Cesium.BoundingSphere.fromPoints(positions)
    viewer.camera.flyToBoundingSphere(boundingSphere, {
        duration: 0,
        offset: new Cesium.HeadingPitchRange(0, 0, boundingSphere.radius * 2),
    })
}
// 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 templateXML = await res.fileInfoObj['wpmz/template.kml']
        const templateXMLJSON = XMLToJSON(templateXML)?.['Document']
        const templateXMLObj = removeTextKey(templateXMLJSON.Folder)
        resolve(templateXMLObj)
    })
}
// 绘制线和飞行
const drawLine = async () => {
  let prexUrl = ref(import.meta.env.VITE_APP_AIRLINE_URL+ props.wayLineFile);
    const res = await analysis(prexUrl.value);
  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();
        await drawLine();
    }
})
const removeMap = () => {
    viewer.entities.removeAll()
    viewer.destroy()
}
onBeforeUnmount(() => {
    removeMap()
})
onMounted(() => {
    nextTick(() => {
        init()
    })
})
</script>
<style scoped lang="scss">
#taskMap {
    height: 100%;
    :deep() {
        .cesium-viewer {
            height: 100%;
            overflow: hidden;
            .cesium-viewer-cesiumWidgetContainer {
                width: 100%;
                height: 100%;
                .cesium-widget {
                    width: 100%;
                    height: 100%;
                    canvas {
                        width: 100%;
                        height: 100%;
                    }
                }
            }
        }
        .cesium-viewer-bottom {
            display: none;
        }
    }
}
</style>
src/views/TaskManage/TaskIntermediateContent/TaskTable.vue
New file
@@ -0,0 +1,86 @@
<template>
  <el-table :data="tableData" style="width: 100%" height="400" @selection-change="handleSelectionChange">
    <el-table-column type="selection" width="55" />
    <el-table-column type="index" label="序号" width="60" />
    <el-table-column prop="nickname" label="机巢名称" />
    <el-table-column prop="estimated_arrival_time" label="预计到达时间" />
    <el-table-column prop="exe_distance" label="执行里程" />
  </el-table>
  <div class="pagination">
    <el-pagination
      v-model:current-page="pageParams.page"
      v-model:page-size="pageParams.limit"
      :page-sizes="[10, 20, 30]"
      layout="total, sizes, prev, pager, next"
      :total="total"
      @size-change="handleSizeChange"
      @current-change="handleCurrentChange"
    />
  </div>
</template>
<script setup>
import { getFlyingNestBy } from '@/api/home/task';
const props = defineProps({
  waylineId: {
    type: String,
    default: ''
  },
  waylineType: {
    type: Number,
    default: 0
  }
});
// 选中的数据
const selectedRows = ref([]);
// 分页参数
let pageParams = reactive({
  wayline_id: props.waylineId,
  type: props.waylineType,
  page: 1,
  limit: 10
});
// 获取可用机巢列表数据
const total = ref(0);
const tableData = ref([]);
const getNestList = async () => {
    tableData.value = [];
  const res = await getFlyingNestBy(pageParams);
  if (res.data.code === 0) {
    tableData.value = res.data.data;
  }
};
// 分页大小改变
const handleSizeChange = (val) => {
  pageParams.limit = val;
  getNestList();
};
// 页码改变
const handleCurrentChange = (val) => {
  pageParams.page = val;
  getNestList();
};
const emit = defineEmits(['update:selected']);
const handleSelectionChange = (val) => {
  selectedRows.value = val;
  // 可以通过emit将选中数据传给父组件
  emit('update:selected', val);
};
// 监听航线ID变化
watch(() => props.waylineId, (newVal) => {
  pageParams.wayline_id = newVal;
  if (newVal) {
    pageParams.page = 1;
    getNestList();
  }
}, { immediate: true });
onMounted(() => {
  // getNestList();
});
</script>
<style lang="scss" scoped>
</style>
src/views/TaskManage/components/TaskAlgorithmBusiness.vue
New file
@@ -0,0 +1,60 @@
<!-- 关联算法和综合业务 -->
<template>
  <div class="task-algorithm" v-if="showAlgorithm">
    <el-select v-model="ai_type" multiple collapse-tags collapse-tags-tooltip placeholder="请选择算法" clearable @change="handleAlgorithmChange">
      <el-option v-for="item in taskAlgorithm" :key="item.id" :label="item.dictValue" :value="item.dictKey" />
    </el-select>
  </div>
  <div class="task-business" v-if="showBusiness">
    <el-select v-model="industry_type" placeholder="请选择类型" clearable @change="handleBusinessChange">
      <el-option v-for="item in taskBusiness" :key="item.id" :label="item.dictValue" :value="item.dictKey" />
    </el-select>
  </div>
</template>
<script setup>
import { getDictionary } from '@/api/home/common';
// 接收父组件传参
const props = defineProps({
  showAlgorithm: {
    type: Boolean,
    default: false
  },
  showBusiness: {
    type: Boolean,
    default: false
  }
});
// 算法
let ai_type = ref('');
let taskAlgorithm = ref([]);
// 综合业务
let industry_type = ref('');
let taskBusiness = ref([]);
// 请求字典字段
const requestDictionary = () => {
  getDictionary('SF,HYLB').then((res) => {
    if (res.code !== 0) {
      // 处理数据
      taskAlgorithm.value = res.data.data['SF'];
      taskBusiness.value = res.data.data['HYLB'];
    }
  });
};
const emit = defineEmits(['algorithmChange', 'businessChange']);
// 算法选择事件
const handleAlgorithmChange = (value) => {
  emit('algorithmChange', value);
};
// 业务选择事件
const handleBusinessChange = (value) => {
  emit('businessChange', value);
};
onMounted(() => {
  requestDictionary();
});
</script>