From 9fb1aa745bb4447c47b0851335077cdb44b41b80 Mon Sep 17 00:00:00 2001
From: shuishen <1109946754@qq.com>
Date: Tue, 27 Jan 2026 12:12:50 +0800
Subject: [PATCH] feat:无人机轨迹显示调整

---
 applications/drone-command/src/views/dataCockpit/components/RealWarning.vue      |  130 +-----------
 applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue      |   57 +++--
 applications/drone-command/src/components/map-container/device-map-container.vue |  250 +++++++++++++++---------
 applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue    |   35 ++
 applications/drone-command/src/views/dataCockpit/index.vue                       |  113 +++++++++++
 5 files changed, 340 insertions(+), 245 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 49bd52b..a4311a4 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
@@ -39,7 +39,7 @@
 			@close="closePopup" />
 
 		<DronePopup :visible="popupVisible && isDronePopup" :position="popupPosition" :drone="selectedDevice"
-			:favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="toggleDroneFavorite"
+			:favorite="Boolean(selectedDevice?.isFavorite)" @close="closePopup" @toggle-favorite="handleDroneFavorite"
 			@signal="handleDroneSignal" @counter="handleDroneCounter" />
 	</div>
 </template>
@@ -78,6 +78,10 @@
 		type: Array,
 		default: () => [],
 	},
+	alarmDrones: {
+		type: Array,
+		default: () => [],
+	},
 	leftCollapsed: {
 		type: Boolean,
 		default: false,
@@ -91,7 +95,7 @@
 		default: true,
 	},
 })
-const emit = defineEmits(['droneSignal', 'droneCounter'])
+const emit = defineEmits(['droneSignal', 'droneCounter', 'droneFavorite'])
 
 const DEFAULT_ZONE_PAGE_SIZE = 999
 
@@ -363,44 +367,86 @@
 	})
 }
 
-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
-		},
+const buildSimulatedTrackPoints = center => {
+	const basePoints = [
+		[
+			{
+				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
+			},
+		],
+
+		[
+			{
+				longitude: 114.931063,
+				latitude: 27.095052,
+				height: 120
+			},
+			{
+				longitude: 114.928653,
+				latitude: 27.096307,
+				height: 120
+			},
+			{
+				longitude: 114.925958,
+				latitude: 27.096963,
+				height: 120
+			},
+			{
+				longitude: 114.925836, 
+				latitude: 27.096102,
+				height: 120
+			},
+			{
+				longitude: 114.928384, 
+				latitude: 27.095866,
+				height: 120
+			},
+			{
+				longitude: 114.929874, 
+				latitude: 27.096095,
+				height: 120
+			},
+			{
+				longitude: 114.931048, 
+				latitude: 27.097666,
+				height: 120
+			},
+		]
 	]
+
+	return basePoints[center.trackIndex]
 }
 
 
@@ -486,70 +532,67 @@
 	droneTrackTickHandler = null
 }
 
-const renderSimulatedDroneTrack = () => {
+const renderSimulatedDroneTrack = (list) => {
 	if (!viewer) return
 	clearDroneTrackEntities()
+	if (!list?.length) return
 	ensureDroneTrackCollections()
 	droneTrackBillboardCollection.show = detailVisible.value
 	droneTrackPolylineCollection.show = detailVisible.value
 	droneTrackRuntime = []
-	const baseCenters = [['默认区域', { longitude: 114.958541, latitude: 27.121917 }]]
 	const segmentDuration = 6
 	const baseTrackColor = Cesium.Color.fromCssColorString('red')
-	baseCenters.forEach(([centerName, center], trackIndex) => {
-		const points = buildSimulatedTrackPoints(center)
-		if (points.length < 2) return
-		const positions = points.map(point =>
-			Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
-		)
-		let trackMaterial = createDroneTrackMaterial({
-			color: baseTrackColor,
-			speed: 4.5,
-			headWidth: 0.2,
-			glowPower: 1.8,
-			backgroundAlpha: 0.34,
+		; (list || []).forEach((item, trackIndex) => {
+			const position = getDevicePosition(item)
+			if (!position) return
+			const points = buildSimulatedTrackPoints({ ...position, height: item.flightHeightM, trackIndex })
+			if (points.length < 2) return
+			const positions = points.map(point =>
+				Cesium.Cartesian3.fromDegrees(point.longitude, point.latitude, point.height)
+			)
+			let trackMaterial = createDroneTrackMaterial({
+				color: baseTrackColor,
+				speed: 4.5,
+				headWidth: 0.2,
+				glowPower: 1.8,
+				backgroundAlpha: 0.34,
+			})
+			if (!trackMaterial) {
+				trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
+			}
+			droneTrackPolylineCollection.add({
+				positions,
+				width: 3,
+				material: trackMaterial,
+			})
+			const droneId = `drone-alarm-${item?.alarmRecordId ?? item?.id ?? trackIndex}`
+			const billboard = droneTrackBillboardCollection.add({
+				position: positions[positions.length - 1],
+				image: droneIcon,
+				width: 36,
+				height: 36,
+				verticalOrigin: Cesium.VerticalOrigin.CENTER,
+				disableDepthTestDistance: Number.POSITIVE_INFINITY,
+			})
+			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,
+			})
+			droneTrackRuntime.push({
+				positions,
+				billboard,
+				segmentDuration,
+				duration: (points.length - 1) * segmentDuration,
+			})
 		})
-		if (!trackMaterial) {
-			trackMaterial = Cesium.Material.fromType('Color', { color: baseTrackColor })
-		}
-		droneTrackPolylineCollection.add({
-			positions,
-			width: 3,
-			material: trackMaterial,
-		})
-		const droneId = `drone-track-${trackIndex}`
-		const billboard = droneTrackBillboardCollection.add({
-			position: positions[positions.length - 1],
-			image: droneIcon,
-			width: 36,
-			height: 36,
-			verticalOrigin: Cesium.VerticalOrigin.CENTER,
-			disableDepthTestDistance: Number.POSITIVE_INFINITY,
-		})
-		billboard.id = droneId
-		const speedMs = Math.round(Cesium.Cartesian3.distance(positions[0], positions[1]) / segmentDuration)
-		dronePickMap.set(droneId, {
-			data: {
-				droneName: `无人机-${trackIndex + 1}`,
-				droneSerialNo: `UAV-${trackIndex + 1}`,
-				flightHeightM: points[0].height,
-				flightSpeedMs: speedMs,
-				longitude: points[0].longitude,
-				latitude: points[0].latitude,
-				flightStatus: 1,
-				deviceName: centerName,
-				dataSource: centerName,
-				counterDeviceName: centerName,
-			},
-			billboard,
-		})
-		droneTrackRuntime.push({
-			positions,
-			billboard,
-			segmentDuration,
-			duration: (points.length - 1) * segmentDuration,
-		})
-	})
 	viewer.clock.multiplier = 1
 	startDroneTrackAnimation()
 	reorderCockpitPrimitives()
@@ -860,9 +903,9 @@
 const popupPosition = ref({ x: 0, y: 0 })
 const popupVisible = computed(() => Boolean(selectedDevice.value))
 const isDronePopup = computed(() => selectedTargetType.value === 'drone')
-const toggleDroneFavorite = () => {
+const handleDroneFavorite = () => {
 	if (!selectedDevice.value) return
-	selectedDevice.value.isFavorite = !selectedDevice.value.isFavorite
+	emit('droneFavorite', selectedDevice.value)
 }
 const handleDroneSignal = () => emit('droneSignal', selectedDevice.value)
 const handleDroneCounter = () => emit('droneCounter', selectedDevice.value)
@@ -911,6 +954,19 @@
 	() => props.onlineDevices,
 	devices => {
 		renderDeviceEntities(devices || [])
+	},
+	{ deep: true }
+)
+
+watch(
+	() => props.alarmDrones,
+	list => {
+		renderSimulatedDroneTrack(list || [])
+		if (selectedTargetType.value === 'drone' && selectedDevice.value) {
+			const selectedId = selectedDevice.value.alarmRecordId ?? selectedDevice.value.id
+			const match = (list || []).find(item => (item?.alarmRecordId ?? item?.id) === selectedId)
+			if (match) selectedDevice.value = { ...selectedDevice.value, ...match }
+		}
 	},
 	{ deep: true }
 )
@@ -970,7 +1026,7 @@
 	loadPartitions()
 	loadAggregation()
 	loadCommandPosts()
-	renderSimulatedDroneTrack()
+	renderSimulatedDroneTrack(props.alarmDrones)
 	initDeviceClickHandler()
 }
 
diff --git a/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue b/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
index b570b0f..2e63d4d 100644
--- a/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
+++ b/applications/drone-command/src/views/basicManage/deviceStock/FormDiaLog.vue
@@ -62,15 +62,20 @@
 						<el-table-column prop="charger" label="负责人" />
 						<el-table-column prop="contactPhone" label="联系电话" />
 						<el-table-column prop="outTarget" label="出库去向" />
-						<el-table-column prop="outTime" label="出库时间" />
+
+						<el-table-column prop="outTime" show-overflow-tooltip label="出库时间">
+							<template v-slot="{ row }">
+								{{ dayjs(row.outTime).format('YYYY-MM-DD') }}
+							</template>
+						</el-table-column>
 						<el-table-column prop="purpose" label="用途" />
 					</el-table>
 				</div>
 
 				<div class="command-table-pagination">
-					<el-pagination popper-class="command-select-popper"
-						v-model:current-page="searchParams.current" v-model:page-size="searchParams.size"
-						layout="total, prev, pager, next, sizes" :total="total" @change="getList" />
+					<el-pagination popper-class="command-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>
 		</div>
@@ -79,8 +84,8 @@
 			<el-row>
 				<el-col :span="12">
 					<el-form-item label="设备名称" prop="deviceName">
-						<el-input class="command-input" v-model="formData.deviceName" maxlength="50"
-							placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.deviceName" maxlength="50" placeholder="请输入"
+							clearable />
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
@@ -94,8 +99,8 @@
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="设备编码" prop="deviceSn">
-						<el-input class="command-input" v-model="formData.deviceSn" maxlength="50"
-							placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.deviceSn" maxlength="50" placeholder="请输入"
+							clearable />
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
@@ -109,40 +114,39 @@
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="型号" prop="deviceModel">
-						<el-input class="command-input" v-model="formData.deviceModel" maxlength="50"
-							placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.deviceModel" maxlength="50" placeholder="请输入"
+							clearable />
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="规格" prop="deviceSpecification">
-						<el-input class="command-input" v-model="formData.deviceSpecification"
-							maxlength="50" placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.deviceSpecification" maxlength="50"
+							placeholder="请输入" clearable />
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="生产厂商" prop="manufacturer">
-						<el-input class="command-input" v-model="formData.manufacturer" maxlength="50"
-							placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.manufacturer" maxlength="50" placeholder="请输入"
+							clearable />
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="来源" prop="source">
-						<el-input class="command-input" v-model="formData.source" maxlength="50"
-							placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.source" maxlength="50" placeholder="请输入"
+							clearable />
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="用途" prop="purpose">
-						<el-input class="command-input" v-model="formData.purpose" maxlength="50"
-							placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.purpose" maxlength="50" placeholder="请输入"
+							clearable />
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="所属部门" prop="belongDept">
-						<el-tree-select class="command-select"
-							popper-class="command-tree-select-popper" v-model="formData.belongDept"
-							node-key="id" :data="deptTree" :props="treeProps" check-strictly @change="belongDeptChange"
-							clearable />
+						<el-tree-select class="command-select" popper-class="command-tree-select-popper"
+							v-model="formData.belongDept" node-key="id" :data="deptTree" :props="treeProps"
+							check-strictly @change="belongDeptChange" clearable />
 					</el-form-item>
 				</el-col>
 				<el-col :span="12">
@@ -155,14 +159,15 @@
 				</el-col>
 				<el-col :span="12">
 					<el-form-item label="联系电话" prop="contactPhone">
-						<el-input class="command-input" v-model="formData.contactPhone" maxlength="50"
-							placeholder="请输入" clearable />
+						<el-input class="command-input" v-model="formData.contactPhone" maxlength="50" placeholder="请输入"
+							clearable />
 					</el-form-item>
 				</el-col>
 			</el-row>
 		</el-form>
 		<template #footer>
-			<el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消' }}</el-button>
+			<el-button v-if="dialogMode != 'view'" color="#2B2B4C" @click="handleCancel">{{ dialogReadonly ? '关闭' : '取消'
+				}}</el-button>
 			<el-button color="#284FE3" v-if="!dialogReadonly" type="primary" :loading="submitting"
 				:disabled="submitting" @click="handleSubmit">
 				保存
@@ -172,6 +177,8 @@
 </template>
 
 <script setup>
+import dayjs from 'dayjs'
+
 import { computed, ref } from 'vue'
 import { ElMessage } from 'element-plus'
 import { fwDeviceDetailApi, fwDeviceSubmitApi } from '@/views/basicManage/deviceStock/fwDevice'
diff --git a/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue b/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
index 65c2764..a524faa 100644
--- a/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
@@ -12,7 +12,16 @@
 			</div>
 
 			<div class="content">
-				<component :is="currentComponent" />
+				<RealWarning
+					v-if="activeIdModel === 1"
+					:data="realWarningData"
+					:loading="realWarningLoading"
+					@signal="emit('real-warning-signal', $event)"
+					@counter="emit('real-warning-counter', $event)"
+					@favorite="emit('real-warning-favorite', $event)"
+				/>
+				<EquipmentWarning v-else-if="activeIdModel === 2" />
+				<HistoryWarning v-else />
 			</div>
 		</div>
 
@@ -24,11 +33,11 @@
 
 <script setup>
 import TitleTemplate from './templateComponents/TitleTemplate.vue'
-import RealWarning from './RealWarning.vue'
 import EquipmentWarning from './EquipmentWarning.vue'
 import HistoryWarning from './HistoryWarning.vue'
 import { alarmStatisticsApi } from '@/api/dataCockpit'
-import { onMounted } from 'vue'
+import { computed, onMounted, ref } from 'vue'
+import RealWarning from './RealWarning.vue'
 
 const props = defineProps({
 	activeId: {
@@ -38,10 +47,24 @@
 	collapsed: {
 		type: Boolean,
 		default: false
+	},
+	realWarningData: {
+		type: Array,
+		default: () => []
+	},
+	realWarningLoading: {
+		type: Boolean,
+		default: false
 	}
 })
 
-const emit = defineEmits(['update:activeId', 'update:collapsed'])
+const emit = defineEmits([
+	'update:activeId',
+	'update:collapsed',
+	'real-warning-signal',
+	'real-warning-counter',
+	'real-warning-favorite'
+])
 
 const activeIdModel = computed({
 	get: () => props.activeId,
@@ -73,10 +96,6 @@
 		component: HistoryWarning
 	}
 ])
-
-const currentComponent = computed(() => {
-	return categoryList.value.find(i => i.id === activeIdModel.value)?.component
-})
 
 const toggleCollapse = () => {
 	collapsedModel.value = !collapsedModel.value
diff --git a/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue b/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
index 132e7a2..9125f86 100644
--- a/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
@@ -2,137 +2,39 @@
  * @Author       : yuan
  * @Date         : 2026-01-08 09:29:07
  * @LastEditors  : yuan
- * @LastEditTime : 2026-01-26 09:59:52
+ * @LastEditTime : 2026-01-27 12:01:37
  * @FilePath     : \applications\drone-command\src\views\dataCockpit\components\RealWarning.vue
  * @Description  : 
  * Copyright 2026 OBKoro1, All Rights Reserved. 
  * 2026-01-08 09:29:07
 -->
 <template>
-	<div
-		class="real-warning"
-		v-loading="loading"
-		element-loading-background="rgba(5, 5, 15, 0.6)"
-		element-loading-text="加载中..."
-	>
+	<div class="real-warning" v-loading="props.loading" element-loading-background="rgba(5, 5, 15, 0.6)"
+		element-loading-text="???...">
 		<div class="list-content">
-			<EmptyState v-if="!realWarningData.length" />
-			<RealTemplate
-				v-else
-				v-for="(item, ind) in realWarningData"
-				:key="ind"
-				:data="item"
-				@signal="() => handleAction('signal', item)"
-				@counter="() => handleAction('counter', item)"
-				@favorite="() => handleFavorite(item)"
-			/>
+			<EmptyState v-if="!props.data.length && !props.loading" />
+			<RealTemplate v-else v-for="(item, ind) in props.data" :key="ind" :data="item"
+				@signal="emit('signal', item)" @counter="emit('counter', item)" @favorite="emit('favorite', item)" />
 		</div>
 	</div>
 </template>
 
 <script setup>
 import RealTemplate from './templateComponents/RealTemplate.vue'
-import {
-	alarmLogApi,
-	alarmFavoriteRemoveApi,
-	alarmFavoriteSaveApi,
-	interferenceAndExpulsionApi
-} from '@/api/dataCockpit'
-import { onMounted, ref } from 'vue'
 import EmptyState from './EmptyState.vue'
-import { ElMessage } from 'element-plus'
 
-const realWarningData = ref([])
-const loading = ref(true)
-const minLoadingMs = 400
-
-const actionTextMap = {
-	signal: '信号干扰',
-	counter: '诱导驱离'
-}
-
-
-const getAlarmRecordId = (item) => item?.alarmRecordId ?? item?.id
-const getFavoriteId = (item) =>
-	item?.favoriteId ??
-	item?.alarmFavoriteId ??
-	item?.fwAlarmFavoriteId ??
-	item?.favoriteRecordId ??
-	getAlarmRecordId(item)
-
-const isFavorited = (item) => {
-	const value = item?.favorited ?? item?.isFavorite
-	return value === 1 || value === '1' || value === true
-}
-
-const buildPayload = (type, item) => ({
-	counterWay: type === 'signal' ? 1 : type === 'counter' ? 2 : '',
-	alarmRecordId: getAlarmRecordId(item)
+const props = defineProps({
+	data: {
+		type: Array,
+		default: () => []
+	},
+	loading: {
+		type: Boolean,
+		default: false
+	}
 })
 
-const handleAction = async (type, item) => {
-	const actionText = actionTextMap[type] || '操作'
-	try {
-		const res = await interferenceAndExpulsionApi(buildPayload(type, item))
-		if (res?.data?.success) {
-			ElMessage({ type: 'success', message: actionText + '成功' })
-			await fetchRealWarning()
-		} else {
-			ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' })
-		}
-	} catch (error) {
-		ElMessage({ type: 'error', message: actionText + '失败' })
-	}
-}
-
-const handleFavorite = async (item) => {
-	const alarmRecordId = getAlarmRecordId(item)
-	if (!alarmRecordId) {
-		ElMessage({ type: 'warning', message: '缺少告警记录ID' })
-		return
-	}
-	const isFavorite = isFavorited(item)
-	const actionText = isFavorite ? '取消关注' : '关注'
-	try {
-		const res = isFavorite
-			? await alarmFavoriteRemoveApi(getFavoriteId(item))
-			: await alarmFavoriteSaveApi({ alarmRecordId })
-		if (res?.data?.success) {
-			ElMessage({ type: 'success', message: actionText + '成功' })
-			await fetchRealWarning()
-		} else {
-			ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' })
-		}
-	} catch (error) {
-		ElMessage({ type: 'error', message: actionText + '失败' })
-	}
-}
-
-const fetchRealWarning = async () => {
-	const startAt = Date.now()
-	loading.value = true
-	try {
-		const params = { current: 1, size: 20, alarmType: '1' }
-		const res = await alarmLogApi(params)
-		const records = res?.data?.data?.records ?? []
-		realWarningData.value = records.map((record) => ({
-			...record,
-			isFavorite:
-				record?.isFavorite ??
-				(record?.favorited === 1 || record?.favorited === '1')
-		}))
-	} finally {
-		const elapsed = Date.now() - startAt
-		if (elapsed < minLoadingMs) {
-			await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed))
-		}
-		loading.value = false
-	}
-}
-
-onMounted(() => {
-	fetchRealWarning()
-})
+const emit = defineEmits(['signal', 'counter', 'favorite'])
 </script>
 
 <style lang="scss" scoped>
diff --git a/applications/drone-command/src/views/dataCockpit/index.vue b/applications/drone-command/src/views/dataCockpit/index.vue
index 7f44448..aafe0df 100644
--- a/applications/drone-command/src/views/dataCockpit/index.vue
+++ b/applications/drone-command/src/views/dataCockpit/index.vue
@@ -12,10 +12,23 @@
   <div class="page-container">
     <DeviceMapContainer
       :online-devices="onlineDevices"
+      :alarm-drones="realWarningData"
       :left-collapsed="leftCollapsed"
       container-id="data-cockpit-map"
+      @drone-signal="handleAlarmAction('signal', $event)"
+      @drone-counter="handleAlarmAction('counter', $event)"
+      @drone-favorite="handleAlarmFavorite"
     />  
-    <LeftContainer class="left-container" v-model:activeId="leftActiveId" v-model:collapsed="leftCollapsed" />  
+    <LeftContainer
+      class="left-container"
+      v-model:activeId="leftActiveId"
+      v-model:collapsed="leftCollapsed"
+      :real-warning-data="realWarningData"
+      :real-warning-loading="realWarningLoading"
+      @real-warning-signal="handleAlarmAction('signal', $event)"
+      @real-warning-counter="handleAlarmAction('counter', $event)"
+      @real-warning-favorite="handleAlarmFavorite"
+    />  
     <RightContainer class="right-container" v-model:collapsed="rightCollapsed" v-model:onlineDevices="onlineDevices" />  
     <CenterContainer @select-warning="onSelectWarning" @select-equipment="onSelectEquipment" />  
     <LegendBar class="legend-container" />
@@ -28,12 +41,23 @@
   import RightContainer from './components/RightContainer.vue';
   import CenterContainer from './components/CenterContainer.vue';
   import LegendBar from './components/LegendBar.vue';
+  import {
+    alarmLogApi,
+    alarmFavoriteRemoveApi,
+    alarmFavoriteSaveApi,
+    interferenceAndExpulsionApi
+  } from '@/api/dataCockpit';
+  import { ElMessage } from 'element-plus';
+  import { onMounted, ref } from 'vue';
 
   const leftActiveId = ref(1);
   const leftCollapsed = ref(false);
 
   const rightCollapsed = ref(false);
   const onlineDevices = ref([]);
+  const realWarningData = ref([]);
+  const realWarningLoading = ref(true);
+  const minLoadingMs = 400;
 
   const onSelectWarning = (type) => {
     leftActiveId.value = type;
@@ -43,6 +67,93 @@
   const onSelectEquipment = () => {
     rightCollapsed.value = false;
   };
+
+  const actionTextMap = {
+    signal: '信号干扰',
+    counter: '诱导驱离'
+  };
+
+  const getAlarmRecordId = (item) => item?.alarmRecordId ?? item?.id;
+  const getFavoriteId = (item) =>
+    item?.favoriteId ??
+    item?.alarmFavoriteId ??
+    item?.fwAlarmFavoriteId ??
+    item?.favoriteRecordId ??
+    getAlarmRecordId(item);
+
+  const isFavorited = (item) => {
+    const value = item?.favorited ?? item?.isFavorite;
+    return value === 1 || value === '1' || value === true;
+  };
+
+  const buildPayload = (type, item) => ({
+    counterWay: type === 'signal' ? 1 : type === 'counter' ? 2 : '',
+    alarmRecordId: getAlarmRecordId(item)
+  });
+
+  const handleAlarmAction = async (type, item) => {
+    const actionText = actionTextMap[type] || '操作';
+    try {
+      const res = await interferenceAndExpulsionApi(buildPayload(type, item));
+      if (res?.data?.success) {
+        ElMessage({ type: 'success', message: actionText + '成功' });
+        await fetchRealWarning();
+      } else {
+        ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' });
+      }
+    } catch (error) {
+      ElMessage({ type: 'error', message: actionText + '失败' });
+    }
+  };
+
+  const handleAlarmFavorite = async (item) => {
+    const alarmRecordId = getAlarmRecordId(item);
+    if (!alarmRecordId) {
+      ElMessage({ type: 'warning', message: '缺少告警记录ID' });
+      return;
+    }
+    const favorite = isFavorited(item);
+    const actionText = favorite ? '取消关注' : '关注';
+    try {
+      const res = favorite
+        ? await alarmFavoriteRemoveApi(getFavoriteId(item))
+        : await alarmFavoriteSaveApi({ alarmRecordId });
+      if (res?.data?.success) {
+        ElMessage({ type: 'success', message: actionText + '成功' });
+        await fetchRealWarning();
+      } else {
+        ElMessage({ type: 'error', message: res?.data?.msg || actionText + '失败' });
+      }
+    } catch (error) {
+      ElMessage({ type: 'error', message: actionText + '失败' });
+    }
+  };
+
+  const fetchRealWarning = async () => {
+    const startAt = Date.now();
+    realWarningLoading.value = true;
+    try {
+      const params = { current: 1, size: 20, alarmType: '1' };
+      const res = await alarmLogApi(params);
+      const records = res?.data?.data?.records ?? [];
+      realWarningData.value = records.map((record) => ({
+        ...record,
+        isFavorite:
+          record?.isFavorite ??
+          (record?.favorited === 1 || record?.favorited === '1')
+      }));
+    } finally {
+      const elapsed = Date.now() - startAt;
+      if (elapsed < minLoadingMs) {
+        await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed));
+      }
+      realWarningLoading.value = false;
+    }
+  };
+
+  onMounted(() => {
+    fetchRealWarning();
+  });
 </script>
 
 <style scoped lang="scss">

--
Gitblit v1.9.3