| | |
| | | <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: [ |
| | | { |
| | |
| | | |
| | | { |
| | | 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, |
| | |
| | | }, |
| | | { |
| | | label: '管控时段', |
| | | prop: 'daterange', |
| | | prop: 'time_period', |
| | | type: 'daterange', |
| | | format: 'YYYY-MM-DD', |
| | | valueFormat: 'YYYY-MM-DD', |
| | |
| | | }, |
| | | |
| | | { |
| | | 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 |
| | | } |
| | |
| | | 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> |