吉安感知网项目-前端
chenyao
2026-02-02 1731a7c28db644bb862eb54089d15fe3991fd27c
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -3,7 +3,7 @@
      <div class="dialog-container">
         <div class="left-container">
            <CommonCesiumMap ref="mapRef" class="leftMap command-cesium" :active="visible" :flat-mode="false"
               :terrain="true" :layer-mode="4" :boundary="false" />
               :terrain="true" :layer-mode="4" :boundary="false" :zoom-to-boundary="dialogMode === 'add'" />
            <div v-if="showTypePanel && !readonly" class="shape-type-panel">
               <div class="panel-header">
                  <span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span>
@@ -14,8 +14,8 @@
               <div class="panel-body">
                  <span class="panel-label">区域类型</span>
                  <el-select class="command-select" popper-class="command-select-popper" v-model="activeAreaType"
                     placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags :multiple="currentShapeType === 'buffer'"
                     @change="handleAreaTypeChange">
                     placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags
                     :multiple="currentShapeType === 'buffer'" @change="handleAreaTypeChange">
                     <el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue"
                        :value="item.dictKey" />
                  </el-select>
@@ -226,14 +226,19 @@
<script setup>
import { Close } from '@element-plus/icons-vue'
import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { computed, inject, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { fwAreaDivideDetailApi, fwAreaDivideSubmitApi } from './partitionApi'
import { saveOperationLog } from '@ztzf/apis'
import { useRoute } from 'vue-router'
import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import { DrawManager, EditManager, buildEllipsePositions } from '@/utils/cesium/shapeTools'
import {
   DrawManager,
   EditManager,
   addBufferRadiusLabelEntity,
   buildEllipsePositions,
} from '@/utils/cesium/shapeTools'
import { cartesian3Convert } from '@/utils/cesium/mapUtil'
import {
   createDeviceRangePrimitive,
@@ -243,8 +248,8 @@
} from '@/utils/cesium/deviceRange'
import * as Cesium from 'cesium'
import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice'
import { fwPoliceStationQueryByPolygonsApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
import { fwDeviceListByPolygonsApi } from '@/views/basicManage/deviceStock/fwDevice'
import polygonIcon from '@/assets/images/areaMap/polygon.png'
import rectIcon from '@/assets/images/areaMap/rect.png'
import ellipseIcon from '@/assets/images/areaMap/ellipse.png'
@@ -302,8 +307,13 @@
const deviceRangePrimitiveMap = new Map()
const activeToolMode = ref('')
const suppressDeviceFly = ref(false)
const isDialogInitializing = ref(false)
let lastSelectedDeviceIds = []
let deviceRangeRenderToken = 0
let deviceListTimer = null
let deviceListRequestToken = 0
let policeStationListTimer = null
let policeStationRequestToken = 0
const rules = {
   areaName: fieldRules(true, 50),
@@ -406,6 +416,14 @@
   () => visible.value,
   val => {
      if (!val) {
         if (deviceListTimer) {
            clearTimeout(deviceListTimer)
            deviceListTimer = null
         }
         if (policeStationListTimer) {
            clearTimeout(policeStationListTimer)
            policeStationListTimer = null
         }
         viewEntity && viewer?.entities?.remove(viewEntity)
         viewEntity = null
         clearActiveTool()
@@ -420,6 +438,15 @@
   val => {
      updateActiveShapeAreaType(val)
   }
)
watch(
   () => shapeList.value,
   () => {
      if (isDialogInitializing.value) return
      queueDeviceListRefresh()
      queuePoliceStationRefresh()
   },
   { deep: true }
)
// 加载详情
@@ -469,6 +496,103 @@
      viewer.scene.primitives.remove(primitive)
   }
   deviceRangePrimitiveMap.clear()
}
function clearDeviceListState () {
   deviceOptions.value = []
   selectedDeviceRows.value = []
   formData.value.deviceIds = ''
   if (deviceTableRef.value) {
      deviceTableRef.value.clearSelection()
   }
}
function clearPoliceStationState () {
   policeStationOptions.value = []
   formData.value.policeStationId = ''
}
function formatWktNumber (value) {
   if (!Number.isFinite(Number(value))) return null
   return Number(value).toFixed(6)
}
function isSameWktPoint (left, right) {
   if (!left || !right) return false
   return Math.abs(left.lat - right.lat) < 0.000001 && Math.abs(left.lng - right.lng) < 0.000001
}
function buildPolygonWktFromPoints (points) {
   if (!Array.isArray(points) || points.length < 3) return null
   const ring = points
      .map(resolveLngLatPoint)
      .filter(Boolean)
      .map(point => ({ lng: point.lng, lat: point.lat }))
   if (ring.length < 3) return null
   const first = ring[0]
   const last = ring[ring.length - 1]
   if (!isSameWktPoint(first, last)) {
      ring.push({ ...first })
   }
   const coordText = ring
      .map(point => {
         const lng = formatWktNumber(point.lng)
         const lat = formatWktNumber(point.lat)
         if (lat === null || lng === null) return null
         return `${lng} ${lat}`
      })
      .filter(Boolean)
      .join(', ')
   if (!coordText) return null
   return `POLYGON((${coordText}))`
}
function buildEllipseWktFromMeta (center, semiMajor, semiMinor) {
   if (!viewer) return null
   const resolvedCenter = resolveLngLatPoint(center)
   if (!resolvedCenter) return null
   if (!Number.isFinite(Number(semiMajor)) || !Number.isFinite(Number(semiMinor))) return null
   const centerCartesian = Cesium.Cartesian3.fromDegrees(
      resolvedCenter.lng,
      resolvedCenter.lat,
      resolvedCenter.height || 0
   )
   const positions = buildEllipsePositions(centerCartesian, Number(semiMajor), Number(semiMinor))
   if (!positions.length) return null
   const points = positions
      .map(item => {
         const converted = cartesian3Convert(item, viewer)
         return { lng: converted.longitude, lat: converted.latitude }
      })
      .filter(Boolean)
   return buildPolygonWktFromPoints(points)
}
function resolveShapePolygonsWkt () {
   const polygons = []
   shapeList.value.forEach(shape => {
      if (!shape) return
      if (shape.drawType === 'buffer') {
         const radii = Array.isArray(shape?.meta?.bufferRadii)
            ? shape.meta.bufferRadii.filter(item => Number.isFinite(item) && item > 0)
            : []
         const maxRadius = radii.length ? Math.max(...radii) : null
         const wkt = buildEllipseWktFromMeta(shape?.meta?.center, maxRadius, maxRadius)
         if (wkt) polygons.push(wkt)
         return
      }
      if (shape.drawType === 'ellipse') {
         const wkt = buildEllipseWktFromMeta(shape?.meta?.center, shape?.meta?.semiMajor, shape?.meta?.semiMinor)
         if (wkt) {
            polygons.push(wkt)
            return
         }
      }
      const points = getShapeDisplayPoints(shape)
      const wkt = buildPolygonWktFromPoints(points)
      if (wkt) polygons.push(wkt)
   })
   return polygons
}
async function renderDeviceRangeSingle (device) {
@@ -649,6 +773,10 @@
                     width: 2,
                     material: style.outline,
                  },
               })
               addBufferRadiusLabelEntity(dataSource, centerCartesian, radius, {
                  name: 'shape-display',
                  customData: { drawType: 'buffer', label: 'radius' },
               })
            })
         }
@@ -1060,18 +1188,77 @@
}
// 获取派出所列表
async function getPoliceStationList () {
   const res = await fwPoliceStationListApi()
function queuePoliceStationRefresh () {
   if (!visible.value) return
   if (policeStationListTimer) {
      clearTimeout(policeStationListTimer)
   }
   policeStationListTimer = setTimeout(() => {
      policeStationListTimer = null
      void getPoliceStationListByPolygons()
   }, 200)
}
async function getPoliceStationListByPolygons () {
   if (!visible.value) return
   const polygons = resolveShapePolygonsWkt()
   if (!polygons.length) {
      clearPoliceStationState()
      return
   }
   policeStationRequestToken += 1
   const requestToken = policeStationRequestToken
   const res = await fwPoliceStationQueryByPolygonsApi({ polygons })
   if (requestToken !== policeStationRequestToken) return
   policeStationOptions.value = res?.data?.data ?? []
   await nextTick()
   const matched = policeStationOptions.value.find(
      item => String(item.id) === String(formData.value.policeStationId)
   )
   if (!matched) {
      formData.value.policeStationId = ''
   }
}
// 获取设备列表
async function getDeviceList () {
   if (deviceOptions.value.length) return
   const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id, isTrack: 1 })
function queueDeviceListRefresh () {
   if (!visible.value) return
   if (deviceListTimer) {
      clearTimeout(deviceListTimer)
   }
   deviceListTimer = setTimeout(() => {
      deviceListTimer = null
      void getDeviceListByPolygons()
   }, 200)
}
async function getDeviceListByPolygons () {
   if (!visible.value) return
   const polygons = resolveShapePolygonsWkt()
   if (!polygons.length) {
      clearDeviceListState()
      return
   }
   deviceListRequestToken += 1
   const requestToken = deviceListRequestToken
   const res = await fwDeviceListByPolygonsApi({
      isAreaSelect: 1,
      isTrack: 1,
      polygons,
   })
   if (requestToken !== deviceListRequestToken) return
   deviceOptions.value = res?.data?.data ?? []
   if (dialogMode.value === 'view') {
      deviceOptions.value = deviceOptions.value.filter(item => formData.value.deviceIds.includes(item.id))
   if (dialogMode.value === 'view' && formData.value.deviceIds) {
      const targetIds = formData.value.deviceIds.split(',')
      deviceOptions.value = deviceOptions.value.filter(item => targetIds.includes(String(item.id)))
   }
   await nextTick()
   const prevSuppressDeviceFly = suppressDeviceFly.value
   suppressDeviceFly.value = true
   syncDeviceSelection()
   suppressDeviceFly.value = prevSuppressDeviceFly
   if (!readonly.value) {
      renderDeviceRanges(selectedDeviceRows.value)
   }
}
@@ -1105,14 +1292,16 @@
// 同步选择状态
function syncDeviceSelection () {
   if (isDialogInitializing.value && !deviceOptions.value.length) return
   const rows = []
   const arr = formData.value.deviceIds.split(',')
   const arr = formData.value.deviceIds ? formData.value.deviceIds.split(',') : []
   deviceOptions.value.forEach(row => {
      if (arr.includes(row.id)) {
      if (arr.includes(String(row.id))) {
         rows.push(row)
      }
   })
   selectedDeviceRows.value = rows
   formData.value.deviceIds = rows.map(item => item.id).join(',')
   if (readonly.value) {
      renderDeviceRanges(selectedDeviceRows.value)
      return
@@ -1126,9 +1315,12 @@
// 打开弹框
async function open ({ mode, row } = {}) {
   isDialogInitializing.value = true
   dialogMode.value = mode || 'add'
   formData.value = dialogMode.value === 'add' ? initForm() : row
   selectedDeviceRows.value = []
   deviceOptions.value = []
   policeStationOptions.value = []
   shapeList.value = []
   activeShapeId.value = null
   activeAreaType.value = ''
@@ -1140,8 +1332,8 @@
   viewEntity && viewer?.entities?.remove(viewEntity)
   viewEntity = null
   clearActiveTool()
   await getDeviceList()
   if (dialogMode.value === 'add') {
      mapRef.value?.zoomToAdminBoundary?.()
      // default no draw mode
   } else if (dialogMode.value === 'edit') {
      await loadDetail()
@@ -1159,13 +1351,20 @@
   if (!readonly.value) {
      renderDeviceRanges(selectedDeviceRows.value)
   }
   isDialogInitializing.value = false
   queueDeviceListRefresh()
   queuePoliceStationRefresh()
}
onMounted(() => {
   getPoliceStationList()
})
onBeforeUnmount(() => {
   if (deviceListTimer) {
      clearTimeout(deviceListTimer)
      deviceListTimer = null
   }
   if (policeStationListTimer) {
      clearTimeout(policeStationListTimer)
      policeStationListTimer = null
   }
   hideTypePanel()
})