吉安感知网项目-前端
shuishen
2026-01-29 6dc66c2d76c74ab15c6c8f75b9804ad2adbb0785
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -1,72 +1,61 @@
<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="activeAreaType"
                     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="请选择" @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">
@@ -100,11 +89,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 }">
                              {{ getDictLabel(row.areaType, dictObj.areaType) }}
                           </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 }">
@@ -119,12 +126,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-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"
@@ -141,10 +148,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">
@@ -157,7 +163,7 @@
               <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 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) }}
@@ -183,15 +189,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="类型">
@@ -205,7 +209,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">
                  确定
@@ -233,6 +239,11 @@
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: '', // 区域名称
@@ -357,9 +368,24 @@
   if (!isValid) 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,
@@ -416,8 +442,8 @@
      nextForm.id = detail.id
   }
   formData.value = nextForm
   activeAreaType.value = detail?.areaType ?? ''
   flyDateRange.value = [formData.value.flyDateStart, formData.value.flyDateEnd].filter(Boolean)
   syncShapeListFromDetail(detail)
}
// 格式化可飞行时间段
@@ -527,7 +553,7 @@
         flat: true,
         faceForward: true,
      }),
      asynchronous: false,
      asynchronous: false
   })
}
@@ -636,7 +662,7 @@
}
function renderShapeList () {
   if (!viewer || !visible.value || readonly.value) {
   if (!viewer || !visible.value) {
      clearShapeDisplay()
      return
   }
@@ -798,13 +824,74 @@
}
function getDetectAreaTypeKey () {
   const areaTypeOptions = Array.isArray(dictObj?.value?.areaType)
      ? dictObj.value.areaType
      : dictObj?.areaType
   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 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) {
         return {
            id: item?.id || `shape_${Date.now()}_${index}_${Math.random().toString(16).slice(2, 6)}`,
            extId: item?.extId ?? item?.id,
            areaType: item?.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 = 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]?.areaType ?? ''
   currentShapeType.value = shapeList.value[0]?.drawType ?? 'polygon'
}
function showTypePanelAtCurrent () {
@@ -950,6 +1037,11 @@
// 查看面
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()
@@ -1000,17 +1092,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)
   })
}
// 打开弹框
@@ -1021,6 +1119,7 @@
   shapeList.value = []
   activeShapeId.value = null
   activeAreaType.value = ''
   pointList = []
   await nextTick()
   initMap()
   clearShapeDisplay()
@@ -1033,18 +1132,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: activeAreaType.value,
               drawType: currentShapeType.value,
               points: _.cloneDeep(pointList),
            },
         ]
      }
      viewPolygon()
   } else {
      await loadDetail()
@@ -1132,20 +1219,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 {
@@ -1165,10 +1252,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 {
@@ -1216,23 +1304,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>