<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>
|