From 76ad0866ab16079b054590cfd0dcc10c439f526b Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Thu, 29 Jan 2026 10:27:48 +0800
Subject: [PATCH] feat:区域划分调整
---
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue | 767 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---
1 files changed, 724 insertions(+), 43 deletions(-)
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 302d14e..c253bbf 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -12,6 +12,54 @@
:layer-mode="4"
:boundary="false"
/>
+ <div
+ v-if="showTypePanel && !readonly"
+ class="shape-type-panel"
+ >
+ <div class="panel-header">
+ <span>{{ drawTypeLabelMap[currentShapeType] || '区域' }}</span>
+ <el-icon class="panel-close" @click.stop="handleTypePanelClose"><Close /></el-icon>
+ </div>
+ <div class="panel-body">
+ <span class="panel-label">区域类型</span>
+ <el-select
+ class="command-select"
+ popper-class="command-select-popper"
+ v-model="formData.areaType"
+ placeholder="请选择"
+ @change="handleAreaTypeChange"
+ >
+ <el-option
+ v-for="item in dictObj.areaType"
+ :key="item.dictKey"
+ :label="item.dictValue"
+ :value="item.dictKey"
+ />
+ </el-select>
+ </div>
+ </div>
+ <div class="map-toolbar">
+ <button class="tool-btn" type="button" @click="handleDraw('polygon')">
+ <span class="tool-icon polygon"></span>
+ <span class="tool-text">多边形</span>
+ </button>
+ <button class="tool-btn" type="button" @click="handleDraw('rectangle')">
+ <span class="tool-icon rect"></span>
+ <span class="tool-text">矩形</span>
+ </button>
+ <button class="tool-btn" type="button" @click="handleDraw('ellipse')">
+ <span class="tool-icon ellipse"></span>
+ <span class="tool-text">椭圆</span>
+ </button>
+ <button class="tool-btn" type="button" @click="handleDraw('buffer')">
+ <span class="tool-icon buffer"></span>
+ <span class="tool-text">缓冲圆</span>
+ </button>
+ <button class="tool-btn danger" type="button" @click="handleClearShape">
+ <span class="tool-icon trash"></span>
+ <span class="tool-text">删除</span>
+ </button>
+ </div>
</div>
<div class="right-container">
@@ -132,6 +180,28 @@
</el-select>
</el-form-item>
+ <div class="shape-table-container">
+ <div class="shape-table-title">绘制区域列表</div>
+ <el-table class="command-table" :data="shapeList" row-key="id" height="360">
+ <el-table-column prop="areaType" label="类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.areaType, dictObj.areaType) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="drawType" label="绘制类型">
+ <template v-slot="{ row }">
+ {{ drawTypeLabelMap[row.drawType] || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="操作" class-name="operation-btns" width="120">
+ <template v-slot="{ row }">
+ <el-link @click="handleShapeEdit(row)">编辑</el-link>
+ <el-link @click="handleShapeDelete(row)">删除</el-link>
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+
<div class="search-table-container">
<div class="search-box">
<div class="label">
@@ -173,15 +243,17 @@
<script setup>
import { Close } from '@element-plus/icons-vue'
-import { computed, inject, nextTick, onMounted, ref, watch } from 'vue'
+import { computed, inject, nextTick, onBeforeUnmount, onMounted, 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 { DrawPolygon } from '@/utils/cesium/DrawPolygon'
+import { DrawManager } from './shapeTools/DrawManager'
+import { EditManager } from './shapeTools/EditManager'
import { cartesian3Convert } from '@/utils/cesium/mapUtil'
+import { buildEllipsePositions } from './shapeTools/shapeUtils'
import * as turf from '@turf/turf'
import * as Cesium from 'cesium'
import { fwPoliceStationListApi } from '@/views/areaManage/precinctInfo/precinctInfoApi'
@@ -219,9 +291,39 @@
const deviceOptions = ref([]) // 关联设备
const dictObj = inject('dictObj')
const mapRef = ref(null)
+const currentShapeType = ref('polygon')
+const shapeList = ref([])
+const activeShapeId = ref(null)
+const drawTypeLabelMap = {
+ polygon: '多边形',
+ rectangle: '矩形',
+ ellipse: '椭圆',
+ buffer: '缓冲圆',
+}
+const showTypePanel = ref(false)
+const suppressTypePanelOnce = ref(false)
+const areaTypeStyleMap = {
+ '1': {
+ fill: Cesium.Color.fromBytes(25, 178, 230, 128),
+ outline: Cesium.Color.fromBytes(0, 251, 255, 255),
+ },
+ '2': {
+ fill: Cesium.Color.fromBytes(255, 235, 59, 128),
+ outline: Cesium.Color.fromBytes(255, 235, 59, 255),
+ },
+ '3': {
+ fill: Cesium.Color.fromBytes(247, 20, 20, 128),
+ outline: Cesium.Color.fromBytes(255, 0, 0, 255),
+ },
+}
let viewer
-let drawPolygonExample
+let drawManager
+let editManager
+let activeTool
let pointList = []
+let viewEntity
+let shapeDisplaySource
+const activeToolMode = ref('')
const rules = {
areaName: fieldRules(true, 50),
@@ -274,6 +376,13 @@
// 提交新增/编辑
async function handleSubmit () {
+ if (!shapeList.value.length) {
+ ElMessage.warning('区域不可为空,请先绘制区域')
+ return
+ }
+
+ console.log(shapeList.value, 1111111)
+
const isValid = await formRef.value?.validate().catch(() => false)
if (!isValid) return
if (dialogMode.value === 'add' && Number(formData.value.areaSize) < 0.5) {
@@ -306,6 +415,24 @@
formData.value.flyDateEnd = flyDateRange.value[1] || ''
}
)
+watch(
+ () => visible.value,
+ val => {
+ if (!val) {
+ viewEntity && viewer?.entities?.remove(viewEntity)
+ viewEntity = null
+ clearActiveTool()
+ hideTypePanel()
+ clearShapeDisplay()
+ }
+ }
+)
+watch(
+ () => formData.value.areaType,
+ val => {
+ updateActiveShapeAreaType(val)
+ }
+)
// 加载详情
async function loadDetail () {
@@ -334,14 +461,260 @@
viewer = map?.viewer || null
}
+function ensureShapeDisplaySource () {
+ if (!viewer) return null
+ if (!shapeDisplaySource) {
+ shapeDisplaySource = new Cesium.CustomDataSource('partition-shape-display')
+ viewer.dataSources.add(shapeDisplaySource)
+ }
+ return shapeDisplaySource
+}
+
+function clearShapeDisplay () {
+ if (!shapeDisplaySource) return
+ shapeDisplaySource.entities.removeAll()
+}
+
+function normalizeShapePoint (point) {
+ const lng = point?.lng ?? point?.longitude
+ const lat = point?.lat ?? point?.latitude
+ if (!Number.isFinite(Number(lng)) || !Number.isFinite(Number(lat))) return null
+ return {
+ lng: Number(lng),
+ lat: Number(lat),
+ height: Number.isFinite(Number(point?.height)) ? Number(point.height) : 0,
+ }
+}
+
+function resolveLngLatPoint (point) {
+ if (!point) return null
+ const lng = point?.lng ?? point?.longitude
+ const lat = point?.lat ?? point?.latitude
+ if (Number.isFinite(Number(lng)) && Number.isFinite(Number(lat))) {
+ return {
+ lng: Number(lng),
+ lat: Number(lat),
+ height: Number.isFinite(Number(point?.height)) ? Number(point.height) : 0,
+ }
+ }
+ if (!viewer || !Number.isFinite(point?.x) || !Number.isFinite(point?.y) || !Number.isFinite(point?.z)) {
+ return null
+ }
+ const val = cartesian3Convert(point, viewer)
+ return {
+ lng: val.longitude,
+ lat: val.latitude,
+ height: Number.isFinite(val.height) ? val.height : 0,
+ }
+}
+
+function resolveControlPoints (meta, drawType) {
+ if (!meta) return []
+ if (Array.isArray(meta?.controlPoints)) {
+ return meta.controlPoints.map(resolveLngLatPoint).filter(Boolean)
+ }
+ if (drawType === 'ellipse') {
+ return [meta.center, meta.majorPoint, meta.minorPoint].map(resolveLngLatPoint).filter(Boolean)
+ }
+ if (drawType === 'buffer') {
+ return [meta.center, meta.radiusPoint1, meta.radiusPoint2, meta.radiusPoint3]
+ .map(resolveLngLatPoint)
+ .filter(Boolean)
+ }
+ return []
+}
+
+function buildShapePositions (points = []) {
+ const normalized = points.map(normalizeShapePoint).filter(Boolean)
+ return normalized.map(point =>
+ Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.height)
+ )
+}
+
+function getShapeDisplayPoints (shape) {
+ if (Array.isArray(shape?.displayPoints) && shape.displayPoints.length) {
+ return shape.displayPoints
+ }
+ return shape?.points || []
+}
+
+function getAreaTypeStyle (areaType) {
+ return areaTypeStyleMap?.[String(areaType)] || {
+ fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+ outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+ }
+}
+
+function renderShapeList () {
+ if (!viewer || !visible.value || readonly.value) {
+ clearShapeDisplay()
+ return
+ }
+ const dataSource = ensureShapeDisplaySource()
+ if (!dataSource) return
+ dataSource.entities.removeAll()
+ shapeList.value.forEach(shape => {
+ if (activeToolMode.value === 'edit' && shape.id === activeShapeId.value) return
+ if (shape.drawType === 'buffer' && shape.meta?.bufferRadii?.length && shape.meta?.center) {
+ const center = shape.meta.center
+ const centerCartesian = Cesium.Cartesian3.fromDegrees(center.lng, center.lat, center.height || 0)
+ const [radius1, radius2, radius3] = shape.meta.bufferRadii
+ const radii = [radius1, radius2, radius3].filter(item => Number.isFinite(item) && item > 0)
+ if (radii.length) {
+ const styles = [
+ {
+ fill: Cesium.Color.fromBytes(247, 20, 20, 128),
+ outline: Cesium.Color.fromBytes(255, 0, 0, 255),
+ },
+ {
+ fill: Cesium.Color.fromBytes(255, 235, 59, 128),
+ outline: Cesium.Color.fromBytes(255, 235, 59, 255),
+ },
+ {
+ fill: Cesium.Color.fromBytes(25, 178, 230, 128),
+ outline: Cesium.Color.fromBytes(0, 251, 255, 255),
+ },
+ ]
+ radii.forEach((radius, index) => {
+ const style = styles[index] || styles[styles.length - 1]
+ dataSource.entities.add({
+ name: 'shape-display',
+ customData: { shapeId: shape.id, drawType: shape.drawType, level: index + 1 },
+ position: centerCartesian,
+ ellipse: {
+ semiMajorAxis: radius,
+ semiMinorAxis: radius,
+ material: style.fill,
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ const positions = buildEllipsePositions(centerCartesian, radius, radius)
+ dataSource.entities.add({
+ name: 'shape-display',
+ customData: { shapeId: shape.id, drawType: shape.drawType, level: index + 1, outline: true },
+ polyline: {
+ positions: positions.length ? [...positions, positions[0]] : positions,
+ clampToGround: true,
+ width: 2,
+ material: style.outline,
+ },
+ })
+ })
+ }
+ return
+ }
+
+ if (!shape?.points?.length) return
+ const positions = buildShapePositions(getShapeDisplayPoints(shape))
+ if (positions.length < 3) return
+ const style = getAreaTypeStyle(shape.areaType)
+ dataSource.entities.add({
+ name: 'shape-display',
+ customData: { shapeId: shape.id, drawType: shape.drawType },
+ polygon: {
+ hierarchy: new Cesium.PolygonHierarchy(positions),
+ material: style.fill,
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ polyline: {
+ positions: positions.length ? [...positions, positions[0]] : positions,
+ clampToGround: true,
+ width: 2,
+ material: style.outline,
+ },
+ })
+ })
+}
+
// 绘制完成回调
const drawFinished = async data => {
- pointList = _.cloneDeep(data).map(item => {
+ const positions = Array.isArray(data?.positions) ? data.positions : data
+ if (!Array.isArray(positions) || positions.length < 3) {
+ pointList = []
+ formData.value.areaSize = null
+ formData.value.longitude = null
+ formData.value.latitude = null
+ hideTypePanel()
+ return
+ }
+ const meta = data?.meta
+ const bufferRadii = Array.isArray(meta?.bufferRadii) ? meta.bufferRadii : null
+ const centerCartesian = meta?.center
+ const centerLngLat = centerCartesian ? cartesian3Convert(centerCartesian, viewer) : null
+ pointList = _.cloneDeep(positions).map(item => {
const val = cartesian3Convert(item, viewer)
return { ...val, lng: val.longitude, lat: val.latitude }
})
+ applyPolygonMetrics(pointList)
+ const controlPoints = resolveControlPoints(meta, currentShapeType.value)
+ if (!formData.value.areaType) {
+ const detectAreaType = getDetectAreaTypeKey()
+ if (detectAreaType) {
+ formData.value.areaType = detectAreaType
+ }
+ }
+ if (!suppressTypePanelOnce.value) {
+ showTypePanelAtCurrent()
+ }
+ suppressTypePanelOnce.value = false
+
+ if (!activeShapeId.value) {
+ activeShapeId.value = `shape_${Date.now()}_${Math.random().toString(16).slice(2, 6)}`
+ }
+ const targetIndex = shapeList.value.findIndex(item => item.id === activeShapeId.value)
+ const displayPoints = ['ellipse', 'buffer'].includes(currentShapeType.value)
+ ? _.cloneDeep(pointList)
+ : null
+ let shapeMeta = null
+ if (currentShapeType.value === 'buffer' && bufferRadii?.length && centerLngLat) {
+ shapeMeta = {
+ bufferRadii,
+ center: { lng: centerLngLat.longitude, lat: centerLngLat.latitude, height: centerLngLat.height ?? 0 },
+ }
+ if (controlPoints.length) {
+ shapeMeta.controlPoints = controlPoints
+ }
+ } else if (currentShapeType.value === 'ellipse') {
+ const centerPoint = resolveLngLatPoint(meta?.center)
+ const majorPoint = resolveLngLatPoint(meta?.majorPoint)
+ const minorPoint = resolveLngLatPoint(meta?.minorPoint)
+ if (centerPoint || majorPoint || minorPoint) {
+ shapeMeta = {
+ center: centerPoint,
+ majorPoint,
+ minorPoint,
+ semiMajor: Number.isFinite(Number(meta?.semiMajor)) ? Number(meta.semiMajor) : null,
+ semiMinor: Number.isFinite(Number(meta?.semiMinor)) ? Number(meta.semiMinor) : null,
+ }
+ if (controlPoints.length) {
+ shapeMeta.controlPoints = controlPoints
+ }
+ }
+ }
+ const shapePayload = {
+ id: activeShapeId.value,
+ areaType: formData.value.areaType,
+ drawType: currentShapeType.value,
+ points: ['ellipse', 'buffer'].includes(currentShapeType.value) && controlPoints.length
+ ? controlPoints
+ : _.cloneDeep(pointList),
+ displayPoints,
+ meta: shapeMeta,
+ }
+ if (targetIndex === -1) {
+ shapeList.value.push(shapePayload)
+ } else {
+ shapeList.value.splice(targetIndex, 1, shapePayload)
+ }
+ renderShapeList()
+}
+
+function applyPolygonMetrics(points) {
+ if (!points?.length) return
const polygon = turf.polygon([
- [...pointList.map(item => [item.longitude, item.latitude]), [pointList[0].longitude, pointList[0].latitude]],
+ [...points.map(item => [item.longitude, item.latitude]), [points[0].longitude, points[0].latitude]],
])
const center = turf.centerOfMass(polygon)
const areaSqm = turf.area(polygon) // 平方米
@@ -350,41 +723,163 @@
formData.value.latitude = center.geometry.coordinates[1]
}
-// 新增模式绘制
-function addPolygon () {
- drawPolygonExample = new DrawPolygon(viewer)
- drawPolygonExample.initHandler(viewer)
- drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
+function getDetectAreaTypeKey () {
+ const areaTypeOptions = Array.isArray(dictObj?.value?.areaType)
+ ? dictObj.value.areaType
+ : dictObj?.areaType
+
+
+ console.log(areaTypeOptions, 1)
+
+ if (!Array.isArray(areaTypeOptions)) return ''
+ const target = areaTypeOptions.find(item => item?.dictKey === '1')
+ return target?.dictKey ?? ''
}
-// 编辑面
-function editPolygon () {
- if (!formData.value?.geom) return
- pointList = geomAnalysis(formData.value.geom)
- drawPolygonExample?.destroy()
- drawPolygonExample = new DrawPolygon(viewer)
- let pointList1 = _.cloneDeep(pointList)
- pointList1.pop()
- drawPolygonExample.initPolygon(
- viewer,
- pointList.map(item => ({ lng: item.longitude, lat: item.latitude }))
- )
- drawPolygonExample.subscribe('getPolygonPositions', drawFinished)
- if (pointList1.length) {
- const result = pointList1.map(item => [item.longitude, item.latitude]).flat()
- const tempEntity = viewer.entities?.add({
- position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
- polyline: {
- positions: Cesium.Cartesian3.fromDegreesArray(result),
- clampToGround: true,
- width: 1,
- material: Cesium.Color.TRANSPARENT,
- },
- })
- if (tempEntity) {
- viewer.flyTo(tempEntity, { duration: 0 })
- viewer.entities.remove(tempEntity)
+function showTypePanelAtCurrent () {
+ if (readonly.value) return
+ showTypePanel.value = true
+}
+
+function hideTypePanel () {
+ showTypePanel.value = false
+}
+
+function handleTypePanelClose () {
+ hideTypePanel()
+ if (readonly.value) return
+ if (activeToolMode.value !== 'edit') return
+ const positions = activeTool?.getPositions?.()
+ if (Array.isArray(positions) && positions.length >= 3) {
+ suppressTypePanelOnce.value = true
+ drawFinished(positions)
+ }
+ if (positions?.positions?.length >= 3) {
+ suppressTypePanelOnce.value = true
+ drawFinished(positions)
+ }
+ clearActiveTool()
+}
+
+function handleAreaTypeChange (value) {
+ updateActiveShapeAreaType(value)
+}
+
+function updateActiveShapeAreaType (value) {
+ if (currentShapeType.value !== 'buffer') {
+ activeTool?.setStyle?.(getAreaTypeStyle(value))
+ }
+ if (!activeShapeId.value) return
+ const targetIndex = shapeList.value.findIndex(item => item.id === activeShapeId.value)
+ if (targetIndex === -1) return
+ const target = shapeList.value[targetIndex]
+ shapeList.value.splice(targetIndex, 1, {
+ ...target,
+ areaType: value,
+ })
+ renderShapeList()
+}
+
+// Tool lifecycle
+function clearActiveTool () {
+ activeTool?.destroy?.()
+ activeTool = null
+ drawManager?.destroy()
+ editManager?.destroy()
+ activeToolMode.value = ''
+ renderShapeList()
+}
+
+function startDraw (type) {
+ currentShapeType.value = type
+ activeToolMode.value = 'draw'
+ drawManager ||= new DrawManager(viewer)
+ activeTool = drawManager.start(type, { style: getAreaTypeStyle(formData.value.areaType) })
+ activeTool?.subscribe('getPolygonPositions', drawFinished)
+}
+
+function startEdit (type, points) {
+ currentShapeType.value = type
+ activeToolMode.value = 'edit'
+ editManager ||= new EditManager(viewer)
+ activeTool = editManager.start(type, points, { style: getAreaTypeStyle(formData.value.areaType) })
+ activeTool?.subscribe('getPolygonPositions', drawFinished)
+}
+
+function handleDraw (type) {
+ if (readonly.value) return
+ if (!viewer) return
+ clearActiveTool()
+ viewEntity && viewer?.entities?.remove(viewEntity)
+ viewEntity = null
+ hideTypePanel()
+ renderShapeList()
+ if (dialogMode.value === 'edit' && pointList.length >= 3) {
+ const editPoints = _.cloneDeep(pointList)
+ if (editPoints.length > 1) {
+ const first = editPoints[0]
+ const last = editPoints[editPoints.length - 1]
+ if (first.longitude === last.longitude && first.latitude === last.latitude) {
+ editPoints.pop()
+ }
}
+ activeShapeId.value = activeShapeId.value || (shapeList.value[0]?.id ?? null)
+ startEdit(type, editPoints)
+ return
+ }
+ const defaultAreaType = getDetectAreaTypeKey()
+ if (defaultAreaType) {
+ formData.value.areaType = defaultAreaType
+ }
+ activeShapeId.value = null
+ startDraw(type)
+}
+
+function handleClearShape () {
+ if (readonly.value) return
+ clearActiveTool()
+ viewEntity && viewer?.entities?.remove(viewEntity)
+ viewEntity = null
+ pointList = []
+ shapeList.value = []
+ activeShapeId.value = null
+ formData.value.areaSize = null
+ formData.value.longitude = null
+ formData.value.latitude = null
+ hideTypePanel()
+ clearShapeDisplay()
+}
+
+function handleShapeEdit (row) {
+ if (!row?.points?.length) return
+ if (activeToolMode.value === 'edit' && activeShapeId.value === row.id) return
+ activeShapeId.value = row.id
+ currentShapeType.value = row.drawType
+ formData.value.areaType = row.areaType
+ const displayPoints = getShapeDisplayPoints(row)
+ pointList = _.cloneDeep(displayPoints)
+ clearActiveTool()
+ const editPayload = ['buffer', 'ellipse'].includes(row.drawType)
+ ? { points: displayPoints, meta: row.meta }
+ : row.points
+ startEdit(row.drawType, editPayload)
+ applyPolygonMetrics(pointList)
+ showTypePanelAtCurrent()
+ renderShapeList()
+}
+
+function handleShapeDelete (row) {
+ if (!row) return
+ shapeList.value = shapeList.value.filter(item => item.id !== row.id)
+ renderShapeList()
+ if (activeShapeId.value === row.id) {
+ activeShapeId.value = null
+ clearActiveTool()
+ pointList = []
+ formData.value.areaSize = null
+ formData.value.longitude = null
+ formData.value.latitude = null
+ hideTypePanel()
}
}
@@ -393,7 +888,7 @@
if (!formData.value?.geom) return
pointList = geomAnalysis(formData.value.geom)
const result = pointList.map(item => [item.longitude, item.latitude]).flat()
- const mian = viewer.entities?.add({
+ viewEntity = viewer.entities?.add({
customType: 'control_group',
position: Cesium.Cartesian3.fromDegrees(result[0], result[1]),
polyline: {
@@ -403,7 +898,7 @@
material: Cesium.Color.RED,
},
})
- viewer.flyTo(mian, { duration: 0 })
+ viewer.flyTo(viewEntity, { duration: 0 })
}
// 获取派出所列表
@@ -449,14 +944,32 @@
dialogMode.value = mode || 'add'
formData.value = dialogMode.value === 'add' ? initForm() : row
selectedDeviceRows.value = []
+ shapeList.value = []
+ activeShapeId.value = null
await nextTick()
initMap()
+ clearShapeDisplay()
+ viewEntity && viewer?.entities?.remove(viewEntity)
+ viewEntity = null
+ clearActiveTool()
await getDeviceList()
if (dialogMode.value === 'add') {
- addPolygon()
+ // default no draw mode
} else if (dialogMode.value === 'edit') {
await loadDetail()
- editPolygon()
+ pointList = geomAnalysis(formData.value.geom) || []
+ if (pointList.length) {
+ activeShapeId.value = `shape_${Date.now()}_${Math.random().toString(16).slice(2, 6)}`
+ shapeList.value = [
+ {
+ id: activeShapeId.value,
+ areaType: formData.value.areaType,
+ drawType: currentShapeType.value,
+ points: _.cloneDeep(pointList),
+ },
+ ]
+ }
+ viewPolygon()
} else {
await loadDetail()
viewPolygon()
@@ -467,7 +980,10 @@
onMounted(() => {
getPoliceStationList()
- getDeviceList()
+})
+
+onBeforeUnmount(() => {
+ hideTypePanel()
})
defineExpose({
@@ -475,4 +991,169 @@
})
</script>
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.left-container {
+ position: relative;
+}
+
+.shape-type-panel {
+ position: absolute;
+ right: 16px;
+ bottom: 16px;
+ z-index: 6;
+ min-width: 260px;
+ background: rgba(22, 33, 44, 0.9);
+ border: 1px solid rgba(255, 255, 255, 0.15);
+ border-radius: 8px;
+ box-shadow: 0 10px 24px rgba(0, 0, 0, 0.35);
+ backdrop-filter: blur(6px);
+ color: #e5e7f0;
+}
+
+.panel-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 8px 12px;
+ border-bottom: 1px solid rgba(255, 255, 255, 0.12);
+ font-size: 13px;
+ font-weight: 600;
+}
+
+.panel-close {
+ cursor: pointer;
+ color: rgba(255, 255, 255, 0.7);
+}
+
+.panel-body {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 10px 12px 12px;
+}
+
+.panel-label {
+ font-size: 12px;
+ color: rgba(255, 255, 255, 0.75);
+ white-space: nowrap;
+}
+
+.map-toolbar {
+ position: absolute;
+ top: 64px;
+ right: 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ padding: 10px 8px;
+ background: rgba(19, 28, 36, 0.75);
+ backdrop-filter: blur(4px);
+ border-radius: 8px;
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.2);
+}
+
+.tool-btn {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 6px;
+ width: 64px;
+ padding: 8px 6px;
+ background: rgba(255, 255, 255, 0.08);
+ border: 1px solid rgba(255, 255, 255, 0.18);
+ border-radius: 6px;
+ color: #ffffff;
+ font-size: 12px;
+ line-height: 1;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.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-icon {
+ position: relative;
+ width: 20px;
+ height: 20px;
+ border: 2px solid #ffffff;
+ border-radius: 3px;
+}
+
+.tool-icon.polygon {
+ border-radius: 4px;
+ transform: rotate(15deg);
+}
+
+.tool-icon.rect {
+ border-radius: 2px;
+}
+
+.tool-icon.ellipse {
+ border-radius: 50%;
+}
+
+.tool-icon.buffer {
+ border-radius: 50%;
+ box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.5);
+}
+
+.tool-icon.trash {
+ border: none;
+ box-shadow: none;
+}
+
+.tool-icon.trash::before,
+.tool-icon.trash::after {
+ content: "";
+ position: absolute;
+ left: 4px;
+ right: 4px;
+ background: #ffffff;
+}
+
+.tool-icon.trash::before {
+ top: 4px;
+ height: 10px;
+ border-radius: 2px;
+}
+
+.tool-icon.trash::after {
+ top: 1px;
+ height: 3px;
+ border-radius: 2px;
+}
+
+.shape-table-container {
+ margin-top: 8px;
+ padding: 8px;
+ border-radius: 8px;
+ background: rgba(27, 34, 56, 0.6);
+}
+
+.shape-table-title {
+ display: flex;
+ align-items: center;
+ justify-content: flex-start;
+ padding-right: 1.2rem;
+ font-family: Source Han Sans CN, Source Han Sans CN;
+ font-weight: 500;
+ font-size: 1.4rem;
+ color: #D4D5D7;
+ font-style: normal;
+ text-transform: none;
+ box-sizing: border-box;
+}
+</style>
--
Gitblit v1.9.3