From ea6e49ccb4d3bdb510ccf87844490823a96ebf08 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Fri, 12 Sep 2025 20:11:51 +0800
Subject: [PATCH] feat:空域录入完善,增删改查
---
src/api/airspace/airspace.js | 49 +++++-
src/views/airspace/airspaceEntering.vue | 110 ++++++++-------
src/utils/cesium/publicCesium.js | 2
src/utils/drawPolygon/drawPolygon.js | 69 ++++++++-
src/views/airspace/components/AirspaceMap.vue | 145 ++++++++++++++++++--
5 files changed, 288 insertions(+), 87 deletions(-)
diff --git a/src/api/airspace/airspace.js b/src/api/airspace/airspace.js
index d01085a..8d9c3f2 100644
--- a/src/api/airspace/airspace.js
+++ b/src/api/airspace/airspace.js
@@ -1,4 +1,4 @@
-import request from '@/axios';
+import request from '@/axios'
// 算法列表
export const getAirSpaceTypeList = (params) => {
return request({
@@ -6,27 +6,60 @@
method: 'get',
params: params,
})
-};
+}
export const airSpaceTypeEdit = data => {
return request({
url: '/drone-device-core/airspace/update',
method: 'put',
data: data,
- });
-};
+ })
+}
export const airSpaceTypeAdd = data => {
return request({
url: '/drone-device-core/airspace/add',
method: 'post',
data: data,
- });
-};
+ })
+}
export const airSpaceTypeDelete = data => {
return request({
url: '/drone-device-core/airspace/delete/' + data,
method: 'delete',
- });
-};
\ No newline at end of file
+ })
+}
+
+// ----------空域录入相关接口调用----------
+export const airspaceEnteringAdd = data => {
+ return request({
+ url: '/drone-device-core/airrange/add',
+ method: 'post',
+ data
+ })
+}
+
+export const airspaceEnteringDel = id => {
+ return request({
+ url: `/drone-device-core/airrange/delete/${id}`,
+ method: 'delete',
+ })
+}
+
+export const airspaceEnteringUpdate = data => {
+ return request({
+ url: '/drone-device-core/airrange/update',
+ method: 'put',
+ data
+ })
+}
+
+export const airspaceEnteringPage = params => {
+ return request({
+ url: '/drone-device-core/airrange/page',
+ method: 'get',
+ params
+ })
+}
+// ----------空域录入相关接口调用----------
\ No newline at end of file
diff --git a/src/utils/cesium/publicCesium.js b/src/utils/cesium/publicCesium.js
index 2bdcc29..ef65590 100644
--- a/src/utils/cesium/publicCesium.js
+++ b/src/utils/cesium/publicCesium.js
@@ -149,6 +149,8 @@
requestWaterMask: true, // 启用水体遮罩效果
}).then(terrainProvider => {
this.viewer.terrainProvider = terrainProvider
+
+ options?.terrainInit?.()
})
} catch (error) {
console.error('地形加载失败:', error)
diff --git a/src/utils/drawPolygon/drawPolygon.js b/src/utils/drawPolygon/drawPolygon.js
index 2f98842..8bf2482 100644
--- a/src/utils/drawPolygon/drawPolygon.js
+++ b/src/utils/drawPolygon/drawPolygon.js
@@ -1,4 +1,5 @@
import * as Cesium from 'cesium'
+import { flyVisual } from '@/utils/cesium/mapUtil'
import _, { cloneDeep, throttle } from 'lodash'
import * as turf from '@turf/turf'
import '@/utils/drawPolygon/drawPolygon.css'
@@ -33,6 +34,7 @@
constructor() {
// Cesium 视图对象
this.viewer = null
+ this.polygonHeight = 120
// 当前绘制的多边形
this.curPolygon = null
// 绘制模式标识
@@ -47,6 +49,7 @@
this.polygonEntity = null
// 存储端点的 DataSource
this.editPolygonDataSource = null
+ this.editPolyhedronDataSource = null
this.editPolygonPointDataSource = null
// 鼠标事件处理器
this.handler = null
@@ -97,7 +100,9 @@
DEFAULT_POLYGON: Cesium.Color.fromBytes(45, 140, 240, 99), // 默认面颜色
DEFAULT_LINE: Cesium.Color.fromBytes(45, 140, 240, 255), // 默认边界线颜色
ERROR_POLYGON: Cesium.Color.fromCssColorString('rgba(255, 0, 0, .3)'), // 错误(自交)面颜色
- ERROR_LINE: Cesium.Color.fromCssColorString('rgba(255, 0, 0, 1)') // 错误(自交)线颜色
+ ERROR_LINE: Cesium.Color.fromCssColorString('rgba(255, 0, 0, 1)'), // 错误(自交)线颜色
+ // 新增多变体(立体)颜色:橙色,透明度 0.3
+ DEFAULT_POLYHEDRON: Cesium.Color.fromCssColorString('rgba(255, 165, 0, 0.3)')
};
// ============ 发布订阅机制 ============
@@ -111,7 +116,13 @@
notify (key, data) {
this.listeners
.filter(subscriber => subscriber.key === key)
- .forEach(subscriber => subscriber.listener(data))
+ .forEach(subscriber => {
+ subscriber.listener(data)
+ })
+ }
+
+ setPolygonHeight (height) {
+ this.polygonHeight = height
}
// ============ 绘制相关 ============
@@ -128,6 +139,11 @@
this.viewer?.dataSources.add(this.editPolygonDataSource)
}
+ if (!this.editPolyhedronDataSource) {
+ this.editPolyhedronDataSource = new Cesium.CustomDataSource('editPolyhedronDataSource')
+ this.viewer?.dataSources.add(this.editPolyhedronDataSource)
+ }
+
if (!this.editPolygonPointDataSource) {
this.editPolygonPointDataSource = new Cesium.CustomDataSource('editPolygonPointDataSource')
this.viewer?.dataSources.add(this.editPolygonPointDataSource)
@@ -135,6 +151,7 @@
// 清空之前的点
this.editPolygonDataSource?.entities.removeAll()
+ this.editPolyhedronDataSource?.entities.removeAll()
this.editPolygonPointDataSource?.entities.removeAll()
}
@@ -159,6 +176,29 @@
false
)
},
+ })
+
+ this.editPolyhedronDataSource.entities.add({
+ polygon: {
+ hierarchy: new Cesium.CallbackProperty(() => this.curPolygon, false),
+ material: DrawPolygon.COLORS.DEFAULT_POLYHEDRON,
+ outline: false,
+ outlineWidth: 2,
+ height: 0,
+ extrudedHeight: new Cesium.CallbackProperty(() => {
+ let heights = this.curPolygon.positions.map(item => {
+ // 获取点的位置
+ const cartographic = Cesium.Cartographic.fromCartesian(item)
+ // 获取地形高度
+ const terrainHeight = this.viewer.scene.globe.getHeight(cartographic) || 0
+
+
+ return terrainHeight
+ })
+
+ return Math.max(...heights) + Number(this.polygonHeight)
+ }, false),
+ }
})
}
@@ -392,6 +432,11 @@
this.editPolygonDataSource = null
}
+ if (this.editPolyhedronDataSource) {
+ this.editPolyhedronDataSource.entities.removeAll()
+ this.editPolyhedronDataSource = null
+ }
+
if (this.editPolygonPointDataSource) {
this.editPolygonPointDataSource.entities.removeAll()
this.editPolygonPointDataSource = null
@@ -538,20 +583,25 @@
this.initHandler(viewer)
this.startDrawing()
- positions.forEach(item => {
+ let disposePosition = positions.map(item => Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item.height)))
+
+ disposePosition.forEach(item => {
this.addPosition(
- Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item.height)),
+ item,
false
)
})
+ this.notify('getPolygonPositions', disposePosition)
+
// 视角飞入区域
- const newBox = boxTransformScale(positions.map(item => [item.lng, item.lat]), 5)
- viewer.camera.flyTo({
- destination: Cesium.Rectangle.fromDegrees(...newBox),
- offset: new Cesium.HeadingPitchRange(0, Cesium.Math.toRadians(-90), 0),
- duration: 0.5,
+ flyVisual({
+ positionsData: positions.map(item => [item.lng, item.lat]),
+ viewer,
+ pitch: -60,
+ multiple: 6
})
+
this.drawingMode = false
this.editingMode = true
@@ -603,6 +653,7 @@
* 销毁实例,释放资源
*/
destroy () {
+ this.prompt.destroy()
if (!this.viewer) return
this.removeMenuPopup()
diff --git a/src/views/airspace/airspaceEntering.vue b/src/views/airspace/airspaceEntering.vue
index 927c2f9..117677a 100644
--- a/src/views/airspace/airspaceEntering.vue
+++ b/src/views/airspace/airspaceEntering.vue
@@ -1,9 +1,9 @@
<template>
<basic-container>
<avue-crud :option="option" :table-loading="loading" :data="data" v-model:page="page" :permission="permissionList"
- v-model="form" ref="crud" @row-update="rowUpdate" @row-save="rowSave" @row-del="rowDel" :before-open="beforeOpen"
- @search-change="searchChange" @search-reset="searchReset" @selection-change="selectionChange"
- @current-change="currentChange" @size-change="sizeChange" @refresh-change="refreshChange" @on-load="onLoad">
+ v-model="form" ref="crud" @row-update="rowUpdate" @row-save="rowSave" :before-open="beforeOpen"
+ @search-change="searchChange" @search-reset="searchReset" @current-change="currentChange"
+ @size-change="sizeChange" @refresh-change="refreshChange" @on-load="onLoad">
<template #menu-left>
<el-button type="primary" icon="el-icon-plus" @click="addAirspace">新增空域</el-button>
</template>
@@ -14,10 +14,16 @@
<template #category="{ row }">
<el-tag>{{ row.categoryName }}</el-tag>
</template>
+
+ <template #menu="{ row }">
+ <el-button type="text" icon="el-icon-edit" @click="handleEdit(row)">编辑</el-button>
+ <el-button type="text" icon="el-icon-delete" @click="rowDel(row)">删除</el-button>
+ </template>
</avue-crud>
</basic-container>
- <AirspaceMap v-model:show="airspaceMapShow" @submitClick="searchReset" />
+ <AirspaceMap :title="dialogTitle" :type="dialogType" :curEditRow="curEditRow" v-model:show="airspaceMapShow"
+ @submitClick="searchReset" />
</template>
<script setup>
@@ -28,6 +34,12 @@
editSpotTypeApi,
deleteSpotTypeApi,
} from '@/api/patchManagement/index'
+
+import {
+ airspaceEnteringPage,
+ airspaceEnteringDel
+} from '@/api/airspace/airspace'
+
import { ref, computed, watch } from 'vue'
import { enable, disable } from '@/api/resource/oss'
import { useStore } from 'vuex'
@@ -46,22 +58,21 @@
pageSize: 10,
currentPage: 1,
total: 0,
- lotValue: '',
- userName: '',
})
-const selectionList = ref([])
const option = ref({
addBtn: false,
+ editBtn: false,
+ delBtn: false,
tip: false,
searchShow: true,
- searchMenuSpan: 8,
+ searchMenuSpan: 16,
searchMenuPosition: 'right',
border: true,
index: true,
indexLabel: '序号',
indexWidth: 60,
- selection: true,
+ selection: false,
grid: false,
menuWidth: 180,
labelWidth: 100,
@@ -73,10 +84,11 @@
gridBtn: false,
searchShowBtn: false,
columnBtn: false,
+ align: 'center',
column: [
{
label: '名称',
- prop: 'patches_type',
+ prop: 'name',
search: true,
searchSpan: 4,
searchLabelWidth: 54,
@@ -84,12 +96,15 @@
},
{
label: '类型',
- prop: 'patches_type',
+ prop: 'flight_type',
type: 'select',
- dicUrl: `/blade-system/dict/dictionary?code=flow`,
+ dicUrl: `/drone-device-core/airspace/page?current=1&size=10000&typeName=`,
props: {
- label: 'dictValue',
- value: 'dictKey',
+ label: 'type_name',
+ value: 'id',
+ },
+ dicFormatter (res) {
+ return res.data.records
},
search: true,
searchSpan: 4,
@@ -98,34 +113,31 @@
},
{
label: '高度',
- prop: 'patches_type',
+ prop: 'height',
rules: [{ required: true, message: '请输入高度', trigger: 'blur' }],
},
{
- label: '面积',
- prop: 'patches_type',
+ label: '面积(㎡)',
+ prop: 'area',
rules: [{ required: true, message: '请输入面积', trigger: 'blur' }],
},
{
label: '管控时段',
- prop: 'daterange',
+ prop: 'time_period',
type: 'daterange',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
startPlaceholder: '开始日期',
endPlaceholder: '结束日期',
- search: true,
- searchSpan: 8,
- searchRange: true,
},
{
label: '创建人',
- prop: 'patches_type',
+ prop: 'nick_name',
rules: [{ required: true, message: '请输入创建人', trigger: 'blur' }],
},
{
label: '创建时间',
- prop: 'patches_type',
+ prop: 'create_time',
rules: [{ required: true, message: '请输入创建时间', trigger: 'blur' }],
},
],
@@ -170,15 +182,23 @@
editBtn: validData(permission.value.oss_edit),
}))
-const ids = computed(() => {
- return selectionList.value.map(ele => ele.id).join(',')
-})
-
const airspaceMapShow = ref(false)
+const curEditRow = ref({})
+const dialogTitle = ref('新增空域')
+const dialogType = ref('add')
// ---------------- methods ----------------
const addAirspace = () => {
form.value = {}
+ dialogTitle.value = '新增空域'
+ dialogType.value = 'add'
+ airspaceMapShow.value = true
+}
+
+const handleEdit = (row) => {
+ curEditRow.value = row
+ dialogTitle.value = '编辑空域'
+ dialogType.value = 'edit'
airspaceMapShow.value = true
}
@@ -219,16 +239,14 @@
cancelButtonText: '取消',
type: 'warning',
})
- .then(() => deleteSpotTypeApi([row.id]))
+ .then(() => airspaceEnteringDel(row.id))
.then(() => {
- onLoad(page.value)
ElMessage.success('操作成功!')
+ onLoad(page.value)
})
}
const searchReset = () => {
- page.value.userName = ''
- page.value.lotValue = ''
page.value.currentPage = 1
page.value.pageSize = 10
onLoad(page.value)
@@ -236,19 +254,8 @@
const searchChange = (params, done) => {
page.value.currentPage = 1
- page.value.lotValue = params.patches_type
- page.value.userName = params.user_name
- onLoad(page.value)
+ onLoad(page.value, params)
done()
-}
-
-const selectionChange = list => {
- // selectionList.value = list
-}
-
-const selectionClear = () => {
- selectionList.value = []
- // crudRef.value.toggleSelection()
}
const handleEnable = row => {
@@ -300,19 +307,20 @@
}
const onLoad = (pageParam, params = {}) => {
- const searchparams = {
+ loading.value = true
+ airspaceEnteringPage({
current: pageParam.currentPage,
size: pageParam.pageSize,
- lotValue: pageParam.lotValue,
- userName: pageParam.userName,
- }
- loading.value = true
- listOfSpotTypesApi(searchparams).then(res => {
+ name: params.name || '',
+ flightType: params.flight_type || '',
+ }).then(res => {
const resData = res.data.data
page.value.total = resData.total
- data.value = resData.records
+ data.value = resData.records.map(item => ({
+ ...item,
+ time_period: item.time_period.split('~'),
+ }))
loading.value = false
- selectionClear()
})
}
diff --git a/src/views/airspace/components/AirspaceMap.vue b/src/views/airspace/components/AirspaceMap.vue
index 89707c6..828c467 100644
--- a/src/views/airspace/components/AirspaceMap.vue
+++ b/src/views/airspace/components/AirspaceMap.vue
@@ -6,7 +6,8 @@
<avue-form ref="formEle" :option="option" v-model="form"></avue-form>
</div>
- <div class="map-container">
+ <div class="map-container" v-loading="mapLoading" element-loading-text="地形初始化中,请稍后..."
+ element-loading-background="rgba(0, 0, 0, 0.7)">
<div class="tool-tip warning" v-show="isShowWaringTip">
<span class="icon">
<el-icon>
@@ -31,15 +32,33 @@
import * as Cesium from 'cesium'
import * as turf from '@turf/turf'
+
+import { getPointPositionsHeight } from '@/utils/cesium/mapUtil'
import { DrawPolygon } from '@/utils/drawPolygon/drawPolygon'
import { PublicCesium } from '@/utils/cesium/publicCesium'
import { ElMessageBox, ElMessage } from 'element-plus'
+
+import {
+ airspaceEnteringAdd,
+ airspaceEnteringUpdate
+} from '@/api/airspace/airspace'
+
const props = defineProps({
title: {
type: String,
default: '新增空域',
},
+
+ type: {
+ type: String,
+ default: 'add',
+ },
+
+ curEditRow: {
+ type: Object,
+ default: () => { },
+ }
})
const emit = defineEmits(['submitClick'])
@@ -47,7 +66,9 @@
const dialogShow = defineModel('show')
const formEle = ref(null)
-const form = ref({})
+const form = ref({
+ height: 120,
+})
const option = ref({
submitBtn: false,
emptyBtn: false,
@@ -70,23 +91,38 @@
label: '高度',
prop: 'height',
type: 'input',
+ value: 120,
+ min: 5,
+ max: 500,
+ type: 'number',
+ change ({ column, value }) {
+ drawPolygonExample?.setPolygonHeight?.(value)
+ },
rules: [
{
required: true,
message: '请输入高度',
trigger: 'blur'
+ },
+ {
+ pattern: /^(?:[5-9]|[1-9]\d|[1-4]\d{2}|500)$/, // 5-500
+ message: '高度需在5-500之间',
+ trigger: ['blur', 'change']
}
]
},
{
label: '类型',
- prop: 'type',
+ prop: 'flight_type',
type: 'select',
- dicUrl: `/blade-system/dict/dictionary?code=flow`,
+ dicUrl: `/drone-device-core/airspace/page?current=1&size=10000&typeName=`,
props: {
- label: 'dictValue',
- value: 'dictKey',
+ label: 'type_name',
+ value: 'id',
+ },
+ dicFormatter (res) {
+ return res.data.records
},
rules: [
{
@@ -98,7 +134,7 @@
},
{
label: '管控时段',
- prop: 'daterange',
+ prop: 'time_period',
type: 'daterange',
format: 'YYYY-MM-DD',
valueFormat: 'YYYY-MM-DD',
@@ -130,11 +166,13 @@
let publicCesiumInstance = null
let viewer = null
// 地图
+const mapLoading = ref(true)
+
const isShowWaringTip = ref(false)
const curDrawPolygonData = ref([])
-const initMap = () => {
+const initMap = async () => {
if (!document.getElementById('AirspaceMap')) {
return
}
@@ -145,24 +183,36 @@
layerMode: 4,
contour: true,
flyToContour: true,
+ terrainInit () {
+ setTimeout(async () => {
+ initDrawPolygon()
+ mapLoading.value = false
+ }, 1500)
+ }
})
+
viewer = publicCesiumInstance.getViewer()
+
drawPolygonExample = new DrawPolygon()
+
drawPolygonExample.initHandler(viewer)
- drawPolygonExample?.subscribe('getShowWaringTip', data => {
+ drawPolygonExample.subscribe('getShowWaringTip', data => {
isShowWaringTip.value = data
})
- drawPolygonExample?.subscribe('getPolygonPositions', data => {
+ drawPolygonExample.subscribe('getPolygonPositions', data => {
curDrawPolygonData.value = data.map(item => {
let cartographic = Cesium.Cartographic.fromCartesian(item)
+
let lng = Cesium.Math.toDegrees(cartographic.longitude) // 经度
let lat = Cesium.Math.toDegrees(cartographic.latitude) // 纬度
+ let height = cartographic.height
return {
lng: _.round(lng, 6),
lat: _.round(lat, 6),
+ height
}
})
})
@@ -170,11 +220,32 @@
watch(dialogShow, async (show) => {
if (show) {
+ if (props.type === 'edit') {
+ form.value = { ...props.curEditRow }
+ }
+
+ initDrawPolygon()
+
+ if (viewer) return
+
await nextTick()
cesiumContextMenu()
initMap()
}
})
+
+const initDrawPolygon = async () => {
+ if (props.type === 'edit' && drawPolygonExample) {
+ drawPolygonExample?.setPolygonHeight?.(props.curEditRow.height)
+
+ let polygon = JSON.parse(props.curEditRow.flight_range)
+
+ if (polygon.length > 0) {
+ const aslData = await getPointPositionsHeight(polygon, viewer)
+ drawPolygonExample?.initPolygon(viewer, aslData.map(item => ({ ...item, height: item.ASL })))
+ }
+ }
+}
const handleSubmit = () => {
if (ElMessage.value) {
@@ -201,8 +272,38 @@
let area = turf.area(polygon)
- emit('submitClick')
- done()
+ if (props.type === 'edit') {
+ airspaceEnteringUpdate({
+ id: props.curEditRow.id,
+ name: form.value.name,
+ flight_range: JSON.stringify(curDrawPolygonData.value),
+ height: form.value.height,
+ time_period: form.value.time_period.join('~'),
+ flight_type: form.value.flight_type,
+ remark: form.value.remark,
+ area: area
+ }).then(res => {
+ ElMessage.success('更新成功')
+ dialogShow.value = false
+ emit('submitClick')
+ done()
+ })
+ } else {
+ airspaceEnteringAdd({
+ name: form.value.name,
+ flight_range: JSON.stringify(curDrawPolygonData.value),
+ height: form.value.height,
+ time_period: form.value.time_period.join('~'),
+ flight_type: form.value.flight_type,
+ remark: form.value.remark,
+ area: area
+ }).then(res => {
+ ElMessage.success('新增成功')
+ dialogShow.value = false
+ emit('submitClick')
+ done()
+ })
+ }
} else {
return false
}
@@ -211,15 +312,12 @@
}
const handleClose = () => {
+ drawPolygonExample?.delPolygon()
isShowWaringTip.value = false
curDrawPolygonData.value = []
- cesiumContextMenu(false)
- drawPolygonExample?.destroy()
- publicCesiumInstance?.viewerDestroy()
- publicCesiumInstance = null
- viewer = null
- drawPolygonExample = null
- form.value = {}
+ form.value = {
+ height: 120
+ }
formEle.value.resetForm()
dialogShow.value = false
}
@@ -239,6 +337,15 @@
cesium.removeEventListener('contextmenu', preventDefault)
}
}
+
+onBeforeUnmount(() => {
+ drawPolygonExample?.destroy()
+ drawPolygonExample = null
+ cesiumContextMenu(false)
+ publicCesiumInstance?.viewerDestroy()
+ publicCesiumInstance = null
+ viewer = null
+})
</script>
<style scoped lang="scss">
--
Gitblit v1.9.3