无人机管理后台前端(已迁走)
罗广辉
2025-10-11 02409bfbe15f22fc3b5dccadabfd860a660a49d9
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>