From 794dfadf25327ed83f87fb63bacf6d86c80563d3 Mon Sep 17 00:00:00 2001
From: 张含笑 <zhx18749296735@163.com>
Date: Tue, 27 Jan 2026 14:51:50 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/drone-command/src/views/dataCockpit/components/RealWarning.vue                   |    4 
 applications/drone-command/src/components/map-container/device-map-container.vue              |  123 ++++++++++++++++++++++++++++--
 applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue           |   31 +++++--
 applications/task-work-order/src/styles/common/cockpit.scss                                   |   24 +++--
 applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue |   58 +-------------
 5 files changed, 155 insertions(+), 85 deletions(-)

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 a4311a4..f7fb7e9 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
@@ -119,6 +119,14 @@
 let droneTrackTickHandler = null
 let droneTrackStartTime = null
 let droneTrackRuntime = []
+let favoritePulseRafId = null
+let favoritePulseStartAt = 0
+let favoritePulseElapsed = 0
+const favoritePulseEntities = []
+const pulseBaseColor = Cesium.Color.fromCssColorString('#FF3B30')
+const PULSE_MIN_RADIUS_M = 60
+const PULSE_MAX_RADIUS_M = 180
+const PULSE_DURATION_S = 1.6
 const detailVisible = ref(true)
 const clusterVisible = ref(false)
 const countyCenterMap = new Map()
@@ -172,6 +180,57 @@
  */
 
 const adminBoundaryVisible = ref(treeCheckedKeys.value.includes('admin'))
+
+const isFavorited = item => {
+	const value = item?.favorited ?? item?.isFavorite
+	return value === 1 || value === '1' || value === true
+}
+
+let pulseCanvas = null
+const getPulseCanvas = () => {
+	if (pulseCanvas) return pulseCanvas
+	const size = 256
+	const canvas = document.createElement('canvas')
+	canvas.width = size
+	canvas.height = size
+	const ctx = canvas.getContext('2d')
+	const center = size / 2
+	const radius = center - 2
+	const gradient = ctx.createRadialGradient(center, center, radius * 0.1, center, center, radius)
+	gradient.addColorStop(0, 'rgba(255,59,48,0.55)')
+	gradient.addColorStop(0.35, 'rgba(255,59,48,0.35)')
+	gradient.addColorStop(0.7, 'rgba(255,59,48,0.15)')
+	gradient.addColorStop(1, 'rgba(255,59,48,0)')
+	ctx.fillStyle = gradient
+	ctx.beginPath()
+	ctx.arc(center, center, radius, 0, Math.PI * 2)
+	ctx.fill()
+	pulseCanvas = canvas
+	return canvas
+}
+
+const createFavoritePulseEntity = (billboard) => {
+	if (!viewer || !billboard) return
+	const material = new Cesium.ImageMaterialProperty({
+		image: getPulseCanvas(),
+		transparent: true,
+	})
+	const pulse = {
+		material,
+		offset: Math.random() * PULSE_DURATION_S,
+		entity: null,
+	}
+	pulse.entity = viewer.entities.add({
+		position: new Cesium.CallbackProperty(() => billboard.position, false),
+		ellipse: {
+			semiMajorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false),
+			semiMinorAxis: new Cesium.CallbackProperty(() => getPulseRadius(pulse), false),
+			material,
+			heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
+		},
+	})
+	favoritePulseEntities.push(pulse)
+}
 
 const getDevicePosition = item => {
 	const longitudeRaw = item.longitude ?? item.lng ?? item.lon
@@ -311,6 +370,12 @@
 	}
 }
 
+const getPulseRadius = pulse => {
+	const phase = ((favoritePulseElapsed + pulse.offset) % PULSE_DURATION_S) / PULSE_DURATION_S
+	const wave = phase < 0.5 ? phase * 2 : (1 - phase) * 2
+	return PULSE_MIN_RADIUS_M + (PULSE_MAX_RADIUS_M - PULSE_MIN_RADIUS_M) * wave
+}
+
 const setDetailVisibility = visible => {
 	detailVisible.value = visible
 	if (defenseZonePrimitive) defenseZonePrimitive.show = visible
@@ -345,6 +410,11 @@
 const setDroneVisibility = visible => {
 	if (droneTrackBillboardCollection) droneTrackBillboardCollection.show = visible
 	if (droneTrackPolylineCollection) droneTrackPolylineCollection.show = visible
+	if (favoritePulseEntities.length) {
+		favoritePulseEntities.forEach(pulse => {
+			if (pulse?.entity) pulse.entity.show = visible
+		})
+	}
 	if (!visible && selectedTargetType.value === 'drone') {
 		closePopup()
 	}
@@ -453,6 +523,7 @@
 const clearDroneTrackEntities = () => {
 	if (!viewer) return
 	stopDroneTrackAnimation()
+	stopFavoritePulseAnimation()
 	if (droneTrackBillboardCollection) {
 		if (!droneTrackBillboardCollection.isDestroyed?.()) {
 			removeCockpitPrimitive(droneTrackBillboardCollection)
@@ -466,6 +537,12 @@
 		droneTrackPolylineCollection = null
 	}
 	droneTrackRuntime = []
+	if (favoritePulseEntities.length) {
+		favoritePulseEntities.forEach(pulse => {
+			if (pulse?.entity) viewer?.entities?.remove(pulse.entity)
+		})
+		favoritePulseEntities.length = 0
+	}
 	dronePickMap.clear()
 	if (selectedTargetType.value === 'drone') {
 		closePopup()
@@ -532,6 +609,28 @@
 	droneTrackTickHandler = null
 }
 
+const startFavoritePulseAnimation = () => {
+	if (!viewer || favoritePulseRafId) return
+	favoritePulseStartAt = performance.now()
+	const tick = now => {
+		if (!viewer || viewer.isDestroyed?.() || !favoritePulseEntities.length) {
+			favoritePulseRafId = null
+			return
+		}
+		favoritePulseElapsed = (now - favoritePulseStartAt) / 1000
+		viewer.scene.requestRender()
+		favoritePulseRafId = requestAnimationFrame(tick)
+	}
+	favoritePulseRafId = requestAnimationFrame(tick)
+}
+
+const stopFavoritePulseAnimation = () => {
+	if (favoritePulseRafId) {
+		cancelAnimationFrame(favoritePulseRafId)
+		favoritePulseRafId = null
+	}
+}
+
 const renderSimulatedDroneTrack = (list) => {
 	if (!viewer) return
 	clearDroneTrackEntities()
@@ -576,16 +675,19 @@
 			})
 			billboard.id = droneId
 			const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
-			dronePickMap.set(droneId, {
-				data: {
-					...item,
-					flightHeightM: item.flightHeightM ?? points[0].height,
-					flightSpeedMs: item.flightSpeedMs ?? speedMs,
-					longitude: item.longitude ?? points[0].longitude,
-					latitude: item.latitude ?? points[0].latitude,
-				},
-				billboard,
-			})
+		dronePickMap.set(droneId, {
+			data: {
+				...item,
+				flightHeightM: item.flightHeightM ?? points[0].height,
+				flightSpeedMs: item.flightSpeedMs ?? speedMs,
+				longitude: item.longitude ?? points[0].longitude,
+				latitude: item.latitude ?? points[0].latitude,
+			},
+			billboard,
+		})
+		if (isFavorited(item)) {
+			createFavoritePulseEntity(billboard)
+		}
 			droneTrackRuntime.push({
 				positions,
 				billboard,
@@ -595,6 +697,7 @@
 		})
 	viewer.clock.multiplier = 1
 	startDroneTrackAnimation()
+	startFavoritePulseAnimation()
 	reorderCockpitPrimitives()
 }
 
diff --git a/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue b/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
index 9125f86..09d99e6 100644
--- a/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
@@ -2,7 +2,7 @@
  * @Author       : yuan
  * @Date         : 2026-01-08 09:29:07
  * @LastEditors  : yuan
- * @LastEditTime : 2026-01-27 12:01:37
+ * @LastEditTime : 2026-01-27 14:49:56
  * @FilePath     : \applications\drone-command\src\views\dataCockpit\components\RealWarning.vue
  * @Description  : 
  * Copyright 2026 OBKoro1, All Rights Reserved. 
@@ -10,7 +10,7 @@
 -->
 <template>
 	<div class="real-warning" v-loading="props.loading" element-loading-background="rgba(5, 5, 15, 0.6)"
-		element-loading-text="???...">
+		element-loading-text="加载中...">
 		<div class="list-content">
 			<EmptyState v-if="!props.data.length && !props.loading" />
 			<RealTemplate v-else v-for="(item, ind) in props.data" :key="ind" :data="item"
diff --git a/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue b/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
index 95955ad..37310d6 100644
--- a/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
+++ b/applications/drone-command/src/views/permissionManage/permissionDept/FormDiaLog.vue
@@ -71,14 +71,14 @@
 				<el-col :span="12">
 					<el-form-item label="部门类型" prop="deptCategory">
 						<el-select class="command-select" popper-class="command-select-popper" v-model="formData.deptCategory" placeholder="请选择" clearable>
-							<el-option v-for="item in dictObj.org_category" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
+							<el-option v-for="item in dictObj.org_category" :key="item.dictKey" :label="item.dictValue" :value="Number(item.dictKey)" />
 						</el-select>
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="部门性质" prop="deptNature">
 						<el-select class="command-select" popper-class="command-select-popper" v-model="formData.deptNature" placeholder="请选择" clearable>
-							<el-option v-for="item in dictObj.org_nature" :key="item.dictKey" :label="item.dictValue" :value="item.dictKey" />
+							<el-option v-for="item in dictObj.org_nature" :key="item.dictKey" :label="item.dictValue" :value="Number(item.dictKey)" />
 						</el-select>
 					</el-form-item>
 				</el-col>
@@ -137,9 +137,9 @@
 	fullName: '',
 	parentId: '',
 	parentName: '',
-	deptCategory: '',
-	deptNature: '',
-	areaCode: '',
+	deptCategory: null,
+	deptNature: null,
+	areaCode: [],
 	areaName: '',
 	deploymentMode: 0,
 	sort: 0,
@@ -154,7 +154,7 @@
 const areaProps = {
 	label: 'title',
 	value: 'value',
-	emitPath: false,
+	emitPath: true,
 	checkStrictly: true,
 	lazy: true,
 	lazyLoad(node, resolve) {
@@ -230,19 +230,26 @@
 }
 
 function getFullAreaCode(areaCode) {
-	if (!areaCode) return ''
+	if (!areaCode) return []
+	if (Array.isArray(areaCode)) return areaCode
 	const code = areaCode.toString()
-	if (code.includes(',')) return code
+	if (code.includes(',')) return code.split(',')
 	if (code.endsWith('0000000000')) {
-		return code
+		return [code]
 	} else if (code.endsWith('00000000') && !code.endsWith('0000000000')) {
 		const provinceCode = code.substring(0, 2) + '0000000000'
-		return `${provinceCode},${code}`
+		return [provinceCode, code]
 	} else {
 		const provinceCode = code.substring(0, 2) + '0000000000'
 		const cityCode = code.substring(0, 4) + '00000000'
-		return `${provinceCode},${cityCode},${code}`
+		return [provinceCode, cityCode, code]
 	}
+}
+
+function normalizeNumber(value) {
+	if (value === '' || value === null || value === undefined) return null
+	const num = Number(value)
+	return Number.isNaN(num) ? null : num
 }
 
 function handleCancel() {
@@ -283,6 +290,8 @@
 	const data = res?.data?.data ?? {}
 	formData.value = {
 		...data,
+		deptCategory: normalizeNumber(data.deptCategory),
+		deptNature: normalizeNumber(data.deptNature),
 		areaCode: getFullAreaCode(data.areaCode),
 	}
 }
diff --git a/applications/task-work-order/src/styles/common/cockpit.scss b/applications/task-work-order/src/styles/common/cockpit.scss
index e84887a..3c10b27 100644
--- a/applications/task-work-order/src/styles/common/cockpit.scss
+++ b/applications/task-work-order/src/styles/common/cockpit.scss
@@ -475,6 +475,7 @@
     .detail-container {
       display: flex;
       justify-content: space-between;
+      height: 680px;
 
       .detail-left {
         margin-right: 20px;
@@ -484,16 +485,21 @@
       }
 
       .detail-right {
-        &>div {
-          .detail-title {
-            margin: 10px 0;
-          }
+        width: 920px;
+        flex: 1;
+        overflow: auto;
+        display: flex;
+        flex-direction: column;
 
-          &:first-child {
-            .detail-title {
-              margin-top: 0;
-            }
-          }
+        .view-container {
+          display: flex;
+          flex-direction: column;
+        }
+
+        .gd-cesium {
+          width: 100%;
+          min-height: 380px;
+          flex: 1;
         }
       }
     }
diff --git a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
index ccbe7a3..89a3c4e 100644
--- a/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
+++ b/applications/task-work-order/src/views/orderView/orderManage/inspectionRequest/ViewDiaLog.vue
@@ -5,11 +5,10 @@
 		:title="titleEnum[dialogMode]"
 		@closed="visible = false"
 		destroy-on-close
-		width="auto"
 		:close-on-click-modal="false"
 	>
-		<div class="content" style="display: flex">
-			<div class="processBox" v-if="dialogMode !== 'add' && processList.length">
+		<div class="detail-container" style="display: flex">
+			<div class="detail-left" v-if="dialogMode !== 'add' && processList.length">
 				<el-timeline class="gd-timeline">
 					<el-timeline-item
 						v-for="(activity, index) in processList"
@@ -27,9 +26,10 @@
 					</el-timeline-item>
 				</el-timeline>
 			</div>
-			<div class="leftBox">
+
+			<div class="detail-right">
 				<!-- 查看模式 -->
-				<div class="detail-container" v-if="dialogReadonly">
+				<div class="view-container" v-if="dialogReadonly">
 					<div class="detail-title">工单详情</div>
 					<el-row class="detail-row-view">
 						<el-col :span="12">
@@ -507,52 +507,4 @@
 </script>
 
 <style lang="scss" scoped>
-.content {
-	display: flex;
-	gap: 0 20px;
-	height: 680px;
-
-	.leftBox {
-		width: 920px;
-		flex: 1;
-		overflow: auto;
-		display: flex;
-		flex-direction: column;
-		.gd-cesium {
-			width: 100%;
-			min-height: 380px;
-			flex: 1;
-		}
-	}
-	.processBox {
-		width: 312px;
-		padding: 24px 0;
-		border: 1px solid #e4e4e4;
-		overflow: auto;
-
-		.item-content {
-			position: relative;
-			.flowName {
-				width: 80px;
-				position: absolute;
-				left: -120px;
-				top: 0px;
-			}
-		}
-	}
-	.imgBox {
-		display: flex;
-		flex-wrap: wrap;
-		gap: 10px;
-		> div {
-			width: calc((100% - 40px) / 5); /* 5列布局,减去4个10px间隙 */
-			height: 110px;
-			flex-shrink: 0;
-		}
-		.el-image {
-			width: 100%;
-			height: 100%;
-		}
-	}
-}
 </style>

--
Gitblit v1.9.3