From 02409bfbe15f22fc3b5dccadabfd860a660a49d9 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Sat, 11 Oct 2025 10:37:14 +0800
Subject: [PATCH] feat: 名称修改

---
 src/views/airspace/components/AirspaceMap.vue |  299 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 273 insertions(+), 26 deletions(-)

diff --git a/src/views/airspace/components/AirspaceMap.vue b/src/views/airspace/components/AirspaceMap.vue
index cd2d1aa..4c14cac 100644
--- a/src/views/airspace/components/AirspaceMap.vue
+++ b/src/views/airspace/components/AirspaceMap.vue
@@ -5,35 +5,78 @@
 			<div class="form-container">
 				<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>
+							<WarningFilled />
+						</el-icon>
+					</span>
+					<span>空域不支持交叉面,无法生成空域</span>
+				</div>
 				<div id="AirspaceMap" class="ztzf-cesium"></div>
+			</div>
+
+			<div class="btn-container">
+				<el-button icon="el-icon-check" type="primary" @click="handleSubmit">确定</el-button>
+				<el-button icon="el-icon-close" @click="dialogShow = false">取消</el-button>
 			</div>
 		</div>
 	</el-dialog>
 </template>
 
 <script setup>
+import _, { cloneDeep, throttle } from 'lodash'
+
 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'])
 
 const dialogShow = defineModel('show')
 
 const formEle = ref(null)
-const form = ref({})
+const form = ref({
+	height: 120,
+})
 const option = ref({
 	submitBtn: false,
 	emptyBtn: false,
+	menuBtn: false,
 	column: [
 		{
 			label: '名称',
-			prop: 'input',
+			prop: 'name',
 			type: 'input',
 			rules: [
 				{
@@ -46,31 +89,41 @@
 
 		{
 			label: '高度',
-			prop: 'input',
+			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: 'select',
+			prop: 'flight_type',
 			type: 'select',
-			dicData: [
-				{
-					label: '字典1',
-					value: 0,
-				},
-				{
-					label: '字典2',
-					value: 1,
-				},
-			],
+			dicUrl: `/drone-device-core/airspace/page?current=1&size=10000`,
+			props: {
+				label: 'type_name',
+				value: 'id',
+			},
+			dicFormatter (res) {
+				return res.data.records
+			},
 			rules: [
 				{
 					required: true,
@@ -81,7 +134,7 @@
 		},
 		{
 			label: '管控时段',
-			prop: 'daterange',
+			prop: 'time_period',
 			type: 'daterange',
 			format: 'YYYY-MM-DD',
 			valueFormat: 'YYYY-MM-DD',
@@ -97,22 +150,29 @@
 		},
 
 		{
-			label: '备注',
-			prop: 'input',
+			label: '管控原因',
+			prop: 'remark',
 			type: 'textarea',
 			span: 24,
 			rows: 1,
 			minRows: 1,
 			maxRows: 1,
-			placeholder: '请输入备注',
+			placeholder: '请输入管控原因',
 		},
 	],
 })
 
+let drawPolygonExample = null
 let publicCesiumInstance = null
 let viewer = null
 // 地图
-const initMap = () => {
+const mapLoading = ref(true)
+
+const isShowWaringTip = ref(false)
+
+const curDrawPolygonData = ref([])
+
+const initMap = async () => {
 	if (!document.getElementById('AirspaceMap')) {
 		return
 	}
@@ -121,37 +181,224 @@
 		flatMode: false,
 		terrain: true,
 		layerMode: 4,
-		contour: false,
+		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 => {
+		isShowWaringTip.value = 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
+			}
+		})
+	})
 }
 
 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) {
+		ElMessage.warning('空域不支持交叉,请重新绘制或调整')
+		return
+	}
+
+	if (curDrawPolygonData.value.length === 0) {
+		ElMessage.warning('请绘制空域区域')
+		return
+	}
+
+	if (formEle.value) {
+		formEle.value.validate((valid, done, msg) => {
+			if (valid) {
+				let disposePolygon = curDrawPolygonData.value.map(i => [i.lng, i.lat])
+
+				let polygon = turf.polygon([
+					[
+						...disposePolygon,
+						disposePolygon[0]
+					]
+				])
+
+				let area = turf.area(polygon)
+
+				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
+			}
+		})
+	}
+}
+
 const handleClose = () => {
-	publicCesiumInstance?.viewerDestroy()
-	publicCesiumInstance = null
-	viewer = null
-	form.value = {}
+	drawPolygonExample?.delPolygon()
+	isShowWaringTip.value = false
+	curDrawPolygonData.value = []
+	form.value = {
+		height: 120
+	}
 	formEle.value.resetForm()
 	dialogShow.value = false
 }
+
+// 阻止右键默认行为
+const preventDefault = event => {
+	event.preventDefault()
+	return
+}
+
+const cesiumContextMenu = (isAdd = true) => {
+	let cesium = document.getElementById('AirspaceMap')
+	if (!cesium) return
+	if (isAdd) {
+		cesium.addEventListener('contextmenu', preventDefault)
+	} else {
+		cesium.removeEventListener('contextmenu', preventDefault)
+	}
+}
+
+onBeforeUnmount(() => {
+	drawPolygonExample?.destroy()
+	drawPolygonExample = null
+	cesiumContextMenu(false)
+	publicCesiumInstance?.viewerDestroy()
+	publicCesiumInstance = null
+	viewer = null
+})
 </script>
 
 <style scoped lang="scss">
 .custom-container {
 	.map-container {
-		height: 440px;
+		height: 620px;
 
 		#AirspaceMap {
+			position: relative;
 			width: 100%;
 			height: 100%;
+			overflow: hidden;
 		}
 	}
+
+	.btn-container {
+		margin-top: 16px;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+	}
+}
+
+.tool-tip {
+	display: flex;
+	justify-content: center;
+	align-items: center;
+	position: absolute;
+	width: 800px;
+	height: 64px;
+	font-family: Source Han Sans CN, Source Han Sans CN;
+	font-size: 18px;
+	color: #ffffff;
+
+	background: rgba(11, 31, 58, 0.7);
+	-moz-user-select: none;
+	-webkit-user-select: none;
+	-ms-user-select: none;
+	user-select: none;
+	z-index: 9;
+}
+
+.warning {
+	top: 234px;
+	left: 50%;
+	transform: translateX(-50%);
+	color: #fff;
+	background: rgba(140, 0, 0, 0.4);
+
+	.icon {
+		display: flex;
+		align-items: center;
+		color: #ff1f1f;
+	}
 }
 </style>

--
Gitblit v1.9.3