| | |
| | | <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> |
| | |
| | | <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> |
| | |
| | | <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 { |
| | | DrawManager, |
| | | EditManager, |
| | | addBufferRadiusLabelEntity, |
| | | buildEllipsePositions, |
| | | } from '@/utils/cesium/shapeTools' |
| | | import { cartesian3Convert } from '@/utils/cesium/mapUtil' |
| | | import { |
| | | createDeviceRangePrimitive, |
| | |
| | | } 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 { fwPoliceStationQueryByPolygonsApi } from '@/views/areaManage/precinctInfo/precinctInfoApi' |
| | | 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' |
| | |
| | | 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 |
| | | let policeStationListTimer = null |
| | | let policeStationRequestToken = 0 |
| | | |
| | | const rules = { |
| | | areaName: fieldRules(true, 50), |
| | |
| | | () => visible.value, |
| | | val => { |
| | | if (!val) { |
| | | if (deviceListTimer) { |
| | | clearTimeout(deviceListTimer) |
| | | deviceListTimer = null |
| | | } |
| | | if (policeStationListTimer) { |
| | | clearTimeout(policeStationListTimer) |
| | | policeStationListTimer = null |
| | | } |
| | | viewEntity && viewer?.entities?.remove(viewEntity) |
| | | viewEntity = null |
| | | clearActiveTool() |
| | |
| | | val => { |
| | | updateActiveShapeAreaType(val) |
| | | } |
| | | ) |
| | | watch( |
| | | () => shapeList.value, |
| | | () => { |
| | | if (isDialogInitializing.value) return |
| | | queueDeviceListRefresh() |
| | | queuePoliceStationRefresh() |
| | | }, |
| | | { deep: true } |
| | | ) |
| | | |
| | | // 加载详情 |
| | |
| | | viewer.scene.primitives.remove(primitive) |
| | | } |
| | | deviceRangePrimitiveMap.clear() |
| | | } |
| | | |
| | | function clearDeviceListState () { |
| | | deviceOptions.value = [] |
| | | selectedDeviceRows.value = [] |
| | | formData.value.deviceIds = '' |
| | | if (deviceTableRef.value) { |
| | | deviceTableRef.value.clearSelection() |
| | | } |
| | | } |
| | | |
| | | function clearPoliceStationState () { |
| | | policeStationOptions.value = [] |
| | | formData.value.policeStationId = '' |
| | | } |
| | | |
| | | 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) { |
| | |
| | | width: 2, |
| | | material: style.outline, |
| | | }, |
| | | }) |
| | | addBufferRadiusLabelEntity(dataSource, centerCartesian, radius, { |
| | | name: 'shape-display', |
| | | customData: { drawType: 'buffer', label: 'radius' }, |
| | | }) |
| | | }) |
| | | } |
| | |
| | | } |
| | | |
| | | // 获取派出所列表 |
| | | async function getPoliceStationList () { |
| | | const res = await fwPoliceStationListApi() |
| | | function queuePoliceStationRefresh () { |
| | | if (!visible.value) return |
| | | if (policeStationListTimer) { |
| | | clearTimeout(policeStationListTimer) |
| | | } |
| | | policeStationListTimer = setTimeout(() => { |
| | | policeStationListTimer = null |
| | | void getPoliceStationListByPolygons() |
| | | }, 200) |
| | | } |
| | | |
| | | async function getPoliceStationListByPolygons () { |
| | | if (!visible.value) return |
| | | const polygons = resolveShapePolygonsWkt() |
| | | if (!polygons.length) { |
| | | clearPoliceStationState() |
| | | return |
| | | } |
| | | policeStationRequestToken += 1 |
| | | const requestToken = policeStationRequestToken |
| | | const res = await fwPoliceStationQueryByPolygonsApi({ polygons }) |
| | | if (requestToken !== policeStationRequestToken) return |
| | | policeStationOptions.value = res?.data?.data ?? [] |
| | | await nextTick() |
| | | const matched = policeStationOptions.value.find( |
| | | item => String(item.id) === String(formData.value.policeStationId) |
| | | ) |
| | | if (!matched) { |
| | | formData.value.policeStationId = '' |
| | | } |
| | | } |
| | | |
| | | // 获取设备列表 |
| | | 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) |
| | | } |
| | | } |
| | | |
| | |
| | | |
| | | // 同步选择状态 |
| | | 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 |
| | |
| | | |
| | | // 打开弹框 |
| | | 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 = [] |
| | | shapeList.value = [] |
| | | activeShapeId.value = null |
| | | activeAreaType.value = '' |
| | |
| | | viewEntity && viewer?.entities?.remove(viewEntity) |
| | | viewEntity = null |
| | | clearActiveTool() |
| | | await getDeviceList() |
| | | if (dialogMode.value === 'add') { |
| | | mapRef.value?.zoomToAdminBoundary?.() |
| | | // default no draw mode |
| | | } else if (dialogMode.value === 'edit') { |
| | | await loadDetail() |
| | |
| | | if (!readonly.value) { |
| | | renderDeviceRanges(selectedDeviceRows.value) |
| | | } |
| | | isDialogInitializing.value = false |
| | | queueDeviceListRefresh() |
| | | queuePoliceStationRefresh() |
| | | } |
| | | |
| | | onMounted(() => { |
| | | getPoliceStationList() |
| | | }) |
| | | |
| | | onBeforeUnmount(() => { |
| | | if (deviceListTimer) { |
| | | clearTimeout(deviceListTimer) |
| | | deviceListTimer = null |
| | | } |
| | | if (policeStationListTimer) { |
| | | clearTimeout(policeStationListTimer) |
| | | policeStationListTimer = null |
| | | } |
| | | hideTypePanel() |
| | | }) |
| | | |