Compare commits

...

2 Commits

18 changed files with 3411 additions and 251 deletions

View File

@ -118,6 +118,7 @@
"mapbox-gl-utils": "^0.44.0",
"mars3d": "^3.7.0",
"mars3d-cesium": "^1.113.0",
"mars3d-space": "^3.7.0",
"min-dash": "^4.2.1",
"mockjs": "^1.1.0",
"mqtt": "^5.13.1",

BIN
public/map/air-point.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
public/map/node.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 723 B

BIN
public/map/start-point.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/map/start.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

36
src/api/sys/workplan.ts Normal file
View File

@ -0,0 +1,36 @@
import { defHttp } from '@/utils/http/axios';
enum Api{
GetTaskPageList = '/api/Manage/GetTaskPageList',
AddTask = '/api/Manage/AddTask',
EditTask = '/api/Manage/EditTask',
DeleteTask = '/api/Manage/DeleteTask',
GetUavPageList = '/api/Manage/GetUavPageList',
GetAirLineList = '/api/Manage/GetAirLineList'
}
export function getTaskPageList(params) {
return defHttp.get({ url: Api.GetTaskPageList, params });
}
export function addTask(params){
return defHttp.post({ url: Api.AddTask, params });
}
export function editTask(params){
return defHttp.post({ url: Api.EditTask, params });
}
export function deleteTask(params){
return defHttp.post({ url: Api.DeleteTask+"?id="+params.id, params });
}
export function getUavPageList(params) {
return defHttp.get({ url: Api.GetUavPageList, params });
}
export function getAirLineList(params) {
return defHttp.get({ url: Api.GetAirLineList, params });
}

View File

@ -46,6 +46,7 @@
<div class="search-container">
<a-input-search
v-model:value="pageQuery.key"
placeholder="输入路线名称"
/>
</div>
@ -72,13 +73,13 @@
<div>
<ImportOutlined />
&nbsp;
<PlusOutlined />
<PlusOutlined @click="createAirLine" />
</div>
</div>
<div class="ari-line" v-for="(item,index) in ariLineList" :key="index" @click="checkAriLine(item)">
<div class="title">
<div style="flex:1;">
{{item.name}}
{{item.airLineName}}
</div>
<div style="">
<EditOutlined />
@ -88,9 +89,9 @@
</div>
<div class="type" >
<img src="/public/iocn/uav.png" alt="">
{{item.uav}}
{{item.uavId}}
</div>
<div class="time">更新时间{{item.updateTime}}</div>
<div class="time">更新时间{{item.createTime}}</div>
</div>
</div>
</div>
@ -99,28 +100,33 @@
<script lang="ts" setup>
import { PlusOutlined,FileAddOutlined,LeftOutlined,ImportOutlined,MoreOutlined,EditOutlined } from '@ant-design/icons-vue';
import {ref,defineEmits} from 'vue'
import { getAirLineList } from '@/api/sys/workplan';
const emit = defineEmits(["checkAriLine"])
const emit = defineEmits(["checkAriLine","createAirLine"])
const createAirLine = () => {
emit("createAirLine");
}
const checkAriLine = (item)=> {
emit("checkAriLine",item);
}
const expandedKeys = ref<string[]>(['0-0', '0-1']);
const selectedKeys = ref<string[]>([]);
const treeData = ref(
const selectedKeys = ref<string[]>([]);
const treeData = ref(
[
{
title: '默认文件夹',
key: '1',
children: [
{
title: '文件夹1',
title: '面状航线',
key: '2',
isLeaf: true,
},
{
title: '文件夹2',
title: '航点航线',
key: '3',
isLeaf: true,
},
@ -129,29 +135,24 @@ const treeData = ref(
]
)
const ariLineList = ref([
{
name:"航线1",
uav:"Matrice 3TD",
updateTime:"2025-06-03 17:01:47"
},
{
name:"航线1",
uav:"Matrice 3TD",
updateTime:"2025-06-03 17:01:47"
},
{
name:"航线1",
uav:"Matrice 3TD",
updateTime:"2025-06-03 17:01:47"
},
{
name:"航线1",
uav:"Matrice 3TD",
updateTime:"2025-06-03 17:01:47"
}
])
const ariLineList = ref([])
const pageQuery = ref({
page:1,
limit:10,
key:null,
})
const getAirList = async ()=>{
let res = await getAirLineList(pageQuery.value);
ariLineList.value = res.items;
}
getAirList();
</script>
<style scoped>
.container{

View File

@ -0,0 +1,315 @@
<template>
<div class="containner">
<div class="title">
<LeftOutlined />
<div>航点航线</div>
<SaveOutlined/>
<div style="flex:1;">
<a-input size="middle" placeholder="航线名称" />
</div>
</div>
<div class="area-info">
<div class="item">
<div>航点数量</div>
<div>1</div>
</div>
<div class="bar"></div>
<div class="item">
<div>航线长度</div>
<div>{{props.airInfo.length}} km</div>
</div>
<div class="bar"></div>
<div class="item">
<div>预计用时</div>
<div>{{props.airInfo.time}} mins</div>
</div>
<div class="bar"></div>
<div class="item">
<div>照片数量</div>
<div>{{props.airInfo.picture}} </div>
</div>
</div>
<div class="air-point-list">
<div class="air-point-item">
<!-- <div class="cel">经度</div>
<div class="cel">纬度</div> -->
<div class="cel">航点</div>
<div class="cel">高度</div>
<div class="cel">偏航角</div>
<div class="cel">水平角</div>
<div class="cel">俯仰角</div>
<div class="cel">照片</div>
</div>
<div class="air-point-item" v-for="(item,index) in props.airPoints" :key="index">
<!-- <div class="cel">{{item.lng}}</div>
<div class="cel">{{item.lat}}</div> -->
<div class="cel">
<img src="/map/air-point.png" width="18" alt="">
{{index+1}}
</div>
<div class="cel">{{item.alt}}</div>
<div class="cel">{{item.aircraftHorizontalAngle}}</div>
<div class="cel">{{item.cameraHorizontalAngle}}</div>
<div class="cel">{{item.cameraVerticalAngle}}</div>
<div class="cel">{{item.focalLength}}</div>
</div>
</div>
<div class="area-options" v-if="false">
<div class="item">
<div class="label">参考起飞点</div>
<div class="content">
<a-button type="link" style="color:#408eff" @click="setFlyPoint()">
<img src="./start-fly.png" style="width:24px;height:24px;position:relative;left:-2px;top:-2px;" alt="">
设置起飞点
</a-button>
</div>
</div>
<div class="item flex-column">
<div class="label">选择镜头</div>
<div class="content">
<a-radio-group button-style="solid" v-model:value="props.polygonAirForm.lensMode">
<a-radio-button :value="1">红外</a-radio-button>
<a-radio-button :value="2">可见光</a-radio-button>
</a-radio-group>
</div>
</div>
<div class="item flex-column">
<div class="label">采集方式</div>
<div class="content">
<a-radio-group button-style="solid" v-model:value="props.polygonAirForm.gatherMode">
<a-radio-button :value="1">正射采集</a-radio-button>
<a-radio-button :value="2">倾斜采集</a-radio-button>
</a-radio-group>
</div>
</div>
<div class="item">
<div class="label">智能摆动拍摄</div>
</div>
<div class="item flex-column">
<div class="label">航线高度模式</div>
<div class="content">
<a-radio-group button-style="solid" v-model:value="props.polygonAirForm.heightMode">
<a-radio-button :value="1">海拔高度</a-radio-button>
<a-radio-button :value="2">相对起点高度</a-radio-button>
<a-radio-button :value="3">相对地形高度</a-radio-button>
</a-radio-group>
<div style="margin-top:12px;">
<a-input-number v-model:value="props.polygonAirForm.height" :min="1" :max="100000" />
</div>
</div>
</div>
<div class="item">
<div class="label">全局航线速度</div>
<div class="content">
<a-input-number style="width:160px" v-model:value="props.polygonAirForm.speed" :min="1" :max="20" />
</div>
</div>
<div class="item">
<div class="label"> 主航线角度</div>
<div class="content">
<a-input-number width="160px" v-model:value="props.polygonAirForm.angle" :min="0" :max="180" />
</div>
</div>
<div class="item">
<div class="label"> 高程优化</div>
</div>
<div class="item">
<div class="label"> 完成动作</div>
<div class="content">
<a-select
ref="select"
style="width:160px;"
v-model:value="props.polygonAirForm.complete"
>
<a-select-option :value="1">自动返航</a-select-option>
<a-select-option :value="2">返回航线起始点悬停</a-select-option>
<a-select-option :value="3">退出航线模式</a-select-option>
<a-select-option :value="4">原地降落</a-select-option>
</a-select>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref,defineEmits,defineProps} from 'vue'
import { SaveOutlined,LeftOutlined } from '@ant-design/icons-vue';
const emits = defineEmits(["setFlyPoint"])
const props = defineProps(["airPoints","airInfo","polygonAirForm"])
const airInfo = ref({
area:0.0,
length:0.0,
time:'4m 11s',
picture:23
})
const setFlyPoint = ()=>{
emits("setFlyPoint");
}
</script>
<style scoped>
.container{
width:100%;
height:100%;
color:#fff;
}
.title{
padding:20px;
font-size:16px;
color:#ffffff;
display:flex;
gap:12px;
line-height:24px;
}
.area-info{
width:100%;
border-top:1px solid rgba(255,255,255,0.2);
border-bottom:1px solid rgba(255,255,255,0.2);
display:flex;
padding:20px 0px;
}
.area-info .item{
flex:1;
color:#fff;
text-align: center;
display:flex;
gap:12px;
flex-direction: column;
}
.area-options{
width:100%;
height: calc( 100vh - 260px);
padding:10px;
overflow-y:auto;
line-height:32px;
}
.area-options .item{
padding:10px;
margin:15px 0px;
background:#232323;
color:#fff;
display:flex;
}
.area-options .item .label{
flex:1;
}
.area-options .item .content{
cursor:pointer;
}
.flex-column{
flex-direction: column;
gap:10px;
}
.air-point-list{
width:100%;
height: calc( 100vh - 260px);
padding:10px;
overflow-y:auto;
line-height:32px;
text-align: center;
}
.air-point-item{
display:flex;
gap:15px;
border-bottom:1px solid rgba(255,255,255,0.2);
padding:6px 0px;
}
.air-point-item .cel{
flex:1;
color:#fff;
}
::v-deep .ant-radio-group {
display: block;
width: 100%;
}
::v-deep .ant-select-selector{
border:none!important;
color:#fff!important;
background:#3F4150!important ;
border-radius: 3px!important;
}
::v-deep .ant-select-selection-placeholder {
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-select-arrow {
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-select-selection-search-input::placeholder{
color:rgba(255, 255, 255, 0.933)!important;
}
::v-deep .ant-tree {
background:none!important;
color:#fff!important;
}
::v-deep .ant-tree-treenode-selected{
background:#3a57e877!important;
}
::v-deep .ant-tree-treenode-selected::before{
background:none!important;
}
::v-deep .ant-tree-treenode-selected::after{
content:"";
height:28px;
width:4px;
background:#3A57E8;
position:absolute;
top:0px;
left:0px;
}
::v-deep .ant-input{
background:#3c3c3c!important ;
border:none!important;
border-top-left-radius: 3px !important;
border-bottom-left-radius: 3px !important;
color:#fff!important;
}
::v-deep .ant-input::placeholder{
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-btn-default{
background:none!important;
border:none!important;
outline:none!important;
color:#fff!important;
height:30px !important;
background:#3F4150!important ;
}
</style>

View File

@ -0,0 +1,154 @@
<template>
<div class="container">
<div class="config-item">
<div class="item-title">
航点经度
</div>
<div class="item-content">
<a-input-number
v-model:value="config.lng"
:min="-180"
:max="180"
:step="0.00000001"
string-mode
/>
</div>
</div>
<div class="config-item">
<div class="item-title">
航点纬度
</div>
<div class="item-content">
<a-input-number
v-model:value="config.lat"
:min="-90"
:max="90"
:step="0.00000001"
string-mode
/>
</div>
</div>
<div class="config-item">
<div class="item-title">
航点高度
</div>
<div class="item-content">
<a-input-number
v-model:value="config.alt"
:min="0"
:step="0.01"
string-mode
/>
</div>
</div>
<div class="config-item">
<div class="item-title">
<div class="label">
飞行器偏航角
</div>
<div>
{{ config.aircraftHorizontalAngle }} °
</div>
</div>
<div class="item-content">
<a-slider v-model:value="config.aircraftHorizontalAngle" :min="-180" :max="180" :step="0.01" />
</div>
</div>
<div class="config-item">
<div class="item-title">
<div class="label">
云台偏航角
</div>
<div>
{{ config.cameraHorizontalAngle }} °
</div>
</div>
<div class="item-content">
<a-slider v-model:value="config.cameraHorizontalAngle" :min="-180" :max="180" :step="0.01" />
</div>
</div>
<div class="config-item">
<div class="item-title">
<div class="label">
云台俯仰角
</div>
<div class="">
{{ config.cameraVerticalAngle }} °
</div>
</div>
<div class="item-content">
<a-slider v-model:value="config.cameraVerticalAngle" :min="-180" :max="180" :step="0.01" />
</div>
</div>
<div class="config-item">
<div class="item-title">
<div class="label">
相机焦距
</div>
<div class="">
× {{ config.cameraFocalLength }}
</div>
</div>
<div class="item-content">
<a-slider v-model:value="config.cameraFocalLength" :min="1" :max="56" />
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
const config = ref({
id:"1",
name:"航点",
lng:118.023154,
lat:35.21458,
alt:167.23,
aircraftHorizontalAngle:0,
cameraHorizontalAngle:0,
cameraVerticalAngle:0,
cameraFocalLength:2,
focalLength:2
})
</script>
<style scoped>
.container{
width:100%;
height:100%;
padding:15px;
color:#fff;
overflow-y:auto;
}
.config-item {
color:#fff;
background:#232323;
padding:15px 10px;
margin-bottom:15px;
}
.config-item .item-content{
margin-top:12px;
}
.config-item .item-title{
display:flex;
}
.config-item .item-title .label{
flex:1;
}
::v-deep .ant-slider .ant-slider-rail{
background:#fff!important;
}
</style>

View File

@ -0,0 +1,268 @@
<template>
<div class="containner">
<div class="title">
<LeftOutlined />
<div>面状航线</div>
<SaveOutlined/>
<div style="flex:1;">
<a-input size="small" placeholder="航线名称" />
</div>
</div>
<div class="area-info">
<div class="item">
<div>测区面积</div>
<div>{{props.airInfo.area}} </div>
</div>
<div class="bar"></div>
<div class="item">
<div>航线长度</div>
<div>{{props.airInfo.length}} km</div>
</div>
<div class="bar"></div>
<div class="item">
<div>预计用时</div>
<div>{{props.airInfo.time}} mins</div>
</div>
<div class="bar"></div>
<div class="item">
<div>照片数量</div>
<div>{{props.airInfo.picture}} </div>
</div>
</div>
<div class="area-options">
<div class="item">
<div class="label">参考起飞点</div>
<div class="content">
<a-button type="link" style="color:#408eff" @click="setFlyPoint()">
<img src="./start-fly.png" style="width:24px;height:24px;position:relative;left:-2px;top:-2px;" alt="">
设置起飞点
</a-button>
</div>
</div>
<div class="item flex-column">
<div class="label">选择镜头</div>
<div class="content">
<a-radio-group button-style="solid" v-model:value="props.polygonAirForm.lensMode">
<a-radio-button :value="1">红外</a-radio-button>
<a-radio-button :value="2">可见光</a-radio-button>
</a-radio-group>
</div>
</div>
<div class="item flex-column">
<div class="label">采集方式</div>
<div class="content">
<a-radio-group button-style="solid" v-model:value="props.polygonAirForm.gatherMode">
<a-radio-button :value="1">正射采集</a-radio-button>
<a-radio-button :value="2">倾斜采集</a-radio-button>
</a-radio-group>
</div>
</div>
<div class="item">
<div class="label">智能摆动拍摄</div>
</div>
<div class="item flex-column">
<div class="label">航线高度模式</div>
<div class="content">
<a-radio-group button-style="solid" v-model:value="props.polygonAirForm.heightMode">
<a-radio-button :value="1">海拔高度</a-radio-button>
<a-radio-button :value="2">相对起点高度</a-radio-button>
<a-radio-button :value="3">相对地形高度</a-radio-button>
</a-radio-group>
<div style="margin-top:12px;">
<a-input-number v-model:value="props.polygonAirForm.height" :min="1" :max="100000" />
</div>
</div>
</div>
<div class="item">
<div class="label">全局航线速度</div>
<div class="content">
<a-input-number style="width:160px" v-model:value="props.polygonAirForm.speed" :min="1" :max="20" />
</div>
</div>
<div class="item">
<div class="label"> 主航线角度</div>
<div class="content">
<a-input-number width="160px" v-model:value="props.polygonAirForm.angle" :min="0" :max="180" />
</div>
</div>
<div class="item">
<div class="label"> 高程优化</div>
</div>
<div class="item">
<div class="label"> 完成动作</div>
<div class="content">
<a-select
ref="select"
style="width:160px;"
v-model:value="props.polygonAirForm.complete"
>
<a-select-option :value="1">自动返航</a-select-option>
<a-select-option :value="2">返回航线起始点悬停</a-select-option>
<a-select-option :value="3">退出航线模式</a-select-option>
<a-select-option :value="4">原地降落</a-select-option>
</a-select>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref,defineEmits,defineProps} from 'vue'
import { SaveOutlined,LeftOutlined } from '@ant-design/icons-vue';
const emits = defineEmits(["setFlyPoint"])
const props = defineProps(["airInfo","polygonAirForm"])
const airInfo = ref({
area:0.0,
length:0.0,
time:'4m 11s',
picture:23
})
const setFlyPoint = ()=>{
emits("setFlyPoint");
}
</script>
<style scoped>
.container{
width:100%;
height:100%;
color:#fff;
}
.title{
padding:20px;
font-size:16px;
color:#ffffff;
display:flex;
gap:12px;
line-height:24px;
}
.area-info{
width:100%;
border-top:1px solid rgba(255,255,255,0.2);
border-bottom:1px solid rgba(255,255,255,0.2);
display:flex;
padding:20px 0px;
}
.area-info .item{
flex:1;
color:#fff;
text-align: center;
display:flex;
gap:12px;
flex-direction: column;
}
.area-options{
width:100%;
height: calc( 100vh - 260px);
padding:10px;
overflow-y:auto;
line-height:32px;
}
.area-options .item{
padding:10px;
margin:15px 0px;
background:#232323;
color:#fff;
display:flex;
}
.area-options .item .label{
flex:1;
}
.area-options .item .content{
cursor:pointer;
}
.flex-column{
flex-direction: column;
gap:10px;
}
::v-deep .ant-radio-group {
display: block;
width: 100%;
}
::v-deep .ant-select-selector{
border:none!important;
color:#fff!important;
background:#3F4150!important ;
border-radius: 3px!important;
}
::v-deep .ant-select-selection-placeholder {
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-select-arrow {
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-select-selection-search-input::placeholder{
color:rgba(255, 255, 255, 0.933)!important;
}
::v-deep .ant-tree {
background:none!important;
color:#fff!important;
}
::v-deep .ant-tree-treenode-selected{
background:#3a57e877!important;
}
::v-deep .ant-tree-treenode-selected::before{
background:none!important;
}
::v-deep .ant-tree-treenode-selected::after{
content:"";
height:28px;
width:4px;
background:#3A57E8;
position:absolute;
top:0px;
left:0px;
}
::v-deep .ant-input{
background:#3c3c3c!important ;
border:none!important;
border-top-left-radius: 3px !important;
border-bottom-left-radius: 3px !important;
color:#fff!important;
}
::v-deep .ant-input::placeholder{
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-btn-default{
background:none!important;
border:none!important;
outline:none!important;
color:#fff!important;
height:30px !important;
background:#3F4150!important ;
}
</style>

View File

@ -4,10 +4,6 @@
<div style="flex:1;">
<LeftOutlined @click="checkAriLine(null);"/> &nbsp; 选择飞行器
</div>
<div>
<PlusOutlined @click="createWorkPlan" />
</div>
</div>
<div class="filter-container">
<div>
@ -26,6 +22,8 @@
<div class="filter-buttons">
<a-input-search
v-model:value="pageQuery.key"
@keyup.enter="getList()"
placeholder="输入飞行器名称"
/>
</div>
@ -36,82 +34,43 @@
<div class="routers-list">
<div class="ari-line" v-for="(item,index) in ariLineList" :key="index" @click="checkAriLine(item)">
<div class="title">
<div style="flex:1;">
{{item.name}}
</div>
<div style="">
<EditOutlined />
&nbsp;
<MoreOutlined />
</div>
</div>
<div class="type" >
<img src="/public/iocn/uav.png" alt="">
{{item.uav}}
{{item.name}}
</div>
<div class="time">更新时间{{item.updateTime}}</div>
<div class="time">无人机型号{{item.firmwareVersion}}</div>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { PlusOutlined,FileAddOutlined,LeftOutlined,ImportOutlined,MoreOutlined,EditOutlined } from '@ant-design/icons-vue';
import {ref,defineEmits} from 'vue'
import { getUavPageList } from '@/api/sys/workplan';
const emit = defineEmits(["checkAriLine"])
const checkAriLine = (item)=> {
emit("checkAriLine",item);
}
const expandedKeys = ref<string[]>(['0-0', '0-1']);
const selectedKeys = ref<string[]>([]);
const treeData = ref(
[
{
title: '默认文件夹',
key: '1',
children: [
{
title: '文件夹1',
key: '2',
isLeaf: true,
},
{
title: '文件夹2',
key: '3',
isLeaf: true,
},
],
}
]
)
const ariLineList = ref([])
const pageQuery = ref({
page:1,
limit:10,
key:null,
})
const getList = async ()=>{
let res = await getUavPageList(pageQuery.value);
ariLineList.value = res.items;
}
getList();
const ariLineList = ref([
{
name:"航线1",
uav:"Matrice 3TD",
updateTime:"2025-06-03 17:01:47"
},
{
name:"航线1",
uav:"Matrice 3TD",
updateTime:"2025-06-03 17:01:47"
},
{
name:"航线1",
uav:"Matrice 3TD",
updateTime:"2025-06-03 17:01:47"
},
{
name:"航线1",
uav:"Matrice 3TD",
updateTime:"2025-06-03 17:01:47"
}
])
</script>
<style scoped>
.title{
@ -285,4 +244,17 @@ const treeData = ref(
height:30px !important;
background:#3F4150!important ;
}
::v-deep .ant-input{
background:#3c3c3c!important ;
border:none!important;
border-top-left-radius: 3px !important;
border-bottom-left-radius: 3px !important;
color:#fff!important;
}
::v-deep .ant-input::placeholder{
color:rgba(255, 255, 255, 0.533)!important;
}
</style>

View File

@ -0,0 +1,316 @@
<template>
<div class="container">
<div class="title">
<div style="flex:1;">
<LeftOutlined @click="checkAriLine(null);"/> &nbsp; 新建航线
</div>
</div>
<div class="item-label">航线类型</div>
<div class="draw-type">
<div v-for="(item,index) in airLineType"
:class="item.checked ? 'air-line-type-active':'air-line-type'"
@click="checkType('airLine',index)"
>
<div v-html="item.icon"></div>
<div>{{item.name}}</div>
</div>
</div>
<div class="item-label">选择飞行器</div>
<div class="draw-type">
<div class="air-line-type" v-for="(item,index) in airType"
:class="item.checked ? 'air-line-type-active':'air-line-type'"
@click="checkType('airType',index)"
>
<div>{{item.name}}</div>
</div>
</div>
<div class="item-label">选择型号</div>
<div class="draw-type">
<div class="air-line-type" v-for="(item,index) in airModel"
:class="item.checked ? 'air-line-type-active':'air-line-type'"
@click="checkType('airModel',index)"
>
<div>{{item.name}}</div>
</div>
</div>
<div class="item-label">航线名称</div>
<a-input v-model:value="airLineInfo.name" placeholder="航线名称" />
<div class="item-label"></div>
<div class="operate-button">
<a-button style="width:44%;" @click="cancle"></a-button>
<a-button style="margin-left: 10px;width:54%;" type="primary" @click="createAirLine"></a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import { PlusOutlined,FileAddOutlined,LeftOutlined,ImportOutlined,MoreOutlined,EditOutlined } from '@ant-design/icons-vue';
import {ref,defineEmits} from 'vue'
const emit = defineEmits(["checkAriLine","cancle","createAirLine"])
const checkAriLine = (item)=> {
emit("checkAriLine",item);
}
const cancle = () => {
emit("cancle");
}
const airLineInfo = ref({
airLineType:null,
airType:null,
airModel:null,
name:null,
})
const createAirLine = () => {
getCheckedData();
emit("createAirLine",airLineInfo.value);
}
const airLineType = ref([
{
name:"航点航线",
checked:false,
icon:'<svg t="1749104723424" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7226" width="40" height="40"><path d="M784 454.4c-28.8 0-54.4 12.8-70.4 35.2L326.4 352l188.8-134.4c12.8 6.4 25.6 12.8 41.6 12.8 48 0 86.4-38.4 86.4-86.4S608 64 560 64s-86.4 38.4-86.4 86.4c0 9.6 3.2 16 3.2 25.6l-179.2 128c-12.8-19.2-35.2-28.8-57.6-28.8-48 0-86.4 38.4-86.4 86.4S192 448 240 448c25.6 0 51.2-12.8 67.2-32l352 124.8L320 608c-12.8-32-41.6-57.6-80-57.6-48 0-86.4 38.4-86.4 86.4s38.4 86.4 86.4 86.4c35.2 0 64-19.2 76.8-51.2l396.8-83.2c16 22.4 41.6 38.4 70.4 38.4 48 0 86.4-38.4 86.4-86.4 0-48-38.4-86.4-86.4-86.4z m-224-342.4c19.2 0 35.2 16 35.2 35.2s-16 35.2-35.2 35.2c-19.2 0-35.2-16-35.2-35.2s16-35.2 35.2-35.2z m-320 284.8c-19.2 0-35.2-16-35.2-35.2s16-35.2 35.2-35.2 35.2 16 35.2 35.2-16 35.2-35.2 35.2z m0 275.2c-19.2 0-35.2-16-35.2-35.2s16-35.2 35.2-35.2 35.2 16 35.2 35.2S259.2 672 240 672z m544-92.8c-19.2 0-35.2-16-35.2-35.2 0-19.2 16-35.2 35.2-35.2 19.2 0 35.2 16 35.2 35.2 0 16-16 35.2-35.2 35.2z m-246.4 182.4l-150.4-51.2c-35.2-12.8-67.2 22.4-57.6 57.6l51.2 150.4c12.8 38.4 67.2 41.6 83.2 3.2l25.6-54.4 54.4-25.6c35.2-16 35.2-67.2-6.4-80z m-76.8 57.6c-9.6 3.2-19.2 12.8-22.4 22.4l-12.8 32-35.2-102.4 102.4 35.2-32 12.8z" p-id="7227" fill="#ffffff"></path></svg>',
},{
name:"面状航线",
checked:false,
icon:'<svg t="1749104746320" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7498" width="40" height="40"><path d="M918.485333 261.930667L857.813333 208.64a20.992 20.992 0 0 0-27.008 0L770.133333 261.973333a15.36 15.36 0 0 0 0 23.68 20.48 20.48 0 0 0 13.482667 4.906667 20.48 20.48 0 0 0 13.525333-4.906667l15.36-13.482666v141.994666c0 15.402667 14.250667 27.904 31.786667 27.904 17.578667 0 31.829333-12.501333 31.829333-27.904V272.128l15.36 13.482667a20.48 20.48 0 0 0 13.482667 4.906666 20.48 20.48 0 0 0 13.525333-4.906666 15.36 15.36 0 0 0 0-23.68z m-277.461333 159.146666c0 15.445333 14.250667 27.946667 31.829333 27.946667s31.829333-12.501333 31.829334-27.946667V346.325333c0-31.786667-11.349333-61.952-31.914667-84.906666a115.328 115.328 0 0 0-85.632-38.698667c-32.426667 0-63.573333 14.08-85.632 38.698667a126.848 126.848 0 0 0-31.914667 84.906666v335.530667c0 36.693333-24.704 67.754667-53.888 67.754667-29.226667 0-53.930667-31.061333-53.930666-67.754667V355.498667l0.469333-7.68 0.042667-1.493334c0-31.786667-11.306667-61.952-31.914667-84.906666a115.328 115.328 0 0 0-85.632-38.698667c-32.384 0-63.573333 14.08-85.632 38.698667a126.848 126.848 0 0 0-31.914667 84.906666v138.496c0 15.402667 14.250667 27.904 31.829334 27.904s31.829333-12.501333 31.829333-27.904V346.325333c0-36.693333 24.704-67.754667 53.888-67.754666 29.013333 0 53.589333 30.634667 53.930667 67.072l-0.512 8.405333v327.808c0 31.786667 11.306667 61.952 31.914666 84.906667a115.328 115.328 0 0 0 85.632 38.698666c32.384 0 63.573333-14.08 85.632-38.698666a126.848 126.848 0 0 0 31.914667-84.906667V346.325333c0-36.693333 24.661333-67.754667 53.888-67.754666 29.226667 0 53.888 31.018667 53.888 67.754666V421.12z" p-id="7499" fill="#ffffff"></path><path d="M858.453333 786.432l72.106667-122.624a27.861333 27.861333 0 0 0 0-28.288l-72.106667-122.624a28.928 28.928 0 0 0-24.96-14.165333h-144.128a28.928 28.928 0 0 0-24.96 14.165333l-72.106666 122.624a27.861333 27.861333 0 0 0 0 28.288l72.106666 122.624c5.12 8.746667 14.634667 14.165333 24.96 14.165333h144.128a28.928 28.928 0 0 0 24.96-14.165333z m-152.32-231.04h110.805334l55.466666 94.293333-55.466666 94.293334H706.133333l-55.424-94.293334 55.466667-94.293333z" p-id="7500" fill="#ffffff"></path><path d="M703.786667 650.410667a57.173333 57.173333 0 0 0 57.642666 56.618666 57.173333 57.173333 0 0 0 57.642667-56.618666 57.173333 57.173333 0 0 0-57.642667-56.618667 57.173333 57.173333 0 0 0-57.642666 56.618667z m57.6 14.165333q1.408 0 2.816-0.298667 1.408-0.256 2.688-0.810666 1.322667-0.512 2.517333-1.28 1.194667-0.768 2.176-1.749334 1.024-1.024 1.792-2.133333 0.810667-1.194667 1.322667-2.474667 0.554667-1.28 0.853333-2.645333 0.256-1.365333 0.256-2.773333t-0.256-2.773334q-0.298667-1.365333-0.853333-2.645333-0.512-1.28-1.28-2.432-0.853333-1.194667-1.834667-2.133333-0.981333-1.024-2.176-1.792-1.194667-0.768-2.517333-1.28-1.28-0.554667-2.688-0.853334-1.408-0.256-2.816-0.256t-2.816 0.256q-1.365333 0.298667-2.688 0.853334-1.28 0.512-2.517334 1.28-1.152 0.768-2.133333 1.749333-1.024 0.981333-1.834667 2.133333-0.768 1.194667-1.322666 2.474667-0.554667 1.28-0.853334 2.645333-0.256 1.365333-0.256 2.773334t0.298667 2.773333q0.256 1.365333 0.810667 2.645333t1.322666 2.432q0.810667 1.152 1.792 2.133334 1.024 1.024 2.176 1.792 1.194667 0.768 2.517334 1.28 1.28 0.554667 2.688 0.853333 1.408 0.256 2.816 0.256z" p-id="7501" fill="#ffffff"></path><path d="M162.389333 510.464a43.690667 43.690667 0 1 1 0 87.381333 43.690667 43.690667 0 0 1 0-87.381333z m0-37.632c-44.8 0-81.322667 36.48-81.322666 81.322667 0 44.8 36.48 81.322667 81.322666 81.322666s81.322667-36.48 81.322667-81.322666-36.48-81.322667-81.322667-81.322667z" p-id="7502" fill="#ffffff"></path><path d="M234.112 554.154667q0-29.653333-21.034667-50.688t-50.688-21.034667q-29.653333 0-50.688 21.034667-20.992 21.034667-20.992 50.688t20.992 50.645333q21.034667 21.034667 50.688 21.034667t50.688-21.034667q21.034667-20.992 21.034667-50.645333z m19.2 0q0 37.589333-26.666667 64.256-26.624 26.666667-64.256 26.666666-37.589333 0-64.256-26.666666t-26.666666-64.256q0-37.632 26.666666-64.256 26.666667-26.666667 64.256-26.666667 37.632 0 64.256 26.666667 26.666667 26.624 26.666667 64.256z m-144.213333 0q0-22.101333 15.616-37.717334t37.674666-15.616q22.101333 0 37.717334 15.616t15.616 37.717334q0 22.058667-15.616 37.674666t-37.717334 15.616q-22.058667 0-37.674666-15.616t-15.616-37.674666z m19.2 0q0 14.08 9.984 24.106666 9.984 9.984 24.106666 9.984 14.122667 0 24.106667-9.984 9.984-9.984 9.984-24.106666 0-14.122667-9.984-24.106667-9.984-9.984-24.106667-9.984-14.08 0-24.106666 9.984-9.984 9.984-9.984 24.106667z" p-id="7503" fill="#ffffff"></path></svg>',
},{
name:"带状航线",
checked:false,
icon:'<svg t="1749104685102" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6766" width="40" height="40"><path d="M712.313523 0.469118v51.176544c-208.544417 0-446.600641 148.795802-446.600641 279.040107 0 41.197118 9.552955 70.367748 31.68681 107.342801l7.25001 11.600017 17.314731 27.422098 3.497064 5.714714c31.729457 53.820666 34.075049 100.007497-2.004415 164.959061-51.38978 92.458956-145.682562 173.914956-282.281288 244.965057l-18.039731 9.211778L0.234559 856.098288c137.920786-68.960393 230.507684-146.919329 278.485694-233.237099 26.867686-48.404481 25.502978-75.314814 2.644122-114.123694l-21.579443-34.20299-6.22648-10.107367c-26.611803-44.438299-39.022115-81.882471-39.022114-133.741369 0-162.059056 255.669485-324.629878 485.878638-330.088709L712.313523 0.469118z m49.470659 185.514973l22.901504 45.717712-18.892674 9.638249-16.845613 9.083837c-68.661863 38.126525-82.053059 63.288326-72.329516 121.757528 3.539711 21.110324 17.442672 42.775061 44.566241 71.092749l8.316188 8.529424 9.595602 9.425014 31.089751 29.852984c59.876557 57.744201 90.3266 98.514847 104.058973 153.231102 20.129441 80.645704 21.408854 148.326684 2.857357 203.000292l-3.966182 10.747074-47.508892-19.020616c18.039732-45.120653 18.039732-105.850152-1.066178-182.316438-10.022073-40.088293-33.307401-72.926575-80.219233-119.411936l-42.34859-40.813294-12.282371-12.154429a601.06851 601.06851 0 0 1-5.458831-5.62942c-33.648578-35.141227-52.498605-64.525093-58.128025-98.131023-14.926492-89.644246 14.07355-130.628129 114.592812-183.809088l21.067677-10.832368zM574.904502 797.970264v50.579484h-29.852984v-50.622131h29.852984z m0-136.513432v85.29424h-29.852984v-85.29424h29.852984z m-25.161801-139.882554c14.627962 30.492691 23.072092 58.767731 24.863271 87.511891l-29.852984 1.833826c-1.450002-24.479447-8.827954-49.172129-21.92062-76.466286z m-68.278039-119.028112l7.505893 14.286786 35.269168 58.213318-25.588272 15.352964-31.302986-51.517721a446.856524 446.856524 0 0 1-12.538253-22.858857l26.65445-13.47649zM458.264629 268.719504l25.75886 15.09708a192.082629 192.082629 0 0 0-15.99267 34.927991c-4.264712 12.836783-5.117654 24.820624-2.985298 37.955937l-29.426513 4.861772a106.191329 106.191329 0 0 1 4.136771-52.370663c4.776477-14.07355 11.002957-27.55004 18.50885-40.472117z m104.186914-99.282496l15.011786 25.844155a284.712174 284.712174 0 0 0-64.013327 49.641248l-21.707384-20.513265a314.351922 314.351922 0 0 1 70.708925-54.972138z m131.011953-51.261838l7.25001 29.000041a533.515472 533.515472 0 0 0-78.811878 26.014744l-11.642663-27.507393c25.588272-10.875016 53.436841-20.044146 83.161884-27.507392z" fill="#ffffff" p-id="6767"></path></svg>',
}
])
const airType = ref([
{
name:"经纬 M30 系列",
checked:false,
},{
name:"Mavic 3 行业系列",
checked:false,
},{
name:"Matrice 3D 系列",
checked:false,
},{
name:"Matrice 4 行业系列",
checked:false,
},{
name:"Matrice 4D 系列",
checked:false,
}
])
const airModel = ref([
{
name:"Matrice 4D",
checked:false,
},{
name:"Matrice 4TD",
checked:false,
}
])
const checkType = (type,index)=>{
if(type == 'airLine'){
airLineType.value?.forEach((item,idx)=>{
if(idx == index){
airLineType.value[idx].checked = true;
}else{
airLineType.value[idx].checked = false;
}
})
}
if(type == 'airType'){
airType.value?.forEach((item,idx)=>{
if(idx == index){
airType.value[idx].checked = true;
}else{
airType.value[idx].checked = false;
}
})
}
if(type == 'airModel'){
airModel.value?.forEach((item,idx)=>{
if(idx == index){
airModel.value[idx].checked = true;
}else{
airModel.value[idx].checked = false;
}
})
}
}
const getCheckedData = () => {
airLineType.value?.forEach((item,idx)=>{
if(item.checked){
airLineInfo.value.airLineType = item.name;
}
})
console.log("airLineInfo.value.airLineType",airLineInfo.value);
}
const form = {
"id": "",
"airLineName": "测试航线01", // 线
"airLineType": "waypoint", // 线 templateType waypointmapping2dmappingStrip
"uavId": "1",
"flyToFirstPointMode": "safely", // :flyToWaylineMode safelypointToPoint
"safeTakeoffAltitude": 0, // takeOffSecurityHeight
"safeTakeoffSpeed": 0, //
"globalRouteSpeed": 0, // 线 autoFlightSpeed
"taskCompletionAction": "string", // finishAction goHomeautoLandgotoFirstWaypoint
"outOfControlOption": "string", // exitOnRCLost goContinueexecuteLostAction
"typeOfOutOfControlAction": "string", // executeRCLostAction goBacklandinghover
"globalWayPointType": "string", // globalWaypointTurnMode toPointAndStopWithDiscontinuityCurvature
"ptzControlMode": "string", //
"aircraftYawAngleMode": "string", //
"createTime": "2025-06-16T08:23:11.740Z",
"createId": 0,
"wpml": "string" // 线
}
</script>
<style scoped>
.container{
padding:20px;
color:#fff;
}
.title{
padding:15px;
color:#fff;
font-size:14px;
display:flex;
}
.item-label{
padding:10px 0px;
color:#999;
}
.draw-type{
display:grid;
grid-template-columns: repeat(3, 1fr); /* 创建 3 列,每列等宽 */
gap: 16px; /* 设置网格间隙 */
}
.air-line-type{
text-align: center;
background:#3c3c3c;
padding:20px;
border: 1px solid #3c3c3c;
border-radius: 4px;
line-height:20px;
}
.air-line-type-active{
text-align: center;
background:rgba(45, 139, 240, 0.478);
padding:20px;
border: 1px solid#2d8cf0;
border-radius: 4px;
line-height:20px;
cursor: pointer;
}
.air-line-type svg{
width:50px;
}
.air-line-type:hover{
background:rgba(45, 139, 240, 0.478);
border: 1px solid#2d8cf0;
cursor: pointer;
}
.operate-button{
padding:20px 0px;
}
::v-deep .ant-select-selector{
border:none!important;
color:#fff!important;
background:#3F4150!important ;
border-radius: 3px!important;
}
::v-deep .ant-select-selection-placeholder {
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-select-arrow {
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-select-selection-search-input::placeholder{
color:rgba(255, 255, 255, 0.933)!important;
}
::v-deep .ant-tree {
background:none!important;
color:#fff!important;
}
::v-deep .ant-tree-treenode-selected{
background:#3a57e877!important;
}
::v-deep .ant-tree-treenode-selected::before{
background:none!important;
}
::v-deep .ant-tree-treenode-selected::after{
content:"";
height:28px;
width:4px;
background:#3A57E8;
position:absolute;
top:0px;
left:0px;
}
::v-deep .ant-input{
background:#3c3c3c!important ;
border:none!important;
border-top-left-radius: 3px !important;
border-bottom-left-radius: 3px !important;
color:#fff!important;
}
::v-deep .ant-input::placeholder{
color:rgba(255, 255, 255, 0.533)!important;
}
::v-deep .ant-btn-default{
background:none!important;
border:none!important;
outline:none!important;
color:#fff!important;
height:30px !important;
background:#3F4150!important ;
}
</style>

View File

@ -13,7 +13,7 @@
:size="'middle'"
>
<a-form-item ref="name" label="计划名称" name="name">
<a-input v-model:value="submitForm.name" />
<a-input v-model:value="submitForm.name" />
</a-form-item>
<a-form-item ref="name" label="任务类型" name="name">

File diff suppressed because one or more lines are too long

View File

@ -1,13 +1,76 @@
<template>
<div ref="vChartRef" id="mars3d-container" class="mars3d-container"></div>
<div ref="vChartRef" id="mars3d-container" class="mars3d-container">
<!-- 航点航线 -->
<div v-if="props.airRoute.airLineType == '航点航线'" class="air-container">
<airPoint :airPoints="airPoints" @setFlyPoint="setFlyPoint" :airInfo="airInfo" :polygonAirForm="polygonAirForm"></airPoint>
</div>
<!-- 航面航线 -->
<div v-if="props.airRoute.airLineType == '面状航线'" class="airpolygon-container">
<airPolygon @setFlyPoint="setFlyPoint" :airInfo="airInfo" :polygonAirForm="polygonAirForm"></airPolygon>
</div>
<!-- 航点航线配置 -->
<div class="airpoint-config-container">
<airPointConfig></airPointConfig>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref,onMounted} from 'vue'
import {ref,onMounted,defineProps,watch} from 'vue'
import { message } from 'ant-design-vue';
import {buildUUID} from '@/utils/uuid'
import airPoint from './airPoint.vue'
import * as mars3d from "mars3d";
import "mars3d-space";
import * as Cesium from 'mars3d-cesium'
import * as turf from '@turf/turf'
import airPolygon from './airPolygon.vue'
import airPointConfig from './airPointConfig.vue'
const props = defineProps(["airRoute"]);
watch(
()=> props.airRoute,
(newVal,oldVal)=>{
}
)
const airPoints = ref([])
let map: mars3d.Map; //
let graphicLayer = null;
let graphicLayer:mars3d.layer.GraphicLayer;
// 线
let polygonGraphicLayer:mars3d.layer.GraphicLayer;
let polygonLineGraphicLayer:mars3d.layer.GeoJsonLayer;
let graphic = null;
const airInfo = ref({
area:0,
length:0,
time:0,
picture:'- -'
})
const polygonAirForm = ref({
startingPoint:null,
lensMode:1,
gatherMode:1,
heightMode:2,
height:120,
speed:15,
angle:0,
complete:1
})
const vChartRef = ref<HTMLElement>()
@ -22,13 +85,7 @@ const initMap = ()=>{
map = new mars3d.Map(vChartRef.value,
{
"scene": {
"center": {
"lat": 35.362625,
"lng": 118.033886,
"alt": 8306.3,
"heading": 360,
"pitch": -45
},
"center": {"lat":35.134762,"lng":118.295245,"alt":953.2,"heading":360,"pitch":-90},
"scene3DOnly": false,
"shadows": false,
"removeDblClick": true,
@ -49,8 +106,9 @@ const initMap = ()=>{
"powerPreference": "high-performance"
}
},
"globe": {
"depthTestAgainstTerrain": false,
"depthTestAgainstTerrain": true,
"baseColor": "#546a53",
"showGroundAtmosphere": true,
"enableLighting": false
@ -246,105 +304,479 @@ const initMap = ()=>{
map.on(mars3d.EventType.load, function (event) {
//
handlerBindMapMenus();
graphicLayer = new mars3d.layer.GraphicLayer({
isAutoEditing: true //
isAutoEditing: false //
})
map.addLayer(graphicLayer);
graphicLayer.on(mars3d.EventType.editStop, function (e) {
console.log("停止编辑", e.graphic.options.position)
})
graphicLayer.on(mars3d.EventType.editMovePoint, function (e) {
console.log("编辑修改了点", e.graphic.options.position)
})
bindLayerContextMenu();
drawPint();
//
// handlerDrawStartPoint();
})
}
//
const handlerBindMapMenus = ()=>{
const mapContextmenuItems = [
{
text: "添加航点",
icon: "fa fa-camera-retro", // font-class
callback: (e) => {
handlerDrawPoint(e);
}
},{
text: "添加面区域",
icon: "fa fa-camera-retro", // font-class
callback: (e) => {
if(!polygonAirForm.value.startingPoint){
message.warning("请先设置起飞点");
return null;
}
handlerDrawPolygon();
}
}
]
map.bindContextMenu(mapContextmenuItems)
}
const uavPoints = ref([]);
//
const handlerDrawPoint = (e) => {
let position = mars3d.LngLatPoint.fromCartesian(e.position);
position._alt = position._alt + 100;
let uuid = buildUUID();
const graphic = new mars3d.graphic.BillboardEntity({
id:uuid,
name: "航点",
position: [position._lng,position._lat,position._alt+180],
style: {
image: "/map/node.png",
scale: 1,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
label: {
text: "航点",
font_size: 14,
color: "#ffffff",
outline: true,
outlineColor: "#000000",
pixelOffsetY: -35
}
},
})
uavPoints.value.push(graphic);
const rectSensor = new mars3d.graphic.RectSensor({
id: "-rectSensor",
position: [position._lng,position._lat,position._alt+180],
style: {
angle1: 30, // 1
angle2: 30, // 2
length: 100, //
rayEllipsoid: true,
color: "rgba(0,255,255,0.3)",
outline: true,
topShow: true,
topSteps: 2,
flat: true,
cameraHpr: true,
heading: 0,
pitch: 180,
roll: 30, //
clippingPlanes: [{
distance: 0, // 0
normal: new Cesium.Cartesian3(0, 0, -1) //
}]
}
})
graphicLayer.addGraphic(rectSensor)
let airPointInfo = {
id:uuid,
name:"航点",
lng:position._lng,
lat:position._lat,
alt:position._alt,
aircraftHorizontalAngle:0,
cameraHorizontalAngle:0,
cameraVerticalAngle:0,
focalLength:2,
}
airPoints.value?.push(airPointInfo);
graphicLayer.addGraphic(graphic)
handlerDrawLine();
}
// 线
const handlerDrawLine = () => {
console.log("uavPoints",uavPoints.value);
let positions = [];
uavPoints.value?.forEach((item,index)=>{
positions.push(item._position);
})
console.log("positions",positions);
if(positions.length>1){
const graphic = new mars3d.graphic.PolylineEntity({
positions: positions,
style: {
width: 2,
color: "#3388ff",
},
})
graphicLayer.addGraphic(graphic)
}
}
const startPosition = ref(null);
//
const setFlyPoint = async ()=>{
const graphic = await graphicLayer.startDraw({
type: "billboardP",
style: {
image: "/map/start.png",
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
label: {
text: "参考起飞点",
font_size: 16,
color: "#ffffff",
outline: true,
outlineColor: "#000000",
pixelOffsetY: -50
}
}
})
startPosition.value = graphic.toJSON().position
polygonAirForm.value.startingPoint = graphic.toJSON().position;
}
//
const handlerDrawPolygon = async () => {
if(polygonGraphicLayer==null){
polygonGraphicLayer = new mars3d.layer.GraphicLayer({
isAutoEditing: false //
})
polygonGraphicLayer.bindContextMenu([
{
text: "删除测区",
icon: "fa fa-camera-retro", // font-class
callback: (e) => {
handlerRemovePolygonArea(e);
}
}
])
map.addLayer(polygonGraphicLayer);
}
const graphic = await polygonGraphicLayer.startDraw({
type: "polygon",
style: {
color: "#408eff",
opacity: 0.3,
outline: true,
outlineColor: "#408eff",
outlineWidth: 3.0,
clampToGround:true
}
})
let coordinates = graphic.toJSON().positions;
coordinates.push(coordinates[0])
let polygon = turf.polygon([coordinates])
let lines = generateScanLines(polygon,0.003,0);
CalculateAreaInfo(polygon,lines);
// 线
handlerDrawPolygonLine(lines);
}
// 线
const handlerDrawPolygonLine = (lines) => {
const graphic = new mars3d.graphic.BillboardEntity({
id:"节点",
name: "航点",
position:lines.geometry.coordinates[0],
style: {
image: "/map/node.png",
scale: 1,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
label: {
text: "",
font_size: 14,
color: "#ffffff",
outline: true,
outlineColor: "#000000",
pixelOffsetY: -35
}
},
})
polygonGraphicLayer.addGraphic(graphic);
//
lines.geometry.coordinates.unshift([polygonAirForm.value.startingPoint[0],polygonAirForm.value.startingPoint[1],polygonAirForm.value.startingPoint[2]+20]);
lines.geometry.coordinates.unshift(polygonAirForm.value.startingPoint);
if(polygonLineGraphicLayer){
polygonLineGraphicLayer.clear();
polygonLineGraphicLayer.loadGeoJSON(lines);
}else{
polygonLineGraphicLayer = new mars3d.layer.GeoJsonLayer({
name: "面航线",
data:lines,
symbol: {
type: "polylineP",
styleOptions: {
width: 2.0,
color:"#0aed8b",
// clampToGround: true,
}
},
flyTo: true
})
map.addLayer(polygonLineGraphicLayer)
}
}
// 线
const handlerRemovePolygonArea = () => {
if(polygonGraphicLayer){
polygonGraphicLayer.clear();
}
if(polygonLineGraphicLayer){
polygonLineGraphicLayer.clear();
}
airInfo.value = {
area:0,
length:0,
time:0,
picture:'- -'
};
}
//
// 线
function generateScanLines(polygon, spacing, angle = 0) {
const bindLayerContextMenu =()=> {
graphicLayer.bindContextMenu([
{
text: "删除对象",
icon: "fa fa-trash-o",
show: (event) => {
const graphic = event.graphic
if (!graphic || graphic.isDestroy || graphic.isPrivate || graphic.graphicIds) {
return false
} else {
return true
}
},
callback: (e) => {
const graphic = e.graphic
if (!graphic) {
return
}
const parent = graphic.parent //
graphicLayer.removeGraphic(graphic)
if (parent) {
graphicLayer.removeGraphic(parent)
if (!turf.booleanValid(polygon)) throw new Error('无效的多边形');
spacing = Math.abs(spacing);
//
const bbox = turf.bbox(polygon);
const center = turf.center(polygon).geometry.coordinates;
// 线()
const diagLength = turf.distance(turf.point([bbox[0], bbox[1]]),turf.point([bbox[2], bbox[3]]));
const lineCount = Math.ceil(diagLength / spacing) + 2;
// 线
const lines = [];
for (let i = -1; i <= lineCount; i++) {
const y = bbox[1] + (i * spacing*0.1) - spacing*0.1;
lines.push(
turf.lineString([[bbox[0] - spacing, y],[bbox[2] + spacing, y]])
);
}
// 线
const rotatedLines = lines.map(line =>
turf.transformRotate(line, angle, { pivot: center })
);
// 线
const coverageLines = [];
rotatedLines.forEach(line => {
try {
const intersection = turf.lineIntersect(line, polygon);
if (intersection.features.length >= 2) {
// 线
const sortedPoints = intersection.features.map(f => f.geometry.coordinates).sort((a, b) => turf.distance(line.geometry.coordinates[0], turf.point(a)) -
turf.distance(line.geometry.coordinates[0], turf.point(b)));
// 线
coverageLines.push(turf.lineString([sortedPoints[0],sortedPoints[sortedPoints.length - 1]]));
}
} catch (e) {
console.warn('处理线段时出错:', e);
}
},
{
text: "停止编辑对象",
icon: "fa fa-edit",
show: function (e) {
const graphic = e.graphic
if (!graphic || !graphic.hasEdit) {
return false
}
return graphic.isEditing
},
callback: (e) => {
const graphic = e.graphic
if (!graphic) {
return false
}
if (graphic) {
graphic.stopEditing()
}
}
},
])
});
let connectedLine = connectLinesManual(turf.featureCollection(coverageLines));
console.log("connectedLine",connectedLine)
return connectedLine;
}
const drawPint = async ()=>{
const graphic = await graphicLayer.startDraw({
type: "polylineP",
style: {
pixelSize: 12,
color: "#3388ff",
label: {
// label
text: "可以同时支持文字",
font_size: 20,
color: "#ffffff",
outline: true,
outlineColor: "#000000",
pixelOffsetY: -20
}
}
// 线
const connectLinesManual= (lines) => {
// FeatureCollection
if (lines.type !== 'FeatureCollection') {
lines = turf.featureCollection([lines]);
}
//
let allCoords = [];
lines.features.forEach((line,index) => {
console.log("line.geometry.coordinates",line.geometry.coordinates);
line.geometry.coordinates?.forEach((item,idx)=>{
line.geometry.coordinates[idx].push(180);
})
console.log("标绘完成", graphic.toJSON())
if (line.geometry.type === 'LineString' && index % 2 == 0) {
allCoords = allCoords.concat(line.geometry.coordinates.reverse());
}else{
allCoords = allCoords.concat(line.geometry.coordinates);
}
});
console.log("allCoords",allCoords);
//
const cleanedCoords = [];
if (allCoords.length > 0) {
cleanedCoords.push(allCoords[0]);
for (let i = 1; i < allCoords.length; i++) {
const prev = cleanedCoords[cleanedCoords.length - 1];
if (prev[0] !== allCoords[i][0] || prev[1] !== allCoords[i][1]) {
cleanedCoords.push(allCoords[i]);
}
}
}
return turf.lineString(cleanedCoords);
}
//
const handlerDrawStartPoint = async ()=>{
graphic = await graphicLayer.startDraw({
type: "billboard",
style: {
image: "/map/start.png",
scale:0.7,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
label: {
text: "无人机起始位置",
font_size: 16,
color: "#ffffff",
outline: true,
outlineColor: "#000000",
pixelOffsetY: -60
}
}
})
// 线
}
//
const CalculateAreaInfo = (polygon,lines)=>{
//
airInfo.value.area = turf.area(polygon).toFixed(2);
// 线
airInfo.value.length = turf.length(lines).toFixed(2);
//
airInfo.value.time = (airInfo.value.length * 1000 / polygonAirForm.value.speed / 60).toFixed(2);
//
}
</script>
<style scoped>
.mars3d-container{
width:100%;
height:100%;
background:red;
position:relative;
}
.air-container{
width:400px;
background:#0d0e15c1 ;
overflow-y:hidden;
position:absolute;
height: calc( 100vh - 80px);
top:0px;
left:0px;
z-index:999;
}
.airpolygon-container{
width:400px;
background:#0d0e15c1 ;
overflow-y:hidden;
position:absolute;
height: calc( 100vh - 80px);
top:0px;
left:0px;
z-index:999;
}
.airpoint-config-container{
width:400px;
background:#0d0e15c1 ;
overflow-y:hidden;
position:absolute;
height: calc( 100vh - 80px);
top:0px;
right:0px;
z-index:999;
}
</style>

View File

@ -1,5 +1,5 @@
<template>
<div>
<div class="container">
<div class="title">
<div style="flex:1;">
工作计划
@ -22,7 +22,8 @@
</div>
<div class="filter-buttons">
<a-input-search
<a-input-search v-model:value="pageQuery.key"
@keyup.enter="getTaskList"
placeholder="输入计划名称"
/>
</div>
@ -34,11 +35,11 @@
<div class="routers-list">
<div class="ari-line" v-for="(item,index) in ariLineList" :key="index" @click="checkAriLine(item)">
<div class="title">
<div class="state">
进行中
<div :class="item.status == 0 ? 'state-no' : 'state' ">
{{item.status == 0 ? '未开始' : '进行中'}}
</div>
<div style="flex:1;">
{{item.name}}
{{item.taskName}}
</div>
<div>
<a-dropdown>
@ -48,7 +49,7 @@
<a-menu-item>
<a href="javascript:;">编辑</a>
</a-menu-item>
<a-menu-item>
<a-menu-item @click="deletePlan(item.id)">
<a href="javascript:;">删除</a>
</a-menu-item>
</a-menu>
@ -61,82 +62,107 @@
{{item.description}}
</div>
<div class="time">更新时间{{item.createTime}}</div>
<div class="username"><UserOutlined />&nbsp;{{item.createUserName}}</div>
<div class="username"><UserOutlined />&nbsp;{{item.createId}}</div>
</div>
</div>
</div>
<div class="pagenation">
<a-pagination v-model:current="pageQuery.page" :total="lineListTotal" @change="onPageChange" show-less-items />
</div>
</div>
</template>
<script lang="ts" setup>
import { UserOutlined,PlusOutlined,FileAddOutlined,LeftOutlined,ImportOutlined,MoreOutlined,EditOutlined } from '@ant-design/icons-vue';
import {ref,defineEmits} from 'vue'
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
import {ref,defineEmits,createVNode} from 'vue'
import { Modal, message } from 'ant-design-vue';
import { getTaskPageList, deleteTask } from '@/api/sys/workplan';
const emit = defineEmits(["checkAriLine","createWorkPlan"])
const createWorkPlan = () => {
emit("createWorkPlan");
}
const ariLineList = ref([]);
const lineListTotal = ref(0);
const pageQuery = ref({
page:0,
limit:10,
key:null
})
const getTaskList = async ()=>{
let res = await getTaskPageList(pageQuery.value);
ariLineList.value = res.items;
lineListTotal.value = res.total;
}
getTaskList();
const deletePlan = async (id) => {
Modal.confirm({
title: '是否确认删除?',
icon: createVNode(ExclamationCircleOutlined),
onCancel() {},
async onOk() {
let res = await deleteTask({id:id});
if(res){
message.success("操作成功!");
getTaskList();
}else{
message.error("操作失败!");
}
},
});
}
const checkAriLine = (item)=> {
emit("checkAriLine",item);
}
const expandedKeys = ref<string[]>(['0-0', '0-1']);
const selectedKeys = ref<string[]>([]);
const treeData = ref(
[
{
title: '默认文件夹',
key: '1',
children: [
{
title: '文件夹1',
key: '2',
isLeaf: true,
},
{
title: '文件夹2',
key: '3',
isLeaf: true,
},
],
}
]
)
const selectedKeys = ref<string[]>([]);
const treeData = ref(
[
{
title: '默认文件夹',
key: '1',
children: [
{
title: '文件夹1',
key: '2',
isLeaf: true,
},
{
title: '文件夹2',
key: '3',
isLeaf: true,
},
],
}
]
)
const onPageChange = (e)=>{
pageQuery.value.page = e;
getTaskList();
}
const ariLineList = ref([
{
name:"工作计划1",
state:"进行中",
description:" 预置航线、照片、模型和地图标注等辅助了解司空2 功能。",
createUserName:"17861857725",
createTime:"2025-06-03 17:01:47"
},
{
name:"工作计划1",
state:"进行中",
description:" 预置航线、照片、模型和地图标注等辅助了解司空2 功能。",
createUserName:"17861857725",
createTime:"2025-06-03 17:01:47"
},
{
name:"工作计划1",
state:"进行中",
description:" 预置航线、照片、模型和地图标注等辅助了解司空2 功能。",
createUserName:"17861857725",
createTime:"2025-06-03 17:01:47"
},
{
name:"工作计划1",
state:"进行中",
description:" 预置航线、照片、模型和地图标注等辅助了解司空2 功能。",
createUserName:"17861857725",
createTime:"2025-06-03 17:01:47"
}
])
</script>
<style scoped>
.container{
width:100%;
height:100%;
}
.title{
padding:30px 15px;
color:#fff;
@ -169,6 +195,8 @@ const treeData = ref(
cursor:pointer;
}
.routers-container{
height: calc( 100% - 160px);
overflow-y:auto;
display:flex;
gap:10px;
font-size:14px;
@ -232,6 +260,17 @@ const treeData = ref(
top:2px;
}
.ari-line .state-no{
border:1px solid rgb(246, 18, 18);
color:rgb(246, 18, 18);;
font-size:12px;
height:24px;
line-height:20px;
padding:0px 4px;
position:relative;
top:2px;
}
.ari-line .type{
padding:12px 0px;
@ -253,6 +292,10 @@ const treeData = ref(
color:#ccc;
}
.pagenation{
padding:10px 15px;
}
::v-deep .ant-select-selector{
border:none!important;
color:#fff!important;
@ -316,6 +359,26 @@ const treeData = ref(
background:#3F4150!important ;
}
::v-deep .ant-pagination-item-link{
color:#fff!important;
}
::v-deep .ant-pagination .ant-pagination-item a {
color:#fff!important;
}
::v-deep .ant-pagination .ant-pagination-item-active a{
color:#408eff;
}
::v-deep .ant-input{
background:#3c3c3c!important ;
border:none!important;
border-top-left-radius: 3px !important;
border-bottom-left-radius: 3px !important;
color:#fff!important;
}
::v-deep .ant-input::placeholder{
color:rgba(255, 255, 255, 0.533)!important;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,22 +1,22 @@
<template>
<div>
<div style="width:100%;height: calc( 100vh - 80px);" >
<Map></Map>
<Map :airRoute="airRoute"></Map>
</div>
<!-- 工作计划列表 -->
<div v-if="planListShow" style="width:360px;background:#0d0e15c1 ;position:absolute;top:0px;left:0px;z-index:1;height: calc( 100vh - 104px);overflow-y:hidden;">
<planList @createWorkPlan="createWorkPlan" ></planList>
<div v-show="planListShow" style="width:360px;background:#0d0e15c1 ;position:absolute;top:0px;left:0px;z-index:1;height: calc( 100vh - 104px);overflow-y:hidden;">
<planList @createWorkPlan="toCreateWorkPlan" ></planList>
</div>
<!-- 创建计划弹窗 -->
<div v-if="workPlanFormShow" style="width:380px;background:#0d0e15ce ;position:absolute;top:30px;left:30px;z-index:1;height: calc( 100vh - 164px);overflow-y:hidden;">
<WorkPlanForm @cancleCraete="cancleCraete" @selectAircraft="selectAircraft" @selectAriLine="selectAriLine"></WorkPlanForm>
<createWorkPlan @cancleCraete="cancleCraete" @selectAircraft="selectAircraft" @selectAriLine="selectAriLine"></createWorkPlan>
</div>
<!-- 航线库 -->
<div v-if="ariLineShow" style="width:566px;background:#0d0e15c1 ;position:absolute;top:30px;left:440px;z-index:1;height: calc( 100vh - 164px);overflow-y:hidden;">
<airLineList @checkAriLine="checkAriLine" ></airLineList>
<airLineList @checkAriLine="checkAriLine" @createAirLine="handlerCreateAirLine"></airLineList>
</div>
<!-- 飞行器 -->
@ -24,6 +24,13 @@
<aircraft @checkAriLine="checkAriLine" ></aircraft>
</div>
<!-- 新建航线 -->
<div v-if="createAirLineShow" style="width:566px;background:#0d0e15c1 ;position:absolute;top:30px;left:440px;z-index:1;height: calc( 100vh - 164px);overflow-y:hidden;">
<createAirLine @createAirLine="handlerCreateAirRoute" @cancle="cancleCreateAirLine"></createAirLine>
</div>
</div>
</template>
<script lang="ts" setup>
@ -31,13 +38,19 @@ import {ref} from 'vue';
import planList from './components/planList.vue';
import airLineList from './components/airLineList.vue';
import aircraft from './components/aircraft.vue';
import WorkPlanForm from './components/workPlanForm.vue';
import createWorkPlan from './components/createWorkPlan.vue';
import createAirLine from './components/createAirLine.vue'
import Map from './components/map.vue'
import AirPolygon from './components/airPolygon.vue';
const airRoute = ref({})
const planListShow = ref(true);
const workPlanFormShow = ref(false);
const ariLineShow = ref(false);
const aircraftShow = ref(false);
const createAirLineShow = ref(false);
const selectAriLine = ()=> {
ariLineShow.value = true;
aircraftShow.value = false;
@ -57,12 +70,30 @@ const cancleCraete = ()=>{
const checkAriLine = (item)=>{
if(item){
ariLineShow.value = false;
}
}
const createWorkPlan = ()=> {
const toCreateWorkPlan = ()=> {
planListShow.value = false;
workPlanFormShow.value = true;
}
// 线
const handlerCreateAirLine = ()=>{
createAirLineShow.value = true;
}
const cancleCreateAirLine = ()=>{
createAirLineShow.value = false;
}
const handlerCreateAirRoute = (info)=>{
workPlanFormShow.value = false;
ariLineShow.value = false;
aircraftShow.value = false;
planListShow.value = false;
createAirLineShow.value = false;
airRoute.value = info
}
</script>