吉安感知网项目-前端
罗广辉
2026-01-12 2dbd3103d91934a6ec6ef48baa288b56a4843472
feat: 完成区域
3 files modified
2 files added
700 ■■■■■ changed files
applications/drone-command/src/utils/cesium/DrawPolygon.js 70 ●●●● patch | view | raw | blame | history
applications/drone-command/src/utils/cesium/mapUtil.js 17 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue 379 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/index.vue 179 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/partitionApi.js 55 ●●●●● patch | view | raw | blame | history
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()
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 })))
            })
    })
}
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
New file
@@ -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>
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>
applications/drone-command/src/views/areaManage/partition/partitionApi.js
New file
@@ -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,
    })
}