applications/drone-command/src/styles/common.scss
@@ -250,4 +250,4 @@ .cesium-viewer-bottom { display: none; } } } applications/task-work-order/src/main.js
@@ -32,6 +32,7 @@ import basicContainer from 'components/basic-container/main.vue' import welContainer from 'components/wel-container/main.vue' import basicMainContent from 'components/basic-main-content/main.vue' import _ from 'lodash' import thirdRegister from './components/third-register/main.vue' import NfDesignBase from '@saber/nf-design-base-elp' @@ -88,6 +89,7 @@ window.$crudCommon = crudCommon window.axios = axios window._ = _ const app = createApp(App) for (const [key, component] of Object.entries(ElementPlusIconsVue)) { app.component(key, component) applications/task-work-order/src/store/modules/common.js
@@ -23,8 +23,18 @@ }, safeHeight: 0, // 获取选择航线高度 positionsArr: [], // 获取选择航线点数据 // 地图设置 mapSetting: { mode: 17, // 0为标准地图, 1为卫星地图 roadLine: true, visual: '3D', isDark: false, }, }, mutations: { SET_MAP_MODE: (state, mode) => { state.mapSetting.mode = mode }, setSafeHeight (state, data) { state.safeHeight = data }, applications/task-work-order/src/styles/common.scss
@@ -123,7 +123,7 @@ } .avue--collapse { .avue-layout { > .layer-page-leaf-container { width: 53px; @@ -268,7 +268,7 @@ } } .work-cesium { .gd-cesium { .cesium-viewer { height: 100%; overflow: hidden; @@ -292,4 +292,5 @@ .cesium-viewer-bottom { display: none; } } } applications/task-work-order/src/styles/common/cockpit.scss
@@ -458,7 +458,6 @@ padding: 30px; display: flex; flex-direction: column; width: 770px; background: #FFFFFF; border: 1px solid #CCCCCC; applications/task-work-order/src/views/orderView/orderManage/orderManage/FormDiaLog.vue
@@ -4,151 +4,129 @@ v-model="visible" :title="titleEnum[dialogMode]" @closed="visible = false" :style="{ width: pxToRem(1216) }" destroy-on-close width="1000px" > <div class="detail-container" v-if="dialogReadonly"> <el-row> <el-col :span="12"> <div class="label">工单名称</div> <div class="val">{{ formData.workOrderName }}</div> </el-col> <el-col :span="12"> <div class="label">工单类型</div> <div class="val">{{ getDictLabel(formData.workOrderType, dictObj.workOrderType) }}</div> </el-col> <el-col :span="12"> <div class="label">执行开始时间</div> <div class="val">{{ formData.executeStartTime }}</div> </el-col> <el-col :span="12"> <div class="label">执行结束时间</div> <div class="val">{{ formData.executeEndTime }}</div> </el-col> <el-col :span="12"> <div class="label">设备需求</div> <div class="val">{{ getDictLabel(formData.deviceLoadDemand, dictObj.deviceLoadDemand) }}</div> </el-col> <el-col :span="12"> <div class="label">工单备注</div> <div class="val">{{ formData.remark }}</div> </el-col> <el-col :span="24"> <div class="label">工单经纬度面</div> <div class="val">{{ formData.geom }}</div> </el-col> <el-col :span="24"> <div class="label">推荐设备</div> <div class="val">{{ formData.recommendDeviceIds }}</div> </el-col> </el-row> <div style="display: flex"> <div class="leftBox"> <div class="detail-container" v-if="dialogReadonly"> <el-row> <el-col :span="12"> <div class="label">工单名称</div> <div class="val">{{ formData.workOrderName }}</div> </el-col> <el-col :span="12"> <div class="label">工单类型</div> <div class="val">{{ getDictLabel(formData.workOrderType, dictObj.workOrderType) }}</div> </el-col> <el-col :span="12"> <div class="label">执行时间范围</div> <div class="val"> {{ dayjs(formData.executeStartTime).format('YYYY-MM-DD') }} ~ {{ dayjs(formData.executeEndTime).format('YYYY-MM-DD') }} </div> </el-col> <el-col :span="12"> <div class="label">设备需求</div> <div class="val">{{ getDictLabel(formData.deviceLoadDemand, dictObj.deviceLoadDemand) }}</div> </el-col> <el-col :span="24"> <div class="label">工单备注</div> <div class="val">{{ formData.remark }}</div> </el-col> </el-row> </div> <el-form class="gd-dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="dialogReadonly" label-width="90px" > <el-row> <el-col :span="12"> <el-form-item label="工单名称" prop="workOrderName"> <el-input class="gd-input" v-model="formData.workOrderName" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="工单类型" prop="workOrderType"> <el-select class="gd-select" popper-class="gd-select-popper" v-model="formData.workOrderType" placeholder="请选择" clearable > <el-option v-for="item in dictObj.workOrderType" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="执行时间" prop="executeStartTime"> <el-date-picker class="gd-date-picker" popper-class="gd-date-picker-popper" v-model="dateRange" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="设备需求" prop="deviceLoadDemand"> <el-select class="gd-select" popper-class="gd-select-popper" v-model="formData.deviceLoadDemand" placeholder="请选择" clearable > <el-option v-for="item in dictObj.deviceLoadDemand" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="工单备注" prop="remark"> <el-input class="gd-input" type="textarea" v-model="formData.remark" placeholder="请输入" clearable /> </el-form-item> </el-col> </el-row> </el-form> <div id="mapContainer" class="gd-cesium"></div> </div> <div> <h3>推荐设备</h3> <el-table class="command-data-cockpit-table" ref="deviceTableRef" :data="deviceList" row-key="id" @selection-change="handleDeviceSelectionChange" > <el-table-column type="selection" width="55" :reserve-selection="true" /> <el-table-column prop="nickname" label="设备名称" /> <el-table-column prop="deviceName" label="设备型号" /> <el-table-column prop="devicePayload" label="设备负载" /> </el-table> </div> </div> <el-form class="gd-dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="dialogReadonly" label-width="120px" > <el-row> <el-col :span="12"> <el-form-item label="工单名称" prop="workOrderName"> <el-input class="gd-input" v-model="formData.workOrderName" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="工单类型" prop="workOrderType"> <el-select class="gd-select" popper-class="gd-select-popper" v-model="formData.workOrderType" placeholder="请选择" clearable > <el-option v-for="item in dictObj.workOrderType" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="执行开始时间" prop="executeStartTime"> <el-date-picker class="gd-date-picker" popper-class="gd-date-picker-popper" v-model="formData.executeStartTime" type="datetime" placeholder="请选择" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="执行结束时间" prop="executeEndTime"> <el-date-picker class="gd-date-picker" popper-class="gd-date-picker-popper" v-model="formData.executeEndTime" type="datetime" placeholder="请选择" value-format="YYYY-MM-DD HH:mm:ss" /> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="设备需求" prop="deviceLoadDemand"> <el-select class="gd-select" popper-class="gd-select-popper" v-model="formData.deviceLoadDemand" placeholder="请选择" clearable > <el-option v-for="item in dictObj.deviceLoadDemand" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" /> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="工单备注" prop="remark"> <el-input class="gd-input" v-model="formData.remark" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="工单经纬度面" prop="geom"> <el-input class="gd-input" v-model="formData.geom" placeholder="预留输入框" clearable /> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="推荐设备" prop="recommendDeviceIds"> <el-form-item prop="recommendDeviceIds" label-width="0"> <el-table class="command-data-cockpit-table" ref="deviceTableRef" :data="deviceList" row-key="id" @selection-change="handleDeviceSelectionChange" > <el-table-column type="selection" width="55" :reserve-selection="true" /> <el-table-column prop="deviceName" label="设备名称" /> <el-table-column prop="deviceCode" label="设备编码" /> <el-table-column prop="deviceType" label="设备类型" /> </el-table> </el-form-item> </el-form-item> </el-col> </el-row> </el-form> <template #footer> <el-button color="#F2F3F5" @click="visible = false">{{ dialogReadonly ? '关闭' : '取消' }}</el-button> <el-button @@ -166,12 +144,18 @@ </template> <script setup> import { computed, ref, onMounted } from 'vue' import { computed, ref, onMounted, nextTick } from 'vue' import { ElMessage } from 'element-plus' import { fieldRules } from '@ztzf/utils' import { getDictLabel } from '@ztzf/utils' import { dateRangeFormat, fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils' import { gdWorkOrderDetailApi, gdWorkOrderSaveApi } from './orderManageApi' import { gdManageDevicePageApi } from './gdManageDeviceApi' import { gdManageDeviceListApi, gdManageDevicePageApi } from './gdManageDeviceApi' import { cartesian3Convert } from '@/utils/cesium/mapUtil' import * as turf from '@turf/turf' import * as Cesium from 'cesium' import { DrawPolygon } from '@/utils/cesium/DrawPolygon' import { PublicCesium } from '@/utils/cesium/publicCesium' import { pxToRem } from '@/utils/rem' import dayjs from 'dayjs' // 初始化表单数据 const initForm = () => ({ @@ -186,9 +170,7 @@ }) const dictObj = inject('dictObj') const deptTree = inject('deptTree') const treeProps = inject('treeProps') const dateRange = ref([]) const emit = defineEmits(['success']) const formRef = ref(null) // 表单实例 const formData = ref(initForm()) // 表单数据 @@ -201,20 +183,22 @@ const deviceList = ref([]) // 设备列表 const selectedDevices = ref([]) // 选中的设备 const deviceTableRef = ref(null) // 设备表格实例 let viewer let drawPolygonExample let pointList = [] const rules = { workOrderName: fieldRules(true), workOrderName: fieldRules(true, 50), workOrderType: fieldRules(true), executeStartTime: fieldRules(true), executeEndTime: fieldRules(true), deviceLoadDemand: fieldRules(true), } // 获取推荐设备列表 async function getDeviceList() { try { const res = await gdManageDevicePageApi({ current: 1, size: 1000 }) deviceList.value = res?.data?.data?.records ?? [] const res = await gdManageDeviceListApi({ current: 1, size: 1000, ids: formData.value.recommendDeviceIds }) deviceList.value = res?.data?.data ?? [] } catch (error) { console.error('获取设备列表失败:', error) } @@ -230,8 +214,17 @@ async function handleSubmit() { const isValid = await formRef.value?.validate().catch(() => false) if (!isValid) return if (!pointList.length) { ElMessage.warning('请绘制范围') return } submitting.value = true try { const str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',') formData.value.geom = `POLYGON((${str}))` if (!formData.value.id) { formData.value.workOrderStatus = '10' } await gdWorkOrderSaveApi(formData.value) ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功') visible.value = false @@ -241,34 +234,124 @@ } } watch( () => dateRange.value, newVal => { const range = dateRangeFormat(dateRange.value) formData.value.executeStartTime = range[0] formData.value.executeEndTime = range[1] } ) // 加载详情 async function loadDetail() { if (!formData.value.id) return const res = await gdWorkOrderDetailApi({ id: formData.value.id }) formData.value = res?.data?.data ?? {} dateRange.value = [formData.value.executeStartTime, formData.value.executeEndTime] } // 回显选中的设备 if (formData.value.recommendDeviceIds) { const deviceIds = formData.value.recommendDeviceIds.split(',').map(id => parseInt(id)) nextTick(() => { deviceList.value.forEach(device => { if (deviceIds.includes(device.id)) { deviceTableRef.value?.toggleRowSelection(device, true) } }) }) } // 新增模式绘制 function addPolygon() { drawPolygonExample = new DrawPolygon(viewer) drawPolygonExample.initHandler(viewer) drawPolygonExample.subscribe('getPolygonPositions', drawFinished) } // 绘制完成回调 const drawFinished = async data => { pointList = _.cloneDeep(data).map(item => { const val = cartesian3Convert(item, viewer) return { ...val, lng: val.longitude, lat: val.latitude } }) } // 编辑面 function editPolygon() { if (!formData.value?.geom) return pointList = geomAnalysis(formData.value.geom) drawPolygonExample?.destroy() drawPolygonExample = new DrawPolygon(viewer) let noClosedList = _.cloneDeep(pointList) noClosedList.pop() drawPolygonExample.initPolygon( viewer, noClosedList.map(item => ({ lng: item.longitude, lat: item.latitude })) ) drawPolygonExample.subscribe('getPolygonPositions', drawFinished) } // 查看面 function viewPolygon() { if (!formData.value?.geom) return pointList = geomAnalysis(formData.value.geom) const result = pointList.map(item => [item.longitude, item.latitude]).flat() const mian = viewer.entities?.add({ customType: 'control_group', position: Cesium.Cartesian3.fromDegrees(result[0], result[1]), polyline: { positions: Cesium.Cartesian3.fromDegreesArray(result), clampToGround: true, width: 3, material: Cesium.Color.RED, }, }) viewer.flyTo(mian) } // 同步关联场景 function syncSelection() { if (!deviceTableRef.value) return deviceTableRef.value.clearSelection() const deviceIds = formData.value.recommendDeviceIds.split(',') deviceList.value.forEach(device => { if (deviceIds.includes(String(device.id))) { deviceTableRef.value?.toggleRowSelection(device, true) } }) } // 打开弹框 async function open({ mode = 'add', row } = {}) { dialogMode.value = mode formData.value = dialogMode.value === 'add' ? initForm() : row await getDeviceList() initMap() if (dialogMode.value !== 'add') { await loadDetail() } await getDeviceList() if (dialogMode.value === 'add') { addPolygon() } else if (dialogMode.value === 'edit') { editPolygon() } else { viewPolygon() } await nextTick() syncSelection() } // 初始化地图实例 function initMap() { const publicCesiumInstance = new PublicCesium({ dom: 'mapContainer', flatMode: false, terrain: true, layerMode: 4, boundary: false, }) viewer = publicCesiumInstance.getViewer() } defineExpose({ open }) </script> <style lang="scss" scoped> .leftBox { width: 681px; } #mapContainer { width: 681px; height: 423px; } </style> applications/task-work-order/src/views/orderView/orderManage/orderManage/gdManageDeviceApi.js
@@ -18,6 +18,15 @@ }) } // 列表 export const gdManageDeviceListApi = params => { return request({ url: `/drone-gd/workOrder/gdManageDevice/list`, method: 'get', params, }) } // 新增或修改 export const gdManageDeviceSubmitApi = data => { return request({ applications/task-work-order/src/views/orderView/orderManage/orderManage/index.vue
@@ -51,7 +51,7 @@ <el-date-picker class="gd-date-picker" popper-class="gd-date-picker-popper" v-model="executeTime" v-model="dateRange" type="daterange" range-separator="-" start-placeholder="开始日期" @@ -76,6 +76,7 @@ <div class="gd-table-content gd-table-content-bg"> <el-table class="gd-table" :data="list" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="46" /> <el-table-column type="index" width="64" label="序号" /> <el-table-column prop="workOrderName" show-overflow-tooltip label="工单名称" /> <el-table-column prop="workOrderCode" show-overflow-tooltip label="工单编号" /> <el-table-column prop="workOrderType" show-overflow-tooltip label="工单类型"> @@ -83,14 +84,21 @@ {{ getDictLabel(row.workOrderType, dictObj.workOrderType) }} </template> </el-table-column> <el-table-column prop="status" show-overflow-tooltip label="阶段状态" /> <el-table-column prop="status" show-overflow-tooltip label="阶段状态" > <template v-slot="{ row }"> {{ getDictLabel(row.workOrderStatus, dictObj.workOrderStatus).split('_')[0] }} </template> </el-table-column> <el-table-column prop="workOrderStatus" show-overflow-tooltip label="工单状态"> <template v-slot="{ row }"> {{ getDictLabel(row.workOrderStatus, dictObj.workOrderStatus) }} {{ getDictLabel(row.workOrderStatus, dictObj.workOrderStatus).split('_')[1] }} </template> </el-table-column> <el-table-column prop="executeTime" show-overflow-tooltip label="执行时间范围"> <template v-slot="{ row }">{{ row.executeStartTime }} ~ {{ row.executeEndTime }}</template> <template v-slot="{ row }"> {{ dayjs(row.executeStartTime).format('YYYY-MM-DD') }} ~ {{ dayjs(row.executeEndTime).format('YYYY-MM-DD') }} </template> </el-table-column> <el-table-column prop="serviceParty" show-overflow-tooltip label="服务方名称" /> <el-table-column prop="createDeptName" show-overflow-tooltip label="需求方名称" /> @@ -126,9 +134,10 @@ import { ElMessage, ElMessageBox } from 'element-plus' import { getDictionaryByCode } from '@/api/system/dictbiz' import { getDeptTree } from '@/api/system/dept' import { getDictLabel } from '@ztzf/utils' import { dateRangeFormat, getDictLabel } from '@ztzf/utils' import FormDiaLog from './FormDiaLog.vue' import { gdWorkOrderPageApi, gdWorkOrderRemoveApi } from './orderManageApi' import dayjs from 'dayjs' // 初始化查询参数 const initSearchParams = () => ({ @@ -141,7 +150,7 @@ size: 10, // 每页大小 }) const searchParams = ref(initSearchParams()) // 查询参数 const executeTime = ref([]) // 执行时间范围 const dateRange = ref([]) // 执行时间范围 const total = ref(0) // 总条数 const loading = ref(true) // 列表加载中 const list = ref([]) // 列表数据 @@ -165,9 +174,10 @@ // 获取列表 async function getList() { const range = dateRangeFormat(dateRange.value) loading.value = true try { const res = await gdWorkOrderPageApi(searchParams.value) const res = await gdWorkOrderPageApi({ ...searchParams.value, startTime: range[0], endTime: range[1] }) list.value = res?.data?.data?.records ?? [] total.value = res?.data?.data?.total ?? 0 } finally { @@ -177,14 +187,6 @@ // 查询 function handleSearch() { // 处理执行时间范围 if (executeTime.value && executeTime.value.length === 2) { searchParams.value.executeStartTime = executeTime.value[0] searchParams.value.executeEndTime = executeTime.value[1] } else { searchParams.value.executeStartTime = '' searchParams.value.executeEndTime = '' } searchParams.value.current = 1 getList() } @@ -192,7 +194,7 @@ // 重置查询 function resetForm() { queryParamsRef.value?.resetFields() executeTime.value = [] dateRange.value = [] searchParams.value.current = 1 getList() } applications/task-work-order/src/views/orderView/orderManage/orderManage/test.vue
New file @@ -0,0 +1,294 @@ <template> <el-dialog v-model="visible" :title="titleEnum[dialogMode]" :close-on-click-modal="false" width="80%"> <div class="bodyBox"> <div class="leftMap ztzf-cesium" id="mapContainer"></div> <div class="rightInfo"> <div v-if="readonly"> <div>防区名称: {{ formData.zoneName }}</div> <div>防区面积: {{ formatZoneArea(formData.zoneArea) }}</div> <el-table :data="sceneList" row-key="id"> <el-table-column prop="sceneName" label="场景名称" /> <el-table-column prop="sceneType" label="场景类型"> <template v-slot="{ row }"> {{ getDictLabel(row.sceneType, dictObj.sceneType) }} </template> </el-table-column> </el-table> </div> <el-form v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly" label-width="100px"> <el-row> <el-col :span="24"> <el-form-item label="防区名称" prop="zoneName"> <el-input v-model="formData.zoneName" maxlength="50" placeholder="请输入" clearable /> </el-form-item> </el-col> <el-col :span="24"> <el-form-item label="防区面积" prop="zoneArea">{{ formatZoneArea(formData.zoneArea) }}</el-form-item> </el-col> <el-col :span="24"> <div> <div> 关联场景 <el-input v-model="searchName" placeholder="请输入" clearable></el-input> </div> <el-form-item label="关联场景" prop="defenseSceneIds" label-width="0"> <el-table ref="sceneTableRef" :data="sceneList.filter(item => item.sceneName.includes(searchName))" row-key="id" @selection-change="handleSceneSelectionChange" > <el-table-column type="selection" width="55" :reserve-selection="true" /> <el-table-column prop="sceneName" label="场景名称" /> <el-table-column prop="sceneType" label="场景类型"> <template v-slot="{ row }"> {{ getDictLabel(row.sceneType, dictObj.sceneType) }} </template> </el-table-column> </el-table> </el-form-item> </div> </el-col> </el-row> </el-form> </div> </div> <template #footer> <el-button @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button> <el-button v-if="!readonly" type="primary" :loading="submitting" :disabled="submitting" @click="handleSubmit"> 确定 </el-button> </template> </el-dialog> </template> <script setup> import { computed, inject, nextTick, ref } from 'vue' import { ElMessage } from 'element-plus' import { fwDefenseZoneDetailApi, fwDefenseZoneSubmitApi } from './defenseZoneApi' import { fwDefenseSceneListApi } from '@/views/areaManage/sceneConfig/sceneConfigApi' import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils' import { PublicCesium } from '@/utils/cesium/publicCesium' import { DrawPolygon } from '@/utils/cesium/DrawPolygon' import { cartesian3Convert } from '@/utils/cesium/mapUtil' import * as turf from '@turf/turf' import * as Cesium from 'cesium' // 初始化表单 const initForm = () => ({ defenseSceneIds: '', // 关联场景ID geom: '', // 经纬度面 zoneArea: null, // 防区面积 zoneName: '', // 防区名称 }) const emit = defineEmits(['success']) const formRef = ref(null) // 表单实例 const formData = ref(initForm()) // 表单数据 const visible = defineModel() // 弹框显隐 const dialogMode = ref('add') // 弹框模式 const submitting = ref(false) // 提交中 const readonly = computed(() => dialogMode.value === 'view') const titleEnum = ref({ edit: '编辑', view: '查看', add: '新增' }) const sceneTableRef = ref(null) const searchName = ref('') const sceneList = ref([]) // 关联场景列表 const selectedSceneRows = ref([]) // 选中场景 const dictObj = inject('dictObj') let viewer let drawPolygonExample let pointList = [] const rules = { zoneName: fieldRules(true, 50), defenseSceneIds: fieldRules(true), zoneArea: fieldRules(true), } // 关闭弹框 function handleCancel() { visible.value = false } // 提交新增/编辑 async function handleSubmit() { const isValid = await formRef.value?.validate().catch(() => false) if (!isValid) return if (!pointList.length) { ElMessage.warning('请绘制防区范围') return } submitting.value = true try { const ids = selectedSceneRows.value.map(item => item.id) formData.value.defenseSceneIds = ids.join(',') const str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',') formData.value.geom = `POLYGON((${str}))` await fwDefenseZoneSubmitApi(formData.value) ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功') visible.value = false emit('success') } finally { submitting.value = false } } // 加载详情 async function loadDetail() { if (!formData.value.id) return const res = await fwDefenseZoneDetailApi({ id: formData.value.id }) formData.value = res?.data?.data ?? initForm() } // 获取场景列表 async function getSceneList() { const res = await fwDefenseSceneListApi({ filterSelected: 1, zoneId: formData.value.id }) sceneList.value = res?.data?.data ?? [] if (readonly.value) { const ids = getSceneIdList() sceneList.value = sceneList.value.filter(item => ids.includes(item.id)) } } // 获取场景ID列表 function getSceneIdList() { if (!formData.value.defenseSceneIds) return [] return formData.value.defenseSceneIds.split(',') } // 关联场景变更 function handleSceneSelectionChange(rows) { selectedSceneRows.value = rows formData.value.defenseSceneIds = rows.map(item => item.id).join(',') } // 同步关联场景 function syncSceneSelection() { if (!sceneTableRef.value) return sceneTableRef.value.clearSelection() const ids = getSceneIdList() const rows = [] sceneList.value.forEach(row => { if (ids.includes(row.id)) { sceneTableRef.value.toggleRowSelection(row, true) rows.push(row) } }) selectedSceneRows.value = rows } // 格式化防区面积 function formatZoneArea(value) { if (value == null || value === '') return '' return `${value}k㎡` } // 初始化地图实例 function initMap() { const publicCesiumInstance = new PublicCesium({ dom: 'mapContainer', flatMode: false, terrain: true, layerMode: 4, boundary: false, }) viewer = publicCesiumInstance.getViewer() } // 绘制完成回调 const drawFinished = async data => { pointList = _.cloneDeep(data).map(item => { const val = cartesian3Convert(item, viewer) return { ...val, lng: val.longitude, lat: val.latitude } }) if (!pointList.length) return const polygon = turf.polygon([ [...pointList.map(item => [item.longitude, item.latitude]), [pointList[0].longitude, pointList[0].latitude]], ]) const areaSqm = turf.area(polygon) // 平方米 formData.value.zoneArea = _.round(areaSqm / 1_000_000, 2) // 平方千米 } // 新增模式绘制 function addPolygon() { drawPolygonExample = new DrawPolygon(viewer) drawPolygonExample.initHandler(viewer) drawPolygonExample.subscribe('getPolygonPositions', drawFinished) } // 编辑面 function editPolygon() { if (!formData.value?.geom) return pointList = geomAnalysis(formData.value.geom) drawPolygonExample?.destroy() drawPolygonExample = new DrawPolygon(viewer) let noClosedList = _.cloneDeep(pointList) noClosedList.pop() drawPolygonExample.initPolygon( viewer, noClosedList.map(item => ({ lng: item.longitude, lat: item.latitude })) ) drawPolygonExample.subscribe('getPolygonPositions', drawFinished) } // 查看面 function viewPolygon() { if (!formData.value?.geom) return pointList = geomAnalysis(formData.value.geom) const result = pointList.map(item => [item.longitude, item.latitude]).flat() const mian = viewer.entities?.add({ customType: 'control_group', position: Cesium.Cartesian3.fromDegrees(result[0], result[1]), polyline: { positions: Cesium.Cartesian3.fromDegreesArray(result), clampToGround: true, width: 3, material: Cesium.Color.RED, }, }) viewer.flyTo(mian) } // 打开弹框 async function open({ mode, row } = {}) { dialogMode.value = mode || 'add' formData.value = dialogMode.value === 'add' ? initForm() : row selectedSceneRows.value = [] pointList = [] await nextTick() initMap() if (dialogMode.value !== 'add') { await loadDetail() } await getSceneList() if (dialogMode.value === 'add') { addPolygon() } else if (dialogMode.value === 'edit') { editPolygon() } else { viewPolygon() } await nextTick() syncSceneSelection() } defineExpose({ open, }) </script> <style scoped lang="scss"> .bodyBox { display: flex; height: 600px; .leftMap { width: 70%; height: 100%; } .rightInfo { width: 30%; height: 100%; overflow: auto; } } </style>