吉安感知网项目-前端
张含笑
2026-01-30 6c4bd2467bb6651c6edcf8a9741dcd4e9b1aa6d3
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -1,72 +1,62 @@
<template>
   <el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false"
      :close-on-click-modal="false">
   <el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false" :close-on-click-modal="false">
      <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"
            />
            <div
               v-if="showTypePanel && !readonly"
               class="shape-type-panel"
            >
            <CommonCesiumMap ref="mapRef" class="leftMap command-cesium" :active="visible" :flat-mode="false"
               :terrain="true" :layer-mode="4" :boundary="false" />
            <div v-if="showTypePanel && !readonly" class="shape-type-panel">
               <div class="panel-header">
                  <span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span>
                  <el-icon class="panel-close" @click.stop="handleTypePanelClose"><Close /></el-icon>
                  <el-icon class="panel-close" @click.stop="handleTypePanelClose">
                     <Close />
                  </el-icon>
               </div>
               <div class="panel-body">
                  <span class="panel-label">区域类型</span>
                  <el-select
                     class="command-select"
                     popper-class="command-select-popper"
                     v-model="formData.areaType"
                     placeholder="请选择"
                     @change="handleAreaTypeChange"
                  >
                     <el-option
                        v-for="item in dictObj.areaType"
                        :key="item.dictKey"
                        :label="item.dictValue"
                        :value="item.dictKey"
                     />
                  <el-select class="command-select" popper-class="command-select-popper" v-model="activeAreaType"
                     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>
               </div>
            </div>
            <div class="map-toolbar">
               <button class="tool-btn" type="button" @click="handleDraw('polygon')">
                  <span class="tool-icon polygon"></span>
                  <span class="tool-text">多边形</span>
               </button>
               <button class="tool-btn" type="button" @click="handleDraw('rectangle')">
                  <span class="tool-icon rect"></span>
                  <span class="tool-text">矩形</span>
               </button>
               <button class="tool-btn" type="button" @click="handleDraw('ellipse')">
                  <span class="tool-icon ellipse"></span>
                  <span class="tool-text">椭圆</span>
               </button>
               <button class="tool-btn" type="button" @click="handleDraw('buffer')">
                  <span class="tool-icon buffer"></span>
                  <span class="tool-text">缓冲圆</span>
               </button>
               <button class="tool-btn danger" type="button" @click="handleClearShape">
                  <span class="tool-icon trash"></span>
                  <span class="tool-text">删除</span>
               </button>
               <el-tooltip content="多边形" placement="left">
                  <button class="tool-btn" type="button" @click="handleDraw('polygon')">
                     <img class="tool-icon-image" :src="polygonIcon" alt="多边形" />
                  </button>
               </el-tooltip>
               <el-tooltip content="矩形" placement="left">
                  <button class="tool-btn" type="button" @click="handleDraw('rectangle')">
                     <img class="tool-icon-image" :src="rectIcon" alt="矩形" />
                  </button>
               </el-tooltip>
               <el-tooltip content="椭圆" placement="left">
                  <button class="tool-btn" type="button" @click="handleDraw('ellipse')">
                     <img class="tool-icon-image" :src="ellipseIcon" alt="椭圆" />
                  </button>
               </el-tooltip>
               <el-tooltip content="缓冲圆" placement="left">
                  <button class="tool-btn" type="button" @click="handleDraw('buffer')">
                     <img class="tool-icon-image" :src="bufferIcon" alt="缓冲圆" />
                  </button>
               </el-tooltip>
               <el-tooltip content="删除" placement="left">
                  <button class="tool-btn danger" type="button" @click="handleClearShape">
                     <img class="tool-icon-image" :src="trashIcon" alt="删除" />
                  </button>
               </el-tooltip>
            </div>
         </div>
         <div class="right-container">
            <div class="header">
               <span>{{ titleEnum[dialogMode] }}</span>
               <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
               <el-icon class="close-btn" @click.stop="visible = false">
                  <Close />
               </el-icon>
            </div>
            <div class="content" v-if="readonly">
@@ -75,18 +65,6 @@
                  <el-col :span="24">
                     <div class="label">区域名称</div>
                     <div class="val">{{ formData.areaName }}</div>
                  </el-col>
                  <el-col :span="24">
                     <div class="label">区域位置</div>
                     <div class="val">{{ formatLocation(formData) }}</div>
                  </el-col>
                  <el-col :span="24">
                     <div class="label">区域面积</div>
                     <div class="val">{{ formData.areaSize || '-' }}k㎡</div>
                  </el-col>
                  <el-col :span="24">
                     <div class="label">区域类型</div>
                     <div class="val">{{ getDictLabel(formData.areaType, dictObj.areaType) }}</div>
                  </el-col>
                  <el-col :span="24">
                     <div class="label">触发条件</div>
@@ -112,11 +90,29 @@
                  </el-col>
               </el-row>
               <div class="detail-title">绘制区域列表</div>
               <div class="command-table-container">
                  <div class="command-table-content">
                     <el-table class="command-table" :data="shapeList" row-key="id">
                        <el-table-column prop="areaType" label="区域类型">
                           <template v-slot="{ row }">
                              {{ resolveShapeAreaTypeLabel(row) }}
                           </template>
                        </el-table-column>
                        <el-table-column prop="drawType" label="绘制类型">
                           <template v-slot="{ row }">
                              {{ drawTypeLabelMap[row.drawType] || '-' }}
                           </template>
                        </el-table-column>
                     </el-table>
                  </div>
               </div>
               <div class="detail-title">关联设备</div>
               <div class="command-table-container">
                  <div class="command-table-content">
                     <el-table class="command-table" ref="deviceTableRef" :data="deviceOptions"
                        row-key="id" @row-click="handleDeviceRowClick">
                     <el-table class="command-table" ref="deviceTableRef" :data="deviceOptions" row-key="id"
                        @row-click="handleDeviceRowClick">
                        <el-table-column prop="deviceName" label="设备名称" />
                        <el-table-column prop="deviceType" label="类型">
                           <template v-slot="{ row }">
@@ -131,25 +127,12 @@
            <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly"
               label-width="96px">
               <el-form-item label="区域名称" prop="areaName">
                  <el-input class="command-input" v-model="formData.areaName" maxlength="50"
                     placeholder="请输入" clearable />
               </el-form-item>
               <el-form-item label="区域位置" prop="longitude">
                  <div class="val">{{ formatLocation(formData) }}</div>
               </el-form-item>
               <el-form-item label="区域面积" prop="areaSize">
                  <div class="val">{{ formData.areaSize || '-' }}k㎡</div>
               </el-form-item>
               <el-form-item label="区域类型" prop="areaType">
                  <el-select class="command-select" popper-class="command-select-popper"
                     v-model="formData.areaType" placeholder="请选择" clearable>
                     <el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue"
                        :value="item.dictKey" />
                  </el-select>
                  <el-input class="command-input" v-model="formData.areaName" maxlength="50" placeholder="请输入"
                     clearable />
               </el-form-item>
               <el-form-item label="响应机制" prop="responseMechanism">
                  <el-input class="command-input" v-model="formData.responseMechanism"
                     maxlength="200" placeholder="请输入" clearable />
                  <el-input class="command-input" v-model="formData.responseMechanism" maxlength="200"
                     placeholder="请输入" clearable />
               </el-form-item>
               <el-form-item label="触发条件" prop="triggerCondition">
                  <el-select class="command-select" popper-class="command-select-popper"
@@ -166,10 +149,9 @@
                  </el-select>
               </el-form-item>
               <el-form-item label="可飞行时段" prop="flyDateStart">
                  <el-date-picker class="command-date-picker"
                     popper-class="command-date-picker-popper" v-model="flyDateRange"
                     type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"
                     :disabled-date="disabledFlyDate" :disabled-time="disabledFlyTime"
                  <el-date-picker class="command-date-picker" popper-class="command-date-picker-popper"
                     v-model="flyDateRange" type="datetimerange" range-separator="至" start-placeholder="开始时间"
                     end-placeholder="结束时间" :disabled-date="disabledFlyDate" :disabled-time="disabledFlyTime"
                     value-format="YYYY-MM-DD HH:mm:ss" clearable />
               </el-form-item>
               <el-form-item label="关联派出所" prop="policeStationId">
@@ -182,10 +164,10 @@
               <div class="shape-table-container">
                  <div class="shape-table-title">绘制区域列表</div>
                  <el-table class="command-table" :data="shapeList" row-key="id" height="360">
                     <el-table-column prop="areaType" label="类型">
                  <el-table class="command-table" :data="shapeList" row-key="id" height="auto">
                     <el-table-column prop="areaType" label="区域类型">
                        <template v-slot="{ row }">
                           {{ getDictLabel(row.areaType, dictObj.areaType) }}
                           {{ resolveShapeAreaTypeLabel(row) }}
                        </template>
                     </el-table-column>
                     <el-table-column prop="drawType" label="绘制类型">
@@ -208,15 +190,13 @@
                        关联设备
                     </div>
                     <el-input class="command-input" v-model="searchName" placeholder="请输入"
                        clearable></el-input>
                     <el-input class="command-input" v-model="searchName" placeholder="请输入" clearable></el-input>
                  </div>
                  <el-form-item prop="deviceIds" label-width="0">
                     <el-table class="command-table" ref="deviceTableRef"
                        :data="deviceOptions.filter(item => item.deviceName.includes(searchName))" row-key="id"
                        @selection-change="handleDeviceSelectionChange"
                        @row-click="handleDeviceRowClick">
                        @selection-change="handleDeviceSelectionChange" @row-click="handleDeviceRowClick">
                        <el-table-column type="selection" width="55" :reserve-selection="true" />
                        <el-table-column prop="deviceName" label="设备名称" />
                        <el-table-column prop="deviceType" label="类型">
@@ -230,7 +210,9 @@
            </el-form>
            <div class="footer">
               <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
               <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' :
                  '取消'
               }}</el-button>
               <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
                  :disabled="submitting" @click="handleSubmit">
                  确定
@@ -251,25 +233,23 @@
import { useRoute } from 'vue-router'
import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
import { DrawManager } from './shapeTools/DrawManager'
import { EditManager } from './shapeTools/EditManager'
import { DrawManager, EditManager, buildEllipsePositions } from '@/utils/cesium/shapeTools'
import { cartesian3Convert } from '@/utils/cesium/mapUtil'
import { buildEllipsePositions } from './shapeTools/shapeUtils'
import * as turf from '@turf/turf'
import * as Cesium from 'cesium'
import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
import { fwDeviceListApi } 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'
import bufferIcon from '@/assets/images/areaMap/buffer.png'
import trashIcon from '@/assets/images/areaMap/trash.png'
const initForm = () => ({
   areaName: '', // 区域名称
   areaSize: null, // 区域面积
   areaType: '', // 区域类型
   controlLevel: '', // 管控级别
   deviceIds: '', // 关联设备ID
   flyDateEnd: '', // 可飞行结束
   flyDateStart: '', // 可飞行开始
   latitude: null, // 区域中心纬度
   longitude: null, // 区域中心经度
   policeStationId: '', // 关联派出所id
   responseMechanism: '', // 响应机制
   triggerCondition: '', // 触发条件
@@ -293,6 +273,7 @@
const dictObj = inject('dictObj')
const mapRef = ref(null)
const currentShapeType = ref('polygon')
const activeAreaType = ref('')
const shapeList = ref([])
const activeShapeId = ref(null)
const drawTypeLabelMap = {
@@ -301,6 +282,7 @@
   ellipse: '椭圆',
   buffer: '缓冲圆',
}
const bufferAreaTypeValue = '1,2,3'
const showTypePanel = ref(false)
const suppressTypePanelOnce = ref(false)
const areaTypeStyleMap = {
@@ -330,8 +312,6 @@
const rules = {
   areaName: fieldRules(true, 50),
   longitude: fieldRules(true),
   areaType: fieldRules(true),
   responseMechanism: fieldRules(true, 200),
   triggerCondition: fieldRules(true, 200),
   controlLevel: fieldRules(true),
@@ -384,19 +364,28 @@
      return
   }
   console.log(shapeList.value, 1111111)
   const isValid = await formRef.value?.validate().catch(() => false)
   if (!isValid) return
   if (dialogMode.value === 'add' && Number(formData.value.areaSize) < 0.5) {
      ElMessage.warning('区域面积不能小于0.5km²,请重新绘制')
      return
   }
   submitting.value = true
   try {
      let str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
      formData.value.geom = `POLYGON((${str}))`
      await fwAreaDivideSubmitApi(formData.value)
      const areaDivide = { ...formData.value }
      delete areaDivide.geom
      const areaDivideExtList = shapeList.value.map(shape => {
         const areaTypeKey = shape?.areaType ?? ''
         const areaTypeValue = getDictLabel(areaTypeKey, resolveAreaTypeOptions())
         return {
            id: shape?.extId,
            areaDivideId: areaDivide?.id ?? '',
            areaCode: areaDivide?.areaCode ?? '',
            areaTypeKey,
            areaTypeValue,
            geomJson: JSON.stringify(shape),
         }
      })
      await fwAreaDivideSubmitApi({
         areaDivide,
         areaDivideExtList,
      })
      const actionText = dialogMode.value === 'add' ? '新增' : '编辑'
      saveOperationLog({
         requestUri: route.path,
@@ -432,7 +421,7 @@
   }
)
watch(
   () => formData.value.areaType,
   () => activeAreaType.value,
   val => {
      updateActiveShapeAreaType(val)
   }
@@ -442,14 +431,19 @@
async function loadDetail () {
   if (!formData.value.id) return
   const res = await fwAreaDivideDetailApi({ id: formData.value.id })
   formData.value = res?.data?.data ?? initForm()
   const detail = res?.data?.data ?? {}
   const nextForm = initForm()
   Object.keys(nextForm).forEach(key => {
      if (detail[key] !== undefined) {
         nextForm[key] = detail[key]
      }
   })
   if (detail?.id) {
      nextForm.id = detail.id
   }
   formData.value = nextForm
   flyDateRange.value = [formData.value.flyDateStart, formData.value.flyDateEnd].filter(Boolean)
}
// 格式化区域位置
function formatLocation (row) {
   if (row?.longitude == null || row?.latitude == null) return ''
   return `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
   syncShapeListFromDetail(detail)
}
// 格式化可飞行时间段
@@ -559,7 +553,7 @@
         flat: true,
         faceForward: true,
      }),
      asynchronous: false,
      asynchronous: false
   })
}
@@ -668,7 +662,7 @@
}
function renderShapeList () {
   if (!viewer || !visible.value || readonly.value) {
   if (!viewer || !visible.value) {
      clearShapeDisplay()
      return
   }
@@ -755,9 +749,6 @@
   const positions = Array.isArray(data?.positions) ? data.positions : data
   if (!Array.isArray(positions) || positions.length < 3) {
      pointList = []
      formData.value.areaSize = null
      formData.value.longitude = null
      formData.value.latitude = null
      hideTypePanel()
      return
   }
@@ -769,13 +760,15 @@
      const val = cartesian3Convert(item, viewer)
      return { ...val, lng: val.longitude, lat: val.latitude }
   })
   applyPolygonMetrics(pointList)
   const controlPoints = resolveControlPoints(meta, currentShapeType.value)
   if (!formData.value.areaType) {
   if (!activeAreaType.value) {
      const detectAreaType = getDetectAreaTypeKey()
      if (detectAreaType) {
         formData.value.areaType = detectAreaType
         activeAreaType.value = detectAreaType
      }
   }
   if (currentShapeType.value === 'buffer') {
      activeAreaType.value = bufferAreaTypeValue.split(',')
   }
   if (!suppressTypePanelOnce.value) {
      showTypePanelAtCurrent()
@@ -817,7 +810,7 @@
   }
   const shapePayload = {
      id: activeShapeId.value,
      areaType: formData.value.areaType,
      areaType: currentShapeType.value === 'buffer' ? bufferAreaTypeValue : activeAreaType.value,
      drawType: currentShapeType.value,
      points: ['ellipse', 'buffer'].includes(currentShapeType.value) && controlPoints.length
         ? controlPoints
@@ -833,29 +826,86 @@
   renderShapeList()
}
function applyPolygonMetrics(points) {
   if (!points?.length) return
   const polygon = turf.polygon([
      [...points.map(item => [item.longitude, item.latitude]), [points[0].longitude, points[0].latitude]],
   ])
   const center = turf.centerOfMass(polygon)
   const areaSqm = turf.area(polygon) // 平方米
   formData.value.areaSize = _.round(areaSqm / 1_000_000, 2) // 平方千米
   formData.value.longitude = center.geometry.coordinates[0]
   formData.value.latitude = center.geometry.coordinates[1]
}
function getDetectAreaTypeKey () {
   const areaTypeOptions = Array.isArray(dictObj?.value?.areaType)
      ? dictObj.value.areaType
      : dictObj?.areaType
   console.log(areaTypeOptions, 1)
   const areaTypeOptions = resolveAreaTypeOptions()
   if (!Array.isArray(areaTypeOptions)) return ''
   const target = areaTypeOptions.find(item => item?.dictKey === '1')
   return target?.dictKey ?? ''
}
function resolveAreaTypeOptions () {
   return Array.isArray(dictObj?.value?.areaType)
      ? dictObj.value.areaType
      : dictObj?.areaType
}
function resolveShapeAreaTypeLabel (shape) {
   return getDictLabel(shape?.areaType, resolveAreaTypeOptions())
}
function parseGeomJson (geomJson) {
   if (!geomJson) return null
   if (typeof geomJson === 'object') return geomJson
   if (typeof geomJson !== 'string') return null
   const trimmed = geomJson.trim()
   if (!trimmed) return null
   try {
      return JSON.parse(trimmed)
   } catch (error) {
      const legacyPoints = geomAnalysis(trimmed)
      if (Array.isArray(legacyPoints) && legacyPoints.length >= 3) {
         return { drawType: 'polygon', points: legacyPoints }
      }
   }
   return null
}
function syncShapeListFromDetail (detail) {
   const extList = Array.isArray(detail?.fwAreaDivideExtList) ? detail.fwAreaDivideExtList : []
   if (!extList.length) {
      shapeList.value = []
      activeAreaType.value = ''
      currentShapeType.value = 'polygon'
      return
   }
   shapeList.value = extList.map((item, index) => {
      const isShapePayload = item?.drawType || item?.points
      if (isShapePayload) {
         const areaType = item?.drawType === 'buffer'
            ? bufferAreaTypeValue
            : (item?.areaType ?? '')
         return {
            id: item?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
            extId: item?.extId ?? item?.id,
            areaType,
            drawType: item?.drawType ?? 'polygon',
            points: Array.isArray(item?.points) ? item.points : [],
            displayPoints: Array.isArray(item?.displayPoints) ? item.displayPoints : null,
            meta: item?.meta ?? null,
         }
      }
      const parsed = parseGeomJson(item?.geomJson) || {}
      const drawType = parsed?.drawType || 'polygon'
      const areaType = drawType === 'buffer'
         ? bufferAreaTypeValue
         : (item?.areaTypeKey ?? parsed?.areaType ?? '')
      const points = Array.isArray(parsed?.points) ? parsed.points : []
      const displayPoints = Array.isArray(parsed?.displayPoints) ? parsed.displayPoints : null
      const meta = parsed?.meta ?? null
      return {
         id: parsed?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
         extId: item?.id,
         areaType,
         drawType,
         points,
         displayPoints,
         meta,
      }
   })
   activeAreaType.value = shapeList.value[0]?.drawType === 'buffer'
      ? bufferAreaTypeValue.split(',')
      : (shapeList.value[0]?.areaType ?? '')
   currentShapeType.value = shapeList.value[0]?.drawType ?? 'polygon'
}
function showTypePanelAtCurrent () {
@@ -888,9 +938,8 @@
}
function updateActiveShapeAreaType (value) {
   if (currentShapeType.value !== 'buffer') {
      activeTool?.setStyle?.(getAreaTypeStyle(value))
   }
   if (currentShapeType.value === 'buffer') return
   activeTool?.setStyle?.(getAreaTypeStyle(value))
   if (!activeShapeId.value) return
   const targetIndex = shapeList.value.findIndex(item => item.id === activeShapeId.value)
   if (targetIndex === -1) return
@@ -916,7 +965,7 @@
   currentShapeType.value = type
   activeToolMode.value = 'draw'
   drawManager ||= new DrawManager(viewer)
   activeTool = drawManager.start(type, { style: getAreaTypeStyle(formData.value.areaType) })
   activeTool = drawManager.start(type, { style: getAreaTypeStyle(activeAreaType.value) })
   activeTool?.subscribe('getPolygonPositions', drawFinished)
}
@@ -924,7 +973,7 @@
   currentShapeType.value = type
   activeToolMode.value = 'edit'
   editManager ||= new EditManager(viewer)
   activeTool = editManager.start(type, points, { style: getAreaTypeStyle(formData.value.areaType) })
   activeTool = editManager.start(type, points, { style: getAreaTypeStyle(activeAreaType.value) })
   activeTool?.subscribe('getPolygonPositions', drawFinished)
}
@@ -936,22 +985,12 @@
   viewEntity = null
   hideTypePanel()
   renderShapeList()
   if (dialogMode.value === 'edit' && pointList.length >= 3) {
      const editPoints = _.cloneDeep(pointList)
      if (editPoints.length > 1) {
         const first = editPoints[0]
         const last = editPoints[editPoints.length - 1]
         if (first.longitude === last.longitude && first.latitude === last.latitude) {
            editPoints.pop()
         }
      }
      activeShapeId.value = activeShapeId.value || (shapeList.value[0]?.id ?? null)
      startEdit(type, editPoints)
      return
   }
   pointList = []
   const defaultAreaType = getDetectAreaTypeKey()
   if (defaultAreaType) {
      formData.value.areaType = defaultAreaType
   if (type === 'buffer') {
      activeAreaType.value = bufferAreaTypeValue
   } else if (defaultAreaType) {
      activeAreaType.value = defaultAreaType
   }
   activeShapeId.value = null
   startDraw(type)
@@ -965,9 +1004,7 @@
   pointList = []
   shapeList.value = []
   activeShapeId.value = null
   formData.value.areaSize = null
   formData.value.longitude = null
   formData.value.latitude = null
   activeAreaType.value = ''
   hideTypePanel()
   clearShapeDisplay()
}
@@ -977,7 +1014,7 @@
   if (activeToolMode.value === 'edit' && activeShapeId.value === row.id) return
   activeShapeId.value = row.id
   currentShapeType.value = row.drawType
   formData.value.areaType = row.areaType
   activeAreaType.value = row.drawType === 'buffer' ? bufferAreaTypeValue.split(',') : row.areaType
   const displayPoints = getShapeDisplayPoints(row)
   pointList = _.cloneDeep(displayPoints)
   clearActiveTool()
@@ -985,7 +1022,6 @@
      ? { points: displayPoints, meta: row.meta }
      : row.points
   startEdit(row.drawType, editPayload)
   applyPolygonMetrics(pointList)
   showTypePanelAtCurrent()
   renderShapeList()
}
@@ -998,15 +1034,17 @@
      activeShapeId.value = null
      clearActiveTool()
      pointList = []
      formData.value.areaSize = null
      formData.value.longitude = null
      formData.value.latitude = null
      hideTypePanel()
   }
}
// 查看面
function viewPolygon () {
   if (!viewer) return
   if (shapeList.value.length) {
      renderShapeList()
      return
   }
   if (!formData.value?.geom) return
   pointList = geomAnalysis(formData.value.geom)
   const result = pointList.map(item => [item.longitude, item.latitude]).flat()
@@ -1057,17 +1095,23 @@
// 同步选择状态
function syncDeviceSelection () {
   if (!deviceTableRef.value) return
   deviceTableRef.value.clearSelection()
   const rows = []
   const arr = formData.value.deviceIds.split(',')
   deviceOptions.value.forEach(row => {
      if (arr.includes(row.id)) {
         deviceTableRef.value.toggleRowSelection(row, true)
         rows.push(row)
      }
   })
   selectedDeviceRows.value = rows
   if (readonly.value) {
      renderDeviceRanges(selectedDeviceRows.value)
      return
   }
   if (!deviceTableRef.value) return
   deviceTableRef.value.clearSelection()
   rows.forEach(row => {
      deviceTableRef.value.toggleRowSelection(row, true)
   })
}
// 打开弹框
@@ -1077,6 +1121,8 @@
   selectedDeviceRows.value = []
   shapeList.value = []
   activeShapeId.value = null
   activeAreaType.value = ''
   pointList = []
   await nextTick()
   initMap()
   clearShapeDisplay()
@@ -1089,18 +1135,6 @@
      // default no draw mode
   } else if (dialogMode.value === 'edit') {
      await loadDetail()
      pointList = geomAnalysis(formData.value.geom) || []
      if (pointList.length) {
         activeShapeId.value = `shape_${Date.now()}_${Math.random().toString(16).slice(2, 6)}`
         shapeList.value = [
            {
               id: activeShapeId.value,
               areaType: formData.value.areaType,
               drawType: currentShapeType.value,
               points: _.cloneDeep(pointList),
            },
         ]
      }
      viewPolygon()
   } else {
      await loadDetail()
@@ -1188,20 +1222,20 @@
}
.tool-btn {
   padding: 0;
   display: flex;
   flex-direction: column;
   align-items: center;
   gap: 6px;
   width: 64px;
   padding: 8px 6px;
   background: rgba(255, 255, 255, 0.08);
   border: 1px solid rgba(255, 255, 255, 0.18);
   border-radius: 6px;
   border-radius: 8px;
   color: #ffffff;
   font-size: 12px;
   line-height: 1;
   cursor: pointer;
   transition: all 0.2s ease;
   overflow: hidden;
}
.tool-btn:hover {
@@ -1221,10 +1255,11 @@
.tool-icon {
   position: relative;
   width: 20px;
   height: 20px;
   border: 2px solid #ffffff;
   border-radius: 3px;
}
.tool-icon-image {
   width: 36px;
   height: 36px;
}
.tool-icon.polygon {
@@ -1272,23 +1307,21 @@
}
.shape-table-container {
   margin-top: 8px;
   padding: 8px;
   border-radius: 8px;
   background: rgba(27, 34, 56, 0.6);
}
   margin-bottom: 20px;
.shape-table-title {
   display: flex;
    align-items: center;
    justify-content: flex-start;
    padding-right: 1.2rem;
    font-family: Source Han Sans CN, Source Han Sans CN;
    font-weight: 500;
    font-size: 1.4rem;
    color: #D4D5D7;
    font-style: normal;
    text-transform: none;
    box-sizing: border-box;
   .shape-table-title {
      margin-bottom: 20px;
      display: flex;
      align-items: center;
      justify-content: flex-start;
      padding-right: 1.2rem;
      font-family: Source Han Sans CN, Source Han Sans CN;
      font-weight: 500;
      font-size: 1.4rem;
      color: #D4D5D7;
      font-style: normal;
      text-transform: none;
      box-sizing: border-box;
   }
}
</style>