From d07c8e0b866dfadd65f12cbe3c9c74c800b0619a Mon Sep 17 00:00:00 2001
From: 罗广辉 <guanghui.luo@foxmail.com>
Date: Fri, 09 Jan 2026 16:04:59 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/master'

---
 applications/drone-command/src/assets/images/no-data-bg.png                                              |    0 
 applications/drone-command/src/views/dataCockpit/components/EmptyState.vue                               |   59 +++
 applications/drone-command/src/views/dataCockpit/components/templateComponents/HistoryTemplate.vue       |   55 ++
 applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue |   32 +
 applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue                         |  104 ++++-
 applications/drone-command/src/views/dataCockpit/components/RealWarning.vue                              |  135 ++++---
 applications/drone-command/src/views/dataCockpit/components/RightContainer.vue                           |  105 ++++-
 applications/drone-command/src/views/api.txt                                                             |  107 ++++++
 applications/drone-command/src/views/dataCockpit/components/templateComponents/EquipmentTemplate.vue     |    2 
 applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue          |   59 ++
 applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue                           |  117 +++---
 applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue                            |   35 +
 applications/drone-command/src/api/dataCockpit/index.js                                                  |   46 ++
 applications/drone-command/src/views/dataCockpit/components/CenterContainer.vue                          |   41 +
 uniapps/work-app/src/pages/user/index.vue                                                                |   83 ++--
 15 files changed, 714 insertions(+), 266 deletions(-)

diff --git a/applications/drone-command/src/api/dataCockpit/index.js b/applications/drone-command/src/api/dataCockpit/index.js
new file mode 100644
index 0000000..f8b68b6
--- /dev/null
+++ b/applications/drone-command/src/api/dataCockpit/index.js
@@ -0,0 +1,46 @@
+/*
+ * @Author       : yuan
+ * @Date         : 2026-01-09 11:14:04
+ * @LastEditors  : yuan
+ * @LastEditTime : 2026-01-09 11:35:14
+ * @FilePath     : \applications\drone-command\src\api\dataCockpit\index.js
+ * @Description  : 
+ * Copyright 2026 OBKoro1, All Rights Reserved. 
+ * 2026-01-09 11:14:04
+ */
+import request from '@/axios'
+
+// 设备查询
+export const deviceSearchApi = (params) => {
+	return request({
+		url: '/drone-fw/cockpit/dataCockpit/deviceSearch',
+		method: 'get',
+		params
+	})
+}
+
+// 历史告警/实时告警
+export const alarmLogApi = (params) => {
+	return request({
+		url: '/drone-fw/cockpit/dataCockpit/alarmLog',
+		method: 'get',
+		params
+	})
+}
+
+// 告警记录统计
+export const alarmStatisticsApi = () => {
+	return request({
+		url: '/drone-fw/cockpit/dataCockpit/alarmStatistics',
+		method: 'get'
+	})
+}
+
+// 信号干扰/诱导驱离
+export const interferenceAndExpulsionApi = (data) => {
+	return request({
+		url: '/drone-fw/cockpit/dataCockpit/interferenceAndExpulsion',
+		method: 'post',
+		data
+	})
+}
diff --git a/applications/drone-command/src/assets/images/no-data-bg.png b/applications/drone-command/src/assets/images/no-data-bg.png
new file mode 100644
index 0000000..3f902b6
--- /dev/null
+++ b/applications/drone-command/src/assets/images/no-data-bg.png
Binary files differ
diff --git a/applications/drone-command/src/views/api.txt b/applications/drone-command/src/views/api.txt
index e69de29..86dc87e 100644
--- a/applications/drone-command/src/views/api.txt
+++ b/applications/drone-command/src/views/api.txt
@@ -0,0 +1,107 @@
+
+
+## 信号干扰,诱导驱离
+
+
+**接口地址**:`/cockpit/dataCockpit/interferenceAndExpulsion`
+
+
+**请求方式**:`POST`
+
+
+**请求数据类型**:`application/json`
+
+
+**响应数据类型**:`*/*`
+
+
+**接口描述**:<p>传入fwEffectEval</p>
+
+
+
+**请求示例**:
+
+
+```javascript
+{
+  "alarmRecordId": 0,
+  "areaCode": "",
+  "counterEffect": "",
+  "coverRadiusM": 0,
+  "deployLatitude": 0,
+  "deployLongitude": 0,
+  "deviceCode": "",
+  "deviceId": 0,
+  "deviceModel": "",
+  "deviceName": "",
+  "deviceType": "",
+  "droneDeviceCode": "",
+  "droneName": "",
+  "droneType": "",
+  "findTime": "",
+  "id": 0,
+  "workMode": ""
+}
+```
+
+
+**请求参数**:
+
+
+**请求参数**:
+
+
+| 参数名称 | 参数说明 | 请求类型    | 是否必须 | 数据类型 | schema |
+| -------- | -------- | ----- | -------- | -------- | ------ |
+|fwEffectEval|fwEffectEval|body|true|FwEffectEvalDTO|FwEffectEvalDTO|
+|&emsp;&emsp;alarmRecordId|告警记录ID(ja_fw_drone_alarm_record.id)||false|integer(int64)||
+|&emsp;&emsp;areaCode|区域编码||false|string||
+|&emsp;&emsp;counterEffect|反制效果(success/fail)||false|string||
+|&emsp;&emsp;coverRadiusM|覆盖范围(米)||false|integer(int32)||
+|&emsp;&emsp;deployLatitude|部署纬度(冗余)||false|number(double)||
+|&emsp;&emsp;deployLongitude|部署经度(冗余)||false|number(double)||
+|&emsp;&emsp;deviceCode|反制设备编码(快照)||false|string||
+|&emsp;&emsp;deviceId|反制设备ID(ja_fw_device.id)||false|integer(int64)||
+|&emsp;&emsp;deviceModel|反制设备型号(快照)||false|string||
+|&emsp;&emsp;deviceName|反制设备名称(快照)||false|string||
+|&emsp;&emsp;deviceType|反制设备类型(快照)||false|string||
+|&emsp;&emsp;droneDeviceCode|无人机设备编码||false|string||
+|&emsp;&emsp;droneName|无人机名称||false|string||
+|&emsp;&emsp;droneType|无人机类型||false|string||
+|&emsp;&emsp;findTime|发现时间||false|string(date-time)||
+|&emsp;&emsp;id|主键id||false|integer(int64)||
+|&emsp;&emsp;workMode|工作模式(机动/固定)||false|string||
+
+
+**响应状态**:
+
+
+| 状态码 | 说明 | schema |
+| -------- | -------- | ----- | 
+|200|OK|R|
+|201|Created||
+|401|Unauthorized||
+|403|Forbidden||
+|404|Not Found||
+
+
+**响应参数**:
+
+
+| 参数名称 | 参数说明 | 类型 | schema |
+| -------- | -------- | ----- |----- | 
+|code|状态码|integer(int32)|integer(int32)|
+|data|承载数据|object||
+|msg|返回消息|string||
+|success|是否成功|boolean||
+
+
+**响应示例**:
+```javascript
+{
+	"code": 0,
+	"data": {},
+	"msg": "",
+	"success": true
+}
+```
\ No newline at end of file
diff --git a/applications/drone-command/src/views/dataCockpit/components/CenterContainer.vue b/applications/drone-command/src/views/dataCockpit/components/CenterContainer.vue
index 4eac750..abbda6a 100644
--- a/applications/drone-command/src/views/dataCockpit/components/CenterContainer.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/CenterContainer.vue
@@ -39,45 +39,46 @@
 </template>
 
 <script setup>
-import { ref } from 'vue'
+import { onMounted, ref } from 'vue'
 import warningLogo from '@/assets/images/dataCockpit/warning.png'
 import equipmentLogo from '@/assets/images/dataCockpit/equipment.png'
+import { alarmStatisticsApi } from '@/api/dataCockpit'
 
-let warningList = ref([
+const warningList = ref([
 	{
 		title: '实时告警',
-		value: 56,
+		value: 0,
 		type: 1
 	},
 	{
 		title: '设备告警',
-		value: 127,
+		value: 0,
 		type: 2
 	},
 	{
 		title: '历史告警',
-		value: 619,
+		value: 0,
 		type: 3
 	},
 ])
 
-let equipmentList = ref([
+const equipmentList = ref([
 	{
 		title: '设备统计',
-		value: 18,
+		value: 0,
 		isHighlight: true,
 	},
 	{
 		title: '无线电',
-		value: 9,
+		value: 0,
 	},
 	{
 		title: '光电',
-		value: 6,
+		value: 0,
 	},
 	{
 		title: '雷达',
-		value: 3,
+		value: 0,
 	}
 ])
 
@@ -92,6 +93,26 @@
 		emit('select-equipment')
 	}
 }
+
+const updateStats = async () => {
+	const res = await alarmStatisticsApi()
+	const data = res?.data?.data || {}
+	warningList.value = [
+		{ title: '实时告警', value: data.realTimeAlarmCount ?? 0, type: 1 },
+		{ title: '设备告警', value: data.deviceAlarmCount ?? 0, type: 2 },
+		{ title: '历史告警', value: data.historyAlarmCount ?? 0, type: 3 }
+	]
+	equipmentList.value = [
+		{ title: '设备统计', value: data.deviceCount ?? 0, isHighlight: true },
+		{ title: '无线电', value: data.radioCount ?? 0 },
+		{ title: '光电', value: data.opticalCount ?? 0 },
+		{ title: '雷达', value: data.radarCount ?? 0 }
+	]
+}
+
+onMounted(() => {
+	updateStats()
+})
 </script>
 
 <style lang="scss" scoped>
diff --git a/applications/drone-command/src/views/dataCockpit/components/EmptyState.vue b/applications/drone-command/src/views/dataCockpit/components/EmptyState.vue
new file mode 100644
index 0000000..929fb57
--- /dev/null
+++ b/applications/drone-command/src/views/dataCockpit/components/EmptyState.vue
@@ -0,0 +1,59 @@
+<!--
+ * @Author       : yuan
+ * @Date         : 2026-01-09 15:06:02
+ * @LastEditors  : yuan
+ * @LastEditTime : 2026-01-09 15:10:44
+ * @FilePath     : \applications\drone-command\src\views\dataCockpit\components\EmptyState.vue
+ * @Description  : 
+ * Copyright 2026 OBKoro1, All Rights Reserved. 
+ * 2026-01-09 15:06:02
+-->
+<template>
+	<div class="empty-state">
+		<div class="empty-content">
+			<img :src="noDataBg" alt="暂无数据">
+			<span class="empty-text">暂无数据</span>
+		</div>
+	</div>
+</template>
+
+<script setup>
+import noDataBg from '@/assets/images/no-data-bg.png'
+</script>
+
+<style scoped lang="scss">
+.empty-state {
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	flex: 1;
+	width: 100%;
+	height: 100%;
+	min-height: 220px;
+
+	.empty-content {
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		flex-direction: column;
+		text-align: center;
+	}
+
+	img {
+		max-width: 78%;
+		max-height: 78%;
+		object-fit: contain;
+	}
+
+	.empty-text {
+		font-family: Source Han Sans CN, Source Han Sans CN;
+		font-weight: 400;
+		font-size: 14px;
+		color: #9E9EBA;
+		line-height: 18px;
+		text-align: center;
+		font-style: normal;
+		text-transform: none;
+	}
+}
+</style>
diff --git a/applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue b/applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue
index 15531ce..4f2c8a0 100644
--- a/applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/EquipmentWarning.vue
@@ -9,44 +9,83 @@
  * 2026-01-08 09:29:07
 -->
 <template>
-	<div class="equipment-warning">
-		<EquipmentTemplate v-for="(item, ind) in equipmentWarningData" :key="ind" :data="item" @click="onCardClick" />
+	<div
+		class="equipment-warning"
+		v-loading="loading"
+		element-loading-background="rgba(5, 5, 15, 0.6)"
+		element-loading-text="加载中..."
+	>
+		<EmptyState v-if="!equipmentWarningData.length" />
+		<EquipmentTemplate
+			v-else
+			v-for="(item, ind) in equipmentWarningData"
+			:key="ind"
+			:data="item"
+			@click="onCardClick"
+		/>
 	</div>
 </template>
 
 <script setup>
+import { onMounted, ref } from 'vue'
 import EquipmentTemplate from './templateComponents/EquipmentTemplate.vue'
+import { deviceSearchApi } from '@/api/dataCockpit'
+import EmptyState from './EmptyState.vue'
 
-const equipmentWarningData = ref([
-	{
-		id: 'dev_001',
-		name: '反制设备名称',
-		status: '离线',
-		statusType: 'offline', // offline | fault | online
-		type: '察打一体',
-		vendor: '厂商名称',
-		azimuth: 14,          // 方位角(°)
-		pitch: 14,            // 俯仰角(°)
-		range: '1.2KM',       // 有效范围
-		model: 'TDOAX1A',     // 设备型号
-		longitude: 113.877571,
-		latitude: 22.547155
-	},
-	{
-		id: 'dev_002',
-		name: '反制设备名称',
-		status: '故障',
-		statusType: 'fault',
-		type: '察打一体',
-		vendor: '厂商名称',
-		azimuth: 14,
-		pitch: 14,
-		range: '1.2KM',
-		model: 'TDOAX1A',
-		longitude: 113.877571,
-		latitude: 22.547155
+const equipmentWarningData = ref([])
+const loading = ref(false)
+const minLoadingMs = 400
+
+const statusMap = {
+	0: { text: '在线', statusType: 'online' },
+	1: { text: '离线', statusType: 'offline' },
+	2: { text: '故障', statusType: 'fault' },
+	3: { text: '报废', statusType: 'offline' }
+}
+const deviceTypeMap = {
+	1: '便捷侦测箱',
+	2: '反制枪',
+	3: '察打一体'
+}
+
+const formatDeviceItem = (item) => {
+	const statusInfo = statusMap[item?.status] || { text: '-', statusType: '' }
+	return {
+		id: item.id,
+		name: item.deviceName || item.deviceModel || '-',
+		status: statusInfo.text,
+		statusType: statusInfo.statusType,
+		type: deviceTypeMap[item.deviceType] || item.deviceType || '-',
+		manufacturer: item.manufacturer || '-',
+		azimuth: item.azimuth ?? 0,
+		pitch: item.elevation ?? 0,
+		range: item.effectiveRangeKm ?? 0,
+		model: item.deviceModel || '-',
+		longitude: item.longitude ?? '-',
+		latitude: item.latitude ?? '-'
 	}
-])
+}
+
+const fetchDeviceWarning = async () => {
+	const startAt = Date.now()
+	loading.value = true
+	try {
+		const params = { current: 1, size: 20, deviceStatusList: '1,2,3' }
+		const res = await deviceSearchApi(params)
+		const records = res?.data?.data?.records ?? []
+		equipmentWarningData.value = records.map(formatDeviceItem)
+	} finally {
+		const elapsed = Date.now() - startAt
+		if (elapsed < minLoadingMs) {
+			await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed))
+		}
+		loading.value = false
+	}
+}
+
+onMounted(() => {
+	fetchDeviceWarning()
+})
 
 const onCardClick = (item) => {
 	console.log('点击设备卡片:', item.id)
@@ -58,4 +97,5 @@
 	width: 100%;
 	height: 100%;
 }
-</style>
\ No newline at end of file
+
+</style>
diff --git a/applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue b/applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue
index 4ce65c7..01d5958 100644
--- a/applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/HistoryWarning.vue
@@ -1,5 +1,10 @@
 <template>
-	<div class="history-warning">
+	<div
+		class="history-warning"
+		v-loading="loading"
+		element-loading-background="rgba(5, 5, 15, 0.6)"
+		element-loading-text="加载中..."
+	>
 		<!-- 搜索区(设计稿红框) -->
 		<div class="search-box">
 			<!-- 关键字 -->
@@ -20,39 +25,27 @@
 
 		<!-- 列表 -->
 		<div class="list-content">
-			<HistoryTemplate v-for="(item, ind) in filteredList" :key="item.id || ind" :data="item"
-				@click="onCardClick" />
+			<EmptyState v-if="!filteredList.length" />
+			<HistoryTemplate
+				v-else
+				v-for="(item, ind) in filteredList"
+				:key="item.id || ind"
+				:data="item"
+				@click="onCardClick"
+			/>
 		</div>
 	</div>
 </template>
 
 <script setup>
-import { ref, computed } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import HistoryTemplate from './templateComponents/HistoryTemplate.vue'
+import { alarmLogApi } from '@/api/dataCockpit'
+import EmptyState from './EmptyState.vue'
 
-// 原始数据(别动它,过滤用 computed)
-const historyWarningData = ref([
-	{
-		id: 'drone_001',
-		name: '无人机名称',
-		serialNumber: 'XLH78945645456',
-		dataSource: '侦测反制设备名称',
-		frequency: '5800MHZ',
-		discoveredAt: { date: '2025年12月30日', time: '14:55:06' },
-		stayTime: '00:10:50',
-		counterMethod: '信号干扰'
-	},
-	{
-		id: 'drone_002',
-		name: '无人机名称',
-		serialNumber: 'XLH78945645456',
-		dataSource: '侦测反制设备名称',
-		frequency: '2400MHZ',
-		discoveredAt: { date: '2025年12月30日', time: '15:08:12' },
-		stayTime: '00:03:21',
-		counterMethod: '诱导驱离'
-	}
-])
+const historyWarningData = ref([])
+const loading = ref(false)
+const minLoadingMs = 400
 
 // 查询条件:keyword + dateRange(YYYY-MM-DD数组)
 const query = ref({
@@ -61,53 +54,46 @@
 })
 
 const onSearch = () => {
-	// 目前是本地过滤:computed 会自动更新
-	// 后续如果你要走接口,这里换成 fetchList(query.value) 即可
+	fetchHistoryWarning()
 }
 
 const onCardClick = (item) => {
 	console.log('点击无人机卡片:', item.id)
 }
 
-// "2025年12月30日" -> "2025-12-30"
-const cnDateToISO = (cn) => {
-	if (!cn) return ''
-	const m = cn.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/)
-	if (!m) return ''
-	const y = m[1]
-	const mm = String(m[2]).padStart(2, '0')
-	const dd = String(m[3]).padStart(2, '0')
-	return `${y}-${mm}-${dd}`
-}
-
-const inRange = (targetISO, startISO, endISO) => {
-	if (!targetISO) return false
-	const t = new Date(`${targetISO}T00:00:00`).getTime()
-	const s = startISO ? new Date(`${startISO}T00:00:00`).getTime() : null
-	const e = endISO ? new Date(`${endISO}T23:59:59`).getTime() : null
-	if (s !== null && t < s) return false
-	if (e !== null && t > e) return false
-	return true
+const fetchHistoryWarning = async () => {
+	const startAt = Date.now()
+	loading.value = true
+	try {
+		const [startTime, endTime] = query.value.dateRange || []
+		const keyword = (query.value.keyword || '').trim()
+		const params = {
+			current: 1,
+			size: 20,
+			alarmType: '2',
+			startTime,
+			endTime,
+			droneName: keyword || undefined,
+			droneSerialNo: keyword || undefined
+		}
+		const res = await alarmLogApi(params)
+		const records = res?.data?.data?.records ?? []
+		historyWarningData.value = records
+	} finally {
+		const elapsed = Date.now() - startAt
+		if (elapsed < minLoadingMs) {
+			await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed))
+		}
+		loading.value = false
+	}
 }
 
 const filteredList = computed(() => {
-	const kw = (query.value.keyword || '').trim().toLowerCase()
-	const [start, end] = query.value.dateRange || []
+	return historyWarningData.value
+})
 
-	return historyWarningData.value.filter((item) => {
-		// 1) 关键字:name / serialNumber / dataSource
-		const hitKeyword = !kw
-			? true
-			: [item.name, item.serialNumber, item.dataSource]
-				.filter(Boolean)
-				.some((v) => String(v).toLowerCase().includes(kw))
-		if (!hitKeyword) return false
-
-		// 2) 日期范围:按 discoveredAt.date
-		if (!start && !end) return true
-		const itemISO = cnDateToISO(item?.discoveredAt?.date)
-		return inRange(itemISO, start, end)
-	})
+onMounted(() => {
+	fetchHistoryWarning()
 })
 </script>
 
@@ -133,4 +119,5 @@
 		flex: 1;
 	}
 }
-</style>
\ No newline at end of file
+
+</style>
diff --git a/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue b/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
index f21d5f8..07b66eb 100644
--- a/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/LeftContainer.vue
@@ -7,7 +7,7 @@
 				<div class="category-item" v-for="(item, ind) in categoryList" :key="ind"
 					@click="activeIdModel = item.id" :class="{ active: activeIdModel === item.id }">
 					{{ item.name }}
-					<span class="badge">{{ item.badge }}</span>
+					<span class="badge" v-if="item.badge">{{ item.badge }}</span>
 				</div>
 			</div>
 
@@ -27,6 +27,8 @@
 import RealWarning from './RealWarning.vue'
 import EquipmentWarning from './EquipmentWarning.vue'
 import HistoryWarning from './HistoryWarning.vue'
+import { alarmStatisticsApi } from '@/api/dataCockpit'
+import { onMounted } from 'vue'
 
 const props = defineProps({
 	activeId: {
@@ -55,19 +57,19 @@
 	{
 		id: 1,
 		name: '实时告警',
-		badge: 8,
+		badge: 0,
 		component: RealWarning
 	},
 	{
 		id: 2,
 		name: '设备告警',
-		badge: 8,
+		badge: 0,
 		component: EquipmentWarning
 	},
 	{
 		id: 3,
 		name: '历史告警',
-		badge: 8,
+		badge: 0,
 		component: HistoryWarning
 	}
 ])
@@ -79,6 +81,24 @@
 const toggleCollapse = () => {
 	collapsedModel.value = !collapsedModel.value
 }
+
+const updateBadges = async () => {
+	const res = await alarmStatisticsApi()
+	const data = res?.data?.data || {}
+	const realTotal = data.realTimeAlarmCount ?? 0
+	const historyTotal = data.historyAlarmCount ?? 0
+	const deviceTotal = data.deviceAlarmCount ?? 0
+	categoryList.value = categoryList.value.map((item) => {
+		if (item.id === 1) return { ...item, badge: realTotal }
+		if (item.id === 2) return { ...item, badge: deviceTotal }
+		if (item.id === 3) return { ...item, badge: historyTotal }
+		return item
+	})
+}
+
+onMounted(() => {
+	updateBadges()
+})
 </script>
 
 <style lang="scss" scoped>
@@ -170,13 +190,12 @@
 			position: absolute;
 			top: 16px;
 			right: 4px;
-			min-width: 16px;
-			height: 16px;
-			line-height: 16px;
+			min-width: 18px;
+			line-height: 18px;
 			font-size: 12px;
 			color: #FFFFFF;
 			background: #FF3B30;
-			border-radius: 9px;
+			border-radius: 50%;
 			text-align: center;
 			box-shadow: 0px 2px 6px rgba(255, 59, 48, 0.6);
 			pointer-events: none;
diff --git a/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue b/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
index 623a317..7210005 100644
--- a/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/RealWarning.vue
@@ -9,63 +9,95 @@
  * 2026-01-08 09:29:07
 -->
 <template>
-	<div class="real-warning">
-		<RealTemplate v-for="(item, ind) in realWarningData" :key="ind" :data="item"
-			@signal="() => console.log('信号干扰', item.id)" @counter="() => console.log('诱导驱离', item.id)"
-			@favorite="() => console.log('收藏', item.id)" />
+	<div
+		class="real-warning"
+		v-loading="loading"
+		element-loading-background="rgba(5, 5, 15, 0.6)"
+		element-loading-text="加载中..."
+	>
+		<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="() => console.log('收藏', item.id)"
+		/>
 	</div>
 </template>
 
 <script setup>
 import RealTemplate from './templateComponents/RealTemplate.vue'
+import { alarmLogApi, interferenceAndExpulsionApi } from '@/api/dataCockpit'
+import { onMounted, ref } from 'vue'
+import EmptyState from './EmptyState.vue'
+import { ElMessage } from 'element-plus'
 
-const realWarningData = ref([
-	{
-		"id": "drone_001",
-		"name": "无人机A-01",
-		"status": "侦测中",
-		"statusType": "detecting",
-		"serialNumber": "XLH789456456",
-		"dataSource": "侦测反制设备名称",
-		"longitude": 113.99238,
-		"latitude": 22.541819,
-		"frequency": "5800MHz",
-		"discoverTime": "14:56:04",
-		"altitude": "150m",
-		"speed": "5.01m/s",
-		"isFavorite": false
-	},
-	{
-		"id": "drone_002",
-		"name": "无人机B-07",
-		"status": "反制中",
-		"statusType": "countering",
-		"serialNumber": "XLH789456789",
-		"dataSource": "反制设备A区",
-		"longitude": 113.99312,
-		"latitude": 22.542031,
-		"frequency": "2400MHz",
-		"discoverTime": "15:02:41",
-		"altitude": "120m",
-		"speed": "3.86m/s",
-		"isFavorite": true
-	},
-	{
-		"id": "drone_002",
-		"name": "无人机B-07",
-		"status": "反制中",
-		"statusType": "countering",
-		"serialNumber": "XLH789456789",
-		"dataSource": "反制设备A区",
-		"longitude": 113.99312,
-		"latitude": 22.542031,
-		"frequency": "2400MHz",
-		"discoverTime": "15:02:41",
-		"altitude": "120m",
-		"speed": "3.86m/s",
-		"isFavorite": true
+const realWarningData = ref([])
+const loading = ref(false)
+const minLoadingMs = 400
+
+const actionTextMap = {
+	signal: '信号干扰',
+	counter: '诱导驱离'
+}
+
+const buildPayload = (item) => ({
+	alarmRecordId: item.id,
+	areaCode: item.areaCode,
+	counterEffect: '',
+	coverRadiusM: item.coverRadiusM,
+	deployLatitude: item.latitude,
+	deployLongitude: item.longitude,
+	deviceCode: item.deviceCode,
+	deviceId: item.deviceId,
+	deviceModel: item.deviceModel,
+	deviceName: item.deviceName,
+	deviceType: item.deviceType,
+	droneDeviceCode: item.droneSerialNo,
+	droneName: item.droneName,
+	droneType: item.droneType,
+	findTime: item.alarmTime,
+	id: item.id,
+	workMode: ''
+})
+
+const handleAction = async (type, item) => {
+	const actionText = actionTextMap[type] || '操作'
+	try {
+		const res = await interferenceAndExpulsionApi(buildPayload(item))
+		if (res?.data?.success) {
+			ElMessage({ type: 'success', message: `${actionText}成功` })
+		} 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
+	} finally {
+		const elapsed = Date.now() - startAt
+		if (elapsed < minLoadingMs) {
+			await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed))
+		}
+		loading.value = false
+	}
+}
+
+onMounted(() => {
+	fetchRealWarning()
+})
 </script>
 
 <style lang="scss" scoped>
@@ -73,4 +105,5 @@
 	width: 100%;
 	height: 100%;
 }
-</style>
\ No newline at end of file
+
+</style>
diff --git a/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue b/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
index 43cf0d0..33235ae 100644
--- a/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/RightContainer.vue
@@ -26,9 +26,21 @@
 				</div>
 			</div>
 
-			<div class="content">
-				<RealEquipmentTemplate v-for="item in counterDeviceStatusList" :key="item.id" :data="item"
-					@history="onHistory" @click="onCardClick" />
+			<div
+				class="content"
+				v-loading="loading"
+				element-loading-background="rgba(5, 5, 15, 0.6)"
+				element-loading-text="加载中..."
+			>
+				<EmptyState v-if="!counterDeviceStatusList.length" />
+				<RealEquipmentTemplate
+					v-else
+					v-for="item in counterDeviceStatusList"
+					:key="item.id"
+					:data="item"
+					@history="onHistory"
+					@click="onCardClick"
+				/>
 			</div>
 		</div>
 
@@ -44,8 +56,10 @@
 import TitleTemplate from './templateComponents/TitleTemplate.vue'
 import RealEquipmentTemplate from './templateComponents/RealEquipmentTemplate.vue'
 import DeviceHistoryDialog from './DeviceHistoryDialog.vue'
+import EmptyState from './EmptyState.vue'
 import zcsbLogo from '@/assets/images/dataCockpit/zcsb.png'
 import fzsbLogo from '@/assets/images/dataCockpit/fzsb.png'
+import { deviceSearchApi } from '@/api/dataCockpit'
 
 const props = defineProps({
 	collapsed: {
@@ -76,39 +90,12 @@
 	}
 ])
 
-const counterDeviceStatusList = ref([
-	{
-		id: 'cd_001',
-		name: '反制设备名称',
-		actionText: '侦测中',
-		actionType: 'detecting',       // detecting | jamming | idle
-		type: '察打一体',
-		battery: 100,                  // 电量 %
-		azimuth: 14,                   // 方位角
-		pitch: 14,                     // 俯仰角
-		statusText: '在线',            // 状态
-		vendor: '厂商名称',            // 厂商
-		targetCount: 10,               // 侦测目标
-		range: '1.2KM'                 // 有效范围
-	},
-	{
-		id: 'cd_002',
-		name: '反制设备名称',
-		actionText: '信号干扰中',
-		actionType: 'jamming',
-		type: '察打一体',
-		battery: 100,
-		azimuth: 14,
-		pitch: 14,
-		statusText: '在线',
-		vendor: '厂商名称',
-		targetCount: 10,
-		range: '1.2KM'
-	}
-])
+const counterDeviceStatusList = ref([])
 
 const historyVisible = ref(false)
 const historyDevice = ref(null)
+const loading = ref(false)
+const minLoadingMs = 400
 
 const onHistory = (item) => {
 	historyDevice.value = item
@@ -122,6 +109,57 @@
 const toggleCollapse = () => {
 	collapsedModel.value = !collapsedModel.value
 }
+
+const statusMap = {
+	0: { text: '在线', actionType: 'detecting' },
+	1: { text: '离线', actionType: '' },
+	2: { text: '故障', actionType: 'jamming' },
+	3: { text: '报废', actionType: '' }
+}
+const deviceTypeMap = {
+	1: '便捷侦测箱',
+	2: '反制枪',
+	3: '察打一体'
+}
+
+const formatDeviceItem = (item) => {
+	const statusInfo = statusMap[item?.status] || { text: '-', actionType: '' }
+	return {
+		id: item.id,
+		name: item.deviceName || item.deviceModel || '-',
+		actionText: statusInfo.text,
+		actionType: statusInfo.actionType,
+		type: deviceTypeMap[item.deviceType] || item.deviceType || '-',
+		batteryPct: item.batteryPct ?? 0,
+		azimuth: item.azimuth ?? 0,
+		elevation: item.elevation ?? 0,
+		status: item.status ?? '-',
+		manufacturer: item.manufacturer || '-',
+		detectTargetCnt: item.detectTargetCnt ?? 0,
+		effectiveRangeKm: item.effectiveRangeKm ?? 0
+	}
+}
+
+const fetchDeviceList = async () => {
+	const startAt = Date.now()
+	loading.value = true
+	try {
+		const params = { current: 1, size: 20, deviceStatusList: '0' }
+		const res = await deviceSearchApi(params)
+		const records = res?.data?.data?.records ?? []
+		counterDeviceStatusList.value = records.map(formatDeviceItem)
+	} finally {
+		const elapsed = Date.now() - startAt
+		if (elapsed < minLoadingMs) {
+			await new Promise((resolve) => setTimeout(resolve, minLoadingMs - elapsed))
+		}
+		loading.value = false
+	}
+}
+
+onMounted(() => {
+	fetchDeviceList()
+})
 </script>
 
 <style lang="scss" scoped>
@@ -195,6 +233,7 @@
 	overflow-y: auto;
 }
 
+
 .right-container {
 	transition: transform 0.3s ease-in-out;
 	position: relative;
diff --git a/applications/drone-command/src/views/dataCockpit/components/templateComponents/EquipmentTemplate.vue b/applications/drone-command/src/views/dataCockpit/components/templateComponents/EquipmentTemplate.vue
index 2310b82..1d5d632 100644
--- a/applications/drone-command/src/views/dataCockpit/components/templateComponents/EquipmentTemplate.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/templateComponents/EquipmentTemplate.vue
@@ -24,7 +24,7 @@
 				</div>
 				<div class="col">
 					<span class="label">厂商</span>
-					{{ data.vendor }}
+					{{ data.manufacturer }}
 				</div>
 			</div>
 			<div class="row">
diff --git a/applications/drone-command/src/views/dataCockpit/components/templateComponents/HistoryTemplate.vue b/applications/drone-command/src/views/dataCockpit/components/templateComponents/HistoryTemplate.vue
index b1bdf9d..494cd94 100644
--- a/applications/drone-command/src/views/dataCockpit/components/templateComponents/HistoryTemplate.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/templateComponents/HistoryTemplate.vue
@@ -2,42 +2,47 @@
 	<div class="drone-info-card" @click="emit('click', data)">
 		<div class="title">
 			<span class="name">
-				{{ data.name }}
+				{{ data.droneName }}
 			</span>
 		</div>
 
 		<div class="rows">
 			<div class="row">
 				<span class="label">序列号</span>
-				<span class="value">{{ data.serialNumber }}</span>
+				<span class="value">{{ data.droneSerialNo }}</span>
 			</div>
 
 			<div class="row">
 				<span class="label">数据源</span>
-				<span class="value">{{ data.dataSource }}</span>
+				<span class="value">{{ getDataSource(data) }}</span>
 			</div>
 
 			<div class="row">
 				<span class="label">信号频段</span>
-				<span class="value">{{ data.frequency }}</span>
+				<span class="value">{{ formatFrequency(data.signalFreqMhz) }}</span>
 			</div>
 
 			<div class="row">
 				<span class="label">发现时间</span>
 				<span class="value">
-					<span class="date">{{ data.discoveredAt?.date }}</span>
-					<span class="time">{{ data.discoveredAt?.time }}</span>
+					<span class="date">{{ getAlarmDate(data.alarmTime) }}</span>
+					<span class="time">{{ getAlarmTime(data.alarmTime) }}</span>
 				</span>
 			</div>
 
 			<div class="row">
 				<span class="label">停留时间</span>
-				<span class="value">{{ data.stayTime }}</span>
+				<span class="value">{{ data.stayDuration }}</span>
 			</div>
 
 			<div class="row">
 				<span class="label">反制方式</span>
-				<span class="value">{{ data.counterMethod }}</span>
+				<span class="value">{{ getCounterWayText(data.counterWay) }}</span>
+			</div>
+
+			<div class="row">
+				<span class="label">飞行状态</span>
+				<span class="value">{{ getFlightStatusText(data.flightStatus) }}</span>
 			</div>
 		</div>
 	</div>
@@ -53,6 +58,40 @@
 })
 
 const emit = defineEmits(['click'])
+
+const getDataSource = (item) => {
+	return item?.deviceName || item?.areaName || '-'
+}
+
+const formatFrequency = (value) => {
+	if (value == null || value === '') return '-'
+	return `${value}MHZ`
+}
+
+const getAlarmDate = (value) => {
+	if (!value) return '-'
+	const parts = value.replace('T', ' ').split(' ')
+	return parts[0] || value
+}
+
+const getAlarmTime = (value) => {
+	if (!value) return '-'
+	const parts = value.replace('T', ' ').split(' ')
+	return parts[1] || value
+}
+
+const getFlightStatusText = (value) => {
+	if (value === 1 || value === '1') return '侦测中'
+	if (value === 2 || value === '2') return '反制中'
+	return value || '-'
+}
+
+const getCounterWayText = (value) => {
+	if (value === 1 || value === '1') return '信号干扰'
+	if (value === 2 || value === '2') return '诱导驱离'
+	if (value === 3 || value === '3') return '无'
+	return value || '-'
+}
 </script>
 
 <style scoped lang="scss">
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 2cf3cc7..ed08cbf 100644
--- a/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealEquipmentTemplate.vue
@@ -6,12 +6,12 @@
 				<span class="name">{{ data.name }}</span>
 
 				<span class="status">
-					<span :class="data.actionType">
+					<span :class="getStatusType(data.status)">
 
 					</span>
 
 					<span>
-						{{ data.actionText }}
+						{{ getStatusText(data.status) }}
 					</span>
 				</span>
 			</div>
@@ -26,7 +26,7 @@
 				</div>
 				<div class="col">
 					<span class="label">电量</span>
-					{{ data.battery }}%
+					{{ data.batteryPct }}%
 				</div>
 			</div>
 
@@ -37,29 +37,29 @@
 				</div>
 				<div class="col">
 					<span class="label">俯仰角</span>
-					{{ data.pitch }}°
+					{{ data.elevation }}°
 				</div>
 			</div>
 
 			<div class="row">
 				<div class="col">
 					<span class="label">状态</span>
-					{{ data.statusText }}
+					{{ getStatusText(data.status) }}
 				</div>
 				<div class="col">
 					<span class="label">厂商</span>
-					{{ data.vendor }}
+					{{ data.manufacturer }}
 				</div>
 			</div>
 
 			<div class="row">
 				<div class="col">
 					<span class="label">侦测目标</span>
-					{{ data.targetCount }}台
+					{{ data.detectTargetCnt }}台
 				</div>
 				<div class="col">
 					<span class="label">有效范围</span>
-					{{ data.range }}KM
+					{{ data.effectiveRangeKm }}KM
 				</div>
 			</div>
 		</div>
@@ -81,6 +81,20 @@
 })
 
 const emit = defineEmits(['history', 'click'])
+
+const getStatusText = (value) => {
+	if (value === 0 || value === '0') return '在线'
+	if (value === 1 || value === '1') return '离线'
+	if (value === 2 || value === '2') return '故障'
+	if (value === 3 || value === '3') return '报废'
+	return value || '-'
+}
+
+const getStatusType = (value) => {
+	if (value === 0 || value === '0') return 'detecting'
+	if (value === 2 || value === '2') return 'jamming'
+	return ''
+}
 </script>
 
 <style scoped lang="scss">
@@ -196,4 +210,4 @@
 		}
 	}
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue b/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue
index f96a28b..d29d7b4 100644
--- a/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue
+++ b/applications/drone-command/src/views/dataCockpit/components/templateComponents/RealTemplate.vue
@@ -2,14 +2,14 @@
 	<div class="drone-card">
 		<div class="header">
 			<div class="title">
-				<span class="name">{{ data.name }}</span>
+				<span class="name">{{ data.droneName }}</span>
 				<span class="status">
-					<span :class="data.statusType">
+					<span :class="getStatusType(data)">
 
 					</span>
 
 					<span>
-						{{ data.status }}
+						{{ getStatusText(data) }}
 					</span>
 				</span>
 			</div>
@@ -24,11 +24,11 @@
 		<div class="content">
 			<div>
 				<span class="label">序列号</span>
-				{{ data.serialNumber }}
+				{{ data.droneSerialNo }}
 			</div>
 			<div>
 				<span class="label">数据源</span>
-				{{ data.dataSource }}
+				{{ getDataSource(data) }}
 			</div>
 			<div>
 				<span class="label">经纬度</span>
@@ -37,21 +37,21 @@
 			<div class="row">
 				<div class="col">
 					<span class="label">信号频段</span>
-					{{ data.frequency }}
+					{{ formatFrequency(data.signalFreqMhz) }}
 				</div>
 				<div class="col">
 					<span class="label">发现时间</span>
-					{{ data.discoverTime }}
+					{{ getAlarmTime(data.alarmTime) }}
 				</div>
 			</div>
 			<div class="row">
 				<div class="col">
 					<span class="label">飞行高度</span>
-					{{ data.altitude }}
+					{{ formatHeight(data.flightHeightM) }}
 				</div>
 				<div class="col">
 					<span class="label">飞行速度</span>
-					{{ data.speed }}
+					{{ formatSpeed(data.flightSpeedMs) }}
 				</div>
 			</div>
 		</div>
@@ -73,6 +73,47 @@
 })
 
 const emit = defineEmits(['signal', 'counter', 'favorite'])
+
+const getStatusText = (item) => {
+	const status = item?.flightStatus
+	if (status === 1 || status === '1') return '侦测中'
+	if (status === 2 || status === '2') return '反制中'
+	return status || '侦测中'
+}
+
+const getStatusType = (item) => {
+	const statusText = getStatusText(item)
+	const counterWay = item?.counterWay
+	if (statusText.includes('反制') || counterWay === 2 || counterWay === '2' || counterWay === '诱导驱离') {
+		return 'countering'
+	}
+	return 'detecting'
+}
+
+const getDataSource = (item) => {
+	return item?.deviceName || item?.areaName || '-'
+}
+
+const formatFrequency = (value) => {
+	if (value == null || value === '') return '-'
+	return `${value}MHz`
+}
+
+const getAlarmTime = (value) => {
+	if (!value) return '-'
+	const parts = value.replace('T', ' ').split(' ')
+	return parts[1] || value
+}
+
+const formatHeight = (value) => {
+	if (value == null || value === '') return '-'
+	return `${value}m`
+}
+
+const formatSpeed = (value) => {
+	if (value == null || value === '') return '-'
+	return `${value}m/s`
+}
 </script>
 
 <style scoped lang="scss">
diff --git a/uniapps/work-app/src/pages/user/index.vue b/uniapps/work-app/src/pages/user/index.vue
index 3d281d5..54831e9 100644
--- a/uniapps/work-app/src/pages/user/index.vue
+++ b/uniapps/work-app/src/pages/user/index.vue
@@ -1,45 +1,48 @@
 <!-- 我的 -->
 <template>
   <view class="page-wrap">
-    <view class="userBox">
-      <view class="flex items-center pb-30rpx pl-30rpx pr-20rpx">
-        <view class="mr-20rpx">
-          <u-avatar :src="user.avatar || defaultAvatar" size="70" />
-        </view>
-        <view class="flex-1">
-          <view class="userName">{{user.nickName }}</view>
-          <view class="departs">
-            <image
-              src="@/static/images/user/department.svg"
-              alt=""
-            />
-            <span>{{ user?.sysDept?.deptName }}</span>
-          </view>
-        </view>
-
-      </view>
-      <view class="personalInformation">
-        <div class="message">
-          <div class="messagebox">个人资料</div>
-          <div class="editInfo" @click="handelEdit(1)">
-            点击编辑
-            <image :src="rightImage" alt="" />
-          </div>
-        </div>
-        <div class="passwordBox">
-          <div class="messagebox">修改密码</div>
-          <div class="editInfo" @click="handelEdit(2)">
-            点击编辑
-            <image :src="rightImage" alt="" />
-          </div>
-        </div>
-      </view>
-
-      <view class="goOutBtn">
-        <div class="goOutStyle" @click="logOut">退出登录</div>
-      </view>
-
-    </view>
+<view class="pageUser">
+	<view class="userBox">
+	  <view class="flex items-center pb-30rpx pl-30rpx pr-20rpx">
+	    <view class="mr-20rpx">
+	      <u-avatar :src="user.avatar || defaultAvatar" size="70" />
+	    </view>
+	    <view class="flex-1">
+	      <view class="userName">{{user.nickName }}</view>
+	      <view class="departs">
+	        <image
+	          src="@/static/images/user/department.svg"
+	          alt=""
+	        />
+	        <span>{{ user?.sysDept?.deptName }}</span>
+	      </view>
+	    </view>
+	
+	  </view>
+	  <view class="personalInformation">
+	    <div class="message">
+	      <div class="messagebox">个人资料</div>
+	      <div class="editInfo" @click="handelEdit(1)">
+	        点击编辑
+	        <image :src="rightImage" alt="" />
+	      </div>
+	    </div>
+	    <div class="passwordBox">
+	      <div class="messagebox">修改密码</div>
+	      <div class="editInfo" @click="handelEdit(2)">
+	        点击编辑
+	        <image :src="rightImage" alt="" />
+	      </div>
+	    </div>
+	  </view>
+	
+	  <view class="goOutBtn">
+	    <div class="goOutStyle" @click="logOut">退出登录</div>
+	  </view>
+	
+	</view>
+	  
+</view>
   </view>
 </template>
 
@@ -96,7 +99,7 @@
   height: 100%;
 }
 .pageUser {
-  background: url("@/static/images/user/userbg.svg")  no-repeat center;
+  background: url("@/static/images/user/userbg.svg")  no-repeat ;
 
 }
 .userBox {

--
Gitblit v1.9.3