From 2dbd3103d91934a6ec6ef48baa288b56a4843472 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Mon, 12 Jan 2026 19:41:26 +0800
Subject: [PATCH] feat: 完成区域
---
applications/drone-command/src/utils/cesium/DrawPolygon.js | 70 ++--
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue | 379 +++++++++++++++++++++++++++++++
applications/drone-command/src/views/areaManage/partition/index.vue | 179 ++++++++++++++
applications/drone-command/src/views/areaManage/partition/partitionApi.js | 55 ++++
applications/drone-command/src/utils/cesium/mapUtil.js | 17 -
5 files changed, 646 insertions(+), 54 deletions(-)
diff --git a/applications/drone-command/src/utils/cesium/DrawPolygon.js b/applications/drone-command/src/utils/cesium/DrawPolygon.js
index 59cbf6c..6c37a35 100644
--- a/applications/drone-command/src/utils/cesium/DrawPolygon.js
+++ b/applications/drone-command/src/utils/cesium/DrawPolygon.js
@@ -88,18 +88,18 @@
// ============ 发布订阅机制 ============
// 外部订阅数据变化
- subscribe (key, listener) {
+ subscribe(key, listener) {
this.listeners.push({ key, listener })
}
// 通知订阅者
- notify (key, data) {
+ notify(key, data) {
this.listeners.filter(subscriber => subscriber.key === key).forEach(subscriber => subscriber.listener(data))
}
// ============ 绘制相关 ============
// 编辑图斑
- editThePatch (data) {
+ editThePatch(data) {
this.isPreviewMode = data
// 关闭编辑能力时隐藏端点/中点,并清空中点,避免残留交互
if (!this.isPreviewMode) {
@@ -117,12 +117,12 @@
}
}
// 删除测区
- deleteTheArea (data) {
+ deleteTheArea(data) {
this.isDeleteTheArea = data
}
// 开始绘制
- startDrawing () {
+ startDrawing() {
this.drawingMode = true
this.curPolygon = new Cesium.PolygonHierarchy()
@@ -150,7 +150,7 @@
}
// 创建多边形实体(含边界线)
- createPolygonEntity () {
+ createPolygonEntity() {
this.polygonEntity = this.editPolygonDataSource.entities.add({
name: DrawPolygon.ENTITY_NAMES.POLYGON,
id: DrawPolygon.ENTITY_NAMES.POLYGON_ID,
@@ -174,7 +174,7 @@
}
// 创建端点实体
- createPointEntity (position, isAdd) {
+ createPointEntity(position, isAdd) {
const pointIndex = isAdd ? this.curPolygon.positions.length - 2 : this.curPolygon.positions.length - 1
this.editPolygonPointDataSource.entities.add({
@@ -193,7 +193,7 @@
})
}
- createMidPointEntity (startInd, position) {
+ createMidPointEntity(startInd, position) {
// 使用 CallbackProperty 让中点位置随端点拖拽实时计算
// startInd 表示该中点属于边:(startInd) -> (startInd + 1)
const updatePosition = () => {
@@ -224,7 +224,7 @@
})
}
- rebuildEditPoints () {
+ rebuildEditPoints() {
// 插入/删除端点后:端点实体 id 与 customData.ind 需要全量重建,保证索引与 positions 一致
if (!this.isPreviewMode) return
if (!this.editPolygonPointDataSource || !this.curPolygon?.positions) return
@@ -248,7 +248,7 @@
})
}
- rebuildMidPoints () {
+ rebuildMidPoints() {
// 仅在“顶点数量变化/切换编辑态”时维护中点实体数量
// 拖拽过程中中点位置由 CallbackProperty 自动更新,不需要重建
if (!this.isPreviewMode) return
@@ -286,7 +286,7 @@
})
}
- insertPointFromMidPoint (midPointEntity) {
+ insertPointFromMidPoint(midPointEntity) {
// 点击中点:在对应边上插入一个新端点(白点),并立即进入拖拽态
if (!this.isPreviewMode) return
if (!this.editingMode) return
@@ -319,7 +319,7 @@
this.notify('getPolygonPositions', this.curPolygon.positions)
}
// 清除不在范围内的点
- removeLastInvalidPoint () {
+ removeLastInvalidPoint() {
const posLen = this.curPolygon.positions.length
if (posLen === 0) return
@@ -348,7 +348,7 @@
}
}
// 添加一个点
- addPosition (position, isAdd = true) {
+ addPosition(position, isAdd = true) {
// 第一个点要重复压入一次,形成动态绘制效果
if (this.curPolygon.positions.length === 0 && isAdd) {
this.curPolygon.positions.push(position.clone())
@@ -372,7 +372,7 @@
// ============ 鼠标事件 ============
// 鼠标左键按下(选中端点拖动)
- handleLeftDown (movement) {
+ handleLeftDown(movement) {
if (!this.editingMode) return
const pickedEntity = this.viewer.scene.pick(movement.position)?.id
@@ -393,7 +393,7 @@
}
// 鼠标左键抬起(拖拽结束)
- handleLeftUp () {
+ handleLeftUp() {
if (!(this.editingMode && this.curPolygon?.positions && this.draggedEntity)) return
if (this.currentDragPointIsValid && this.isDragging) {
@@ -426,7 +426,7 @@
}
// 鼠标移动
- handleMouseMove (movement) {
+ handleMouseMove(movement) {
if (!this.drawingMode && !this.editingMode) return
const cartesian = this.viewer.scene.pickPosition(movement.endPosition)
@@ -464,7 +464,7 @@
}
// 鼠标左键点击
- handleLeftClick (click) {
+ handleLeftClick(click) {
this.removeMenuPopup()
const pickedAllEntity = this.viewer.scene.drillPick(click.position).filter(i => i.id)
@@ -520,7 +520,7 @@
}
// 鼠标右键点击(弹出菜单)
- handleRightClick (click) {
+ handleRightClick(click) {
const that = this
if (that.drawingMode) return
@@ -556,7 +556,7 @@
// ============ 删除相关 ============
// 删除所有实体
- removeEntities () {
+ removeEntities() {
if (this.editPolygonDataSource) {
this.editPolygonDataSource.entities.removeAll()
this.editPolygonDataSource = null
@@ -577,7 +577,7 @@
}
// 完成绘制
- finishDrawing () {
+ finishDrawing() {
this.curPolygon.positions.pop()
if (this.curPolygon.positions.length >= 3) {
@@ -603,20 +603,20 @@
}
// 删除多边形
- delPolygon () {
+ delPolygon() {
this.removeEntities()
this.removeMenuPopup()
this.notify('getPolygonPositions', [])
this.startDrawing()
}
// 删除图斑
- delSpot () {
+ delSpot() {
this.removeEntities()
this.isPreviewMode = true
this.startDrawing()
}
// 删除端点
- delPoint () {
+ delPoint() {
if (this.curPolygon.positions.length <= 3) {
this.removeMenuPopup()
return ElMessage.warning('端点不可少于3个')
@@ -634,7 +634,7 @@
// ============ 工具方法 ============
// 创建右键菜单
- createMenuPopup (type = 'polygon') {
+ createMenuPopup(type = 'polygon') {
const menuPopupVBox = document.createElement('div')
menuPopupVBox.id = 'planarPolygonEdit'
menuPopupVBox.className = 'planar-polygon-edit-tooltip'
@@ -660,7 +660,7 @@
}
// 移除菜单
- removeMenuPopup () {
+ removeMenuPopup() {
const that = this
if (that.menuPopup) {
that.menuPopup.removeEventListener('click', that.delPolygon)
@@ -672,13 +672,13 @@
}
// 更新多边形样式(正常/错误)
- updatePolygonAppearance (polygonColor, lineColor) {
+ updatePolygonAppearance(polygonColor, lineColor) {
this.polygonEntity.polygon.material = polygonColor
this.polygonEntity.polyline.material = lineColor
}
// 禁用地图交互
- disableMapControl () {
+ disableMapControl() {
const controller = this.viewer.scene.screenSpaceCameraController
controller.enableRotate = false
controller.enableTranslate = false
@@ -686,7 +686,7 @@
}
// 启用地图交互
- enableMapControl () {
+ enableMapControl() {
const controller = this.viewer.scene.screenSpaceCameraController
controller.enableRotate = true
controller.enableTranslate = true
@@ -694,7 +694,7 @@
}
// 检查多边形是否自交
- curDragPointIsValid (positions) {
+ curDragPointIsValid(positions) {
if (positions.length < 3) return true
const cartographics = Cesium.Ellipsoid.WGS84.cartesianArrayToCartographicArray(positions)
@@ -710,12 +710,12 @@
return intersections.features.length === 0
}
// 初始化已有多边形
- async initPolygon (viewer, positions, isPurePreview = false) {
+ async initPolygon(viewer, positions, isPurePreview = false) {
this.initHandler(viewer)
this.isPureSpotPreview = isPurePreview
this.startDrawing()
let newPosition = positions.map(item => {
- return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item?.height||0))
+ return Cesium.Cartesian3.fromDegrees(Number(item.lng), Number(item.lat), Number(item?.height || 0))
})
// 预览航线的时候调用
if (!this.isPureSpotPreview) {
@@ -737,7 +737,7 @@
duration: 0.5,
})
- const pointList = await getPointPositionsHeight(positions, viewer)
+ let pointList = await getPointPositionsHeight(positions, viewer)
flyVisual({ positionsData: pointList.map(item => [item.lng, item.lat, item.ASL]), viewer })
this.drawingMode = false
@@ -748,7 +748,7 @@
}
// 初始化事件处理器
- initHandler (viewer) {
+ initHandler(viewer) {
this.viewer = viewer
this.startDrawing()
@@ -771,7 +771,7 @@
}
// 移除事件处理器
- removeHandler () {
+ removeHandler() {
if (this.handler) {
const eventTypes = [
Cesium.ScreenSpaceEventType.LEFT_DOWN,
@@ -792,7 +792,7 @@
/**
* 销毁实例,释放资源
*/
- destroy () {
+ destroy() {
if (!this.viewer) return
this.removeMenuPopup()
diff --git a/applications/drone-command/src/utils/cesium/mapUtil.js b/applications/drone-command/src/utils/cesium/mapUtil.js
index 117ada6..89bf787 100644
--- a/applications/drone-command/src/utils/cesium/mapUtil.js
+++ b/applications/drone-command/src/utils/cesium/mapUtil.js
@@ -346,31 +346,24 @@
return new Promise((resolve, reject) => {
if (!data || !data.length) {
resolve([])
-
return
}
-
// 假设 viewer 已经初始化并且 terrainProvider 是有效的
const terrainProvider = viewer?.terrainProvider
-
// 创建 Cartographic 对象
const cartographics = data.map(item => {
const { lng, lat } = item
-
return Cesium.Cartographic.fromDegrees(Number(lng), Number(lat))
})
-
// 获取地形数据的Promise
const promise = Cesium.sampleTerrainMostDetailed(terrainProvider, cartographics)
-
// 使用 Cesium.when 处理 Promise
promise
.then(function (updatedPositions) {
- // updatedPositions 是一个数组,包含更新后的 Cartographic 对象
+ // 是一个数组,包含更新后的 Cartographic 对象
const newPosition = updatedPositions.map((item, index) => {
const longitude = Cesium.Math.toDegrees(item.longitude)
const latitude = Cesium.Math.toDegrees(item.latitude)
-
let pointData = {
...data[index],
longitude,
@@ -378,18 +371,14 @@
ASL: Number(item.height),
customHeight: Number(item.height),
}
-
if (droneHeight) pointData.TH = Number(item.height) + Number(droneHeight)
-
return pointData
})
-
resolve(newPosition)
- // 在这里,你可以使用 height 值进行后续操作
})
.catch(function (error) {
- // console.error('获取高程时发生错误:', error);
- reject(error)
+ console.log('获取高程时发生错误:', error)
+ resolve(data.map(item => ({ ...item, ASL: 0, customHeight: 0, longitude: item.lng, latitude: item.lat })))
})
})
}
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
new file mode 100644
index 0000000..c5b99ab
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -0,0 +1,379 @@
+<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">
+ <el-row>
+ <el-col :span="24">
+ <div>区域名称: {{ formData.areaName }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div>区域位置: {{ formatLocation(formData) }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div>区域面积: {{ formData.areaSize }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div>区域类型: {{ getOptionLabel(formData.areaType, areaTypeOptions) }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div>触发条件: {{ formData.triggerCondition }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div>响应机制: {{ formData.responseMechanism }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div>管控级别: {{ getOptionLabel(formData.controlLevel, controlLevelOptions) }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div>关联派出所: {{ formData.policeStationName }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div>可飞行时间段: {{ formatFlyDate(formData) }}</div>
+ </el-col>
+ </el-row>
+ </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="areaName">
+ <el-input v-model="formData.areaName" maxlength="50" placeholder="请输入" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="区域位置" prop="longitude">
+ <div>{{ formatLocation(formData) }}</div>
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="区域面积" prop="areaSize">
+ {{ formData.areaSize }}
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="区域类型" prop="areaType">
+ <el-select v-model="formData.areaType" placeholder="请选择" clearable>
+ <el-option
+ v-for="item in areaTypeOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="响应机制" prop="responseMechanism">
+ <el-input v-model="formData.responseMechanism" maxlength="200" placeholder="请输入" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="触发条件" prop="triggerCondition">
+ <el-input v-model="formData.triggerCondition" maxlength="200" placeholder="请输入" clearable />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="管控级别" prop="controlLevel">
+ <el-select v-model="formData.controlLevel" placeholder="请选择" clearable>
+ <el-option
+ v-for="item in controlLevelOptions"
+ :key="item.value"
+ :label="item.label"
+ :value="item.value"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="可飞行时段" prop="flyDateRange">
+ <el-date-picker
+ v-model="flyDateRange"
+ type="datetimerange"
+ range-separator="至"
+ start-placeholder="开始时间"
+ end-placeholder="结束时间"
+ value-format="YYYY-MM-DD HH:mm:ss"
+ clearable
+ />
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="关联派出所" prop="policeStationId">
+ <el-select v-model="formData.policeStationId" placeholder="请选择" clearable>
+ <el-option
+ v-for="item in policeStationOptions"
+ :key="item.id"
+ :label="item.stationName"
+ :value="item.id"
+ />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="24">
+ <el-form-item label="关联设备" prop="deviceIds">
+ <el-select v-model="deviceIdList" multiple collapse-tags placeholder="请选择" clearable>
+ <el-option v-for="item in deviceOptions" :key="item.id" :label="item.deviceName" :value="item.id" />
+ </el-select>
+ </el-form-item>
+ </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, nextTick, onMounted, ref, watch } from 'vue'
+import { ElMessage } from 'element-plus'
+import { fwAreaDivideDetailApi, fwAreaDivideSubmitApi, fwDevicePageApi, fwPoliceStationPageApi } from './partitionApi'
+import { fieldRules } 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 = () => ({
+ areaName: '', // 区域名称
+ areaSize: null, // 区域面积
+ areaType: '', // 区域类型
+ controlLevel: '', // 管控级别
+ deviceIds: '', // 关联设备ID
+ flyDateEnd: '', // 可飞行结束
+ flyDateStart: '', // 可飞行开始
+ latitude: null, // 区域中心纬度
+ longitude: null, // 区域中心经度
+ policeStationId: '', // 关联派出所id
+ responseMechanism: '', // 响应机制
+ triggerCondition: '', // 触发条件
+})
+
+const areaTypeOptions = [
+ { label: '报警区', value: '报警区' },
+ { label: '重点管制区', value: '重点管制区' },
+]
+const controlLevelOptions = [
+ { label: '日常', value: '日常' },
+ { label: '重点', value: '重点' },
+ { label: '严密', value: '严密' },
+]
+
+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 flyDateRange = ref([]) // 飞行时间
+const deviceIdList = ref([]) // 关联设备ID
+const policeStationOptions = ref([]) // 关联派出所
+const deviceOptions = ref([]) // 关联设备
+let viewer
+let drawPolygonExample
+let pointList = []
+
+const rules = {
+ areaName: fieldRules(true, 50),
+ areaType: fieldRules(true, 0),
+ controlLevel: fieldRules(true, 0),
+}
+
+// 关闭弹框
+function handleCancel() {
+ visible.value = false
+}
+
+// 提交新增/编辑
+async function handleSubmit() {
+ const isValid = await formRef.value?.validate().catch(() => false)
+ if (!isValid) return
+ submitting.value = true
+ try {
+ const [start, end] = flyDateRange.value ?? []
+ formData.value.flyDateStart = start || ''
+ formData.value.flyDateEnd = end || ''
+ formData.value.deviceIds = deviceIdList.value.length ? deviceIdList.value.join(',') : ''
+ let str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
+ formData.value.geom = `POLYGON((${str})) | 4326`
+ await fwAreaDivideSubmitApi(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 fwAreaDivideDetailApi({ id: formData.value.id })
+ formData.value = res?.data?.data ?? initForm()
+ flyDateRange.value = [formData.value.flyDateStart, formData.value.flyDateEnd].filter(Boolean)
+ deviceIdList.value = formData.value.deviceIds ? formData.value.deviceIds.split(',') : []
+}
+
+// 格式化区域位置
+function formatLocation(row) {
+ if (row?.longitude == null || row?.latitude == null) return ''
+ return `${row.longitude}, ${row.latitude}`
+}
+
+// 格式化可飞行时间段
+function formatFlyDate(row) {
+ if (!row?.flyDateStart && !row?.flyDateEnd) return ''
+ return `${row.flyDateStart || '-'} ~ ${row.flyDateEnd || '-'}`
+}
+
+// 获取选项文本
+function getOptionLabel(value, options) {
+ const match = options.find(item => item.value === value)
+ return match?.label ?? value ?? ''
+}
+
+// 初始化地图实例
+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 }
+ })
+ const polygon = turf.polygon([
+ [...pointList.map(item => [item.longitude, item.latitude]), [pointList[0].longitude, pointList[0].latitude]],
+ ])
+ const center = turf.centerOfMass(polygon)
+ formData.value.areaSize = _.round(turf.area(polygon), 2)
+ formData.value.longitude = center.geometry.coordinates[0]
+ formData.value.latitude = center.geometry.coordinates[1]
+}
+
+// 新增模式绘制
+function addPolygon() {
+ drawPolygonExample = new DrawPolygon(viewer)
+ drawPolygonExample.initHandler(viewer)
+ drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
+}
+
+// 解析经纬度
+function getLonLat(str) {
+ if (!str) return []
+ return str
+ .replace('POLYGON((', '')
+ .replace(')) | 4326', '')
+ .split(',')
+ .map(pair => {
+ const [lat, lon] = pair.trim().split(' ').map(Number)
+ return [lon, lat]
+ })
+}
+
+// 编辑面
+function editPolygon() {
+ if (!formData.value?.geom) return
+ const grouped = getLonLat(formData.value.geom)
+ grouped.pop()
+ pointList = grouped.map(item => ({ longitude: item[0], latitude: item[1] }))
+ drawPolygonExample?.destroy()
+ drawPolygonExample = new DrawPolygon(viewer)
+ drawPolygonExample.initPolygon(
+ viewer,
+ grouped.map(item => ({ lng: item[0], lat: item[1] }))
+ )
+ drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
+}
+
+// 查看面
+function viewPolygon() {
+ if (!formData.value?.geom) return
+ const grouped = getLonLat(formData.value.geom)
+ grouped.pop()
+ pointList = grouped.map(item => ({ longitude: item[0], latitude: item[1] }))
+ const result = grouped.flat().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 getPoliceStationList() {
+ const res = await fwPoliceStationPageApi({ current: 1, size: 9999 })
+ policeStationOptions.value = res?.data?.data?.records ?? []
+}
+
+// 获取设备列表
+async function getDeviceList() {
+ const res = await fwDevicePageApi({ current: 1, size: 9999 })
+ deviceOptions.value = res?.data?.data?.records ?? []
+}
+
+// 打开弹框
+async function open({ mode, row } = {}) {
+ dialogMode.value = mode || 'add'
+ formData.value = dialogMode.value === 'add' ? initForm() : row
+ await nextTick()
+ initMap()
+ if (dialogMode.value === 'add') {
+ addPolygon()
+ } else if (dialogMode.value === 'edit') {
+ await loadDetail()
+ editPolygon()
+ } else {
+ await loadDetail()
+ viewPolygon()
+ }
+}
+
+onMounted(() => {
+ getPoliceStationList()
+ getDeviceList()
+})
+
+defineExpose({
+ open,
+})
+</script>
+
+<style scoped lang="scss">
+.bodyBox {
+ display: flex;
+ height: 800px;
+ .leftMap {
+ width: 70%;
+ height: 100%;
+ }
+ .rightInfo {
+ width: 30%;
+ height: 100%;
+ overflow: auto;
+ }
+}
+</style>
diff --git a/applications/drone-command/src/views/areaManage/partition/index.vue b/applications/drone-command/src/views/areaManage/partition/index.vue
index 14a6b62..11bbd56 100644
--- a/applications/drone-command/src/views/areaManage/partition/index.vue
+++ b/applications/drone-command/src/views/areaManage/partition/index.vue
@@ -1,9 +1,178 @@
<template>
- <basic-container>
- 区域划分
- </basic-container>
+ <basic-container>
+ <el-form ref="queryParamsRef" :model="searchParams">
+ <el-row :gutter="16">
+ <el-col :span="4">
+ <el-form-item label="区域名称" prop="areaName">
+ <el-input v-model="searchParams.areaName" placeholder="请输入" clearable @clear="handleSearch" />
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item label="区域类型" prop="areaType">
+ <el-select v-model="searchParams.areaType" placeholder="请选择" clearable @change="handleSearch">
+ <el-option v-for="item in areaTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
+ </el-select>
+ </el-form-item>
+ </el-col>
+ <el-col :span="4">
+ <el-form-item>
+ <el-button @click="resetForm">重置</el-button>
+ <el-button type="primary" @click="handleSearch">查询</el-button>
+ </el-form-item>
+ </el-col>
+ </el-row>
+ </el-form>
+ <div>
+ <el-button type="primary" @click="openForm('add')">新增</el-button>
+ <el-button type="danger" :disabled="!selectedIds.length" @click="handleDelete()">删除</el-button>
+ </div>
+ <el-table v-loading="loading" :data="list" @selection-change="handleSelectionChange">
+ <el-table-column type="selection" width="55" />
+ <el-table-column type="index" width="60" label="序号" />
+ <el-table-column prop="areaName" label="区域名称" />
+ <el-table-column label="区域位置">
+ <template v-slot="{ row }">
+ {{ formatLocation(row) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="areaSize" label="区域面积" />
+ <el-table-column prop="areaType" label="区域类型">
+ <template v-slot="{ row }">
+ {{ getOptionLabel(row.areaType, areaTypeOptions) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="triggerCondition" label="触发条件" />
+ <el-table-column prop="responseMechanism" label="响应机制" />
+ <el-table-column prop="controlLevel" label="管控级别">
+ <template v-slot="{ row }">
+ {{ getOptionLabel(row.controlLevel, controlLevelOptions) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="policeStationName" label="关联派出所" />
+ <el-table-column label="可飞行时间段">
+ <template v-slot="{ row }">
+ {{ formatFlyDate(row) }}
+ </template>
+ </el-table-column>
+ <el-table-column label="操作">
+ <template v-slot="{ row }">
+ <el-link @click="openForm('view', row)" type="primary">查看</el-link>
+ <el-link @click="openForm('edit', row)" type="warning">编辑</el-link>
+ <el-link @click="handleDelete(row)" type="danger">删除</el-link>
+ </template>
+ </el-table-column>
+ </el-table>
+ <div>
+ <el-pagination
+ v-model:current-page="searchParams.current"
+ v-model:page-size="searchParams.size"
+ :total="total"
+ @change="getList"
+ />
+ </div>
+
+ <FormDiaLog v-if="dialogVisible" v-model="dialogVisible" ref="dialogRef" @success="getList" />
+ </basic-container>
</template>
+
<script setup>
+import { nextTick, onMounted, ref } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { fwAreaDividePageApi, fwAreaDivideRemoveApi } from './partitionApi'
+import FormDiaLog from './FormDiaLog.vue'
+
+const initSearchParams = () => ({
+ areaName: '', // 区域名称
+ areaType: '', // 区域类型
+ current: 1, // 当前页
+ size: 10, // 每页大小
+})
+
+const areaTypeOptions = [
+ { label: '报警区', value: '报警区' },
+ { label: '重点管制区', value: '重点管制区' },
+]
+const controlLevelOptions = [
+ { label: '日常', value: '日常' },
+ { label: '重点', value: '重点' },
+ { label: '严密', value: '严密' },
+]
+
+const searchParams = ref(initSearchParams()) // 查询参数
+const total = ref(0) // 总条数
+const loading = ref(false) // 列表加载中
+const list = ref([]) // 列表数据
+const selectedIds = ref([]) // 勾选的ID列表
+const queryParamsRef = ref(null) // 查询表单实例
+const dialogRef = ref(null) // 弹框实例
+const dialogVisible = ref(false)
+
+// 获取列表
+async function getList() {
+ loading.value = true
+ try {
+ const res = await fwAreaDividePageApi(searchParams.value)
+ list.value = res?.data?.data?.records ?? []
+ total.value = res?.data?.data?.total ?? 0
+ } finally {
+ loading.value = false
+ }
+}
+
+// 查询
+function handleSearch() {
+ searchParams.value.current = 1
+ getList()
+}
+
+// 重置查询
+function resetForm() {
+ queryParamsRef.value?.resetFields()
+ searchParams.value.current = 1
+ getList()
+}
+
+// 删除
+async function handleDelete(row) {
+ const tips = row ? '该条' : '选中的项'
+ await ElMessageBox.confirm(`确认删除${tips}吗?`, '提示', { type: 'warning' })
+ const ids = row ? row.id : selectedIds.value.join(',')
+ await fwAreaDivideRemoveApi({ ids })
+ ElMessage.success('删除成功')
+ selectedIds.value = []
+ getList()
+}
+
+// 勾选值设置
+function handleSelectionChange(rows) {
+ selectedIds.value = rows.map(item => item.id)
+}
+
+function formatLocation(row) {
+ if (row?.longitude == null || row?.latitude == null) return ''
+ return `${row.longitude}, ${row.latitude}`
+}
+
+function formatFlyDate(row) {
+ if (!row?.flyDateStart && !row?.flyDateEnd) return ''
+ return `${row.flyDateStart || '-'} ~ ${row.flyDateEnd || '-'}`
+}
+
+function getOptionLabel(value, options) {
+ const match = options.find(item => item.value === value)
+ return match?.label ?? value ?? ''
+}
+
+// 新增/编辑/查看 弹框
+function openForm(mode, row) {
+ dialogVisible.value = true
+ nextTick(() => {
+ dialogRef.value?.open({ mode, row })
+ })
+}
+
+onMounted(() => {
+ getList()
+})
</script>
-<style scoped lang="scss">
-</style>
+<style scoped lang="scss"></style>
diff --git a/applications/drone-command/src/views/areaManage/partition/partitionApi.js b/applications/drone-command/src/views/areaManage/partition/partitionApi.js
new file mode 100644
index 0000000..aee6603
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/partitionApi.js
@@ -0,0 +1,55 @@
+import request from '@/axios'
+
+// 查list
+export const fwAreaDividePageApi = params => {
+ return request({
+ url: `/drone-fw/area/fwAreaDivide/page`,
+ method: 'get',
+ params,
+ })
+}
+
+// 增加或更新
+export const fwAreaDivideSubmitApi = data => {
+ return request({
+ url: `/drone-fw/area/fwAreaDivide/submit`,
+ method: 'post',
+ data,
+ })
+}
+
+//删除
+export const fwAreaDivideRemoveApi = params => {
+ return request({
+ url: `/drone-fw/area/fwAreaDivide/remove`,
+ method: 'post',
+ params,
+ })
+}
+
+//详情
+export const fwAreaDivideDetailApi = params => {
+ return request({
+ url: `/drone-fw/area/fwAreaDivide/detail`,
+ method: 'get',
+ params,
+ })
+}
+
+// 关联派出所列表
+export const fwPoliceStationPageApi = params => {
+ return request({
+ url: `/drone-fw/area/fwPoliceStation/page`,
+ method: 'get',
+ params,
+ })
+}
+
+// 关联设备列表
+export const fwDevicePageApi = params => {
+ return request({
+ url: `/drone-fw/device/fwDevice/page`,
+ method: 'get',
+ params,
+ })
+}
--
Gitblit v1.9.3