From 89380e6260a75d1d3b94de687ebcc2f50d50659d Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Tue, 03 Feb 2026 15:44:33 +0800
Subject: [PATCH] feat:环境变量配置调整
---
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue | 585 ++++++++++++++++++++++++++++++++++++++++++---------------
1 files changed, 429 insertions(+), 156 deletions(-)
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 5fc6010..342b43c 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -3,7 +3,7 @@
<div class="dialog-container">
<div class="left-container">
<CommonCesiumMap ref="mapRef" class="leftMap command-cesium" :active="visible" :flat-mode="false"
- :terrain="true" :layer-mode="4" :boundary="false" />
+ :terrain="true" :layer-mode="4" :boundary="false" :zoom-to-boundary="dialogMode === 'add'" />
<div v-if="showTypePanel && !readonly" class="shape-type-panel">
<div class="panel-header">
<span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span>
@@ -14,8 +14,8 @@
<div class="panel-body">
<span class="panel-label">区域类型</span>
<el-select class="command-select" popper-class="command-select-popper" v-model="activeAreaType"
- placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags :multiple="currentShapeType === 'buffer'"
- @change="handleAreaTypeChange">
+ placeholder="请选择" :disabled="currentShapeType === 'buffer'" collapse-tags
+ :multiple="currentShapeType === 'buffer'" @change="handleAreaTypeChange">
<el-option v-for="item in dictObj.areaType" :key="item.dictKey" :label="item.dictValue"
:value="item.dictKey" />
</el-select>
@@ -23,22 +23,26 @@
</div>
<div class="map-toolbar">
<el-tooltip content="多边形" placement="left">
- <button class="tool-btn" type="button" @click="handleDraw('polygon')">
+ <button class="tool-btn" :class="{ active: currentShapeType === 'polygon' }" type="button"
+ @click="handleDraw('polygon')">
<img class="tool-icon-image" :src="polygonIcon" alt="多边形" />
</button>
</el-tooltip>
<el-tooltip content="矩形" placement="left">
- <button class="tool-btn" type="button" @click="handleDraw('rectangle')">
+ <button class="tool-btn" :class="{ active: currentShapeType === 'rectangle' }" type="button"
+ @click="handleDraw('rectangle')">
<img class="tool-icon-image" :src="rectIcon" alt="矩形" />
</button>
</el-tooltip>
<el-tooltip content="椭圆" placement="left">
- <button class="tool-btn" type="button" @click="handleDraw('ellipse')">
+ <button class="tool-btn" :class="{ active: currentShapeType === 'ellipse' }" type="button"
+ @click="handleDraw('ellipse')">
<img class="tool-icon-image" :src="ellipseIcon" alt="椭圆" />
</button>
</el-tooltip>
<el-tooltip content="缓冲圆" placement="left">
- <button class="tool-btn" type="button" @click="handleDraw('buffer')">
+ <button class="tool-btn" :class="{ active: currentShapeType === 'buffer' }" type="button"
+ @click="handleDraw('buffer')">
<img class="tool-icon-image" :src="bufferIcon" alt="缓冲圆" />
</button>
</el-tooltip>
@@ -156,7 +160,8 @@
</el-form-item>
<el-form-item label="关联派出所" prop="policeStationId">
<el-select class="command-select" popper-class="command-select-popper"
- v-model="formData.policeStationId" placeholder="请选择" clearable>
+ v-model="formData.policeStationId" placeholder="请选择" clearable filterable remote
+ :remote-method="handlePoliceStationSearch" :loading="policeStationLoading">
<el-option v-for="item in policeStationOptions" :key="item.id" :label="item.stationName"
:value="item.id" />
</el-select>
@@ -226,24 +231,36 @@
<script setup>
import { Close } from '@element-plus/icons-vue'
-import { computed, inject, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
+import { computed, inject, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { fwAreaDivideDetailApi, fwAreaDivideSubmitApi } from './partitionApi'
import { saveOperationLog } from '@ztzf/apis'
import { useRoute } from 'vue-router'
import { fieldRules, geomAnalysis, getDictLabel } from '@ztzf/utils'
import CommonCesiumMap from '@/components/map-container/common-cesium-map.vue'
-import { DrawManager, EditManager, buildEllipsePositions } from '@/utils/cesium/shapeTools'
-import { cartesian3Convert, getPointPositionsHeight } from '@/utils/cesium/mapUtil'
+import {
+ DrawManager,
+ EditManager,
+ addBufferRadiusLabelEntity,
+ buildEllipsePositions,
+} from '@/utils/cesium/shapeTools'
+import { cartesian3Convert } from '@/utils/cesium/mapUtil'
+import {
+ createDeviceRangePrimitive,
+ getDeviceLngLat,
+ normalizeDeviceRange,
+ resolveDeviceTerrainHeight,
+} from '@/utils/cesium/deviceRange'
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'
import bufferIcon from '@/assets/images/areaMap/buffer.png'
import trashIcon from '@/assets/images/areaMap/trash.png'
+import equipmentIcon from '@/assets/images/dataCockpit/map/equipment.png'
const initForm = () => ({
areaName: '', // 区域名称
@@ -260,7 +277,7 @@
const formRef = ref(null) // 表单实例
const formData = ref(initForm()) // 表单数据
const visible = defineModel() // 弹框显隐
-const dialogMode = ref('add') // 弹框模式
+const dialogMode = ref() // 弹框模式
const submitting = ref(false) // 提交中
const readonly = computed(() => dialogMode.value === 'view')
const route = useRoute()
@@ -270,6 +287,10 @@
const searchName = ref('')
const selectedDeviceRows = ref([])
const policeStationOptions = ref([]) // 关联派出所
+const policeStationAllOptions = ref([])
+const policeStationLoading = ref(false)
+const policeStationSearchTimer = ref(null)
+const policeStationSearchKeyword = ref('')
const deviceOptions = ref([]) // 关联设备
const dictObj = inject('dictObj')
const mapRef = ref(null)
@@ -294,8 +315,14 @@
let viewEntity
let shapeDisplaySource
const deviceRangePrimitiveMap = new Map()
-let deviceRangeMaterialRegistered = false
+const deviceIconEntityMap = 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),
@@ -398,12 +425,21 @@
() => visible.value,
val => {
if (!val) {
+ if (deviceListTimer) {
+ clearTimeout(deviceListTimer)
+ deviceListTimer = null
+ }
+ if (policeStationSearchTimer.value) {
+ clearTimeout(policeStationSearchTimer.value)
+ policeStationSearchTimer.value = null
+ }
viewEntity && viewer?.entities?.remove(viewEntity)
viewEntity = null
clearActiveTool()
hideTypePanel()
clearShapeDisplay()
clearDeviceRangePrimitives()
+ clearDeviceIconEntities()
}
}
)
@@ -412,6 +448,14 @@
val => {
updateActiveShapeAreaType(val)
}
+)
+watch(
+ () => shapeList.value,
+ () => {
+ if (isDialogInitializing.value) return
+ queueDeviceListRefresh()
+ },
+ { deep: true }
)
// 加载详情
@@ -455,42 +499,6 @@
return shapeDisplaySource
}
-function registerDeviceRangeMaterial () {
- if (deviceRangeMaterialRegistered || !Cesium?.Material) return
- deviceRangeMaterialRegistered = true
- Cesium.Material._materialCache.addMaterial('DeviceRangeEllipsoid', {
- fabric: {
- type: 'DeviceRangeEllipsoid',
- uniforms: {
- color: new Cesium.Color(1.0, 0.0, 0.0, 0.7),
- speed: 3.0,
- },
- source: `
- uniform vec4 color;
- uniform float speed;
- czm_material czm_getMaterial(czm_materialInput materialInput){
- czm_material material = czm_getDefaultMaterial(materialInput);
- vec2 st = materialInput.st;
- float time = fract(czm_frameNumber * speed / 1000.0);
- float alpha = abs(smoothstep(0.5,1.,fract( -st.t - time)));
- alpha += .1;
- material.alpha = alpha;
- material.diffuse = color.rgb;
- return material;
- }
- `,
- },
- translucent: () => true,
- })
-}
-
-function createDeviceRangeMaterial () {
- registerDeviceRangeMaterial()
- return Cesium.Material.fromType('DeviceRangeEllipsoid', {
- color: Cesium.Color.fromBytes(0, 255, 255, 155),
- })
-}
-
function clearDeviceRangePrimitives () {
if (!viewer || !deviceRangePrimitiveMap.size) return
for (const primitive of deviceRangePrimitiveMap.values()) {
@@ -499,67 +507,117 @@
deviceRangePrimitiveMap.clear()
}
-function normalizeDeviceRange (value) {
- const range = Number(value)
- if (!Number.isFinite(range) || range <= 0) return 2000
- return range
+function clearDeviceIconEntities () {
+ if (!viewer || !deviceIconEntityMap.size) return
+ for (const entity of deviceIconEntityMap.values()) {
+ viewer.entities.remove(entity)
+ }
+ deviceIconEntityMap.clear()
}
-function getDeviceLngLat (device) {
- const lngRaw = device?.longitude ?? device?.lng ?? device?.lon
- const latRaw = device?.latitude ?? device?.lat
- const lng = Number(lngRaw)
- const lat = Number(latRaw)
- if (!Number.isFinite(lng) || !Number.isFinite(lat)) return null
- return { lng, lat }
-}
-
-async function resolveDeviceTerrainHeight (position) {
- if (!viewer) return 0
- try {
- const results = await getPointPositionsHeight([{ lng: position.lng, lat: position.lat }], viewer)
- const height = Number(results?.[0]?.ASL)
- return Number.isFinite(height) ? height : 0
- } catch (error) {
- return 0
+function clearDeviceListState () {
+ deviceOptions.value = []
+ selectedDeviceRows.value = []
+ formData.value.deviceIds = ''
+ clearDeviceRangePrimitives()
+ clearDeviceIconEntities()
+ if (deviceTableRef.value) {
+ deviceTableRef.value.clearSelection()
}
}
-async function createDeviceRangePrimitive (device) {
+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
- if (!device) return null
- const position = getDeviceLngLat(device)
- if (!position) return null
- const rangeMeters = normalizeDeviceRange(device.effectiveRangeKm)
- const centerHeight = await resolveDeviceTerrainHeight(position)
- const center = Cesium.Cartesian3.fromDegrees(position.lng, position.lat, centerHeight)
- const radii = new Cesium.Cartesian3(rangeMeters, rangeMeters, rangeMeters)
- const geometry = new Cesium.EllipsoidGeometry({
- radii,
- vertexFormat: Cesium.MaterialAppearance.MaterialSupport.TEXTURED?.vertexFormat
- || Cesium.VertexFormat.POSITION_AND_NORMAL,
+ 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)
})
- const instance = new Cesium.GeometryInstance({
- geometry,
- modelMatrix: Cesium.Transforms.eastNorthUpToFixedFrame(center),
- })
- return new Cesium.Primitive({
- geometryInstances: instance,
- appearance: new Cesium.MaterialAppearance({
- material: createDeviceRangeMaterial(),
- closed: true,
- translucent: true,
- flat: true,
- faceForward: true,
- }),
- asynchronous: false
- })
+ return polygons
}
async function renderDeviceRangeSingle (device) {
if (!viewer) return
clearDeviceRangePrimitives()
- const primitive = await createDeviceRangePrimitive(device)
+ clearDeviceIconEntities()
+ const position = getDeviceLngLat(device)
+ if (!position) return
+ addDeviceIconEntity(device, position)
+ const rangeMeters = normalizeDeviceRange(device.effectiveRangeKm)
+ const primitive = await createDeviceRangePrimitive(viewer, position, rangeMeters)
if (!primitive) return
deviceRangePrimitiveMap.set(String(device?.id ?? Date.now()), primitive)
viewer.scene.primitives.add(primitive)
@@ -567,12 +625,20 @@
async function renderDeviceRanges (rows) {
if (!viewer) return
+ deviceRangeRenderToken += 1
+ const renderToken = deviceRangeRenderToken
const selectedRows = Array.isArray(rows) ? rows : []
const nextIds = new Set(selectedRows.map(row => String(row?.id)))
for (const [id, primitive] of deviceRangePrimitiveMap.entries()) {
if (!nextIds.has(id)) {
viewer.scene.primitives.remove(primitive)
deviceRangePrimitiveMap.delete(id)
+ }
+ }
+ for (const [id, entity] of deviceIconEntityMap.entries()) {
+ if (!nextIds.has(id)) {
+ viewer.entities.remove(entity)
+ deviceIconEntityMap.delete(id)
}
}
const pendingRows = selectedRows.filter(row => {
@@ -582,13 +648,21 @@
if (!pendingRows.length) return
const primitives = await Promise.all(
pendingRows.map(async row => {
- const primitive = await createDeviceRangePrimitive(row)
- return { row, primitive }
+ const position = getDeviceLngLat(row)
+ if (!position) return { row, primitive: null, position: null }
+ const rangeMeters = normalizeDeviceRange(row.effectiveRangeKm)
+ const primitive = await createDeviceRangePrimitive(viewer, position, rangeMeters)
+ return { row, primitive, position }
})
)
- primitives.forEach(({ row, primitive }) => {
+ if (renderToken !== deviceRangeRenderToken) return
+ primitives.forEach(({ row, primitive, position }) => {
+ if (position) {
+ addDeviceIconEntity(row, position)
+ }
if (!primitive) return
const id = String(row?.id)
+ if (!nextIds.has(id)) return
deviceRangePrimitiveMap.set(id, primitive)
viewer.scene.primitives.add(primitive)
})
@@ -598,8 +672,9 @@
if (!viewer) return
const position = getDeviceLngLat(device)
if (!position) return
- const height = await resolveDeviceTerrainHeight(position)
- const destination = Cesium.Cartesian3.fromDegrees(position.lng, position.lat, height + device.effectiveRangeKm * 3)
+ const height = await resolveDeviceTerrainHeight(viewer, position)
+ const rangeMeters = normalizeDeviceRange(device?.effectiveRangeKm)
+ const destination = Cesium.Cartesian3.fromDegrees(position.lng, position.lat, height + rangeMeters * 3)
viewer.camera.flyTo({
destination,
orientation: {
@@ -614,6 +689,23 @@
function clearShapeDisplay () {
if (!shapeDisplaySource) return
shapeDisplaySource.entities.removeAll()
+}
+
+function addDeviceIconEntity (device, position) {
+ if (!viewer || !position) return
+ const id = String(device?.id ?? '')
+ if (!id || deviceIconEntityMap.has(id)) return
+ const entity = viewer.entities.add({
+ position: Cesium.Cartesian3.fromDegrees(position.lng, position.lat, position.height || 0),
+ billboard: {
+ image: equipmentIcon,
+ width: 28,
+ height: 38,
+ verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ deviceIconEntityMap.set(id, entity)
}
function normalizeShapePoint (point) {
@@ -724,6 +816,10 @@
material: style.outline,
},
})
+ addBufferRadiusLabelEntity(dataSource, centerCartesian, radius, {
+ name: 'shape-display',
+ customData: { drawType: 'buffer', label: 'radius' },
+ })
})
}
return
@@ -752,8 +848,72 @@
})
}
+function pushRadiusBoundingPoints (positions, center, radius) {
+ if (!center || !Number.isFinite(Number(radius)) || radius <= 0) return
+ const resolved = resolveLngLatPoint(center) || {}
+ const lng = Number(resolved.lng ?? resolved.longitude)
+ const lat = Number(resolved.lat ?? resolved.latitude)
+ if (!Number.isFinite(lng) || !Number.isFinite(lat)) return
+ const baseLat = Cesium.Math.toRadians(lat)
+ const earthRadius = Cesium.Ellipsoid.WGS84.maximumRadius
+ const deltaLat = radius / earthRadius
+ const deltaLng = radius / (earthRadius * Math.max(Math.cos(baseLat), 0.000001))
+ const points = [
+ { lng: Cesium.Math.toDegrees(Cesium.Math.toRadians(lng) + deltaLng), lat },
+ { lng: Cesium.Math.toDegrees(Cesium.Math.toRadians(lng) - deltaLng), lat },
+ { lng, lat: Cesium.Math.toDegrees(baseLat + deltaLat) },
+ { lng, lat: Cesium.Math.toDegrees(baseLat - deltaLat) },
+ ]
+ points.forEach(point => {
+ positions.push(Cesium.Cartesian3.fromDegrees(point.lng, point.lat, resolved.height || 0))
+ })
+}
+
+function collectShapeBoundingPositions () {
+ const positions = []
+ if (!viewer || !shapeList.value.length) return positions
+ shapeList.value.forEach(shape => {
+ let points = getShapeDisplayPoints(shape)
+ if (!Array.isArray(points) || !points.length) {
+ points = resolveControlPoints(shape?.meta, shape?.drawType)
+ }
+ if (Array.isArray(points) && points.length) {
+ points.forEach(point => {
+ const resolved = resolveLngLatPoint(point)
+ const normalized = normalizeShapePoint(resolved)
+ if (!normalized) return
+ positions.push(Cesium.Cartesian3.fromDegrees(normalized.lng, normalized.lat, normalized.height))
+ })
+ }
+ if (shape?.drawType === 'buffer' && shape?.meta?.center && Array.isArray(shape?.meta?.bufferRadii)) {
+ const maxRadius = Math.max(...shape.meta.bufferRadii.filter(item => Number.isFinite(item)))
+ pushRadiusBoundingPoints(positions, shape.meta.center, maxRadius)
+ }
+ if (shape?.drawType === 'ellipse' && shape?.meta?.center) {
+ const major = Number(shape?.meta?.semiMajor)
+ const minor = Number(shape?.meta?.semiMinor)
+ const radius = Number.isFinite(major) && Number.isFinite(minor) ? Math.max(major, minor) : null
+ pushRadiusBoundingPoints(positions, shape.meta.center, radius)
+ }
+ })
+ return positions
+}
+
+function fitViewToShapeList () {
+ if (!viewer || !shapeList.value.length) return
+ const positions = collectShapeBoundingPositions()
+ if (positions.length < 2) return
+ const boundingSphere = Cesium.BoundingSphere.fromPoints(positions)
+ const range = Math.max(boundingSphere.radius * 2.4, 200)
+ viewer.camera.flyToBoundingSphere(boundingSphere, {
+ duration: 0,
+ offset: new Cesium.HeadingPitchRange(viewer.camera.heading, Cesium.Math.toRadians(-90), range),
+ })
+}
+
// 绘制完成回调
-const drawFinished = async data => {
+const drawFinished = async (data, options = {}) => {
+ const shouldContinueDraw = options?.continueDraw || activeToolMode.value === 'draw'
const positions = Array.isArray(data?.positions) ? data.positions : data
if (!Array.isArray(positions) || positions.length < 3) {
pointList = []
@@ -832,6 +992,12 @@
shapeList.value.splice(targetIndex, 1, shapePayload)
}
renderShapeList()
+ if (shouldContinueDraw && !readonly.value) {
+ activeShapeId.value = null
+ pointList = []
+ clearActiveTool()
+ startDraw(currentShapeType.value || 'polygon')
+ }
}
function getDetectAreaTypeKey () {
@@ -932,11 +1098,13 @@
const positions = activeTool?.getPositions?.()
if (Array.isArray(positions) && positions.length >= 3) {
suppressTypePanelOnce.value = true
- drawFinished(positions)
+ drawFinished(positions, { continueDraw: true })
+ return
}
if (positions?.positions?.length >= 3) {
suppressTypePanelOnce.value = true
- drawFinished(positions)
+ drawFinished(positions, { continueDraw: true })
+ return
}
clearActiveTool()
}
@@ -1006,15 +1174,17 @@
function handleClearShape () {
if (readonly.value) return
- clearActiveTool()
viewEntity && viewer?.entities?.remove(viewEntity)
viewEntity = null
pointList = []
shapeList.value = []
activeShapeId.value = null
- activeAreaType.value = ''
hideTypePanel()
clearShapeDisplay()
+ clearActiveTool()
+ if (viewer && currentShapeType.value) {
+ startDraw(currentShapeType.value)
+ }
}
function handleShapeEdit (row) {
@@ -1051,6 +1221,7 @@
if (!viewer) return
if (shapeList.value.length) {
renderShapeList()
+ fitViewToShapeList()
return
}
if (!formData.value?.geom) return
@@ -1070,18 +1241,87 @@
}
// 获取派出所列表
-async function getPoliceStationList () {
- const res = await fwPoliceStationListApi()
- policeStationOptions.value = res?.data?.data ?? []
+async function getPoliceStationList (keyword = '') {
+ if (!visible.value) return
+ policeStationLoading.value = true
+ try {
+ const res = await fwPoliceStationListApi({
+ stationName: keyword || undefined,
+ })
+ const list = res?.data?.data ?? []
+ policeStationOptions.value = list
+ if (!keyword) {
+ policeStationAllOptions.value = list
+ }
+ await nextTick()
+ if (!keyword) {
+ const matched = policeStationOptions.value.find(
+ item => String(item.id) === String(formData.value.policeStationId)
+ )
+ if (!matched) {
+ formData.value.policeStationId = ''
+ }
+ }
+ } finally {
+ policeStationLoading.value = false
+ }
+}
+
+function handlePoliceStationSearch (query) {
+ policeStationSearchKeyword.value = (query || '').trim()
+ if (!policeStationSearchKeyword.value) {
+ policeStationOptions.value = [...policeStationAllOptions.value]
+ return
+ }
+ if (policeStationSearchTimer.value) {
+ clearTimeout(policeStationSearchTimer.value)
+ }
+ policeStationSearchTimer.value = setTimeout(() => {
+ policeStationSearchTimer.value = null
+ void getPoliceStationList(policeStationSearchKeyword.value)
+ }, 300)
}
// 获取设备列表
-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({
+ areaId: dialogMode.value === 'add' ? '' : formData.value.id,
+ 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)
}
}
@@ -1091,9 +1331,15 @@
const ids = rows.map(item => item.id)
formData.value.deviceIds = ids.join(',')
renderDeviceRanges(rows)
- const target = rows[rows.length - 1]
- if (target) {
- void flyToDevice(target)
+ if (suppressDeviceFly.value) {
+ lastSelectedDeviceIds = [...ids]
+ return
+ }
+ const prevSet = new Set(lastSelectedDeviceIds)
+ const addedRow = rows.find(row => !prevSet.has(row.id))
+ lastSelectedDeviceIds = [...ids]
+ if (addedRow) {
+ void flyToDevice(addedRow)
}
}
@@ -1109,14 +1355,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
@@ -1130,9 +1378,18 @@
// 打开弹框
async function open ({ mode, row } = {}) {
+ isDialogInitializing.value = true
dialogMode.value = mode || 'add'
formData.value = dialogMode.value === 'add' ? initForm() : row
selectedDeviceRows.value = []
+ deviceOptions.value = []
+ policeStationOptions.value = []
+ policeStationAllOptions.value = []
+ policeStationSearchKeyword.value = ''
+ if (policeStationSearchTimer.value) {
+ clearTimeout(policeStationSearchTimer.value)
+ policeStationSearchTimer.value = null
+ }
shapeList.value = []
activeShapeId.value = null
activeAreaType.value = ''
@@ -1141,31 +1398,60 @@
initMap()
clearShapeDisplay()
clearDeviceRangePrimitives()
+ clearDeviceIconEntities()
viewEntity && viewer?.entities?.remove(viewEntity)
viewEntity = null
clearActiveTool()
- await getDeviceList()
if (dialogMode.value === 'add') {
- // default no draw mode
+ mapRef.value?.zoomToAdminBoundary?.()
+ handleDraw('polygon')
} else if (dialogMode.value === 'edit') {
await loadDetail()
viewPolygon()
+ activeShapeId.value = null
+ pointList = []
+ const firstShape = shapeList.value[0]
+ const initialType = firstShape?.drawType || 'polygon'
+ currentShapeType.value = initialType
+ if (initialType === 'buffer') {
+ activeAreaType.value = bufferAreaTypeValue.split(',')
+ } else if (firstShape?.areaType) {
+ activeAreaType.value = firstShape.areaType
+ } else {
+ const defaultAreaType = getDetectAreaTypeKey()
+ if (defaultAreaType) {
+ activeAreaType.value = defaultAreaType
+ }
+ }
+ clearActiveTool()
+ startDraw(initialType)
} else {
await loadDetail()
viewPolygon()
}
await nextTick()
+ suppressDeviceFly.value = true
syncDeviceSelection()
+ lastSelectedDeviceIds = selectedDeviceRows.value.map(row => row.id)
+ await nextTick()
+ suppressDeviceFly.value = false
if (!readonly.value) {
renderDeviceRanges(selectedDeviceRows.value)
}
+ isDialogInitializing.value = false
+ queueDeviceListRefresh()
+ void getPoliceStationList()
}
-onMounted(() => {
- getPoliceStationList()
-})
-
onBeforeUnmount(() => {
+ if (deviceListTimer) {
+ clearTimeout(deviceListTimer)
+ deviceListTimer = null
+ }
+ if (policeStationSearchTimer.value) {
+ clearTimeout(policeStationSearchTimer.value)
+ policeStationSearchTimer.value = null
+ }
hideTypePanel()
})
@@ -1227,44 +1513,31 @@
right: 16px;
display: flex;
flex-direction: column;
- gap: 10px;
- padding: 10px 8px;
- background: rgba(19, 28, 36, 0.75);
+ gap: 14px;
+ padding: 32px 14px;
+ background: rgba(26, 25, 25, 0.6);
+ border-radius: 8px 8px 8px 8px;
backdrop-filter: blur(4px);
- border-radius: 8px;
- box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2);
}
.tool-btn {
- padding: 0;
+ padding: 0 !important;
+ width: 46px;
+ height: 46px;
display: flex;
flex-direction: column;
align-items: center;
- gap: 6px;
- background: rgba(255, 255, 255, 0.08);
- border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: 8px;
- color: #ffffff;
- font-size: 12px;
- line-height: 1;
+ border: none !important;
+ box-shadow: none;
cursor: pointer;
- transition: all 0.2s ease;
overflow: hidden;
+ background: transparent !important;
+ box-sizing: border-box;
}
-.tool-btn:hover {
- background: rgba(255, 255, 255, 0.16);
- border-color: rgba(255, 255, 255, 0.35);
- transform: translateY(-1px);
-}
-
-.tool-btn:active {
- transform: translateY(0);
-}
-
-.tool-btn.danger {
- border-color: rgba(255, 112, 112, 0.6);
- color: #ffb3b3;
+.tool-btn.active {
+ border: 2px solid #FFFFFF !important;
}
.tool-icon {
@@ -1272,8 +1545,8 @@
}
.tool-icon-image {
- width: 36px;
- height: 36px;
+ width: 100%;
+ height: 100%;
}
.tool-icon.polygon {
--
Gitblit v1.9.3