吉安感知网项目-前端
shuishen
2026-02-02 2d40c08a9358a1fc1183a45fa806d5e433822e21
feat:区域划分新增根据绘制区域筛选设备处理
4 files modified
1 files added
301 ■■■■ changed files
applications/drone-command/env/.env.development 4 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/api.txt 122 ●●●● patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/1.json patch | view | raw | blame | history
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue 166 ●●●●● patch | view | raw | blame | history
applications/drone-command/src/views/basicManage/deviceStock/fwDevice.js 9 ●●●●● patch | view | raw | blame | history
applications/drone-command/env/.env.development
@@ -2,7 +2,7 @@
 # @Author       : yuan
 # @Date         : 2026-01-31 14:34:31
 # @LastEditors  : yuan
 # @LastEditTime : 2026-01-31 15:18:17
 # @LastEditTime : 2026-02-02 11:01:46
 # @FilePath     : \applications\drone-command\env\.env.development
 # @Description  : 
 # Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -16,7 +16,7 @@
#开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
# VITE_APP_URL=https://wrj.shuixiongit.com/api
VITE_APP_URL=http://192.168.1.33
VITE_APP_URL=http://192.168.1.168
#新大屏地址
VITE_APP_DASHBOARD_URL='https://wrj.shuixiongit.com/command-center-dashboard/'
applications/drone-command/src/views/api.txt
@@ -1,22 +1,36 @@
## 列表
## 列表-多边形过滤
**接口地址**:`/area/fwDefenseSceneManage/list`
**接口地址**:`/device/fwDevice/listByPolygons`
**请求方式**:`GET`
**请求方式**:`POST`
**请求数据类型**:`application/x-www-form-urlencoded`
**请求数据类型**:`application/json`
**响应数据类型**:`*/*`
**接口描述**:<p>传入time</p>
**接口描述**:<p>传入isAreaSelect、areaId、status、isTrack、polygons</p>
**请求示例**:
```javascript
{
  "areaId": 0,
  "isAreaSelect": 0,
  "isTrack": 0,
  "polygons": [],
  "status": 0
}
```
**请求参数**:
@@ -27,7 +41,12 @@
| 参数名称 | 参数说明 | 请求类型    | 是否必须 | 数据类型 | schema |
| -------- | -------- | ----- | -------- | -------- | ------ |
|time|时间筛选(yyyy-MM-dd HH:mm:ss)|query|false|string(date-time)||
|query|设备列表多边形查询参数|body|true|FwDevicePolygonQueryDTO|FwDevicePolygonQueryDTO|
|&emsp;&emsp;areaId|区域id||false|integer(int64)||
|&emsp;&emsp;isAreaSelect|是否区域选择(1过滤)||false|integer(int32)||
|&emsp;&emsp;isTrack|是否出入库 1已出库 2未出库||false|integer(int32)||
|&emsp;&emsp;polygons|多边形WKT集合||false|array|string|
|&emsp;&emsp;status|状态||false|integer(int32)||
**响应状态**:
@@ -35,7 +54,8 @@
| 状态码 | 说明 | schema |
| -------- | -------- | ----- | 
|200|OK|R«List«FwDefenseSceneManageVO»»|
|200|OK|R«List«FwDeviceVO»»|
|201|Created||
|401|Unauthorized||
|403|Forbidden||
|404|Not Found||
@@ -47,18 +67,41 @@
| 参数名称 | 参数说明 | 类型 | schema |
| -------- | -------- | ----- |----- | 
|code|状态码|integer(int32)|integer(int32)|
|data|承载数据|array|FwDefenseSceneManageVO|
|&emsp;&emsp;areaCode|区域编码|string||
|&emsp;&emsp;defenseLeader|防控负责人|string||
|&emsp;&emsp;defenseSceneId|关联场景配置ID|integer(int64)||
|&emsp;&emsp;defenseSceneName|场景配置名称|string||
|&emsp;&emsp;effectiveDateEnd|有效时间-结束|string(date-time)||
|&emsp;&emsp;effectiveDateStart|有效时间-开始|string(date-time)||
|&emsp;&emsp;id||integer(int64)||
|&emsp;&emsp;latitude|指挥点纬度|number(double)||
|&emsp;&emsp;leaderPhone|防控负责人电话|string||
|&emsp;&emsp;longitude|指挥点经度|number(double)||
|data|承载数据|array|FwDeviceVO|
|&emsp;&emsp;azimuth|方位角 0-360°|integer(int32)||
|&emsp;&emsp;batteryPct|电量百分比 0-100|integer(int32)||
|&emsp;&emsp;belongDept|所属部门id|integer(int64)||
|&emsp;&emsp;belongDeptName|所属部门名称|string||
|&emsp;&emsp;charger|负责人|string||
|&emsp;&emsp;contactPhone|联系电话|string||
|&emsp;&emsp;createDept|创建部门|integer(int64)||
|&emsp;&emsp;createTime|创建时间|string(date-time)||
|&emsp;&emsp;createUser|创建人|integer(int64)||
|&emsp;&emsp;detectTargetCnt|侦测目标数量|integer(int32)||
|&emsp;&emsp;deviceAtt|设备属性(无线电/光电/雷达)|string||
|&emsp;&emsp;deviceModel|型号|string||
|&emsp;&emsp;deviceName|设备名称|string||
|&emsp;&emsp;deviceSn|设备sn|string||
|&emsp;&emsp;deviceSpecification|规格|string||
|&emsp;&emsp;deviceType|设备类型(察打一体/便捷侦测箱/反制枪)|string||
|&emsp;&emsp;effectiveRangeKm|有效范围 km|number||
|&emsp;&emsp;elevation|俯仰角 -90-+90°|integer(int32)||
|&emsp;&emsp;id|主键|integer(int64)||
|&emsp;&emsp;isShared|是否被共享给当前机构|boolean||
|&emsp;&emsp;latitude|纬度|string||
|&emsp;&emsp;longitude|经度|string||
|&emsp;&emsp;manufacturer|生产厂商|string||
|&emsp;&emsp;outTarget|出库去向|string||
|&emsp;&emsp;purpose|用途|string||
|&emsp;&emsp;remark|备注|string||
|&emsp;&emsp;sceneName|场景名称|string||
|&emsp;&emsp;shareId|共享记录ID|integer(int64)||
|&emsp;&emsp;source|来源|string||
|&emsp;&emsp;status|运行状态(0:在线/1:离线/2:故障/3:报废)|integer(int32)||
|&emsp;&emsp;trackStatus|设备出入库状态 0未出库1已出库|integer(int32)||
|&emsp;&emsp;updateTime|更新时间|string(date-time)||
|&emsp;&emsp;updateUser|更新人|integer(int64)||
|&emsp;&emsp;workMode|工作模式:1.侦测中/2.信号干扰中/3.诱导驱离中/4.待机|string||
|msg|返回消息|string||
|success|是否成功|boolean||
@@ -69,17 +112,40 @@
    "code": 0,
    "data": [
        {
            "areaCode": "",
            "defenseLeader": "",
            "defenseSceneId": 0,
            "defenseSceneName": "",
            "effectiveDateEnd": "",
            "effectiveDateStart": "",
            "azimuth": 0,
            "batteryPct": 0,
            "belongDept": 0,
            "belongDeptName": "",
            "charger": "",
            "contactPhone": "",
            "createDept": 0,
            "createTime": "",
            "createUser": 0,
            "detectTargetCnt": 0,
            "deviceAtt": "",
            "deviceModel": "",
            "deviceName": "",
            "deviceSn": "",
            "deviceSpecification": "",
            "deviceType": "",
            "effectiveRangeKm": 0,
            "elevation": 0,
            "id": 0,
            "latitude": 0,
            "leaderPhone": "",
            "longitude": 0,
            "sceneName": ""
            "isShared": true,
            "latitude": "",
            "longitude": "",
            "manufacturer": "",
            "outTarget": "",
            "purpose": "",
            "remark": "",
            "sceneName": "",
            "shareId": 0,
            "source": "",
            "status": 0,
            "trackStatus": 0,
            "updateTime": "",
            "updateUser": 0,
            "workMode": ""
        }
    ],
    "msg": "",
applications/drone-command/src/views/areaManage/partition/1.json
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -249,7 +249,7 @@
import * as Cesium from 'cesium'
import { AREA_TYPE_STYLE_MAP, BUFFER_LEVEL_STYLES, DEFAULT_AREA_STYLE } from '@ztzf/constants'
import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
import { fwDeviceListApi } from '@/views/basicManage/deviceStock/fwDevice'
import { fwDeviceListByPolygonsApi } from '@/views/basicManage/deviceStock/fwDevice'
import polygonIcon from '@/assets/images/areaMap/polygon.png'
import rectIcon from '@/assets/images/areaMap/rect.png'
import ellipseIcon from '@/assets/images/areaMap/ellipse.png'
@@ -307,8 +307,11 @@
const deviceRangePrimitiveMap = new Map()
const activeToolMode = ref('')
const suppressDeviceFly = ref(false)
const isDialogInitializing = ref(false)
let lastSelectedDeviceIds = []
let deviceRangeRenderToken = 0
let deviceListTimer = null
let deviceListRequestToken = 0
const rules = {
    areaName: fieldRules(true, 50),
@@ -411,6 +414,10 @@
    () => visible.value,
    val => {
        if (!val) {
            if (deviceListTimer) {
                clearTimeout(deviceListTimer)
                deviceListTimer = null
            }
            viewEntity && viewer?.entities?.remove(viewEntity)
            viewEntity = null
            clearActiveTool()
@@ -425,6 +432,14 @@
    val => {
        updateActiveShapeAreaType(val)
    }
)
watch(
    () => shapeList.value,
    () => {
        if (isDialogInitializing.value) return
        queueDeviceListRefresh()
    },
    { deep: true }
)
// 加载详情
@@ -474,6 +489,98 @@
        viewer.scene.primitives.remove(primitive)
    }
    deviceRangePrimitiveMap.clear()
}
function clearDeviceListState () {
    deviceOptions.value = []
    selectedDeviceRows.value = []
    formData.value.deviceIds = ''
    if (deviceTableRef.value) {
        deviceTableRef.value.clearSelection()
    }
}
function formatWktNumber (value) {
    if (!Number.isFinite(Number(value))) return null
    return Number(value).toFixed(6)
}
function isSameWktPoint (left, right) {
    if (!left || !right) return false
    return Math.abs(left.lat - right.lat) < 0.000001 && Math.abs(left.lng - right.lng) < 0.000001
}
function buildPolygonWktFromPoints (points) {
    if (!Array.isArray(points) || points.length < 3) return null
    const ring = points
        .map(resolveLngLatPoint)
        .filter(Boolean)
        .map(point => ({ lng: point.lng, lat: point.lat }))
    if (ring.length < 3) return null
    const first = ring[0]
    const last = ring[ring.length - 1]
    if (!isSameWktPoint(first, last)) {
        ring.push({ ...first })
    }
    const coordText = ring
        .map(point => {
            const lng = formatWktNumber(point.lng)
            const lat = formatWktNumber(point.lat)
            if (lat === null || lng === null) return null
            return `${lng} ${lat}`
        })
        .filter(Boolean)
        .join(', ')
    if (!coordText) return null
    return `POLYGON((${coordText}))`
}
function buildEllipseWktFromMeta (center, semiMajor, semiMinor) {
    if (!viewer) return null
    const resolvedCenter = resolveLngLatPoint(center)
    if (!resolvedCenter) return null
    if (!Number.isFinite(Number(semiMajor)) || !Number.isFinite(Number(semiMinor))) return null
    const centerCartesian = Cesium.Cartesian3.fromDegrees(
        resolvedCenter.lng,
        resolvedCenter.lat,
        resolvedCenter.height || 0
    )
    const positions = buildEllipsePositions(centerCartesian, Number(semiMajor), Number(semiMinor))
    if (!positions.length) return null
    const points = positions
        .map(item => {
            const converted = cartesian3Convert(item, viewer)
            return { lng: converted.longitude, lat: converted.latitude }
        })
        .filter(Boolean)
    return buildPolygonWktFromPoints(points)
}
function resolveShapePolygonsWkt () {
    const polygons = []
    shapeList.value.forEach(shape => {
        if (!shape) return
        if (shape.drawType === 'buffer') {
            const radii = Array.isArray(shape?.meta?.bufferRadii)
                ? shape.meta.bufferRadii.filter(item => Number.isFinite(item) && item > 0)
                : []
            const maxRadius = radii.length ? Math.max(...radii) : null
            const wkt = buildEllipseWktFromMeta(shape?.meta?.center, maxRadius, maxRadius)
            if (wkt) polygons.push(wkt)
            return
        }
        if (shape.drawType === 'ellipse') {
            const wkt = buildEllipseWktFromMeta(shape?.meta?.center, shape?.meta?.semiMajor, shape?.meta?.semiMinor)
            if (wkt) {
                polygons.push(wkt)
                return
            }
        }
        const points = getShapeDisplayPoints(shape)
        const wkt = buildPolygonWktFromPoints(points)
        if (wkt) polygons.push(wkt)
    })
    return polygons
}
async function renderDeviceRangeSingle (device) {
@@ -1075,12 +1182,44 @@
}
// 获取设备列表
async function getDeviceList () {
    if (deviceOptions.value.length) return
    const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id, isTrack: 1 })
function queueDeviceListRefresh () {
    if (!visible.value) return
    if (deviceListTimer) {
        clearTimeout(deviceListTimer)
    }
    deviceListTimer = setTimeout(() => {
        deviceListTimer = null
        void getDeviceListByPolygons()
    }, 200)
}
async function getDeviceListByPolygons () {
    if (!visible.value) return
    const polygons = resolveShapePolygonsWkt()
    if (!polygons.length) {
        clearDeviceListState()
        return
    }
    deviceListRequestToken += 1
    const requestToken = deviceListRequestToken
    const res = await fwDeviceListByPolygonsApi({
        isAreaSelect: 1,
        isTrack: 1,
        polygons,
    })
    if (requestToken !== deviceListRequestToken) return
    deviceOptions.value = res?.data?.data ?? []
    if (dialogMode.value === 'view') {
        deviceOptions.value = deviceOptions.value.filter(item => formData.value.deviceIds.includes(item.id))
    if (dialogMode.value === 'view' && formData.value.deviceIds) {
        const targetIds = formData.value.deviceIds.split(',')
        deviceOptions.value = deviceOptions.value.filter(item => targetIds.includes(String(item.id)))
    }
    await nextTick()
    const prevSuppressDeviceFly = suppressDeviceFly.value
    suppressDeviceFly.value = true
    syncDeviceSelection()
    suppressDeviceFly.value = prevSuppressDeviceFly
    if (!readonly.value) {
        renderDeviceRanges(selectedDeviceRows.value)
    }
}
@@ -1114,14 +1253,16 @@
// 同步选择状态
function syncDeviceSelection () {
    if (isDialogInitializing.value && !deviceOptions.value.length) return
    const rows = []
    const arr = formData.value.deviceIds.split(',')
    const arr = formData.value.deviceIds ? formData.value.deviceIds.split(',') : []
    deviceOptions.value.forEach(row => {
        if (arr.includes(row.id)) {
        if (arr.includes(String(row.id))) {
            rows.push(row)
        }
    })
    selectedDeviceRows.value = rows
    formData.value.deviceIds = rows.map(item => item.id).join(',')
    if (readonly.value) {
        renderDeviceRanges(selectedDeviceRows.value)
        return
@@ -1135,9 +1276,11 @@
// 打开弹框
async function open ({ mode, row } = {}) {
    isDialogInitializing.value = true
    dialogMode.value = mode || 'add'
    formData.value = dialogMode.value === 'add' ? initForm() : row
    selectedDeviceRows.value = []
    deviceOptions.value = []
    shapeList.value = []
    activeShapeId.value = null
    activeAreaType.value = ''
@@ -1149,7 +1292,6 @@
    viewEntity && viewer?.entities?.remove(viewEntity)
    viewEntity = null
    clearActiveTool()
    await getDeviceList()
    if (dialogMode.value === 'add') {
        mapRef.value?.zoomToAdminBoundary?.()
        // default no draw mode
@@ -1169,6 +1311,8 @@
    if (!readonly.value) {
        renderDeviceRanges(selectedDeviceRows.value)
    }
    isDialogInitializing.value = false
    queueDeviceListRefresh()
}
onMounted(() => {
@@ -1176,6 +1320,10 @@
})
onBeforeUnmount(() => {
    if (deviceListTimer) {
        clearTimeout(deviceListTimer)
        deviceListTimer = null
    }
    hideTypePanel()
})
applications/drone-command/src/views/basicManage/deviceStock/fwDevice.js
@@ -18,6 +18,15 @@
    })
}
// 多边形过滤设备列表
export const fwDeviceListByPolygonsApi = data => {
    return request({
        url: `/drone-fw/device/fwDevice/listByPolygons`,
        method: 'post',
        data,
    })
}
// 增加或更新
export const fwDeviceSubmitApi = data => {
    return request({