吉安感知网项目-前端
张含笑
2026-01-13 cc42e168d58901f9f09d1a0d80eadd0826994a76
Merge remote-tracking branch 'origin/master'
6 files modified
4 files added
719 ■■■■■ 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 368 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/index.vue 185 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/partitionApi.js 45 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/precinctInfo/precinctInfoApi.js 11 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceStock/fwDevice.js 11 ●●●●● patch | view | raw | blame | history
uniapps/work-wx/src/pages.json 2 ●●● patch | view | raw | blame | history
uniapps/work-wx/src/static/images/fj.png patch | view | raw | blame | history
uniapps/work-wx/src/static/images/fj.svg 10 ●●●●● 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,368 @@
<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 }}k㎡</div>
                        </el-col>
                        <el-col :span="24">
                            <div>区域类型: {{ getDictLabel(formData.areaType, dictObj.areaType) }}</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>管控级别: {{ getDictLabel(formData.controlLevel, dictObj.controlLevel) }}</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 }}k㎡</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 dictObj.areaType"
                                        :key="item.dictKey"
                                        :label="item.dictValue"
                                        :value="item.dictKey"
                                    />
                                </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 dictObj.controlLevel"
                                        :key="item.dictKey"
                                        :label="item.dictValue"
                                        :value="item.dictKey"
                                    />
                                </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, inject, nextTick, onMounted, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import {
    fwAreaDivideDetailApi,
    fwAreaDivideSubmitApi,
} from './partitionApi'
import { fieldRules, getDictLabel } 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'
import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice'
const initForm = () => ({
    areaName: '', // 区域名称
    areaSize: null, // 区域面积
    areaType: '', // 区域类型
    controlLevel: '', // 管控级别
    deviceIds: '', // 关联设备ID
    flyDateEnd: '', // 可飞行结束
    flyDateStart: '', // 可飞行开始
    latitude: null, // 区域中心纬度
    longitude: null, // 区域中心经度
    policeStationId: '', // 关联派出所id
    responseMechanism: '', // 响应机制
    triggerCondition: '', // 触发条件
})
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([]) // 关联设备
const dictObj = inject('dictObj')
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}))`
        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 `${_.round(row.longitude, 6)}, ${_.round(row.latitude, 6)}`
}
// 格式化可飞行时间段
function formatFlyDate(row) {
    if (!row?.flyDateStart && !row?.flyDateEnd) return ''
    return `${row.flyDateStart || '-'} ~ ${row.flyDateEnd || '-'}`
}
// 初始化地图实例
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)
    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 addPolygon() {
    drawPolygonExample = new DrawPolygon(viewer)
    drawPolygonExample.initHandler(viewer)
    drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
}
// 解析经纬度
function getLonLat(str) {
    if (!str) return []
    return str
        .replace('POLYGON((', '')
        .replace('))', '')
        .split(',')
        .map(pair => {
            const [lon, lat] = 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 fwPoliceStationListApi()
    policeStationOptions.value = res?.data?.data ?? []
}
// 获取设备列表
async function getDeviceList() {
    const res = await fwDeviceListApi({ isAreaSelect: 1 })
    deviceOptions.value = res?.data?.data ?? []
}
// 打开弹框
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: 600px;
    .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,184 @@
<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 dictObj.areaType"
                                :key="item.dictKey"
                                :label="item.dictValue"
                                :value="item.dictKey"
                            />
                        </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="区域面积(k㎡)" />
            <el-table-column prop="areaType" label="区域类型">
                <template v-slot="{ row }">
                    {{ getDictLabel(row.areaType, dictObj.areaType) }}
                </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 }">
                    {{ getDictLabel(row.controlLevel, dictObj.controlLevel) }}
                </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'
import { getDictionaryByCode } from '@/api/system/dictbiz'
import { getDictLabel } from '@ztzf/utils'
const initSearchParams = () => ({
    areaName: '', // 区域名称
    areaType: '', // 区域类型
    current: 1, // 当前页
    size: 10, // 每页大小
})
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 || '-'}`
}
const dictObj = ref({
    areaType: [], //区域类型
    controlLevel: [], //管控级别
})
provide('dictObj', dictObj)
// 获取字典
function getDictList() {
    getDictionaryByCode('areaType,controlLevel').then(res => {
        dictObj.value = res.data.data
    })
}
// 新增/编辑/查看 弹框
function openForm(mode, row) {
    dialogVisible.value = true
    nextTick(() => {
        dialogRef.value?.open({ mode, row })
    })
}
onMounted(() => {
    getList()
    getDictList()
})
</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,45 @@
import request from '@/axios'
// 查page
export const fwAreaDividePageApi = params => {
    return request({
        url: `/drone-fw/area/fwAreaDivide/page`,
        method: 'get',
        params,
    })
}
// 查list
export const fwAreaDivideListApi = params => {
    return request({
        url: `/drone-fw/area/fwAreaDivide/list`,
        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,
    })
}
applications/drone-command/src/views/areaManage/precinctInfo/precinctInfoApi.js
@@ -1,6 +1,6 @@
import request from '@/axios'
// 查list
// 查page
export const fwPoliceStationPageApi = params => {
    return request({
        url: `/drone-fw/area/fwPoliceStation/page`,
@@ -9,6 +9,15 @@
    })
}
// 查list
export const fwPoliceStationListApi = params => {
    return request({
        url: `/drone-fw/area/fwPoliceStation/list`,
        method: 'get',
        params,
    })
}
// 增加或更新
export const fwPoliceStationSubmitApi = data => {
    return request({
applications/drone-command/src/views/basicManage/deviceStock/fwDevice.js
@@ -1,6 +1,6 @@
import request from '@/axios'
// 查list
// 查page
export const fwDevicePageApi = params => {
    return request({
        url: `/drone-fw/device/fwDevice/page`,
@@ -9,6 +9,15 @@
    })
}
// 查list
export const fwDeviceListApi = params => {
    return request({
        url: `/drone-fw/device/fwDevice/list`,
        method: 'get',
        params,
    })
}
// 增加或更新
export const fwDeviceSubmitApi = data => {
    return request({
uniapps/work-wx/src/pages.json
@@ -84,7 +84,7 @@
                {
                    "path": "regulationsDetail/details",
                    "style": {
                        "navigationBarTitleText": "法律政策详情"
                        "navigationBarTitleText": "详情"
                    }
                },
                {
uniapps/work-wx/src/static/images/fj.png
uniapps/work-wx/src/static/images/fj.svg
New file
@@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<g id="Frame" clip-path="url(#clip0_126_1748)">
<path id="Vector" d="M10.8359 1.48807C11.1239 1.77607 11.3379 2.09807 11.4779 2.45407C11.6179 2.81007 11.6879 3.17607 11.6879 3.55207C11.6879 3.92807 11.6179 4.29407 11.4779 4.65007C11.3379 5.00607 11.1239 5.32807 10.8359 5.61607L6.23991 10.1761C5.95191 10.4641 5.59791 10.7041 5.17791 10.8961C4.75791 11.0881 4.31391 11.2001 3.84591 11.2321C3.37791 11.2641 2.89991 11.2001 2.41191 11.0401C1.92391 10.8801 1.47191 10.5921 1.05591 10.1761C0.647906 9.76807 0.363906 9.32607 0.203906 8.85007C0.0439063 8.37407 -0.0200938 7.90007 0.0119062 7.42807C0.0439062 6.95607 0.153906 6.51207 0.341906 6.09607C0.529906 5.68007 0.767906 5.32807 1.05591 5.04007L5.13591 0.996074C5.20791 0.924074 5.31391 0.906074 5.45391 0.942074C5.59391 0.978074 5.69991 1.03207 5.77191 1.10407C5.83591 1.17607 5.88791 1.28207 5.92791 1.42207C5.96791 1.56207 5.95191 1.66807 5.87991 1.74007L1.81191 5.77207C1.59591 5.98807 1.41591 6.24007 1.27191 6.52807C1.12791 6.81607 1.04391 7.12407 1.01991 7.45207C0.995906 7.78007 1.04391 8.11207 1.16391 8.44807C1.28391 8.78407 1.49991 9.10807 1.81191 9.42007C2.09191 9.70007 2.40191 9.89807 2.74191 10.0141C3.08191 10.1301 3.41991 10.1781 3.75591 10.1581C4.09191 10.1381 4.41191 10.0641 4.71591 9.93607C5.01991 9.80807 5.27991 9.63607 5.49591 9.42007L10.0799 4.87207C10.2959 4.65607 10.4499 4.43207 10.5419 4.20007C10.6339 3.96807 10.6779 3.73807 10.6739 3.51007C10.6699 3.28207 10.6159 3.06007 10.5119 2.84407C10.4079 2.62807 10.2639 2.42807 10.0799 2.24407C9.73591 1.90807 9.34991 1.75007 8.92191 1.77007C8.49391 1.79007 8.06391 2.01607 7.63191 2.44807L3.49191 6.56407C3.30791 6.74807 3.21791 6.95207 3.22191 7.17607C3.22591 7.40007 3.29991 7.58407 3.44391 7.72807C3.61991 7.90407 3.82191 7.97607 4.04991 7.94407C4.27791 7.91207 4.46391 7.82407 4.60791 7.68007L8.36391 3.94807C8.43591 3.87607 8.54191 3.85807 8.68191 3.89407C8.82191 3.93007 8.92791 3.98407 8.99991 4.05607C9.07191 4.12807 9.12791 4.23407 9.16791 4.37407C9.20791 4.51407 9.19191 4.62007 9.11991 4.69207L5.35191 8.42407C5.06391 8.71207 4.78791 8.91207 4.52391 9.02407C4.25991 9.13607 4.01191 9.18607 3.77991 9.17407C3.54791 9.16207 3.33191 9.10007 3.13191 8.98807C2.93191 8.87607 2.74791 8.74007 2.57991 8.58007C2.44391 8.45207 2.32191 8.28807 2.21391 8.08807C2.10591 7.88807 2.04391 7.66807 2.02791 7.42807C2.01191 7.18807 2.05391 6.93207 2.15391 6.66007C2.25391 6.38807 2.44791 6.11207 2.73591 5.83207C2.87991 5.68807 3.00791 5.55607 3.11991 5.43607C3.21591 5.33207 3.30591 5.24007 3.38991 5.16007C3.47391 5.08007 3.51991 5.03607 3.52791 5.02807L6.87591 1.69207C7.16391 1.40407 7.47791 1.17807 7.81791 1.01407C8.15791 0.850074 8.50391 0.756074 8.85591 0.732074C9.20791 0.708074 9.55391 0.756074 9.89391 0.876074C10.2339 0.996074 10.5479 1.20007 10.8359 1.48807Z" fill="#1D6FE9"/>
</g>
<defs>
<clipPath id="clip0_126_1748">
<rect width="12" height="12" fill="white"/>
</clipPath>
</defs>
</svg>