From 8a7fb9a519cae32d38ba66058d243aa7e6b6aeb2 Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Tue, 27 Jan 2026 08:57:49 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue                         |  278 +++++++++++++++++++
 applications/task-work-order/src/views/orderView/flyingHand/index.vue                                    |  222 +++++++++++++++
 applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue        |    4 
 applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue             |    0 
 applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue                   |    6 
 applications/task-work-order/src/views/orderView/organizational/agenciesManagement/agenciesApi.js        |   66 ++++
 applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue |    2 
 applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue               |    0 
 applications/drone-command/src/components/map-container/device-map-container.vue                         |  122 ++++++--
 applications/drone-command/src/views/basicManage/deviceStock/index.vue                                   |    3 
 applications/drone-command/src/views/dataCockpit/components/LegendBar.vue                                |    8 
 applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue     |   28 +
 applications/drone-command/src/components/map-container/device-map-materials.js                          |   40 ++
 applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue                    |    6 
 applications/task-work-order/src/views/orderView/flyingHand/flyingHandApi.js                             |   37 ++
 applications/drone-command/src/api/dataCockpit/index.js                                                  |    5 
 16 files changed, 766 insertions(+), 61 deletions(-)

diff --git a/applications/drone-command/src/api/dataCockpit/index.js b/applications/drone-command/src/api/dataCockpit/index.js
index f08ac7f..68024bd 100644
--- a/applications/drone-command/src/api/dataCockpit/index.js
+++ b/applications/drone-command/src/api/dataCockpit/index.js
@@ -2,7 +2,7 @@
  * @Author       : yuan
  * @Date         : 2026-01-09 11:14:04
  * @LastEditors  : yuan
- * @LastEditTime : 2026-01-26 09:38:16
+ * @LastEditTime : 2026-01-26 19:02:07
  * @FilePath     : \applications\drone-command\src\api\dataCockpit\index.js
  * @Description  : 
  * Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -78,7 +78,8 @@
 export const cockpitAggregationApi = (params) => {
 	return request({
 		url: '/drone-fw/device/fwDevice/statisticalDeviceOut',
-		method: 'get'
+		method: 'get',
+		params
 	})
 }
 
diff --git a/applications/drone-command/src/components/map-container/device-map-container.vue b/applications/drone-command/src/components/map-container/device-map-container.vue
index a8f8566..f5813aa 100644
--- a/applications/drone-command/src/components/map-container/device-map-container.vue
+++ b/applications/drone-command/src/components/map-container/device-map-container.vue
@@ -2,8 +2,9 @@
 	<div class="map-shell">
 		<CommonCesiumMap ref="mapRef" class="command-cesium map-container" :dom-id="props.containerId" :active="true"
 			:flat-mode="false" :terrain="true" :layer-mode="4" :contour="false" :boundary="false"
-			:show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true" :cluster-height="100000"
-			@ready="handleMapReady" @stage-change="handleStageChange" />
+			:show-admin-boundary="true" :zoom-to-boundary="true" :enable-stage-emit="true"
+			:cluster-height="CLUSTER_HEIGHT" :detail-height="DETAIL_HEIGHT" @ready="handleMapReady"
+			@stage-change="handleStageChange" />
 		<div v-if="props.showLayerControl" class="layer-control-root" :class="{ collapsed: props.leftCollapsed }">
 			<div class="layer-control-wrap" ref="layerWrapRef">
 				<div class="layer-control" @click="toggleLayerPanel">
@@ -63,6 +64,9 @@
 	createRadialGradientMaterial,
 	getTexturedVertexFormat,
 } from './device-map-materials'
+
+const CLUSTER_HEIGHT = 100000
+const DETAIL_HEIGHT = 10000
 
 const props = defineProps({
 	onlineDevices: {
@@ -157,6 +161,14 @@
 	const latitude = Number(latitudeRaw)
 	if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) return null
 	return { longitude, latitude }
+}
+
+const getDeviceRange = item => {
+	if (!item) return null
+	const rawRange = item.effectiveRangeKm ?? item.range ?? item.coverRadiusM
+	const range = Number(rawRange)
+	if (!Number.isFinite(range) || range <= 0) return null
+	return range
 }
 
 const ensureCockpitPrimitiveLayer = () => {
@@ -305,6 +317,20 @@
 	if (aggregationSource) aggregationSource.show = visible
 }
 
+const setDroneVisibility = visible => {
+	if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible
+	if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible
+	if (!visible && selectedTargetType.value === 'drone') {
+		closePopup()
+	}
+}
+const getStageByHeight = height => {
+	if (height == null) return 'detail'
+	if (height >= CLUSTER_HEIGHT) return 'cluster'
+	if (height <= DETAIL_HEIGHT) return 'detail'
+	return 'mid'
+}
+
 const ensureCountyCenterMap = () => {
 	if (countyCenterMap.size) return
 	const geojson = JSON.parse(jaGeojsonRaw)
@@ -316,24 +342,44 @@
 	})
 }
 
-const buildSimulatedTrackPoints = base => {
-	ensureCountyCenterMap()
-	const anchor = base || { longitude: 116.397, latitude: 39.908 }
-	const offsets = [
-		[0, 0, 120],
-		[0.02, 0.012, 150],
-		[0.04, 0.006, 180],
-		[0.06, -0.008, 200],
-		[0.03, -0.02, 160],
-		[0.0, -0.015, 140],
-		[-0.02, -0.005, 120],
-		[-0.01, 0.01, 130],
+const buildSimulatedTrackPoints = () => {
+	return [
+		{
+			longitude: 114.963191,
+			latitude: 27.136716,
+			height: 120
+		},
+		{
+			longitude:114.957308,
+			latitude: 27.138452,
+			height: 120
+		},
+		{
+			longitude:114.952,
+			latitude: 27.136317,
+			height: 120
+		},
+		{
+			longitude:114.949293,
+			latitude: 27.133864,
+			height: 120
+		},
+		{
+			longitude:114.944666,
+			latitude: 27.130526,
+			height: 120
+		},
+		{
+			longitude:114.945909,
+			latitude:27.127845,
+			height: 120
+		},
+		{
+			longitude:114.962974,
+			latitude:27.136242,
+			height: 120
+		},
 	]
-	return offsets.map(item => ({
-		longitude: anchor.longitude + item[0],
-		latitude: anchor.latitude + item[1],
-		height: item[2],
-	}))
 }
 
 
@@ -422,13 +468,11 @@
 const renderSimulatedDroneTrack = () => {
 	if (!viewer) return
 	clearDroneTrackEntities()
-	ensureCountyCenterMap()
 	ensureDroneTrackCollections()
+	droneTrackBillboardCollection.show = detailVisible.value
+	droneTrackPolylineCollection.show = detailVisible.value
 	droneTrackRuntime = []
-	const centers = Array.from(countyCenterMap.entries())
-	const baseCenters = centers.length
-		? centers.slice(0, 4)
-		: [['默认区域', { longitude: 116.397, latitude: 39.908 }]]
+	const baseCenters = [['默认区域', { longitude:114.958541, latitude: 27.121917 }]]
 	const segmentDuration = 6
 	const baseTrackColor = Cesium.Color.fromCssColorString('red')
 	baseCenters.forEach(([centerName, center], trackIndex) => {
@@ -454,7 +498,7 @@
 		})
 		const droneId = `drone-track-${trackIndex}`
 		const billboard = droneTrackBillboardCollection.add({
-			position: positions[0],
+			position: positions[positions.length - 1],
 			image: droneIcon,
 			width: 36,
 			height: 36,
@@ -511,7 +555,8 @@
 		const position = getDevicePosition(item)
 		if (!position) return
 		const entityId = `online-device-${item.id ?? index}-${index}`
-		addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle)
+		const rangeMeters = getDeviceRange(item)
+		addDeviceRings(position, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters)
 		const billboard = deviceBillboardCollection.add({
 			position: Cesium.Cartesian3.fromDegrees(position.longitude, position.latitude, 0),
 			image: equipmentIcon,
@@ -528,9 +573,20 @@
 	ringFillInstancesByStyle.forEach((instances, index) => {
 		if (!instances.length) return
 		const ring = RING_STYLES[index]
+		const alpha = ring.alpha ?? 0.64
+		const innerRatio =
+			typeof ring.innerRatio === 'number'
+				? ring.innerRatio
+				: ring.inner && ring.outer
+					? Math.min(Math.max(ring.inner / ring.outer, 0), 0.9)
+					: 0
 		const material = createRadialGradientMaterial(
-			Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.64),
-			Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(0.64)
+			Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(alpha),
+			Cesium.Color.fromCssColorString(ring.gradient[1]).withAlpha(alpha),
+			{
+				gamma: 1.7,
+				innerCutoff: innerRatio,
+			}
 		)
 		const primitive = new Cesium.GroundPrimitive({
 			geometryInstances: instances,
@@ -629,7 +685,7 @@
 const renderDefenseZones = zones => {
 	if (!viewer) return
 	clearDefenseZoneEntities()
-	const result = buildZonePrimitives(zones, '#19D266', '#2AEDBF', '#012B11')
+	const result = buildZonePrimitives(zones, '#72F3FF', '#72F3FF', '#0A7C88')
 	defenseZonePrimitive = result.primitive
 	defenseZoneOutlinePrimitive = result.outlinePrimitive
 	if (defenseZonePrimitive) defenseZonePrimitive.show = detailVisible.value
@@ -640,7 +696,7 @@
 const renderPartitions = zones => {
 	if (!viewer) return
 	clearPartitionEntities()
-	const result = buildZonePrimitives(zones, '#FFCD2A', '#FFC609', '#583300')
+	const result = buildZonePrimitives(zones, '#FFD772', '#FFD772', '#A86B00')
 	partitionPrimitive = result.primitive
 	partitionOutlinePrimitive = result.outlinePrimitive
 	if (partitionPrimitive) partitionPrimitive.show = detailVisible.value
@@ -693,7 +749,7 @@
 				width: 66.73,
 				height: 43,
 				verticalOrigin: Cesium.VerticalOrigin.BOTTOM,
-				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+				// disableDepthTestDistance: Number.POSITIVE_INFINITY,
 			},
 			label: {
 				text: `${count}`,
@@ -706,7 +762,6 @@
 
 				pixelOffset: new Cesium.Cartesian2(0, -35),
 				disableDepthTestDistance: Number.POSITIVE_INFINITY,
-				heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
 			},
 		})
 	})
@@ -855,13 +910,14 @@
 	const showCluster = stage === 'cluster'
 	setClusterVisibility(showCluster)
 	setDetailVisibility(!showCluster)
+	setDroneVisibility(!showCluster)
 }
 
 const handleMapReady = ({ viewer: mapViewer }) => {
 	viewer = mapViewer
 	ensureCockpitPrimitiveLayer()
 	const height = viewer?.camera?.positionCartographic?.height
-	const stage = height != null && height >= 100000 ? 'cluster' : 'detail'
+	const stage = getStageByHeight(height)
 	updateStageDisplay(stage)
 	renderDeviceEntities(props.onlineDevices)
 	loadDefenseZones()
diff --git a/applications/drone-command/src/components/map-container/device-map-materials.js b/applications/drone-command/src/components/map-container/device-map-materials.js
index bf2edc8..6177bb7 100644
--- a/applications/drone-command/src/components/map-container/device-map-materials.js
+++ b/applications/drone-command/src/components/map-container/device-map-materials.js
@@ -1,6 +1,15 @@
 import * as Cesium from 'cesium'
 
-export const RING_STYLES = [{ inner: 0, outer: 2000, gradient: ['#FF361C', '#360B00'] }]
+export const RING_STYLES = [
+	{
+		inner: 0,
+		outer: 2000,
+		gradient: ['#FF6A57', '#A11806'],
+		alpha: 0.42,
+		outlineAlpha: 0.85,
+		innerRatio: 0.12,
+	},
+]
 
 export const MATERIAL_TYPE = 'RadialGradientMaterial'
 export const DRONE_TRACK_MATERIAL_TYPE = 'DroneTrackFlowMaterial'
@@ -26,15 +35,25 @@
 			uniforms: {
 				color1: new Cesium.Color(1.0, 1.0, 1.0, 1.0),
 				color2: new Cesium.Color(0.0, 0.0, 0.0, 1.0),
+				gamma: 1.7,
+				innerCutoff: 0.0,
 			},
 			source: `
+				uniform vec4 color1;
+				uniform vec4 color2;
+				uniform float gamma;
+				uniform float innerCutoff;
+
 				czm_material czm_getMaterial(czm_materialInput materialInput) {
 					czm_material material = czm_getDefaultMaterial(materialInput);
 					vec2 st = materialInput.st - vec2(0.5);
 					float t = clamp(length(st) * 2.0, 0.0, 1.0);
 					vec4 color = mix(color1, color2, t);
+					float ramp = smoothstep(innerCutoff, 1.0, t);
+					float shaped = pow(t, gamma);
+					float alpha = color.a * ramp * shaped;
 					material.diffuse = color.rgb;
-					material.alpha = color.a;
+					material.alpha = alpha;
 					return material;
 				}
 			`,
@@ -82,9 +101,9 @@
 	})
 }
 
-export const createRadialGradientMaterial = (color1, color2) => {
+export const createRadialGradientMaterial = (color1, color2, options = {}) => {
 	registerRadialGradientMaterial()
-	return Cesium.Material.fromType(MATERIAL_TYPE, { color1, color2 })
+	return Cesium.Material.fromType(MATERIAL_TYPE, { color1, color2, ...(options || {}) })
 }
 
 export const createDroneTrackMaterial = options => {
@@ -115,18 +134,19 @@
 	return positions
 }
 
-export const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle) => {
+export const addDeviceRings = (center, ringFillInstancesByStyle, ringOutlineInstancesByStyle, rangeMeters) => {
 	if (!ringFillInstancesByStyle) return
 	const centerPosition = Cesium.Cartesian3.fromDegrees(center.longitude, center.latitude, 0)
 	const vertexFormat = getTexturedVertexFormat()
 	RING_STYLES.forEach((ring, index) => {
 		if (!ringFillInstancesByStyle[index]) return
+		const outer = Number.isFinite(rangeMeters) && rangeMeters > 0 ? rangeMeters : ring.outer
 		ringFillInstancesByStyle[index].push(
 			new Cesium.GeometryInstance({
 				geometry: new Cesium.EllipseGeometry({
 					center: centerPosition,
-					semiMajorAxis: ring.outer,
-					semiMinorAxis: ring.outer,
+					semiMajorAxis: outer,
+					semiMinorAxis: outer,
 					vertexFormat,
 				}),
 			})
@@ -135,7 +155,9 @@
 	if (!ringOutlineInstancesByStyle) return
 	RING_STYLES.forEach((ring, index) => {
 		if (!ringOutlineInstancesByStyle[index]) return
-		const positions = buildCirclePositions(center, ring.outer)
+		const outer = Number.isFinite(rangeMeters) && rangeMeters > 0 ? rangeMeters : ring.outer
+		const positions = buildCirclePositions(center, outer)
+		const outlineAlpha = typeof ring.outlineAlpha === 'number' ? ring.outlineAlpha : 0.8
 		ringOutlineInstancesByStyle[index].push(
 			new Cesium.GeometryInstance({
 				geometry: new Cesium.GroundPolylineGeometry({
@@ -144,7 +166,7 @@
 				}),
 				attributes: {
 					color: Cesium.ColorGeometryInstanceAttribute.fromColor(
-						Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(0.8)
+						Cesium.Color.fromCssColorString(ring.gradient[0]).withAlpha(outlineAlpha)
 					),
 				},
 			})
diff --git a/applications/drone-command/src/views/basicManage/deviceStock/index.vue b/applications/drone-command/src/views/basicManage/deviceStock/index.vue
index 4933b82..e635bdd 100644
--- a/applications/drone-command/src/views/basicManage/deviceStock/index.vue
+++ b/applications/drone-command/src/views/basicManage/deviceStock/index.vue
@@ -62,7 +62,6 @@
 		<div class="command-table-container" v-loading="loading" element-loading-background="rgba(5, 5, 15, 0.6)">
 			<div class="command-table-content command-table-content-bg">
 				<el-table class="command-table" :data="list" @selection-change="handleSelectionChange">
-					<el-table-column type="selection" width="46" />
 					<el-table-column type="index" show-overflow-tooltip width="64" label="序号" />
 					<el-table-column prop="deviceName" show-overflow-tooltip width="130" label="设备名称" />
 					<el-table-column prop="deviceType" show-overflow-tooltip width="130" label="设备类型">
@@ -80,7 +79,7 @@
 					</el-table-column>
 					<el-table-column prop="source" show-overflow-tooltip width="112" label="来源" />
 					<el-table-column prop="purpose" show-overflow-tooltip width="206" label="用途" />
-					<el-table-column prop="belongDeptName" show-overflow-tooltip width="124" label="所属部门" />
+					<el-table-column prop="belongDeptName" show-overflow-tooltip label="所属部门" />
 					<el-table-column prop="charger" show-overflow-tooltip width="100" label="负责人" />
 					<el-table-column prop="yxzt" show-overflow-tooltip width="116" label="运行状态">
 						<template v-slot="{ row }">
diff --git a/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue b/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
index d115225..52535aa 100644
--- a/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/LegendBar.vue
@@ -85,13 +85,13 @@
 }
 
 .legend-color.defense {
-	background: radial-gradient(50% 50% at 50% 50%, #2aedbf 0%, #012b11 100%);
-	border: 1px solid #19d266;
+	background: radial-gradient(50% 50% at 50% 50%, rgba(114, 243, 255, 0.45) 0%, rgba(10, 124, 136, 0.55) 100%);
+	border: 1px solid rgba(114, 243, 255, 0.85);
 }
 
 .legend-color.partition {
-	background: radial-gradient(50% 50% at 50% 50%, #ffc609 0%, #583300 100%);
-	border: 1px solid #ffcd2a;
+	background: radial-gradient(50% 50% at 50% 50%, rgba(255, 215, 114, 0.5) 0%, rgba(168, 107, 0, 0.6) 100%);
+	border: 1px solid rgba(255, 215, 114, 0.9);
 }
 
 .legend-icon {
diff --git a/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue b/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue
index 631a674..0315403 100644
--- a/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue
@@ -67,7 +67,7 @@
 				</div>
 				<div class="col">
 					<span class="label">有效范围</span>
-					{{ data.effectiveRangeKm }}m
+					{{ Number(data.effectiveRangeKm).toFixed(0) }}m
 				</div>
 			</div>
 		</div>
diff --git a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
index ca54c44..a939219 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/DetectionRangeDialog.vue
@@ -123,6 +123,7 @@
 							v-model="formData.effectiveRangeKm"
 							:min="0"
 							:precision="0"
+							:step="1"
 							:controls="false"
 							placeholder="请输入"
 						/>
@@ -227,7 +228,14 @@
 
 function formatRange(value) {
 	if (value == null || value === '') return '-'
-	return `${value}`
+	const normalized = normalizeRange(value)
+	return normalized == null ? '-' : `${normalized}`
+}
+
+function normalizeRange(value) {
+	const numeric = Number(value)
+	if (!Number.isFinite(numeric)) return null
+	return Math.trunc(numeric)
 }
 
 // 关闭弹框
@@ -242,14 +250,16 @@
 	submitting.value = true
 	try {
 		const {longitude, latitude} = parseDeployLocation(formData.value.deployLocation)
+		const normalizedRange = normalizeRange(formData.value.effectiveRangeKm)
 
 		const payload = {
 			id: formData.value.id || formData.value.deviceId || undefined,
-			effectiveRangeKmIsNotNull: formData.value.effectiveRangeKm ? 1 : 2,
-			effectiveRangeKm: formData.value.effectiveRangeKm,
+			effectiveRangeKmIsNotNull: normalizedRange != null ? 1 : 2,
+			effectiveRangeKm: normalizedRange,
 			longitude,
 			latitude
 		}
+		formData.value.effectiveRangeKm = normalizedRange
 		delete payload.deviceId
 		await detectionRangeSubmitApi(payload)
 
@@ -280,6 +290,7 @@
 		current: 1,
 		size: 200,
 		effectiveRangeKmIsNotNull: 2,
+		isTrack: 1
 	})
 	const records = res?.data?.data?.records ?? []
 	deviceOptions.value = records.map(item => ({
@@ -432,9 +443,18 @@
 watch(
 	() => formData.value.effectiveRangeKm,
 	value => {
+		const normalized = normalizeRange(value)
+		if (value != null && normalized == null) {
+			formData.value.effectiveRangeKm = null
+			return
+		}
+		if (value != null && normalized !== value) {
+			formData.value.effectiveRangeKm = normalized
+			return
+		}
 		const parsed = parseDeployLocation(formData.value.deployLocation)
 		if (!parsed) return
-		updateRangeCircle(parsed.longitude, parsed.latitude, value)
+		updateRangeCircle(parsed.longitude, parsed.latitude, normalized)
 	}
 )
 </script>
diff --git a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue
index c2bda53..34735d3 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/detectionRange/index.vue
@@ -204,8 +204,10 @@
 
 function formatRange(row) {
 	if (!row) return '-'
-	const value = row.effectiveRangeKm ?? row.range
-	return value === null || value === undefined || value === '' ? '-' : value
+
+	let value = row.effectiveRangeKm ?? row.range
+	
+	return value === null || value === undefined || value === '' ? '-' : Number(value).toFixed(0)
 }
 
 function formatDeviceCode(row) {
diff --git a/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue b/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue
index ea7f4af..d56f1f3 100644
--- a/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue
+++ b/applications/drone-command/src/views/detectionCountermeasure/deviceAppConfig/index.vue
@@ -151,7 +151,10 @@
 async function getList() {
 	loading.value = true
 	try {
-		const res = await fwDevicePageApi(searchParams.value)
+		const res = await fwDevicePageApi({
+			...searchParams.value,
+			deviceStatusList: '0,1,2'
+		})
 		list.value = res?.data?.data?.records ?? []
 		total.value = res?.data?.data?.total ?? 0
 	} finally {
@@ -176,6 +179,7 @@
 function getDictList() {
 	getDictionaryByCode('deviceType,deviceStatus,deviceAtt').then(res => {
 		dictObj.value = res.data.data
+		dictObj.value.deviceStatus = dictObj.value.deviceStatus.filter(item => item.dictKey != 3)
 	})
 }
 
diff --git a/applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue b/applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue
new file mode 100644
index 0000000..61aa55f
--- /dev/null
+++ b/applications/task-work-order/src/views/orderView/flyingHand/FlyingHandDialog.vue
@@ -0,0 +1,278 @@
+<!-- 飞手信息弹窗 -->
+<template>
+	<el-dialog
+		v-model="dialogVisible"
+		:title="dialogTitle"
+		class="gd-dialog"
+		destroy-on-close
+		:close-on-click-modal="false"
+	>
+		<el-form
+			ref="formRef"
+			:model="formData"
+			:rules="rules"
+			class="gd-form"
+			label-width="160px"
+		>
+			<el-row>
+				<el-col :span="12">
+					<el-form-item label="飞手姓名" prop="flyerName">
+						<el-select
+							class="gd-select"
+							popper-class="gd-select-popper"
+							v-model="formData.flyerName"
+							placeholder="请选择飞手姓名"
+							:disabled="dialogType === 'view'"
+						>
+							<el-option
+								v-for="item in flyingHandList"
+								:key="item.id"
+								:label="item.name"
+								:value="item.id"
+							/>
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="飞手电话" prop="flyerPhone">
+						<el-input
+						class="gd-input"
+							v-model="formData.flyerPhone"
+							placeholder="请输入飞手电话"
+							:disabled="dialogType === 'view'"
+						/>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="飞行时长(小时)" prop="flightHours">
+						<el-input
+							class="gd-input"
+							v-model.number="formData.flightHours"
+							placeholder="请输入飞行时长"
+							type="number"
+							:disabled="dialogType === 'view'"
+						/>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="技术特长" prop="technicalStrength">
+						<el-select
+							class="gd-select"
+							popper-class="gd-select-popper"
+							v-model="formData.technicalStrength"
+							placeholder="请选择技术特长"
+							:disabled="dialogType === 'view'"
+						>
+							<el-option
+								v-for="item in dictObj.technicalStrength"
+								:key="item.dictKey"
+								:label="item.dictValue"
+								:value="item.dictKey"
+							/>
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="擅长机型" prop="skilledUavType">
+						<el-select
+							class="gd-select"
+							popper-class="gd-select-popper"
+							v-model="formData.skilledUavType"
+							placeholder="请选择擅长机型"
+							clearable
+							@change="handleSearch"
+						>
+							<el-option
+								v-for="item in dictObj.skilledUavType"
+								:key="item.dictKey"
+								:label="item.dictValue"
+								:value="item.dictKey"
+							/>
+				</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="12">
+					<el-form-item label="擅长任务类型" prop="skilledTaskType">
+						<el-select
+							class="gd-select"
+							popper-class="gd-select-popper"
+							v-model="formData.skilledTaskType"
+							placeholder="请选择擅长任务类型"
+							:disabled="dialogType === 'view'"
+						>
+							<el-option
+								v-for="item in dictObj.skilledTaskType"
+								:key="item.dictKey"
+								:label="item.dictValue"
+								:value="item.dictKey"
+							/>
+						</el-select>
+					</el-form-item>
+				</el-col>
+				<el-col :span="24">
+					<el-form-item label="项目经验" prop="projectExperience">
+						<el-input
+							class="gd-input"
+							v-model="formData.projectExperience"
+							placeholder="请输入项目经验"
+							type="textarea"
+							rows="3"
+							:disabled="dialogType === 'view'"
+						/>
+					</el-form-item>
+				</el-col>
+			</el-row>
+		</el-form>
+		<template #footer>
+			<el-button @click="handleClose">取消</el-button>
+			<el-button
+				v-if="dialogType !== 'view'"
+				type="primary"
+				@click="handleSubmit"
+			>
+				确定
+			</el-button>
+		</template>
+	</el-dialog>
+</template>
+<script setup>
+import { ref, reactive, computed, watch, inject } from 'vue'
+import { submitFlyingHand } from './flyingHandApi'
+import { ElMessage } from 'element-plus'
+
+const props = defineProps({
+	modelValue: {
+		type: Boolean,
+		default: false
+	},
+	dialogType: {
+		type: String,
+		default: 'add', // add, edit, view
+		validator: (value) => ['add', 'edit', 'view'].includes(value)
+	},
+	flyingHandData: {
+		type: Object,
+		default: () => ({})
+	}
+})
+
+const dictObj = inject('dictObj') // 擅长任务类型
+
+const flyingHandList = inject('flyingHandList') // 飞手列表
+
+const emit = defineEmits(['update:modelValue', 'confirm'])
+
+const dialogVisible = computed({
+	get: () => props.modelValue,
+	set: (value) => emit('update:modelValue', value)
+})
+
+const dialogTitle = computed(() => {
+	switch (props.dialogType) {
+		case 'add':
+			return '添加飞手信息'
+		case 'edit':
+			return '编辑飞手信息'
+		case 'view':
+			return '查看飞手信息'
+		default:
+			return '飞手信息'
+	}
+})
+
+const formRef = ref(null)
+const formData = reactive({
+	flyerName: '',
+	flyerPhone: '',
+	flightHours: 0,
+	projectExperience: '',
+	technicalStrength: '',
+	skilledUavType: ''
+})
+
+const rules = {
+	flyerName: [
+		{ required: true, message: '请输入飞手姓名', trigger: 'blur' }
+	],
+	flyerPhone: [
+		{ required: false, message: '请输入飞手电话', trigger: 'blur' },
+		{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
+	],
+	flightHours: [
+		{ required: true, message: '请输入飞行时长', trigger: 'blur' },
+		{ type: 'number', min: 0, message: '飞行时长必须大于等于0', trigger: 'blur' }
+	],
+	technicalSkills: [
+		{ required: true, message: '请选择技术特长', trigger: 'change' }
+	],
+	skilledTaskType: [
+		{ required: true, message: '请选择擅长任务类型', trigger: 'change' }
+	],
+	projectExperience: [
+		{ required: true, message: '请输入项目经验', trigger: 'blur' }
+	]
+}
+
+// 监听飞手数据变化,更新表单
+watch(
+	() => props.flyingHandData,
+	(newData) => {
+		if (newData) {
+			Object.assign(formData, newData)
+		}
+	},
+	{ deep: true, immediate: true }
+)
+
+// 处理关闭
+function handleClose() {
+	dialogVisible.value = false
+	resetForm()
+}
+
+// 重置表单
+function resetForm() {
+	formRef.value?.resetFields()
+	// 重置为默认值
+	Object.assign(formData, {
+		name: '',
+		phone: '',
+		flightHours: 0,
+		projectExperience: '',
+		technicalSkills: '',
+		technicalExpertise: '',
+		goodAtAircraftType: '',
+		goodAtTaskType: ''
+	})
+}
+
+// 处理提交
+async function handleSubmit() {
+	const isValid = await formRef.value?.validate()
+	if (!isValid) {
+		return
+	}
+
+	try {
+		if (props.dialogType === 'add') {
+			await submitFlyingHand(formData)
+			ElMessage.success('添加成功')
+		} else if (props.dialogType === 'edit') {
+			await submitFlyingHand(formData)
+			ElMessage.success('编辑成功')
+		}
+		emit('confirm')
+		dialogVisible.value = false
+		resetForm()
+	} catch (error) {
+		ElMessage.error('操作失败')
+	}
+}
+</script>
+<style scoped lang="scss">
+.gd-form {
+	.el-form-item {
+		margin-bottom: 20px;
+	}
+}
+</style>
\ No newline at end of file
diff --git a/applications/task-work-order/src/views/orderView/flyingHand/flyingHandApi.js b/applications/task-work-order/src/views/orderView/flyingHand/flyingHandApi.js
new file mode 100644
index 0000000..3d38189
--- /dev/null
+++ b/applications/task-work-order/src/views/orderView/flyingHand/flyingHandApi.js
@@ -0,0 +1,37 @@
+import request from '@/axios'
+
+// 飞手分页查询
+export function getFlyingHandPage(params) {
+    return request({
+        url: '/drone-gd/flyer/gdFlyer/page',
+        method: 'get',
+        params
+    })
+}
+
+// 飞手提交
+export function submitFlyingHand(data) {
+    return request({
+        url: '/drone-gd/flyer/gdFlyer/submit',
+        method: 'post',
+        data
+    })
+}
+
+// 飞手详情查询
+export function getFlyingHandDetail(params) {
+    return request({
+        url: '/drone-gd/flyer/gdFlyer/detail',
+        method: 'get',
+        params
+    })
+}
+
+// 飞手删除
+export function removeFlyingHand(data) {
+    return request({
+        url: '/drone-gd/flyer/gdFlyer/remove',
+        method: 'post',
+        data
+    })
+}
diff --git a/applications/task-work-order/src/views/orderView/flyingHand/index.vue b/applications/task-work-order/src/views/orderView/flyingHand/index.vue
index 60a2c0d..ac5e9d1 100644
--- a/applications/task-work-order/src/views/orderView/flyingHand/index.vue
+++ b/applications/task-work-order/src/views/orderView/flyingHand/index.vue
@@ -1,7 +1,223 @@
+<!-- 飞手信息管理 -->
 <template>
-	<basic-container>飞手</basic-container>
+	<basic-container>
+		<el-form ref="queryParamsRef" :model="searchParams" class="gd-search-form">
+			<el-form-item label="飞手姓名" prop="flyerName">
+				<el-input
+					class="gd-input gray"
+					v-model="searchParams.flyerName"
+					placeholder="请输入飞手姓名"
+					clearable
+					@clear="handleSearch"
+				/>
+			</el-form-item>
+
+			<el-form-item label="擅长机型" prop="skilledUavType">
+				<el-select
+					class="gd-select gray"
+					popper-class="gd-select-popper"
+					v-model="searchParams.skilledUavType"
+					placeholder="请选择"
+					clearable
+					@change="handleSearch"
+				>
+					<el-option
+						v-for="item in dictObj.skilledUavType"
+						:key="item.dictKey"
+						:label="item.dictValue"
+						:value="item.dictKey"
+					/>
+				</el-select>
+			</el-form-item>
+
+			<el-form-item class="gd-search-actions">
+				<el-button :icon="RefreshRight" @click="resetForm"></el-button>
+				<el-button class="search-btn" :icon="Search" @click="handleSearch"></el-button>
+			</el-form-item>
+		</el-form>
+
+        <div class="gd-table-toolbar">
+			<el-button :icon="Plus" color="#4C34FF" type="primary" @click="addFlyingHand">新增</el-button>
+		</div>
+
+		<div class="gd-table-container" v-loading="loading">
+			<div class="gd-table-content gd-table-content-bg">
+				<el-table class="gd-table" :data="list">
+					<el-table-column type="index" width="64" label="序号" />
+					<el-table-column prop="flyerName" show-overflow-tooltip label="飞手姓名" />
+					<el-table-column prop="flyerPhone" show-overflow-tooltip label="飞手电话" />
+					<el-table-column prop="flightHours" show-overflow-tooltip label="飞行时长(小时)" />
+					<el-table-column prop="projectExperience" show-overflow-tooltip label="项目经验" />
+					<el-table-column prop="technicalStrength" show-overflow-tooltip label="技术特长">
+						<template v-slot="{ row }">
+							{{ getDictLabel(row.technicalStrength, dictObj.technicalStrength) }}
+						</template>
+					</el-table-column>
+					<el-table-column prop="skilledUavType" show-overflow-tooltip label="擅长机型" />
+					<el-table-column prop="skilledTaskType" show-overflow-tooltip label="擅长任务类型">
+						<template v-slot="{ row }">
+							{{ getDictLabel(row.skilledTaskType, dictObj.skilledTaskType) }}
+						</template>
+					</el-table-column>
+					<el-table-column label="操作" class-name="operation-btns" width="180">
+						<template v-slot="{ row }">
+							<el-link @click="viewFlyingHand(row)">查看</el-link>
+							<el-link type="primary" @click="editFlyingHand(row)">编辑</el-link>
+							<el-link type="danger" @click="deleteFlyingHand(row.id)">删除</el-link>
+						</template>
+					</el-table-column>
+				</el-table>
+			</div>
+
+			<div class="gd-pagination-parent">
+				<el-pagination
+					popper-class="gd-select-popper"
+					v-model:current-page="searchParams.current"
+					v-model:page-size="searchParams.size"
+					layout="total, prev, pager, next, sizes"
+					:total="total"
+					@change="getList"
+				/>
+			</div>
+		</div>
+
+		<!-- 飞手信息弹窗 -->
+		<FlyingHandDialog
+			ref="flyingHandDialogRef"
+			v-model="dialogVisible"
+			:dialog-type="dialogType"
+			:flying-hand-data="flyingHandData"
+			@confirm="handleConfirm"
+		/>
+	</basic-container>
 </template>
+<script setup>
+import { Search, RefreshRight, Plus } from '@element-plus/icons-vue'
+import { onMounted, ref, nextTick } from 'vue'
+import { getDictionaryByCode } from '@/api/system/dictbiz'
+import { getDictLabel } from '@ztzf/utils'
+import { getFlyingHandPage, removeFlyingHand } from './flyingHandApi'
+import { deviceFlyerApi } from '@/api/zkxt'
+import FlyingHandDialog from './FlyingHandDialog.vue'
+import { prod } from 'mathjs'
 
-<script setup></script>
+// 初始化查询参数
+const initSearchParams = () => ({
+	flyerName: '', // 飞手姓名
+	skilledUavType: '', // 擅长任务类型
+	current: 1, // 当前页
+	size: 10, // 每页大小
+})
+const searchParams = ref(initSearchParams()) // 查询参数
+const total = ref(0) // 总条数
+const loading = ref(true) // 列表加载中
+const list = ref([]) // 列表数据
+const queryParamsRef = ref(null) // 查询表单实例
+const flyingHandDialogRef = ref(null)
+const dialogVisible = ref(false)
+const dialogType = ref('add') // add, edit, view
+const flyingHandData = ref({})
 
-<style scoped lang="scss"></style>
+const dictObj = ref({
+	skilledTaskType: [], // 擅长任务类型
+	skilledUavType: [], // 擅长机型
+	technicalStrength: [] // 技术特长
+})
+
+provide('dictObj', dictObj)
+
+// 获取列表
+async function getList() {
+	loading.value = true
+	try {
+		const res = await getFlyingHandPage(searchParams.value)
+		list.value = res?.data?.data?.records ?? []
+		total.value = res?.data?.data?.total ?? 0
+	} finally {
+		loading.value = false
+	}
+}
+
+// 查询
+function handleSearch() {
+	searchParams.value.current = 1
+	getList()
+}
+
+// 重置查询
+function resetForm() {
+	queryParamsRef.value?.resetFields()
+	searchParams.value.current = 1
+	getList()
+}
+
+// 添加飞手
+function addFlyingHand() {
+	dialogType.value = 'add'
+	flyingHandData.value = {}
+	dialogVisible.value = true
+}
+
+// 查看飞手
+function viewFlyingHand(row) {
+	dialogType.value = 'view'
+	flyingHandData.value = { ...row }
+	dialogVisible.value = true
+}
+
+// 编辑飞手
+function editFlyingHand(row) {
+	dialogType.value = 'edit'
+	flyingHandData.value = { ...row }
+	dialogVisible.value = true
+}
+
+// 删除飞手
+function deleteFlyingHand(id) {
+	ElMessageBox.confirm('确定要删除该飞手信息吗?', '提示', {
+		confirmButtonText: '确定',
+		cancelButtonText: '取消',
+		type: 'warning',
+	}).then(async () => {
+		try {
+			await removeFlyingHand(id)
+			ElMessage.success('删除成功')
+			getList()
+		} catch (error) {
+			ElMessage.error('删除失败')
+		}
+	}).catch(() => {
+		// 取消删除
+	})
+}
+
+// 处理弹窗确认
+function handleConfirm() {
+	dialogVisible.value = false
+	getList()
+}
+
+// 获取字典
+function getDictList() {
+	getDictionaryByCode('skilledTaskType,skilledUavType,technicalStrength').then(res => {
+		dictObj.value = res.data.data
+	})
+}
+
+// 获取飞手列表
+const flyingHandList = ref([])
+function getFlyingHandList() {
+    deviceFlyerApi({ current: 1, size: 10 }).then(res => {
+        flyingHandList.value = res?.data?.data ?? []
+    })
+}
+
+provide('flyingHandList', flyingHandList)
+
+onMounted(() => {
+	getList()
+	getDictList()
+    getFlyingHandList()
+})
+</script>
+<style scoped lang="scss"></style>
\ No newline at end of file
diff --git a/applications/task-work-order/src/views/orderView/organizationalMessage/agenciesManagement/FormDiaLog.vue b/applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue
similarity index 96%
rename from applications/task-work-order/src/views/orderView/organizationalMessage/agenciesManagement/FormDiaLog.vue
rename to applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue
index 888f1df..d29556b 100644
--- a/applications/task-work-order/src/views/orderView/organizationalMessage/agenciesManagement/FormDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/organizational/agenciesManagement/FormDiaLog.vue
@@ -158,6 +158,10 @@
   if (data.status !== undefined) {
     data.status = String(data.status)
   }
+  // 将 areaCode 转换为字符串类型,与 regionOptions 中的 value 类型保持一致
+  if (data.areaCode !== undefined) {
+    data.areaCode = String(data.areaCode)
+  }
   formData.value = data
 }
 
diff --git a/applications/task-work-order/src/views/orderView/organizational/agenciesManagement/agenciesApi.js b/applications/task-work-order/src/views/orderView/organizational/agenciesManagement/agenciesApi.js
new file mode 100644
index 0000000..2f4dd8c
--- /dev/null
+++ b/applications/task-work-order/src/views/orderView/organizational/agenciesManagement/agenciesApi.js
@@ -0,0 +1,66 @@
+import request from '@/axios'
+
+// 列表
+export const agenciesPageApi = params => {
+  return request({
+    url: `/blade-system/dept/page`,
+    method: 'get',
+    params: { ...params },
+  })
+}
+
+// 新增或编辑
+export const agenciesSubmitApi = data => {
+  return request({
+    url: `/blade-system/dept/submit`,
+    method: 'post',
+    data,
+  })
+}
+
+// 删除
+export const agenciesRemoveApi = params => {
+  return request({
+    url: `/blade-system/dept/remove`,
+    method: 'post',
+    params,
+  })
+}
+
+// 详情
+export const agenciesDetailApi = params => {
+  return request({
+    url: `/blade-system/dept/detail`,
+    method: 'get',
+    params,
+  })
+}
+
+// 导入机构
+export const agenciesImportApi = (data,params) => {
+  return request({
+    url: `/blade-system/dept/import-region`,
+    method: 'post',
+    data,
+    params
+  })
+}
+
+// 导出机构
+export const agenciesExportApi = params => {
+  return request({
+    url: `/blade-system/dept/export-region`,
+    method: 'get',
+    params,
+    responseType: 'blob',
+  })
+}
+
+// 懒加载列表
+export const regionLazyTreeApi = params => {
+  return request({
+    url: `/blade-system/region/lazy-tree`,
+    method: 'get',
+    params,
+  })
+}
diff --git a/applications/task-work-order/src/views/orderView/organizationalMessage/agenciesManagement/index.vue b/applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue
similarity index 100%
rename from applications/task-work-order/src/views/orderView/organizationalMessage/agenciesManagement/index.vue
rename to applications/task-work-order/src/views/orderView/organizational/agenciesManagement/index.vue
diff --git a/applications/task-work-order/src/views/orderView/organizationalMessage/zoningManagement/index.vue b/applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue
similarity index 100%
rename from applications/task-work-order/src/views/orderView/organizationalMessage/zoningManagement/index.vue
rename to applications/task-work-order/src/views/orderView/organizational/zoningManagement/index.vue

--
Gitblit v1.9.3