From f083281ab75f61390a04daa2484f330f8df4178f Mon Sep 17 00:00:00 2001
From: chenyao <1219716595@qq.com>
Date: Thu, 29 Jan 2026 08:44:20 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'
---
applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue | 168 ++-
applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js | 34
applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js | 58 +
applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js | 89 +
applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js | 218 +++
applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue | 1065 +++++++++++++++++++
applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue | 2
applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js | 162 ++
applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js | 198 +++
applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue | 9
applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js | 236 ++++
applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js | 314 +++++
applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js | 24
applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue | 2
applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js | 34
applications/drone-command/env/.env.development | 4
applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js | 244 ++++
applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js | 150 ++
applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js | 233 ++++
applications/task-work-order/env/.env.development | 4
20 files changed, 3,182 insertions(+), 66 deletions(-)
diff --git a/applications/drone-command/env/.env.development b/applications/drone-command/env/.env.development
index 82b533b..6524616 100644
--- a/applications/drone-command/env/.env.development
+++ b/applications/drone-command/env/.env.development
@@ -2,7 +2,7 @@
# @Author : yuan
# @Date : 2026-01-07 14:58:30
# @LastEditors : yuan
- # @LastEditTime : 2026-01-26 17:29:11
+ # @LastEditTime : 2026-01-28 09:29:05
# @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.204
+VITE_APP_URL= http://192.168.1.227
#新大屏地址
VITE_APP_DASHBOARD_URL = 'https://wrj.shuixiongit.com/command-center-dashboard/'
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue
new file mode 100644
index 0000000..cc492ca
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog copy.vue
@@ -0,0 +1,1065 @@
+<template>
+ <el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false"
+ :close-on-click-modal="false">
+ <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"
+ />
+ <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">
+ <div class="header">
+ <span>{{ titleEnum[dialogMode] }}</span>
+
+ <el-icon class="close-btn" @click.stop="visible = false"><Close /></el-icon>
+ </div>
+
+ <div class="content" v-if="readonly">
+ <div class="detail-title">区域详情</div>
+ <el-row>
+ <el-col :span="24">
+ <div class="label">区域名称</div>
+ <div class="val">{{ formData.areaName }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">区域位置</div>
+ <div class="val">{{ formatLocation(formData) }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">区域面积</div>
+ <div class="val">{{ formData.areaSize || '-' }}k㎡</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">区域类型</div>
+ <div class="val">{{ getDictLabel(formData.areaType, dictObj.areaType) }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">触发条件</div>
+ <div class="val">
+ {{ getDictLabel(formData.triggerCondition, dictObj.triggeringCondition) }}
+ </div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">响应机制</div>
+ <div class="val">{{ formData.responseMechanism }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">管控级别</div>
+ <div class="val">{{ getDictLabel(formData.controlLevel, dictObj.controlLevel) }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">关联派出所</div>
+ <div class="val">{{ formData.policeStationName }}</div>
+ </el-col>
+ <el-col :span="24">
+ <div class="label">可飞行时间段</div>
+ <div class="val">{{ formatFlyDate(formData) }}</div>
+ </el-col>
+ </el-row>
+
+ <div class="detail-title">关联设备</div>
+ <div class="command-table-container">
+ <div class="command-table-content">
+ <el-table class="command-table" ref="deviceTableRef" :data="deviceOptions"
+ row-key="id">
+ <el-table-column prop="deviceName" label="设备名称" />
+ <el-table-column prop="deviceType" label="类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </div>
+ </div>
+ </div>
+
+ <el-form class="dialog-form" v-else ref="formRef" :model="formData" :rules="rules" :disabled="readonly"
+ label-width="96px">
+ <el-form-item label="区域名称" prop="areaName">
+ <el-input class="command-input" v-model="formData.areaName" maxlength="50"
+ placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="区域位置" prop="longitude">
+ <div class="val">{{ formatLocation(formData) }}</div>
+ </el-form-item>
+ <el-form-item label="区域面积" prop="areaSize">
+ <div class="val">{{ formData.areaSize || '-' }}k㎡</div>
+ </el-form-item>
+ <el-form-item label="区域类型" prop="areaType">
+ <el-select class="command-select" popper-class="command-select-popper"
+ 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-form-item label="响应机制" prop="responseMechanism">
+ <el-input class="command-input" v-model="formData.responseMechanism"
+ maxlength="200" placeholder="请输入" clearable />
+ </el-form-item>
+ <el-form-item label="触发条件" prop="triggerCondition">
+ <el-select class="command-select" popper-class="command-select-popper"
+ v-model="formData.triggerCondition" placeholder="请选择" clearable>
+ <el-option v-for="item in dictObj.triggeringCondition" :key="item.dictKey"
+ :label="item.dictValue" :value="item.dictKey" />
+ </el-select>
+ </el-form-item>
+ <el-form-item label="管控级别" prop="controlLevel">
+ <el-select class="command-select" popper-class="command-select-popper"
+ 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-form-item label="可飞行时段" prop="flyDateStart">
+ <el-date-picker class="command-date-picker"
+ popper-class="command-date-picker-popper" v-model="flyDateRange"
+ type="datetimerange" range-separator="至" start-placeholder="开始时间" end-placeholder="结束时间"
+ :disabled-date="disabledFlyDate" :disabled-time="disabledFlyTime"
+ value-format="YYYY-MM-DD HH:mm:ss" clearable />
+ </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>
+ <el-option v-for="item in policeStationOptions" :key="item.id" :label="item.stationName"
+ :value="item.id" />
+ </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="类型" width="120">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.areaType, dictObj.areaType) }}
+ </template>
+ </el-table-column>
+ <el-table-column prop="drawType" label="绘制类型" width="120">
+ <template v-slot="{ row }">
+ {{ drawTypeLabelMap[row.drawType] || '-' }}
+ </template>
+ </el-table-column>
+ <el-table-column label="操作" class-name="operation-btns">
+ <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">
+ 关联设备
+ </div>
+
+ <el-input class="command-input" v-model="searchName" placeholder="请输入"
+ clearable></el-input>
+ </div>
+
+ <el-form-item prop="deviceIds" label-width="0">
+ <el-table class="command-table" ref="deviceTableRef"
+ :data="deviceOptions.filter(item => item.deviceName.includes(searchName))" row-key="id"
+ @selection-change="handleDeviceSelectionChange">
+ <el-table-column type="selection" width="55" :reserve-selection="true" />
+ <el-table-column prop="deviceName" label="设备名称" />
+ <el-table-column prop="deviceType" label="类型">
+ <template v-slot="{ row }">
+ {{ getDictLabel(row.deviceType, dictObj.deviceType) }}
+ </template>
+ </el-table-column>
+ </el-table>
+ </el-form-item>
+ </div>
+ </el-form>
+
+ <div class="footer">
+ <el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ readonly ? '关闭' : '取消' }}</el-button>
+ <el-button color="#284FE3" v-if="!readonly" type="primary" :loading="submitting"
+ :disabled="submitting" @click="handleSubmit">
+ 确定
+ </el-button>
+ </div>
+ </div>
+ </div>
+ </el-dialog>
+</template>
+
+<script setup>
+import { Close } from '@element-plus/icons-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 { 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'
+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 route = useRoute()
+const titleEnum = ref({ edit: '编辑区域', view: '查看区域', add: '新增区域' })
+const flyDateRange = ref([]) // 飞行时间
+const deviceTableRef = ref(null)
+const searchName = ref('')
+const selectedDeviceRows = ref([])
+const policeStationOptions = ref([]) // 关联派出所
+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 drawManager
+let editManager
+let activeTool
+let pointList = []
+let viewEntity
+let shapeDisplaySource
+const activeToolMode = ref('')
+
+const rules = {
+ areaName: fieldRules(true, 50),
+ longitude: fieldRules(true),
+ areaType: fieldRules(true),
+ responseMechanism: fieldRules(true, 200),
+ triggerCondition: fieldRules(true, 200),
+ controlLevel: fieldRules(true),
+ flyDateStart: fieldRules(true),
+ policeStationId: fieldRules(true),
+ deviceIds: fieldRules(false),
+}
+
+function isSameDay (left, right) {
+ return (
+ left.getFullYear() === right.getFullYear() &&
+ left.getMonth() === right.getMonth() &&
+ left.getDate() === right.getDate()
+ )
+}
+
+const disabledFlyDate = time => {
+ const startOfToday = new Date()
+ startOfToday.setHours(0, 0, 0, 0)
+ return time.getTime() < startOfToday.getTime()
+}
+
+const disabledFlyTime = date => {
+ if (!date) return {}
+ const now = new Date()
+ if (!isSameDay(date, now)) return {}
+ const currentHour = now.getHours()
+ const currentMinute = now.getMinutes()
+ const currentSecond = now.getSeconds()
+ return {
+ disabledHours: () => Array.from({ length: currentHour }, (_, index) => index),
+ disabledMinutes: hour =>
+ hour === currentHour ? Array.from({ length: currentMinute }, (_, index) => index) : [],
+ disabledSeconds: (hour, minute) =>
+ hour === currentHour && minute === currentMinute
+ ? Array.from({ length: currentSecond }, (_, index) => index)
+ : [],
+ }
+}
+
+// 关闭弹框
+function handleCancel () {
+ visible.value = false
+}
+
+// 提交新增/编辑
+async function handleSubmit () {
+ const isValid = await formRef.value?.validate().catch(() => false)
+ if (!isValid) return
+ if (dialogMode.value === 'add' && Number(formData.value.areaSize) < 0.5) {
+ ElMessage.warning('区域面积不能小于0.5km²,请重新绘制')
+ return
+ }
+ submitting.value = true
+ try {
+ let str = [...pointList, pointList[0]].map(item => `${item.longitude} ${item.latitude}`).join(',')
+ formData.value.geom = `POLYGON((${str}))`
+ await fwAreaDivideSubmitApi(formData.value)
+ const actionText = dialogMode.value === 'add' ? '新增' : '编辑'
+ saveOperationLog({
+ requestUri: route.path,
+ title: `${route.name || '区域划分'}-${actionText}`,
+ type: 1,
+ })
+ ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
+ visible.value = false
+ emit('success')
+ } finally {
+ submitting.value = false
+ }
+}
+
+watch(
+ () => flyDateRange.value,
+ () => {
+ formData.value.flyDateStart = flyDateRange.value[0] || ''
+ 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 () {
+ 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)
+}
+
+// 格式化区域位置
+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 () {
+ if (viewer) return
+ const map = mapRef.value?.getMap()
+ 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 buildShapePositions (points = []) {
+ const normalized = points.map(normalizeShapePoint).filter(Boolean)
+ return normalized.map(point =>
+ Cesium.Cartesian3.fromDegrees(point.lng, point.lat, point.height)
+ )
+}
+
+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 (!shape?.points?.length) return
+ 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
+ }
+
+ const positions = buildShapePositions(shape.points)
+ 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 => {
+ 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)
+ 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 shapePayload = {
+ id: activeShapeId.value,
+ areaType: formData.value.areaType,
+ drawType: currentShapeType.value,
+ points: _.cloneDeep(pointList),
+ meta: bufferRadii && centerLngLat
+ ? {
+ bufferRadii,
+ center: {
+ lng: centerLngLat.longitude,
+ lat: centerLngLat.latitude,
+ height: centerLngLat.height ?? 0,
+ },
+ }
+ : null,
+ }
+ 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([
+ [...points.map(item => [item.longitude, item.latitude]), [points[0].longitude, points[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 getDetectAreaTypeKey () {
+ const areaTypeOptions = Array.isArray(dictObj?.value?.areaType)
+ ? dictObj.value.areaType
+ : dictObj?.areaType
+ if (!Array.isArray(areaTypeOptions)) return ''
+ const target = areaTypeOptions.find(item => item?.dictKey === '1')
+ return target?.dictKey ?? ''
+}
+
+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)
+ }
+ 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
+ pointList = _.cloneDeep(row.points)
+ clearActiveTool()
+ const editPayload = row.drawType === 'buffer' ? { points: row.points, 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()
+ }
+}
+
+// 查看面
+function viewPolygon () {
+ if (!formData.value?.geom) return
+ pointList = geomAnalysis(formData.value.geom)
+ const result = pointList.map(item => [item.longitude, item.latitude]).flat()
+ viewEntity = 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(viewEntity, { duration: 0 })
+}
+
+// 获取派出所列表
+async function getPoliceStationList () {
+ const res = await fwPoliceStationListApi()
+ policeStationOptions.value = res?.data?.data ?? []
+}
+
+// 获取设备列表
+async function getDeviceList () {
+ if (deviceOptions.value.length) return
+ const res = await fwDeviceListApi({ isAreaSelect: 1, areaId: formData.value.id, isTrack: 1 })
+ deviceOptions.value = res?.data?.data ?? []
+ if (dialogMode.value === 'view') {
+ deviceOptions.value = deviceOptions.value.filter(item => formData.value.deviceIds.includes(item.id))
+ }
+}
+
+// 关联设备变更
+function handleDeviceSelectionChange (rows) {
+ selectedDeviceRows.value = rows
+ const ids = rows.map(item => item.id)
+ formData.value.deviceIds = ids.join(',')
+}
+
+// 同步选择状态
+function syncDeviceSelection () {
+ if (!deviceTableRef.value) return
+ deviceTableRef.value.clearSelection()
+ const rows = []
+ const arr = formData.value.deviceIds.split(',')
+ deviceOptions.value.forEach(row => {
+ if (arr.includes(row.id)) {
+ deviceTableRef.value.toggleRowSelection(row, true)
+ rows.push(row)
+ }
+ })
+ selectedDeviceRows.value = rows
+}
+
+// 打开弹框
+async function open ({ mode, row } = {}) {
+ 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') {
+ // default no draw mode
+ } else if (dialogMode.value === 'edit') {
+ await loadDetail()
+ 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()
+ }
+ await nextTick()
+ syncDeviceSelection()
+}
+
+onMounted(() => {
+ getPoliceStationList()
+ getDeviceList()
+})
+
+onBeforeUnmount(() => {
+ hideTypePanel()
+})
+
+defineExpose({
+ open,
+})
+</script>
+
+<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 {
+ margin-bottom: 8px;
+ color: #e5e7f0;
+ font-size: 13px;
+}
+</style>
diff --git a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
index 8d4a069..302d14e 100644
--- a/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
+++ b/applications/drone-command/src/views/areaManage/partition/FormDiaLog.vue
@@ -1,4 +1,4 @@
-<template>
+<template>
<el-dialog class="command-page-map-view-dialog" v-model="visible" :show-close="false"
:close-on-click-modal="false">
<div class="dialog-container">
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js
new file mode 100644
index 0000000..fdaa63d
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/DrawManager.js
@@ -0,0 +1,34 @@
+import { DrawPolygonTool } from './draw/DrawPolygonTool'
+import { DrawRectangleTool } from './draw/DrawRectangleTool'
+import { DrawEllipseTool } from './draw/DrawEllipseTool'
+import { DrawBufferCircleTool } from './draw/DrawBufferCircleTool'
+
+const DRAW_TOOL_MAP = {
+ polygon: DrawPolygonTool,
+ rectangle: DrawRectangleTool,
+ ellipse: DrawEllipseTool,
+ buffer: DrawBufferCircleTool,
+}
+
+export class DrawManager {
+ constructor(viewer) {
+ this.viewer = viewer
+ this.activeTool = null
+ }
+
+ start(type, options = {}) {
+ this.destroy()
+ const Tool = DRAW_TOOL_MAP[type]
+ if (!Tool) return null
+ this.activeTool = new Tool(this.viewer, options)
+ this.activeTool.start()
+ return this.activeTool
+ }
+
+ destroy() {
+ if (this.activeTool) {
+ this.activeTool.destroy()
+ this.activeTool = null
+ }
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js
new file mode 100644
index 0000000..a502f88
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/EditManager.js
@@ -0,0 +1,34 @@
+import { EditPolygonTool } from './edit/EditPolygonTool'
+import { EditRectangleTool } from './edit/EditRectangleTool'
+import { EditEllipseTool } from './edit/EditEllipseTool'
+import { EditBufferCircleTool } from './edit/EditBufferCircleTool'
+
+const EDIT_TOOL_MAP = {
+ polygon: EditPolygonTool,
+ rectangle: EditRectangleTool,
+ ellipse: EditEllipseTool,
+ buffer: EditBufferCircleTool,
+}
+
+export class EditManager {
+ constructor(viewer) {
+ this.viewer = viewer
+ this.activeTool = null
+ }
+
+ start(type, data, options = {}) {
+ this.destroy()
+ const Tool = EDIT_TOOL_MAP[type]
+ if (!Tool) return null
+ this.activeTool = new Tool(this.viewer, options)
+ this.activeTool.start(data)
+ return this.activeTool
+ }
+
+ destroy() {
+ if (this.activeTool) {
+ this.activeTool.destroy()
+ this.activeTool = null
+ }
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js
new file mode 100644
index 0000000..a6be205
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/ToolBase.js
@@ -0,0 +1,24 @@
+export class ToolBase {
+ constructor(viewer) {
+ this.viewer = viewer
+ this.handler = null
+ this.listeners = []
+ }
+
+ subscribe(key, listener) {
+ this.listeners.push({ key, listener })
+ }
+
+ notify(key, data) {
+ this.listeners.filter(item => item.key === key).forEach(item => item.listener(data))
+ }
+
+ removeHandler(types) {
+ if (!this.handler) return
+ if (types?.length) {
+ types.forEach(type => this.handler.removeInputAction(type))
+ }
+ this.handler.destroy()
+ this.handler = null
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js
new file mode 100644
index 0000000..9fe2151
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/Tooltip.js
@@ -0,0 +1,58 @@
+export class MapTooltip {
+ constructor(viewer, options = {}) {
+ this.viewer = viewer
+ this.options = options
+ this.container = viewer?.container || null
+ this.tooltipEl = null
+ this.isVisible = false
+ this.init()
+ }
+
+ init() {
+ if (!this.container || this.tooltipEl) return
+ const el = document.createElement('div')
+ el.style.position = 'absolute'
+ el.style.pointerEvents = 'none'
+ el.style.padding = '6px 8px'
+ el.style.background = 'rgba(0, 0, 0, 0.65)'
+ el.style.color = '#fff'
+ el.style.fontSize = '12px'
+ el.style.borderRadius = '4px'
+ el.style.whiteSpace = 'nowrap'
+ el.style.zIndex = '1000'
+ el.style.display = 'none'
+ this.container.appendChild(el)
+ this.tooltipEl = el
+ }
+
+ show(text, position) {
+ if (!this.tooltipEl) return
+ this.tooltipEl.textContent = text || ''
+ this.tooltipEl.style.display = 'block'
+ this.isVisible = true
+ if (position) this.move(position)
+ }
+
+ move(position) {
+ if (!this.tooltipEl || !position) return
+ const offset = this.options.offset || { x: 12, y: 12 }
+ this.tooltipEl.style.left = `${position.x + offset.x}px`
+ this.tooltipEl.style.top = `${position.y + offset.y}px`
+ }
+
+ hide() {
+ if (!this.tooltipEl) return
+ this.tooltipEl.style.display = 'none'
+ this.isVisible = false
+ }
+
+ destroy() {
+ this.hide()
+ if (this.tooltipEl && this.container) {
+ this.container.removeChild(this.tooltipEl)
+ }
+ this.tooltipEl = null
+ this.container = null
+ this.viewer = null
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js
new file mode 100644
index 0000000..ca5d4a9
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawBufferCircleTool.js
@@ -0,0 +1,236 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { buildEllipsePositions } from '../shapeUtils'
+
+export class DrawBufferCircleTool extends ToolBase {
+ constructor(viewer) {
+ super(viewer)
+ this.tooltip = new MapTooltip(viewer)
+ this.dataSource = null
+ this.centerCartesian = null
+ this.radiusLevel1 = 0
+ this.radiusLevel2 = 0
+ this.radiusLevel3 = 0
+ this.circleLevel1Entity = null
+ this.circleLevel2Entity = null
+ this.circleLevel3Entity = null
+ this.circleLevel1OutlineEntity = null
+ this.circleLevel2OutlineEntity = null
+ this.circleLevel3OutlineEntity = null
+ this.isDrawing = false
+ this.drawStep = 0
+ this.isCompleted = false
+ }
+
+ start() {
+ this.dataSource = new Cesium.CustomDataSource('draw-buffer-circle')
+ this.viewer.dataSources.add(this.dataSource)
+ this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+ this.handler.setInputAction(click => this.handleLeftClick(click), Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.drawStep = 0
+ this.isCompleted = false
+ this.tooltip.show('单击设置中心点', { x: 0, y: 0 })
+ }
+
+ handleLeftClick(click) {
+ if (this.isCompleted) return
+ const position = this.getPositionFromScreen(click.position)
+ if (!position) return
+
+ if (this.drawStep === 0) {
+ this.centerCartesian = position
+ this.radiusLevel1 = 1
+ this.radiusLevel2 = 1
+ this.radiusLevel3 = 1
+ this.isDrawing = true
+ this.drawStep = 1
+ this.createEntities()
+ this.tooltip.show('单击设置一级缓冲圆', click.position)
+ return
+ }
+
+ if (this.drawStep === 1) {
+ const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+ this.radiusLevel1 = radius
+ this.radiusLevel2 = radius
+ this.radiusLevel3 = radius
+ this.drawStep = 2
+ this.tooltip.show('单击设置二级缓冲圆', click.position)
+ return
+ }
+
+ if (this.drawStep === 2) {
+ const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+ this.radiusLevel2 = Math.max(radius, this.radiusLevel1)
+ this.radiusLevel3 = this.radiusLevel2
+ this.drawStep = 3
+ this.tooltip.show('单击设置三级缓冲圆', click.position)
+ return
+ }
+
+ if (this.drawStep === 3) {
+ const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+ this.radiusLevel3 = Math.max(radius, this.radiusLevel2)
+ this.isDrawing = false
+ this.drawStep = 0
+ this.isCompleted = true
+ const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
+ this.notify('getPolygonPositions', {
+ positions,
+ meta: {
+ center: this.centerCartesian,
+ bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+ },
+ })
+ this.tooltip.hide()
+ this.clearPreviewEntities()
+ }
+ }
+
+ handleMouseMove(movement) {
+ if (this.isCompleted) return
+ this.tooltip.move(movement.endPosition)
+ if (!this.isDrawing) return
+ const position = this.getPositionFromScreen(movement.endPosition)
+ if (!position) return
+ if (this.drawStep === 1) {
+ const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+ this.radiusLevel1 = radius
+ this.radiusLevel2 = radius
+ this.radiusLevel3 = radius
+ return
+ }
+ if (this.drawStep === 2) {
+ const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+ this.radiusLevel2 = Math.max(radius, this.radiusLevel1)
+ this.radiusLevel3 = this.radiusLevel2
+ return
+ }
+ if (this.drawStep === 3) {
+ const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+ this.radiusLevel3 = Math.max(radius, this.radiusLevel2)
+ }
+ }
+
+ createEntities() {
+ if (this.circleLevel1Entity || this.circleLevel2Entity || this.circleLevel3Entity) return
+ this.circleLevel1Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ ellipse: {
+ semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
+ semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
+ material: Cesium.Color.fromBytes(247, 20, 20, 128),
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ this.circleLevel1OutlineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ if (!this.centerCartesian || this.radiusLevel1 <= 0) return []
+ const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel1, this.radiusLevel1)
+ return positions.length ? [...positions, positions[0]] : positions
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: Cesium.Color.fromBytes(255, 0, 0, 255),
+ },
+ })
+
+ this.circleLevel2Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ ellipse: {
+ semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
+ semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
+ material: Cesium.Color.fromBytes(255, 235, 59, 128),
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ this.circleLevel2OutlineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ if (!this.centerCartesian || this.radiusLevel2 <= 0) return []
+ const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel2, this.radiusLevel2)
+ return positions.length ? [...positions, positions[0]] : positions
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: Cesium.Color.fromBytes(255, 235, 59, 255),
+ },
+ })
+
+ this.circleLevel3Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ ellipse: {
+ semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
+ semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
+ material: Cesium.Color.fromBytes(25, 178, 230, 128),
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ this.circleLevel3OutlineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ if (!this.centerCartesian || this.radiusLevel3 <= 0) return []
+ const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
+ return positions.length ? [...positions, positions[0]] : positions
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: Cesium.Color.fromBytes(0, 251, 255, 255),
+ },
+ })
+ }
+
+ clearPreviewEntities() {
+ if (!this.dataSource) return
+ this.dataSource.entities.removeAll()
+ this.circleLevel1Entity = null
+ this.circleLevel2Entity = null
+ this.circleLevel3Entity = null
+ this.circleLevel1OutlineEntity = null
+ this.circleLevel2OutlineEntity = null
+ this.circleLevel3OutlineEntity = null
+ }
+
+ getSurfaceDistance(startCartesian, endCartesian) {
+ const start = Cesium.Cartographic.fromCartesian(startCartesian)
+ const end = Cesium.Cartographic.fromCartesian(endCartesian)
+ const geodesic = new Cesium.EllipsoidGeodesic(start, end)
+ return geodesic.surfaceDistance
+ }
+
+ getPositionFromScreen(screenPosition) {
+ const scene = this.viewer.scene
+ const cartesian = scene.pickPosition(screenPosition)
+ if (cartesian) return cartesian
+ return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+ }
+
+ destroy() {
+ if (this.dataSource) {
+ this.dataSource.entities.removeAll()
+ this.viewer.dataSources.remove(this.dataSource)
+ this.dataSource = null
+ }
+ if (this.handler) {
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.destroy()
+ this.handler = null
+ }
+ this.tooltip?.destroy()
+ this.tooltip = null
+ this.centerCartesian = null
+ this.radiusLevel1 = 0
+ this.radiusLevel2 = 0
+ this.radiusLevel3 = 0
+ this.isDrawing = false
+ this.drawStep = 0
+ this.isCompleted = false
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js
new file mode 100644
index 0000000..724288c
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawEllipseTool.js
@@ -0,0 +1,162 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { buildEllipsePositions } from '../shapeUtils'
+
+const DEFAULT_STYLE = {
+ fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+ outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+ fill: style?.fill || DEFAULT_STYLE.fill,
+ outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+export class DrawEllipseTool extends ToolBase {
+ constructor(viewer, options = {}) {
+ super(viewer)
+ this.tooltip = new MapTooltip(viewer)
+ this.dataSource = null
+ this.ellipseEntity = null
+ this.centerCartesian = null
+ this.majorPoint = null
+ this.minorPoint = null
+ this.semiMajor = 0
+ this.semiMinor = 0
+ this.isDrawing = false
+ this.drawStep = 0
+ this.isCompleted = false
+ this.style = resolveStyle(options?.style)
+ }
+
+ start() {
+ this.dataSource = new Cesium.CustomDataSource('draw-ellipse')
+ this.viewer.dataSources.add(this.dataSource)
+ this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+ this.handler.setInputAction(click => this.handleLeftClick(click), Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.drawStep = 0
+ this.isCompleted = false
+ this.tooltip.show('单击设置中心点', { x: 0, y: 0 })
+ }
+
+ handleLeftClick(click) {
+ if (this.isCompleted) return
+ const position = this.getPositionFromScreen(click.position)
+ if (!position) return
+ if (this.drawStep === 0) {
+ this.centerCartesian = position
+ this.semiMajor = 1
+ this.semiMinor = 1
+ this.isDrawing = true
+ this.drawStep = 1
+ this.createEntities()
+ this.tooltip.show('单击设置长轴', click.position)
+ return
+ }
+ if (this.drawStep === 1) {
+ this.majorPoint = position
+ this.semiMajor = Math.max(1, this.getSurfaceDistance(this.centerCartesian, this.majorPoint))
+ this.semiMinor = this.semiMajor
+ this.drawStep = 2
+ this.tooltip.show('单击设置短轴', click.position)
+ return
+ }
+ if (this.drawStep === 2) {
+ this.minorPoint = position
+ const minorRadius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, this.minorPoint))
+ this.semiMinor = Math.min(minorRadius, this.semiMajor)
+ this.isDrawing = false
+ this.drawStep = 0
+ this.isCompleted = true
+ const positions = buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
+ this.notify('getPolygonPositions', positions)
+ this.tooltip.hide()
+ this.clearPreviewEntities()
+ }
+ }
+
+ handleMouseMove(movement) {
+ if (this.isCompleted) return
+ this.tooltip.move(movement.endPosition)
+ if (!this.isDrawing) return
+ const position = this.getPositionFromScreen(movement.endPosition)
+ if (!position) return
+ if (this.drawStep === 1) {
+ const radius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+ this.semiMajor = radius
+ this.semiMinor = radius
+ return
+ }
+ if (this.drawStep === 2) {
+ const minorRadius = Math.max(1, this.getSurfaceDistance(this.centerCartesian, position))
+ this.semiMinor = Math.min(minorRadius, this.semiMajor)
+ }
+ }
+
+ createEntities() {
+ if (this.ellipseEntity) return
+ this.ellipseEntity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ ellipse: {
+ semiMajorAxis: new Cesium.CallbackProperty(() => this.semiMajor, false),
+ semiMinorAxis: new Cesium.CallbackProperty(() => this.semiMinor, false),
+ material: this.style.fill,
+ outline: true,
+ outlineColor: this.style.outline,
+ outlineWidth: 2,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ }
+
+ getSurfaceDistance(startCartesian, endCartesian) {
+ const start = Cesium.Cartographic.fromCartesian(startCartesian)
+ const end = Cesium.Cartographic.fromCartesian(endCartesian)
+ const geodesic = new Cesium.EllipsoidGeodesic(start, end)
+ return geodesic.surfaceDistance
+ }
+
+ clearPreviewEntities() {
+ if (!this.dataSource) return
+ this.dataSource.entities.removeAll()
+ this.ellipseEntity = null
+ }
+
+ getPositionFromScreen(screenPosition) {
+ const scene = this.viewer.scene
+ const cartesian = scene.pickPosition(screenPosition)
+ if (cartesian) return cartesian
+ return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+ }
+
+ setStyle(style) {
+ this.style = resolveStyle(style)
+ if (this.ellipseEntity?.ellipse) {
+ this.ellipseEntity.ellipse.material = this.style.fill
+ this.ellipseEntity.ellipse.outlineColor = this.style.outline
+ }
+ }
+
+ destroy() {
+ if (this.dataSource) {
+ this.dataSource.entities.removeAll()
+ this.viewer.dataSources.remove(this.dataSource)
+ this.dataSource = null
+ }
+ if (this.handler) {
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.destroy()
+ this.handler = null
+ }
+ this.tooltip?.destroy()
+ this.tooltip = null
+ this.centerCartesian = null
+ this.majorPoint = null
+ this.minorPoint = null
+ this.isDrawing = false
+ this.drawStep = 0
+ this.isCompleted = false
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js
new file mode 100644
index 0000000..9202105
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawPolygonTool.js
@@ -0,0 +1,244 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+
+const POINT_ENTITY_NAME = 'draw-polygon-point'
+const DEFAULT_STYLE = {
+ fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+ outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+ fill: style?.fill || DEFAULT_STYLE.fill,
+ outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+export class DrawPolygonTool extends ToolBase {
+ constructor(viewer, options = {}) {
+ super(viewer)
+ this.tooltip = new MapTooltip(viewer)
+ this.dataSource = null
+ this.polygonEntity = null
+ this.polylineEntity = null
+ this.positions = []
+ this.floatPosition = null
+ this.isDrawing = false
+ this.lastMousePosition = null
+ this.style = resolveStyle(options?.style)
+ }
+
+ start() {
+ this.dataSource = new Cesium.CustomDataSource('draw-polygon')
+ this.viewer.dataSources.add(this.dataSource)
+ this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+ this.handler.setInputAction(click => this.handleLeftClick(click), Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ this.handler.setInputAction(click => this.handleRightClick(click), Cesium.ScreenSpaceEventType.RIGHT_CLICK)
+ this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.isDrawing = true
+ this.tooltip.show(this.getTipText(), { x: 0, y: 0 })
+ }
+
+ handleLeftClick(click) {
+ if (!this.isDrawing) return
+
+ const pickedEntity = this.viewer.scene.pick(click.position)?.id
+ if (this.isFinishTrigger(pickedEntity)) {
+ this.finishDrawing()
+ return
+ }
+ if (pickedEntity?.name === POINT_ENTITY_NAME) return
+
+ const position = this.getPositionFromScreen(click.position)
+ if (!position) return
+
+ this.addPoint(position)
+ this.tooltip.show(this.getTipText(), click.position)
+ }
+
+ handleRightClick(click) {
+ if (!this.isDrawing) return
+ const pickedEntity = this.viewer.scene.pick(click.position)?.id
+ if (!pickedEntity || pickedEntity.name !== POINT_ENTITY_NAME) return
+ const index = pickedEntity.customData?.index
+ if (typeof index !== 'number') return
+ this.removePoint(index)
+ this.tooltip.show(this.getTipText(), click.position)
+ }
+
+ handleMouseMove(movement) {
+ if (!this.isDrawing) return
+ this.tooltip.show(this.getTipText(), movement.endPosition)
+ if (this.positions.length === 0) return
+ const position = this.getPositionFromScreen(movement.endPosition)
+ if (!position) return
+ this.lastMousePosition = position
+ this.floatPosition = position
+ }
+
+ getTipText() {
+ const count = this.positions.length
+ if (count === 0) {
+ return '单击增加点'
+ }
+ if (count < 3) {
+ return '单击增加点,右击删除点'
+ }
+ return '单击增加点,右击删除点,双击结束绘制'
+ }
+
+ isFinishTrigger(pickedEntity) {
+ if (!pickedEntity || pickedEntity.name !== POINT_ENTITY_NAME) return false
+ if (this.positions.length < 3) return false
+ const index = pickedEntity.customData?.index
+ return index === this.positions.length - 1
+ }
+
+ addPoint(position) {
+ this.positions.push(position)
+ if (!this.polygonEntity) {
+ this.createEntities()
+ }
+ this.rebuildPointEntities()
+ }
+
+ removePoint(index) {
+ if (index < 0 || index >= this.positions.length) return
+ this.positions.splice(index, 1)
+ this.rebuildPointEntities()
+ if (this.positions.length === 0) {
+ this.floatPosition = null
+ this.lastMousePosition = null
+ return
+ }
+ if (this.lastMousePosition) {
+ this.floatPosition = this.lastMousePosition
+ } else {
+ this.floatPosition = this.positions[this.positions.length - 1]
+ }
+ }
+
+ createEntities() {
+ const getPreviewPositions = () => {
+ if (this.positions.length === 0) return []
+ if (!this.isDrawing || !this.floatPosition) return this.positions
+ return [...this.positions, this.floatPosition]
+ }
+
+ this.polygonEntity = this.dataSource.entities.add({
+ polygon: {
+ hierarchy: new Cesium.CallbackProperty(
+ () => new Cesium.PolygonHierarchy(getPreviewPositions()),
+ false
+ ),
+ material: this.style.fill,
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ show: new Cesium.CallbackProperty(() => getPreviewPositions().length >= 3, false),
+ },
+ })
+
+ this.polylineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ const positions = getPreviewPositions()
+ if (positions.length < 2) return positions
+ if (positions.length >= 3) {
+ return [...positions, positions[0]]
+ }
+ return positions
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: this.style.outline,
+ show: new Cesium.CallbackProperty(() => getPreviewPositions().length >= 2, false),
+ },
+ })
+ }
+
+ rebuildPointEntities() {
+ this.dataSource.entities.values
+ .slice()
+ .filter(entity => entity?.name === POINT_ENTITY_NAME)
+ .forEach(entity => this.dataSource.entities.remove(entity))
+
+ this.positions.forEach((position, index) => {
+ this.dataSource.entities.add({
+ name: POINT_ENTITY_NAME,
+ position: position.clone ? position.clone() : position,
+ point: {
+ pixelSize: 12,
+ color: Cesium.Color.WHITE,
+ outlineColor: this.style.outline,
+ outlineWidth: 2,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customData: {
+ index,
+ },
+ })
+ })
+ }
+
+ finishDrawing() {
+ if (this.positions.length < 3) return
+ this.isDrawing = false
+ this.floatPosition = null
+ this.notify('getPolygonPositions', this.positions)
+ this.tooltip.hide()
+ this.clearPreviewEntities()
+ }
+
+ setStyle(style) {
+ this.style = resolveStyle(style)
+ if (this.polygonEntity?.polygon) {
+ this.polygonEntity.polygon.material = this.style.fill
+ }
+ if (this.polylineEntity?.polyline) {
+ this.polylineEntity.polyline.material = this.style.outline
+ }
+ if (this.dataSource) {
+ this.dataSource.entities.values
+ .filter(entity => entity?.name === POINT_ENTITY_NAME)
+ .forEach(entity => {
+ if (entity?.point) {
+ entity.point.outlineColor = this.style.outline
+ }
+ })
+ }
+ }
+
+ clearPreviewEntities() {
+ if (!this.dataSource) return
+ this.dataSource.entities.removeAll()
+ this.polygonEntity = null
+ this.polylineEntity = null
+ }
+
+ getPositionFromScreen(screenPosition) {
+ const scene = this.viewer.scene
+ const cartesian = scene.pickPosition(screenPosition)
+ if (cartesian) return cartesian
+ return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+ }
+
+ destroy() {
+ if (this.dataSource) {
+ this.dataSource.entities.removeAll()
+ this.viewer.dataSources.remove(this.dataSource)
+ this.dataSource = null
+ }
+ if (this.handler) {
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.destroy()
+ this.handler = null
+ }
+ this.tooltip?.destroy()
+ this.tooltip = null
+ this.positions = []
+ this.floatPosition = null
+ this.isDrawing = false
+ this.lastMousePosition = null
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js
new file mode 100644
index 0000000..574f6b6
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/draw/DrawRectangleTool.js
@@ -0,0 +1,150 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { getBoundsFromCartesians, rectanglePositionsFromBounds } from '../shapeUtils'
+
+const DEFAULT_STYLE = {
+ fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+ outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+ fill: style?.fill || DEFAULT_STYLE.fill,
+ outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+export class DrawRectangleTool extends ToolBase {
+ constructor(viewer, options = {}) {
+ super(viewer)
+ this.tooltip = new MapTooltip(viewer)
+ this.dataSource = null
+ this.rectangleEntity = null
+ this.outlineEntity = null
+ this.startPosition = null
+ this.endPosition = null
+ this.isDrawing = false
+ this.style = resolveStyle(options?.style)
+ }
+
+ start() {
+ this.dataSource = new Cesium.CustomDataSource('draw-rectangle')
+ this.viewer.dataSources.add(this.dataSource)
+ this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+
+ this.handler.setInputAction(click => this.handleLeftClick(click), Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+
+ this.tooltip.show('单击设置起点', { x: 0, y: 0 })
+ }
+
+ handleLeftClick(click) {
+ const position = this.getPositionFromScreen(click.position)
+ if (!position) return
+
+ if (!this.startPosition) {
+ this.startPosition = position
+ this.endPosition = position
+ this.isDrawing = true
+ this.createEntities()
+ this.tooltip.show('单击结束绘制', click.position)
+ return
+ }
+
+ if (this.isDrawing) {
+ this.endPosition = position
+ this.isDrawing = false
+ const positions = this.getRectanglePositions()
+ this.notify('getPolygonPositions', positions)
+ this.tooltip.hide()
+ this.clearPreviewEntities()
+ }
+ }
+
+ handleMouseMove(movement) {
+ if (!this.isDrawing) {
+ this.tooltip.move(movement.endPosition)
+ return
+ }
+ const position = this.getPositionFromScreen(movement.endPosition)
+ if (!position) return
+ this.endPosition = position
+ this.tooltip.move(movement.endPosition)
+ }
+
+ createEntities() {
+ if (this.rectangleEntity) return
+ const hierarchy = new Cesium.CallbackProperty(() => {
+ const positions = this.getRectanglePositions()
+ return new Cesium.PolygonHierarchy(positions)
+ }, false)
+
+ this.rectangleEntity = this.dataSource.entities.add({
+ polygon: {
+ hierarchy,
+ material: this.style.fill,
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+
+ this.outlineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ const positions = this.getRectanglePositions()
+ return positions.length ? [...positions, positions[0]] : []
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: this.style.outline,
+ },
+ })
+ }
+
+ getRectanglePositions() {
+ if (!this.startPosition || !this.endPosition) return []
+ const bounds = getBoundsFromCartesians([this.startPosition, this.endPosition])
+ return rectanglePositionsFromBounds(bounds)
+ }
+
+ clearPreviewEntities() {
+ if (!this.dataSource) return
+ this.dataSource.entities.removeAll()
+ this.rectangleEntity = null
+ this.outlineEntity = null
+ }
+
+ getPositionFromScreen(screenPosition) {
+ const scene = this.viewer.scene
+ const cartesian = scene.pickPosition(screenPosition)
+ if (cartesian) return cartesian
+ return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+ }
+
+ setStyle(style) {
+ this.style = resolveStyle(style)
+ if (this.rectangleEntity?.polygon) {
+ this.rectangleEntity.polygon.material = this.style.fill
+ }
+ if (this.outlineEntity?.polyline) {
+ this.outlineEntity.polyline.material = this.style.outline
+ }
+ }
+
+ destroy() {
+ if (this.dataSource) {
+ this.dataSource.entities.removeAll()
+ this.viewer.dataSources.remove(this.dataSource)
+ this.dataSource = null
+ }
+ if (this.handler) {
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_CLICK)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.destroy()
+ this.handler = null
+ }
+ this.tooltip?.destroy()
+ this.tooltip = null
+ this.startPosition = null
+ this.endPosition = null
+ this.isDrawing = false
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js
new file mode 100644
index 0000000..1808125
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditBufferCircleTool.js
@@ -0,0 +1,314 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { buildEllipsePositions, cartesianToLocal } from '../shapeUtils'
+
+const normalizePoints = points =>
+ (points || []).map(point => ({
+ lng: point.lng ?? point.longitude,
+ lat: point.lat ?? point.latitude,
+ }))
+
+export class EditBufferCircleTool extends ToolBase {
+ constructor(viewer) {
+ super(viewer)
+ this.tooltip = new MapTooltip(viewer)
+ this.dataSource = null
+ this.centerCartesian = null
+ this.radiusLevel1 = 0
+ this.radiusLevel2 = 0
+ this.radiusLevel3 = 0
+ this.dragType = null
+ this.circleLevel1Entity = null
+ this.circleLevel2Entity = null
+ this.circleLevel3Entity = null
+ this.circleLevel1OutlineEntity = null
+ this.circleLevel2OutlineEntity = null
+ this.circleLevel3OutlineEntity = null
+ this.centerEntity = null
+ this.radiusLevel1Entity = null
+ this.radiusLevel2Entity = null
+ this.radiusLevel3Entity = null
+ }
+
+ start(data = []) {
+ const inputPoints = Array.isArray(data?.points) ? data.points : data
+ const normalized = normalizePoints(inputPoints)
+ if (!normalized.length) return
+ const centerLng = normalized.reduce((sum, p) => sum + p.lng, 0) / normalized.length
+ const centerLat = normalized.reduce((sum, p) => sum + p.lat, 0) / normalized.length
+ this.centerCartesian = Cesium.Cartesian3.fromDegrees(centerLng, centerLat)
+ const radii = Array.isArray(data?.meta?.bufferRadii) ? data.meta.bufferRadii : null
+ if (radii?.length) {
+ const [r1, r2, r3] = radii
+ this.radiusLevel1 = Number.isFinite(r1) && r1 > 0 ? r1 : 1
+ this.radiusLevel2 = Number.isFinite(r2) && r2 > 0 ? Math.max(r2, this.radiusLevel1) : this.radiusLevel1
+ this.radiusLevel3 = Number.isFinite(r3) && r3 > 0 ? Math.max(r3, this.radiusLevel2) : this.radiusLevel2
+ } else {
+ this.radiusLevel3 = this.estimateRadius(normalized)
+ this.radiusLevel2 = this.radiusLevel3
+ this.radiusLevel1 = this.radiusLevel3
+ }
+
+ this.dataSource = new Cesium.CustomDataSource('edit-buffer-circle')
+ this.viewer.dataSources.add(this.dataSource)
+ this.createEntities()
+ this.initHandler()
+ }
+
+ estimateRadius(points) {
+ const center = Cesium.Cartesian3.clone(this.centerCartesian)
+ let max = 1
+ points.forEach(point => {
+ const cartesian = Cesium.Cartesian3.fromDegrees(point.lng, point.lat)
+ const distance = Cesium.Cartesian3.distance(center, cartesian)
+ max = Math.max(max, distance)
+ })
+ return max
+ }
+
+ initHandler() {
+ this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+ this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN)
+ this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
+ this.tooltip.show('拖动控制点编辑', { x: 0, y: 0 })
+ }
+
+ handleLeftDown(evt) {
+ const picked = this.viewer.scene.pick(evt.position)?.id
+ if (!picked) return
+ if (picked.customType === 'circle-center') this.dragType = 'center'
+ if (picked.customType === 'circle-radius-1') this.dragType = 'radius-1'
+ if (picked.customType === 'circle-radius-2') this.dragType = 'radius-2'
+ if (picked.customType === 'circle-radius-3') this.dragType = 'radius-3'
+ if (this.dragType) this.disableMapControl()
+ }
+
+ handleMouseMove(evt) {
+ this.tooltip.move(evt.endPosition)
+ if (!this.dragType) return
+ const position = this.getPositionFromScreen(evt.endPosition)
+ if (!position) return
+
+ if (this.dragType === 'center') {
+ this.centerCartesian = position
+ this.updateRadiusPoints()
+ return
+ }
+
+ const local = cartesianToLocal(this.centerCartesian, position)
+ const radius = Math.max(1, Math.sqrt(local.x * local.x + local.y * local.y))
+ if (this.dragType === 'radius-1') {
+ this.radiusLevel1 = radius
+ if (this.radiusLevel2 < this.radiusLevel1) this.radiusLevel2 = this.radiusLevel1
+ if (this.radiusLevel3 < this.radiusLevel2) this.radiusLevel3 = this.radiusLevel2
+ }
+ if (this.dragType === 'radius-2') {
+ this.radiusLevel2 = Math.max(radius, this.radiusLevel1)
+ if (this.radiusLevel3 < this.radiusLevel2) this.radiusLevel3 = this.radiusLevel2
+ }
+ if (this.dragType === 'radius-3') {
+ this.radiusLevel3 = Math.max(radius, this.radiusLevel2)
+ }
+ this.updateRadiusPoints()
+ }
+
+ handleLeftUp() {
+ if (!this.dragType) return
+ this.dragType = null
+ this.enableMapControl()
+ const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
+ this.notify('getPolygonPositions', {
+ positions,
+ meta: {
+ center: this.centerCartesian,
+ bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+ },
+ })
+ }
+
+ createEntities() {
+ this.circleLevel1Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ ellipse: {
+ semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
+ semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel1, false),
+ material: Cesium.Color.fromBytes(247, 20, 20, 128),
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ this.circleLevel1OutlineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ if (!this.centerCartesian || this.radiusLevel1 <= 0) return []
+ const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel1, this.radiusLevel1)
+ return positions.length ? [...positions, positions[0]] : positions
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: Cesium.Color.fromBytes(255, 0, 0, 255),
+ },
+ })
+
+ this.circleLevel2Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ ellipse: {
+ semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
+ semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel2, false),
+ material: Cesium.Color.fromBytes(255, 235, 59, 128),
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ this.circleLevel2OutlineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ if (!this.centerCartesian || this.radiusLevel2 <= 0) return []
+ const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel2, this.radiusLevel2)
+ return positions.length ? [...positions, positions[0]] : positions
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: Cesium.Color.fromBytes(255, 235, 59, 255),
+ },
+ })
+
+ this.circleLevel3Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ ellipse: {
+ semiMajorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
+ semiMinorAxis: new Cesium.CallbackProperty(() => this.radiusLevel3, false),
+ material: Cesium.Color.fromBytes(25, 178, 230, 128),
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ this.circleLevel3OutlineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ if (!this.centerCartesian || this.radiusLevel3 <= 0) return []
+ const positions = buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3)
+ return positions.length ? [...positions, positions[0]] : positions
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: Cesium.Color.fromBytes(0, 251, 255, 255),
+ },
+ })
+
+ this.centerEntity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ point: {
+ pixelSize: 12,
+ color: Cesium.Color.WHITE,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customType: 'circle-center',
+ })
+
+ this.radiusLevel1Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel1), false),
+ point: {
+ pixelSize: 10,
+ color: Cesium.Color.fromBytes(255, 255, 255, 200),
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customType: 'circle-radius-1',
+ })
+
+ this.radiusLevel2Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel2), false),
+ point: {
+ pixelSize: 10,
+ color: Cesium.Color.fromBytes(255, 255, 255, 200),
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customType: 'circle-radius-2',
+ })
+
+ this.radiusLevel3Entity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.getRadiusPoint(this.radiusLevel3), false),
+ point: {
+ pixelSize: 10,
+ color: Cesium.Color.fromBytes(255, 255, 255, 200),
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customType: 'circle-radius-3',
+ })
+ }
+
+ updateRadiusPoints() {
+ if (this.radiusLevel1Entity) this.radiusLevel1Entity.position = this.getRadiusPoint(this.radiusLevel1)
+ if (this.radiusLevel2Entity) this.radiusLevel2Entity.position = this.getRadiusPoint(this.radiusLevel2)
+ if (this.radiusLevel3Entity) this.radiusLevel3Entity.position = this.getRadiusPoint(this.radiusLevel3)
+ }
+
+ getPositions() {
+ if (!this.centerCartesian) return []
+ return {
+ positions: buildEllipsePositions(this.centerCartesian, this.radiusLevel3, this.radiusLevel3),
+ meta: {
+ center: this.centerCartesian,
+ bufferRadii: [this.radiusLevel1, this.radiusLevel2, this.radiusLevel3],
+ },
+ }
+ }
+
+ getRadiusPoint(radius) {
+ const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian)
+ const local = new Cesium.Cartesian3(radius, 0, 0)
+ return Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3())
+ }
+
+ getPositionFromScreen(screenPosition) {
+ const scene = this.viewer.scene
+ const cartesian = scene.pickPosition(screenPosition)
+ if (cartesian) return cartesian
+ return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+ }
+
+ disableMapControl() {
+ const controller = this.viewer.scene.screenSpaceCameraController
+ controller.enableRotate = false
+ controller.enableTranslate = false
+ controller.enableZoom = false
+ }
+
+ enableMapControl() {
+ const controller = this.viewer.scene.screenSpaceCameraController
+ controller.enableRotate = true
+ controller.enableTranslate = true
+ controller.enableZoom = true
+ }
+
+ destroy() {
+ if (this.dataSource) {
+ this.dataSource.entities.removeAll()
+ this.viewer.dataSources.remove(this.dataSource)
+ this.dataSource = null
+ }
+ if (this.handler) {
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
+ this.handler.destroy()
+ this.handler = null
+ }
+ this.tooltip?.destroy()
+ this.tooltip = null
+ this.centerCartesian = null
+ this.radiusLevel1 = 0
+ this.radiusLevel2 = 0
+ this.radiusLevel3 = 0
+ this.circleLevel1OutlineEntity = null
+ this.circleLevel2OutlineEntity = null
+ this.circleLevel3OutlineEntity = null
+ this.dragType = null
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js
new file mode 100644
index 0000000..c3b2904
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditEllipseTool.js
@@ -0,0 +1,233 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import {
+ getBoundsFromLngLats,
+ getCenterFromBounds,
+ getMetersBetween,
+ buildEllipsePositions,
+ cartesianToLocal,
+} from '../shapeUtils'
+
+const DEFAULT_STYLE = {
+ fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+ outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+ fill: style?.fill || DEFAULT_STYLE.fill,
+ outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+const normalizePoints = points =>
+ (points || []).map(point => ({
+ lng: point.lng ?? point.longitude,
+ lat: point.lat ?? point.latitude,
+ }))
+
+export class EditEllipseTool extends ToolBase {
+ constructor(viewer, options = {}) {
+ super(viewer)
+ this.tooltip = new MapTooltip(viewer)
+ this.dataSource = null
+ this.centerCartesian = null
+ this.semiMajor = 0
+ this.semiMinor = 0
+ this.dragType = null
+ this.centerEntity = null
+ this.majorEntity = null
+ this.minorEntity = null
+ this.ellipseEntity = null
+ this.style = resolveStyle(options?.style)
+ }
+
+ start(points = []) {
+ const normalized = normalizePoints(points)
+ const bounds = getBoundsFromLngLats(normalized)
+ const center = getCenterFromBounds(bounds)
+ if (!center) return
+ this.centerCartesian = Cesium.Cartesian3.fromRadians(center.longitude, center.latitude)
+ const eastWest = getMetersBetween(
+ new Cesium.Cartographic(bounds.west, center.latitude),
+ new Cesium.Cartographic(bounds.east, center.latitude)
+ )
+ const northSouth = getMetersBetween(
+ new Cesium.Cartographic(center.longitude, bounds.south),
+ new Cesium.Cartographic(center.longitude, bounds.north)
+ )
+ this.semiMajor = Math.max(1, eastWest / 2)
+ this.semiMinor = Math.max(1, northSouth / 2)
+
+ this.dataSource = new Cesium.CustomDataSource('edit-ellipse')
+ this.viewer.dataSources.add(this.dataSource)
+ this.createEntities()
+ this.initHandler()
+ }
+
+ initHandler() {
+ this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+ this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN)
+ this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
+ this.tooltip.show('拖动控制点编辑', { x: 0, y: 0 })
+ }
+
+ handleLeftDown(evt) {
+ const picked = this.viewer.scene.pick(evt.position)?.id
+ if (!picked) return
+ if (picked.customType === 'ellipse-center') this.dragType = 'center'
+ if (picked.customType === 'ellipse-major') this.dragType = 'major'
+ if (picked.customType === 'ellipse-minor') this.dragType = 'minor'
+ if (this.dragType) this.disableMapControl()
+ }
+
+ handleMouseMove(evt) {
+ this.tooltip.move(evt.endPosition)
+ if (!this.dragType) return
+ const position = this.getPositionFromScreen(evt.endPosition)
+ if (!position) return
+
+ if (this.dragType === 'center') {
+ this.centerCartesian = position
+ this.updateAxisPoints()
+ return
+ }
+
+ const local = cartesianToLocal(this.centerCartesian, position)
+ if (this.dragType === 'major') {
+ this.semiMajor = Math.max(1, Math.abs(local.x))
+ if (this.semiMajor < this.semiMinor) {
+ this.semiMajor = this.semiMinor
+ }
+ }
+ if (this.dragType === 'minor') {
+ this.semiMinor = Math.max(1, Math.abs(local.y))
+ if (this.semiMinor > this.semiMajor) {
+ this.semiMinor = this.semiMajor
+ }
+ }
+ this.updateAxisPoints()
+ }
+
+ handleLeftUp() {
+ if (!this.dragType) return
+ this.dragType = null
+ this.enableMapControl()
+ const positions = buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
+ this.notify('getPolygonPositions', positions)
+ }
+
+ createEntities() {
+ this.ellipseEntity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ ellipse: {
+ semiMajorAxis: new Cesium.CallbackProperty(() => this.semiMajor, false),
+ semiMinorAxis: new Cesium.CallbackProperty(() => this.semiMinor, false),
+ material: this.style.fill,
+ outline: true,
+ outlineColor: this.style.outline,
+ outlineWidth: 2,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+
+ this.centerEntity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.centerCartesian, false),
+ point: {
+ pixelSize: 12,
+ color: Cesium.Color.WHITE,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customType: 'ellipse-center',
+ })
+
+ this.majorEntity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.getAxisPoint(this.semiMajor, 0), false),
+ point: {
+ pixelSize: 10,
+ color: Cesium.Color.fromBytes(255, 255, 255, 200),
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customType: 'ellipse-major',
+ })
+
+ this.minorEntity = this.dataSource.entities.add({
+ position: new Cesium.CallbackProperty(() => this.getAxisPoint(0, this.semiMinor), false),
+ point: {
+ pixelSize: 10,
+ color: Cesium.Color.fromBytes(255, 255, 255, 200),
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customType: 'ellipse-minor',
+ })
+ }
+
+ updateAxisPoints() {
+ if (!this.majorEntity || !this.minorEntity) return
+ this.majorEntity.position = this.getAxisPoint(this.semiMajor, 0)
+ this.minorEntity.position = this.getAxisPoint(0, this.semiMinor)
+ }
+
+ getPositions() {
+ if (!this.centerCartesian) return []
+ return buildEllipsePositions(this.centerCartesian, this.semiMajor, this.semiMinor)
+ }
+
+ getAxisPoint(eastMeters, northMeters) {
+ const frame = Cesium.Transforms.eastNorthUpToFixedFrame(this.centerCartesian)
+ const local = new Cesium.Cartesian3(eastMeters, northMeters, 0)
+ return Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3())
+ }
+
+ getPositionFromScreen(screenPosition) {
+ const scene = this.viewer.scene
+ const cartesian = scene.pickPosition(screenPosition)
+ if (cartesian) return cartesian
+ return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+ }
+
+ setStyle(style) {
+ this.style = resolveStyle(style)
+ if (this.ellipseEntity?.ellipse) {
+ this.ellipseEntity.ellipse.material = this.style.fill
+ this.ellipseEntity.ellipse.outlineColor = this.style.outline
+ }
+ }
+
+ disableMapControl() {
+ const controller = this.viewer.scene.screenSpaceCameraController
+ controller.enableRotate = false
+ controller.enableTranslate = false
+ controller.enableZoom = false
+ }
+
+ enableMapControl() {
+ const controller = this.viewer.scene.screenSpaceCameraController
+ controller.enableRotate = true
+ controller.enableTranslate = true
+ controller.enableZoom = true
+ }
+
+ destroy() {
+ if (this.dataSource) {
+ this.dataSource.entities.removeAll()
+ this.viewer.dataSources.remove(this.dataSource)
+ this.dataSource = null
+ }
+ if (this.handler) {
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
+ this.handler.destroy()
+ this.handler = null
+ }
+ this.tooltip?.destroy()
+ this.tooltip = null
+ this.centerCartesian = null
+ this.semiMajor = 0
+ this.semiMinor = 0
+ this.dragType = null
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js
new file mode 100644
index 0000000..c6b2a13
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditPolygonTool.js
@@ -0,0 +1,218 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+
+const POINT_ENTITY_NAME = 'edit-polygon-point'
+const DEFAULT_STYLE = {
+ fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+ outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+ fill: style?.fill || DEFAULT_STYLE.fill,
+ outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+const normalizePoints = points =>
+ (points || []).map(point => ({
+ lng: point.lng ?? point.longitude,
+ lat: point.lat ?? point.latitude,
+ height: point.height ?? 0,
+ }))
+
+export class EditPolygonTool extends ToolBase {
+ constructor(viewer, options = {}) {
+ super(viewer)
+ this.tooltip = new MapTooltip(viewer)
+ this.dataSource = null
+ this.polygonEntity = null
+ this.polylineEntity = null
+ this.positions = []
+ this.isDragging = false
+ this.draggedIndex = -1
+ this.style = resolveStyle(options?.style)
+ }
+
+ start(points = []) {
+ const normalized = normalizePoints(points)
+ this.positions = normalized
+ .filter(point => point?.lng !== undefined && point?.lat !== undefined)
+ .map(point => Cesium.Cartesian3.fromDegrees(Number(point.lng), Number(point.lat), Number(point.height || 0)))
+
+ this.dataSource = new Cesium.CustomDataSource('edit-polygon')
+ this.viewer.dataSources.add(this.dataSource)
+
+ this.createEntities()
+ this.rebuildPointEntities()
+ this.initHandler()
+ }
+
+ initHandler() {
+ if (!this.viewer || this.handler) return
+ this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+ this.handler.setInputAction(movement => this.handleMouseMove(movement), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.setInputAction(click => this.handleLeftDown(click), Cesium.ScreenSpaceEventType.LEFT_DOWN)
+ this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
+ this.handler.setInputAction(click => this.handleRightClick(click), Cesium.ScreenSpaceEventType.RIGHT_CLICK)
+ }
+
+ handleLeftDown(click) {
+ const pickedEntity = this.viewer.scene.pick(click.position)?.id
+ if (!pickedEntity || pickedEntity.name !== POINT_ENTITY_NAME) return
+ const index = pickedEntity.customData?.index
+ if (typeof index !== 'number') return
+ this.isDragging = true
+ this.draggedIndex = index
+ this.disableMapControl()
+ }
+
+ handleLeftUp() {
+ if (!this.isDragging) return
+ this.isDragging = false
+ this.draggedIndex = -1
+ this.enableMapControl()
+ this.notify('getPolygonPositions', this.positions)
+ }
+
+ handleMouseMove(movement) {
+ this.tooltip.show('拖动顶点编辑,右键删除', movement.endPosition)
+ if (!this.isDragging || this.draggedIndex < 0) return
+ const position = this.getPositionFromScreen(movement.endPosition)
+ if (!position) return
+ this.positions[this.draggedIndex] = position
+ const pointEntity = this.dataSource.entities.values.find(
+ entity => entity?.name === POINT_ENTITY_NAME && entity.customData?.index === this.draggedIndex
+ )
+ if (pointEntity) {
+ pointEntity.position = position
+ }
+ }
+
+ handleRightClick(click) {
+ const pickedEntity = this.viewer.scene.pick(click.position)?.id
+ if (!pickedEntity || pickedEntity.name !== POINT_ENTITY_NAME) return
+ if (this.positions.length <= 3) return
+ const index = pickedEntity.customData?.index
+ if (typeof index !== 'number') return
+ this.positions.splice(index, 1)
+ this.rebuildPointEntities()
+ this.notify('getPolygonPositions', this.positions)
+ }
+
+ createEntities() {
+ this.polygonEntity = this.dataSource.entities.add({
+ polygon: {
+ hierarchy: new Cesium.CallbackProperty(
+ () => new Cesium.PolygonHierarchy(this.positions),
+ false
+ ),
+ material: this.style.fill,
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ show: new Cesium.CallbackProperty(() => this.positions.length >= 3, false),
+ },
+ })
+
+ this.polylineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ if (this.positions.length < 2) return this.positions
+ return [...this.positions, this.positions[0]]
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: this.style.outline,
+ show: new Cesium.CallbackProperty(() => this.positions.length >= 2, false),
+ },
+ })
+ }
+
+ rebuildPointEntities() {
+ this.dataSource.entities.values
+ .slice()
+ .filter(entity => entity?.name === POINT_ENTITY_NAME)
+ .forEach(entity => this.dataSource.entities.remove(entity))
+
+ this.positions.forEach((position, index) => {
+ this.dataSource.entities.add({
+ name: POINT_ENTITY_NAME,
+ position: position.clone ? position.clone() : position,
+ point: {
+ pixelSize: 12,
+ color: Cesium.Color.WHITE,
+ outlineColor: this.style.outline,
+ outlineWidth: 2,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customData: {
+ index,
+ },
+ })
+ })
+ }
+
+ getPositions() {
+ return this.positions.slice()
+ }
+
+ getPositionFromScreen(screenPosition) {
+ const scene = this.viewer.scene
+ const cartesian = scene.pickPosition(screenPosition)
+ if (cartesian) return cartesian
+ return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+ }
+
+ setStyle(style) {
+ this.style = resolveStyle(style)
+ if (this.polygonEntity?.polygon) {
+ this.polygonEntity.polygon.material = this.style.fill
+ }
+ if (this.polylineEntity?.polyline) {
+ this.polylineEntity.polyline.material = this.style.outline
+ }
+ if (this.dataSource) {
+ this.dataSource.entities.values
+ .filter(entity => entity?.name === POINT_ENTITY_NAME)
+ .forEach(entity => {
+ if (entity?.point) {
+ entity.point.outlineColor = this.style.outline
+ }
+ })
+ }
+ }
+
+ disableMapControl() {
+ const controller = this.viewer.scene.screenSpaceCameraController
+ controller.enableRotate = false
+ controller.enableTranslate = false
+ controller.enableZoom = false
+ }
+
+ enableMapControl() {
+ const controller = this.viewer.scene.screenSpaceCameraController
+ controller.enableRotate = true
+ controller.enableTranslate = true
+ controller.enableZoom = true
+ }
+
+ destroy() {
+ if (this.dataSource) {
+ this.dataSource.entities.removeAll()
+ this.viewer.dataSources.remove(this.dataSource)
+ this.dataSource = null
+ }
+ if (this.handler) {
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.RIGHT_CLICK)
+ this.handler.destroy()
+ this.handler = null
+ }
+ this.tooltip?.destroy()
+ this.tooltip = null
+ this.positions = []
+ this.isDragging = false
+ this.draggedIndex = -1
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js
new file mode 100644
index 0000000..d7dc3cd
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/edit/EditRectangleTool.js
@@ -0,0 +1,198 @@
+import * as Cesium from 'cesium'
+import { MapTooltip } from '../Tooltip'
+import { ToolBase } from '../ToolBase'
+import { getBoundsFromLngLats, rectanglePositionsFromBounds } from '../shapeUtils'
+
+const DEFAULT_STYLE = {
+ fill: Cesium.Color.fromBytes(45, 140, 240, 99),
+ outline: Cesium.Color.fromBytes(45, 140, 240, 255),
+}
+const resolveStyle = style => ({
+ fill: style?.fill || DEFAULT_STYLE.fill,
+ outline: style?.outline || DEFAULT_STYLE.outline,
+})
+
+const normalizePoints = points =>
+ (points || []).map(point => ({
+ lng: point.lng ?? point.longitude,
+ lat: point.lat ?? point.latitude,
+ }))
+
+export class EditRectangleTool extends ToolBase {
+ constructor(viewer, options = {}) {
+ super(viewer)
+ this.tooltip = new MapTooltip(viewer)
+ this.dataSource = null
+ this.bounds = null
+ this.rectangleEntity = null
+ this.outlineEntity = null
+ this.cornerEntities = []
+ this.dragIndex = null
+ this.fixedCorner = null
+ this.style = resolveStyle(options?.style)
+ }
+
+ start(points = []) {
+ const normalized = normalizePoints(points)
+ this.bounds = getBoundsFromLngLats(normalized)
+ if (!this.bounds) return
+ this.dataSource = new Cesium.CustomDataSource('edit-rectangle')
+ this.viewer.dataSources.add(this.dataSource)
+ this.createEntities()
+ this.initHandler()
+ }
+
+ initHandler() {
+ this.handler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
+ this.handler.setInputAction(evt => this.handleLeftDown(evt), Cesium.ScreenSpaceEventType.LEFT_DOWN)
+ this.handler.setInputAction(evt => this.handleMouseMove(evt), Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.setInputAction(() => this.handleLeftUp(), Cesium.ScreenSpaceEventType.LEFT_UP)
+ this.tooltip.show('拖动顶点编辑', { x: 0, y: 0 })
+ }
+
+ handleLeftDown(evt) {
+ const picked = this.viewer.scene.pick(evt.position)?.id
+ if (!picked || picked.customType !== 'rect-corner') return
+ this.dragIndex = picked.customData.index
+ const corners = this.getCornerCartographics()
+ this.fixedCorner = corners[(this.dragIndex + 2) % 4]
+ this.disableMapControl()
+ }
+
+ handleMouseMove(evt) {
+ this.tooltip.move(evt.endPosition)
+ if (this.dragIndex == null) return
+ const position = this.getPositionFromScreen(evt.endPosition)
+ if (!position) return
+ const moved = Cesium.Cartographic.fromCartesian(position)
+ this.bounds = {
+ west: Math.min(moved.longitude, this.fixedCorner.longitude),
+ east: Math.max(moved.longitude, this.fixedCorner.longitude),
+ south: Math.min(moved.latitude, this.fixedCorner.latitude),
+ north: Math.max(moved.latitude, this.fixedCorner.latitude),
+ }
+ this.updateCorners()
+ }
+
+ handleLeftUp() {
+ if (this.dragIndex == null) return
+ this.dragIndex = null
+ this.fixedCorner = null
+ this.enableMapControl()
+ const positions = rectanglePositionsFromBounds(this.bounds)
+ this.notify('getPolygonPositions', positions)
+ }
+
+ createEntities() {
+ const hierarchy = new Cesium.CallbackProperty(() => {
+ const positions = rectanglePositionsFromBounds(this.bounds)
+ return new Cesium.PolygonHierarchy(positions)
+ }, false)
+ this.rectangleEntity = this.dataSource.entities.add({
+ polygon: {
+ hierarchy,
+ material: this.style.fill,
+ outline: false,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ },
+ })
+ this.outlineEntity = this.dataSource.entities.add({
+ polyline: {
+ positions: new Cesium.CallbackProperty(() => {
+ const positions = rectanglePositionsFromBounds(this.bounds)
+ return positions.length ? [...positions, positions[0]] : []
+ }, false),
+ clampToGround: true,
+ width: 2,
+ material: this.style.outline,
+ },
+ })
+ this.createCorners()
+ }
+
+ createCorners() {
+ const positions = rectanglePositionsFromBounds(this.bounds)
+ this.cornerEntities = positions.map((position, index) =>
+ this.dataSource.entities.add({
+ position,
+ point: {
+ pixelSize: 12,
+ color: Cesium.Color.WHITE,
+ heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+ disableDepthTestDistance: Number.POSITIVE_INFINITY,
+ },
+ customType: 'rect-corner',
+ customData: { index },
+ })
+ )
+ }
+
+ updateCorners() {
+ const positions = rectanglePositionsFromBounds(this.bounds)
+ this.cornerEntities.forEach((entity, index) => {
+ entity.position = positions[index]
+ })
+ }
+
+ getPositions() {
+ return rectanglePositionsFromBounds(this.bounds)
+ }
+
+ getCornerCartographics() {
+ return rectanglePositionsFromBounds(this.bounds).map(position =>
+ Cesium.Cartographic.fromCartesian(position)
+ )
+ }
+
+ getPositionFromScreen(screenPosition) {
+ const scene = this.viewer.scene
+ const cartesian = scene.pickPosition(screenPosition)
+ if (cartesian) return cartesian
+ return scene.camera.pickEllipsoid(screenPosition, scene.globe.ellipsoid)
+ }
+
+ setStyle(style) {
+ this.style = resolveStyle(style)
+ if (this.rectangleEntity?.polygon) {
+ this.rectangleEntity.polygon.material = this.style.fill
+ }
+ if (this.outlineEntity?.polyline) {
+ this.outlineEntity.polyline.material = this.style.outline
+ }
+ }
+
+ disableMapControl() {
+ const controller = this.viewer.scene.screenSpaceCameraController
+ controller.enableRotate = false
+ controller.enableTranslate = false
+ controller.enableZoom = false
+ }
+
+ enableMapControl() {
+ const controller = this.viewer.scene.screenSpaceCameraController
+ controller.enableRotate = true
+ controller.enableTranslate = true
+ controller.enableZoom = true
+ }
+
+ destroy() {
+ if (this.dataSource) {
+ this.dataSource.entities.removeAll()
+ this.viewer.dataSources.remove(this.dataSource)
+ this.dataSource = null
+ }
+ if (this.handler) {
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_DOWN)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.MOUSE_MOVE)
+ this.handler.removeInputAction(Cesium.ScreenSpaceEventType.LEFT_UP)
+ this.handler.destroy()
+ this.handler = null
+ }
+ this.tooltip?.destroy()
+ this.tooltip = null
+ this.bounds = null
+ this.dragIndex = null
+ this.fixedCorner = null
+ this.cornerEntities = []
+ }
+}
diff --git a/applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js b/applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js
new file mode 100644
index 0000000..e9d001f
--- /dev/null
+++ b/applications/drone-command/src/views/areaManage/partition/shapeTools/shapeUtils.js
@@ -0,0 +1,89 @@
+import * as Cesium from 'cesium'
+
+export const getBoundsFromCartesians = positions => {
+ const cartographics = positions
+ .map(pos => (pos?.longitude ? pos : Cesium.Cartographic.fromCartesian(pos)))
+ .filter(Boolean)
+
+ if (!cartographics.length) return null
+
+ let west = cartographics[0].longitude
+ let east = cartographics[0].longitude
+ let south = cartographics[0].latitude
+ let north = cartographics[0].latitude
+
+ cartographics.forEach(carto => {
+ west = Math.min(west, carto.longitude)
+ east = Math.max(east, carto.longitude)
+ south = Math.min(south, carto.latitude)
+ north = Math.max(north, carto.latitude)
+ })
+
+ return { west, east, south, north }
+}
+
+export const getBoundsFromLngLats = points => {
+ if (!points?.length) return null
+ let west = points[0].lng
+ let east = points[0].lng
+ let south = points[0].lat
+ let north = points[0].lat
+ points.forEach(point => {
+ west = Math.min(west, point.lng)
+ east = Math.max(east, point.lng)
+ south = Math.min(south, point.lat)
+ north = Math.max(north, point.lat)
+ })
+ return {
+ west: Cesium.Math.toRadians(west),
+ east: Cesium.Math.toRadians(east),
+ south: Cesium.Math.toRadians(south),
+ north: Cesium.Math.toRadians(north),
+ }
+}
+
+export const rectanglePositionsFromBounds = bounds => {
+ if (!bounds) return []
+ const { west, east, south, north } = bounds
+ return [
+ Cesium.Cartesian3.fromRadians(west, south),
+ Cesium.Cartesian3.fromRadians(east, south),
+ Cesium.Cartesian3.fromRadians(east, north),
+ Cesium.Cartesian3.fromRadians(west, north),
+ ]
+}
+
+export const getCenterFromBounds = bounds => {
+ if (!bounds) return null
+ return new Cesium.Cartographic(
+ (bounds.west + bounds.east) / 2,
+ (bounds.south + bounds.north) / 2,
+ 0
+ )
+}
+
+export const getMetersBetween = (start, end) => {
+ const geodesic = new Cesium.EllipsoidGeodesic(start, end)
+ return geodesic.surfaceDistance
+}
+
+export const buildEllipsePositions = (centerCartesian, semiMajor, semiMinor, steps = 60) => {
+ if (!centerCartesian || !semiMajor || !semiMinor) return []
+ const positions = []
+ const frame = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian)
+ for (let i = 0; i < steps; i += 1) {
+ const angle = (i / steps) * Math.PI * 2
+ const x = Math.cos(angle) * semiMajor
+ const y = Math.sin(angle) * semiMinor
+ const local = new Cesium.Cartesian3(x, y, 0)
+ const world = Cesium.Matrix4.multiplyByPoint(frame, local, new Cesium.Cartesian3())
+ positions.push(world)
+ }
+ return positions
+}
+
+export const cartesianToLocal = (centerCartesian, positionCartesian) => {
+ const frame = Cesium.Transforms.eastNorthUpToFixedFrame(centerCartesian)
+ const inverse = Cesium.Matrix4.inverse(frame, new Cesium.Matrix4())
+ return Cesium.Matrix4.multiplyByPoint(inverse, positionCartesian, new Cesium.Cartesian3())
+}
diff --git a/applications/task-work-order/env/.env.development b/applications/task-work-order/env/.env.development
index fe1504c..ed8d14a 100644
--- a/applications/task-work-order/env/.env.development
+++ b/applications/task-work-order/env/.env.development
@@ -2,7 +2,7 @@
# @Author : yuan
# @Date : 2026-01-07 14:58:30
# @LastEditors : yuan
- # @LastEditTime : 2026-01-16 11:02:24
+ # @LastEditTime : 2026-01-28 09:21:21
# @FilePath : \applications\task-work-order\env\.env.development
# @Description :
# Copyright 2026 OBKoro1, All Rights Reserved.
@@ -15,7 +15,7 @@
#开发环境代理地址(推荐本地新建文件 .env.development.local 来进行覆盖)
# VITE_APP_URL = https://wrj.shuixiongit.com/api
-VITE_APP_URL= http://192.168.1.204
+VITE_APP_URL= http://192.168.1.227
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue b/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue
index d0f5699..7757fad 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/appInnovation/index.vue
@@ -64,7 +64,7 @@
<template v-slot="{ row }">
<el-link type="primary" @click="openForm('view', row)">查看</el-link>
<!-- <el-link type="primary" @click="openForm('edit', row)">编辑</el-link>-->
- <el-link type="primary" @click="handleDelete(row)" v-if="!['1', '2'].includes(row.innovationStatus)">删除</el-link>
+ <el-link type="primary" @click="handleDelete(row)" v-if="!['1', '2'].includes(row.innovationStatus) && row.isTheSameDepartment">删除</el-link>
</template>
</el-table-column>
</el-table>
@@ -96,7 +96,9 @@
gdApplicationInnovationPageApi,
gdApplicationInnovationRemoveApi,
} from '@/views/orderView/orderDataManage/appInnovation/appInnovationApi'
-
+import { useStore } from 'vuex'
+const store = useStore()
+const userDepartment = store.state.user.userInfo?.dept_id
// 初始化查询参数
const initSearchParams = () => ({
caseName: '', // 案例名称
@@ -128,6 +130,9 @@
loading.value = true
try {
const res = await gdApplicationInnovationPageApi(searchParams.value)
+ res?.data?.data?.records.forEach(item => {
+ item.isTheSameDepartment = String(item.createDept) === String(userDepartment)
+ })
list.value = res?.data?.data?.records ?? []
total.value = res?.data?.data?.total ?? 0
} finally {
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue
index 239cb33..6d685f0 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/dataObjection/FormDiaLog.vue
@@ -323,10 +323,21 @@
}
// 文件移除
+// const handleFileRemove = (file, fileListVal) => {
+// uploadedFiles.value = fileListVal
+// }
const handleFileRemove = (file, fileListVal) => {
-uploadedFiles.value = fileListVal
+ // 从展示列表中移除
+ fileList.value = fileListVal
+
+ // 从已上传列表中移除
+ const index = uploadedFiles.value.findIndex(item =>
+ item.attachId === file.attachId || item.name === file.name
+ )
+ if (index > -1) {
+ uploadedFiles.value.splice(index, 1)
+ }
}
-
// 上传前验证
const beforeFileUpload = file => {
const allowedExtensions = ['.zip', '.rar', '.7z', '.tar', '.gz']
@@ -364,45 +375,53 @@
// 提交数据的通用函数
async function submitForm(objectionStatus) {
- const isValid = await formRef.value?.validate().catch(() => false)
- if (!isValid) return
- // 只在新增时校验附件是否上传
- if (dialogMode.value === 'add' && uploadedFiles.value.length === 0) {
- ElMessage.warning('请上传附件')
- return
- }
- submitting.value = true
+ const isValid = await formRef.value?.validate().catch(() => false)
+ if (!isValid) return
+
+ // 在编辑模式下,如果已有附件,可以不强制上传新附件
+ if (dialogMode.value === 'add' && uploadedFiles.value.length === 0) {
+ return ElMessage.warning('请上传附件')
+ }
+
+ submitting.value = true
- try {
- // 构建符合接口要求的提交数据
- const submitData = {
- // 编辑模式下传递id
- ...(formData.value.id && { id: formData.value.id }),
- title: formData.value.title,
- objectionType: formData.value.objectionType,
- submitter: formData.value.submitter,
- submitterContact: formData.value.submitterContact,
- catalogResourceName: formData.value.catalogResourceName,
- objectionDesc: formData.value.objectionDesc,
- objectionBasis: formData.value.objectionBasis,
- areaCode: formData.value.areaCode || '',
- handleUnit: formData.value.handleUnit || '',
- // 只在有附件时才构建附件列表
- ...(uploadedFiles.value.length > 0 && {
- attachmentList: uploadedFiles.value.map(file => ({
- attachId: file.attachId,
- attachName: file.originalName,
- })),
- }),
- objectionStatus: objectionStatus
- }
- await gdDataObjectionSubmitApi(submitData)
- ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
- visible.value = false
- emit('success')
- } finally {
- submitting.value = false
- }
+ try {
+ // 构建符合接口要求的提交数据
+ const submitData = {
+ // 编辑模式下传递id
+ ...(formData.value.id && { id: formData.value.id }),
+ title: formData.value.title,
+ objectionType: formData.value.objectionType,
+ submitter: formData.value.submitter,
+ submitterContact: formData.value.submitterContact,
+ catalogResourceName: formData.value.catalogResourceName,
+ objectionDesc: formData.value.objectionDesc,
+ objectionBasis: formData.value.objectionBasis,
+ areaCode: formData.value.areaCode || '',
+ handleUnit: formData.value.handleUnit || '',
+ // 构建附件列表 - 使用当前已上传的文件(包括回显的)
+ ...(uploadedFiles.value.length > 0 && {
+ attachmentList: uploadedFiles.value.map(file => ({
+ attachId: file.attachId,
+ attachName: file.originalName,
+ })),
+ }),
+ objectionStatus: objectionStatus
+ }
+
+ // 如果是编辑且没有修改附件,可以保留原有附件ID
+ if (dialogMode.value === 'edit' && formData.value.attachIds) {
+ // 这里可以根据业务需求决定是否保留原附件ID
+ // 如果接口支持更新时重新上传附件ID列表,可以这样做
+ }
+
+ await gdDataObjectionSubmitApi(submitData)
+ ElMessage.success(dialogMode.value === 'add' ? '新增成功' : '更新成功')
+ visible.value = false
+ emit('success')
+ } finally {
+ submitting.value = false
+ }
}
// 提交新增/编辑(保存)
@@ -439,12 +458,42 @@
}
// 加载详情
async function loadDetail() {
- if (!formData.value.id) return
- const res = await gdDataObjectionDetailApi({ id: formData.value.id })
- formData.value = res?.data?.data ?? {}
- if (formData.value.attachIds) {
- getAttachDetail(formData.value.attachIds)
- }
+ if (!formData.value.id) return
+ const res = await gdDataObjectionDetailApi({ id: formData.value.id })
+ formData.value = res?.data?.data ?? {}
+
+ // 处理附件回显
+ if (formData.value.attachIds) {
+ await getAttachDetail(formData.value.attachIds)
+ // 回显到上传组件
+ updateFileListForDisplay()
+ }
+}
+
+// 更新文件列表用于显示
+function updateFileListForDisplay() {
+ fileList.value = []
+ uploadedFiles.value = []
+
+ if (attachmentDetailsList.value && attachmentDetailsList.value.length > 0) {
+ attachmentDetailsList.value.forEach(attach => {
+ const fileItem = {
+ name: attach.originalName || attach.name,
+ url: attach.link, // 用于预览
+ attachId: attach.id, // 文件ID
+ originalName: attach.originalName,
+ link: attach.link
+ }
+
+ fileList.value.push({
+ name: fileItem.name,
+ url: fileItem.url,
+ status: 'success' // 已上传状态
+ })
+
+ uploadedFiles.value.push(fileItem)
+ })
+ }
}
// 获取附件详情
async function getAttachDetail(attachIds) {
@@ -478,17 +527,22 @@
// 打开弹框
async function open({ mode = 'add', row } = {}) {
- dialogMode.value = mode
- // 重置文件列表
- fileList.value = []
- uploadedFiles.value = []
- // 重置表单数据
- formData.value = dialogMode.value === 'add' ? initForm() : row
- // 重置反馈表单数据
- feedbackFormData.value = initFeedbackForm()
- if (dialogMode.value !== 'add') {
- await loadDetail()
- }
+ dialogMode.value = mode
+ // 重置文件列表
+ fileList.value = []
+ uploadedFiles.value = []
+ // 重置表单数据
+ formData.value = dialogMode.value === 'add' ? initForm() : row
+ // 重置反馈表单数据
+ feedbackFormData.value = initFeedbackForm()
+
+ if (dialogMode.value !== 'add') {
+ // 如果是编辑,设置ID并加载详情
+ if (row && row.id) {
+ formData.value.id = row.id
+ }
+ await loadDetail()
+ }
}
defineExpose({ open })
diff --git a/applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue b/applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
index 3d0a7cf..344d20b 100644
--- a/applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
+++ b/applications/task-work-order/src/views/orderView/orderDataManage/evaluate/index.vue
@@ -127,8 +127,6 @@
item.isTheSameDepartment = String(item.createDept) === String(userDepartment)
})
list.value = res?.data?.data?.records ?? []
- console.log('list',list.value);
-
total.value = res?.data?.data?.total ?? 0
} finally {
loading.value = false
--
Gitblit v1.9.3