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|
+|  alarmRecordId|告警记录ID(ja_fw_drone_alarm_record.id)||false|integer(int64)||
+|  areaCode|区域编码||false|string||
+|  counterEffect|反制效果(success/fail)||false|string||
+|  coverRadiusM|覆盖范围(米)||false|integer(int32)||
+|  deployLatitude|部署纬度(冗余)||false|number(double)||
+|  deployLongitude|部署经度(冗余)||false|number(double)||
+|  deviceCode|反制设备编码(快照)||false|string||
+|  deviceId|反制设备ID(ja_fw_device.id)||false|integer(int64)||
+|  deviceModel|反制设备型号(快照)||false|string||
+|  deviceName|反制设备名称(快照)||false|string||
+|  deviceType|反制设备类型(快照)||false|string||
+|  droneDeviceCode|无人机设备编码||false|string||
+|  droneName|无人机名称||false|string||
+|  droneType|无人机类型||false|string||
+|  findTime|发现时间||false|string(date-time)||
+|  id|主键id||false|integer(int64)||
+|  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