diff --git a/README.md b/README.md
index ff3eb34..f013f4a 100644
--- a/README.md
+++ b/README.md
@@ -114,4 +114,7 @@ npm run build
"clientip": "{\"osName\":\"Windows\",\"osVersion\":\"10\",\"browserName\":\"Chrome\",\"browserVersion\":\"137.0.0.0\",\"SDK_VERSION\":\"1.0.9\"}",
"sdp": "v=0\r\no=...", // SDP内容
"streamurl": "webrtc://qvs-live.thirdmonitor.concoai.com:447/2xenzwqy2ppcl/31011500991180041301_34020000001320000001"
-}
\ No newline at end of file
+}
+
+计划修改:
+CarouselVideoPlayer修改逻辑 找不到流的时候 页面显示空状态 也进行轮播 而不是找不到匹配的视角或视角没有流,找第一个有效视角
\ No newline at end of file
diff --git a/src/api/detail.js b/src/api/detail.js
index a6c1921..d9f4294 100644
--- a/src/api/detail.js
+++ b/src/api/detail.js
@@ -1,4 +1,4 @@
-import request, { concoaiService } from '@/utils/request'
+import request, { concoaiService,service } from '@/utils/request'
// 机器人相关接口
export const robotApi = {
@@ -28,7 +28,7 @@ export const robotApi = {
// 机器人详情
getRobotDetail: (robotId) => {
return concoaiService({
- url: `/robot/detail/${robotId} `,
+ url: `/robot/detail/${robotId}`,
method: 'get',
})
},
@@ -49,22 +49,59 @@ export const robotApi = {
})
},
// 一键处理事件接口
- handleAlarmEvent: (data) => {
- return concoaiService({
- url: `/event/handleEventBatch`,
+ // handleAlarmEvent: (data) => {
+ // return concoaiService({
+ // url: `/event/handleEventBatch`,
+ // method: 'post',
+ // data
+ // })
+ // },
+ // 获取告警信息数量接口添加
+ getAlarmEventCount: (params) => {
+ return service({
+ url: `/api/v1/events/alertCount`,
+ method: 'get',
+ params
+ })
+ },
+ // 获取未处理告警消息列表
+ getUnhandledAlarmMessages: (params) => {
+ return service({
+ url: `/api/v1/events/messagesUnhandled`,
+ method: 'get',
+ params
+ })
+ },
+ // 获取已处理告警消息列表
+ getHandledAlarmMessages: (params) => {
+ return service({
+ url: `/api/v1/events/messagesHandled`,
+ method: 'get',
+ params
+ })
+ },
+ // 一键处理告警事件
+ handleOcrAlerts: (params) => {
+ return service({
+ url: `/api/v1/events/handleOcrAlerts`,
+ method: 'get',
+ params
+ })
+ },
+ // 处理单个告警事件
+ handleSingleAlarmEvent: (data) => {
+ return service({
+ url: `/api/v1/events/handleOcrAlert`,
method: 'post',
data
})
},
- // 单个事件查看详情
- getAlarmEventDetail: (eventId) => {
- return concoaiService({
- url: `/event/eventInfo/${eventId}`,
+ // 查看每一条消息的详情
+ getAlarmEventDetail: (messageId) => {
+ return service({
+ url: `/api/v1/events/alertFront/${messageId}`,
method: 'get',
})
}
-
-
-
}
diff --git a/src/api/index.js b/src/api/index.js
index f649817..3a52be1 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -63,5 +63,12 @@ export const homeApi = {
url: '/api/v1/events/getMonitor',
method: 'get',
})
+ },
+ //查看告警消息详情
+ getAlarmEventDetail:(messageId)=>{
+ return service({
+ url: `/api/v1/events/alertFront/${messageId}`,
+ method: 'get',
+ })
}
}
\ No newline at end of file
diff --git a/src/assets/img/empty.png b/src/assets/img/empty.png
index a365920..8852832 100644
Binary files a/src/assets/img/empty.png and b/src/assets/img/empty.png differ
diff --git a/src/components/LatestAlarms.vue b/src/components/LatestAlarms.vue
index 6ed1068..1dd06c2 100644
--- a/src/components/LatestAlarms.vue
+++ b/src/components/LatestAlarms.vue
@@ -31,56 +31,56 @@
-
+
-
+
-
{{ item.event }}
-
{{ item.robotId }}
-
{{ item.time }}
-
+
{{ item.event }}
+
{{ item.robotId }}
+
{{ item.time }}
+
- 已处理
-
+
已处理
-
-
+
+
+
-
+
-
{{ item.event }}
-
{{ item.robotId }}
-
{{ item.time }}
-
+
{{ item.event }}
+
{{ item.robotId }}
+
{{ item.time }}
+
- 已处理
-
+
已处理
-
-
+
+
+
-
+
-
{{ item.event }}
-
{{ item.robotId }}
-
{{ item.time }}
-
+
{{ item.event }}
+
{{ item.robotId }}
+
{{ item.time }}
+
- 已处理
-
+
已处理
+
@@ -415,20 +415,72 @@ const stopScroll = () => {
const showModal = ref(false);
const currentAlarm = ref({});
-const showAlarmDetail = (alarm) => {
- currentAlarm.value = {
- ...alarm,
- title: alarm.event, // 使用事件名称作为标题
- mainImage: alarm.image,
- subImages: alarm.subImages || [], // 使用API返回的图片数组
- type: eventTypeMap[alarm.type] || alarm.event, // 使用事件类型映射
- time: alarm.time,
- robotName: alarm.robotId,
- status: alarm.handled ? '已处理' : '未处理',
- temperature: alarm.temperature || '',
- messageId: alarm.id // 用于处理时传递
- };
- showModal.value = true;
+const showAlarmDetail = async (alarm) => {
+ try {
+ // 调用获取告警详情API
+ const res = await homeApi.getAlarmEventDetail(alarm.id);
+
+ if (res.code === 200 && res.data) {
+ console.log('获取告警详情成功:', res.data);
+
+ // 合并API返回的详细数据
+ const detailData = res.data;
+
+ currentAlarm.value = {
+ ...alarm,
+ title: alarm.event, // 使用事件名称作为标题
+ mainImage: alarm.image, // 默认使用列表中的图片
+ subImages: alarm.subImages || [], // 默认使用API返回的图片数组
+ type: eventTypeMap[alarm.type] || alarm.event, // 使用事件类型映射
+ time: alarm.time,
+ robotName: alarm.robotId,
+ status: alarm.handled ? '已处理' : '未处理',
+ temperature: alarm.temperature || '',
+ messageId: alarm.id, // 用于处理时传递
+
+ // 保存原始数据
+ rawDetail: detailData
+ };
+
+ showModal.value = true;
+ } else {
+ console.error('获取告警详情失败:', res);
+
+ // 如果API调用失败,仍然使用基本数据显示弹窗
+ currentAlarm.value = {
+ ...alarm,
+ title: alarm.event,
+ mainImage: alarm.image,
+ subImages: alarm.subImages || [],
+ type: eventTypeMap[alarm.type] || alarm.event,
+ time: alarm.time,
+ robotName: alarm.robotId,
+ status: alarm.handled ? '已处理' : '未处理',
+ temperature: alarm.temperature || '',
+ messageId: alarm.id
+ };
+
+ showModal.value = true;
+ }
+ } catch (err) {
+ console.error('获取告警详情错误:', err);
+
+ // 发生错误时,使用基本数据显示弹窗
+ currentAlarm.value = {
+ ...alarm,
+ title: alarm.event,
+ mainImage: alarm.image,
+ subImages: alarm.subImages || [],
+ type: eventTypeMap[alarm.type] || alarm.event,
+ time: alarm.time,
+ robotName: alarm.robotId,
+ status: alarm.handled ? '已处理' : '未处理',
+ temperature: alarm.temperature || '',
+ messageId: alarm.id
+ };
+
+ showModal.value = true;
+ }
};
const handleConfirm = async (data) => {
diff --git a/src/components/common/CarouselVideoPlayer.vue b/src/components/common/CarouselVideoPlayer.vue
new file mode 100644
index 0000000..4e4d3cc
--- /dev/null
+++ b/src/components/common/CarouselVideoPlayer.vue
@@ -0,0 +1,379 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/common/CarouselWebRTCPlayer.vue b/src/components/common/CarouselWebRTCPlayer.vue
new file mode 100644
index 0000000..70ce3e7
--- /dev/null
+++ b/src/components/common/CarouselWebRTCPlayer.vue
@@ -0,0 +1,372 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/components/common/CustomWebRTCPlayer.vue b/src/components/common/CustomWebRTCPlayer.vue
index 89c0c50..4afd605 100644
--- a/src/components/common/CustomWebRTCPlayer.vue
+++ b/src/components/common/CustomWebRTCPlayer.vue
@@ -1,28 +1,31 @@
@@ -208,8 +281,42 @@ onUnmounted(() => {
position: relative;
overflow: hidden;
display: flex;
- justify-content: center;
align-items: center;
+ justify-content: center;
+}
+
+.empty-state {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: rgba(255, 255, 255, 0.5);
+ font-size: 14px;
+}
+
+.video-empty-state {
+ background: #033347;
+ height: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+:deep(.empty-icon) {
+ height: 46px !important;
+}
+
+:deep(.empty-title) {
+ display: none;
+}
+
+:deep(.empty-subtitle) {
+ color: #fff;
+ font-size: 10px;
+ letter-spacing: 1px;
+ margin-top: 5px;
}
.player-container {
@@ -221,25 +328,7 @@ onUnmounted(() => {
.video-element {
width: 100%;
height: 100%;
-}
-
-.video-fill {
- object-fit: fill; /* 拉伸填充,可能会变形 */
-}
-
-.video-cover {
- object-fit: cover; /* 保持比例填充,可能会裁剪 */
-}
-
-.video-stretch-height {
- object-fit: none; /* 不调整大小 */
- width: 100%;
- height: auto !important; /* 自动调整高度 */
- min-height: 100%; /* 确保至少填满容器高度 */
- position: absolute;
- left: 0;
- top: 50%;
- transform: translateY(-50%); /* 垂直居中 */
+ object-fit: cover;
}
.loading-overlay {
@@ -248,51 +337,44 @@ onUnmounted(() => {
left: 0;
width: 100%;
height: 100%;
- background: rgba(3, 51, 71, 0.7);
+ background: rgba(0, 0, 0, 0.5);
display: flex;
flex-direction: column;
- justify-content: center;
align-items: center;
- z-index: 10;
+ justify-content: center;
+ color: white;
}
.loading-spinner {
width: 40px;
height: 40px;
- border: 3px solid rgba(42, 185, 208, 0.3);
- border-top: 3px solid #2AB9D0;
+ border: 4px solid rgba(255, 255, 255, 0.3);
+ border-top: 4px solid #00a8ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 10px;
}
.loading-text {
- color: #B9E8FF;
font-size: 14px;
+ color: #fff;
+}
+
+.error-message {
+ position: absolute;
+ bottom: 10px;
+ left: 10px;
+ right: 10px;
+ background: rgba(255, 0, 0, 0.7);
+ color: white;
+ padding: 8px;
+ border-radius: 4px;
+ font-size: 12px;
+ text-align: center;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
-
-.video-empty-state {
- padding-top: 30px;
- background: #033347;
-}
-
-:deep(.empty-icon) {
- width: 100px;
- height: 74px;
-}
-
-:deep(.empty-title) {
- display: none;
-}
-
-:deep(.empty-subtitle) {
- color: #fff;
- font-size: 10px;
- letter-spacing: 1px;
-}
\ No newline at end of file
diff --git a/src/components/common/EmptyState.vue b/src/components/common/EmptyState.vue
index 77443c7..0cc4199 100644
--- a/src/components/common/EmptyState.vue
+++ b/src/components/common/EmptyState.vue
@@ -1,8 +1,8 @@
-
{{ title }}
-
{{ subtitle }}
+
{{ title }}
+
{{ subtitle }}
@@ -15,11 +15,11 @@ defineProps({
},
title: {
type: String,
- default: '暂无告警事件'
+ default: ''
},
subtitle: {
type: String,
- default: '所有告警已处理完成'
+ default: ''
}
});
@@ -40,7 +40,7 @@ defineProps({
height: 80px;
background-repeat: no-repeat;
background-size: 100% 100%;
- margin-bottom: 15px;
+ margin-bottom: 10px;
}
.empty-title {
@@ -48,10 +48,15 @@ defineProps({
font-size: 20px;
font-weight: bold;
margin-bottom: 5px;
+ min-height: 20px;
}
.empty-subtitle {
color: #2AB9D0;
font-size: 16px;
}
+
+.empty-subtitle.no-title {
+ margin-top: 0;
+}
\ No newline at end of file
diff --git a/src/components/common/VideoPlayer.vue b/src/components/common/VideoPlayer.vue
index bb793c5..8529ecd 100644
--- a/src/components/common/VideoPlayer.vue
+++ b/src/components/common/VideoPlayer.vue
@@ -354,7 +354,7 @@ onUnmounted(() => {
}
:deep(.empty-icon) {
- width: 100px;
+ /* width: 100px; */
height: 74px;
}
diff --git a/src/components/dialog/AlarmDetailModal.vue b/src/components/dialog/AlarmDetailModal.vue
index d45336b..722da8c 100644
--- a/src/components/dialog/AlarmDetailModal.vue
+++ b/src/components/dialog/AlarmDetailModal.vue
@@ -3,13 +3,16 @@
-
监控视图
@@ -17,7 +20,28 @@
主监控
-
![]()
+
+
+
+
+
@@ -26,7 +50,32 @@
云台监控
-
![]()
+
+
+
+
+
@@ -34,7 +83,32 @@
热成像
-
![]()
+
+
+
+
+
@@ -61,7 +135,13 @@
状态:
-
{{ alarmData.status }}
+
+ {{ alarmData.status }}
+
+
+
+
温度:
+
{{ alarmData.temperature }}
@@ -69,7 +149,13 @@
备注
@@ -86,46 +172,94 @@
@@ -361,6 +412,7 @@ const displayCameras = computed(() => {
border-radius: 4px;
overflow: hidden;
position: relative;
+ /* border: 1px solid rgba(0, 168, 255, 0.2); */
}
.camera-header {
@@ -372,8 +424,8 @@ const displayCameras = computed(() => {
display: flex;
align-items: center;
justify-content: space-between;
- padding:4px 8px;
- background: rgba(0, 0, 0, 0.2);
+ padding: 6px 10px;
+ background: rgba(0, 0, 0, 0.7);
color: #B9E8FF;
font-size: 12px;
}
@@ -382,15 +434,25 @@ const displayCameras = computed(() => {
display: flex;
align-items: center;
gap: 5px;
- img {
- width: 14px;
- height: 14px;
- }
+}
+
+.camera-title img {
+ width: 16px;
+ height: 16px;
+}
+
+.camera-title p {
+ margin: 0;
+ font-size: 13px;
+ letter-spacing: 0.5px;
}
.camera-feed {
height: 180px;
overflow: hidden;
+ /* background: rgba(0, 21, 31, 0.5); */
+ background: #033347;
+ border-radius: 4px;
}
.camera-feed img {
@@ -398,30 +460,4 @@ const displayCameras = computed(() => {
height: 100%;
object-fit: cover;
}
-
-.empty-state {
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- height: 200px;
- background: rgba(0, 21, 31, 0.5);
- border-radius: 4px;
- padding: 20px;
-}
-
-.empty-icon {
- width: 100px;
- height: 74px;
- margin-bottom: 10px;
- /* opacity: 0.8; */
- background: url('../assets/img/empty.png') no-repeat;
- background-size: 100% 100%;
-}
-
-.empty-text {
- color: #fff;
- font-size: 10px;
- letter-spacing: 1px;
-}
diff --git a/src/views/Home.vue b/src/views/Home.vue
index b5f1a45..37acc9f 100644
--- a/src/views/Home.vue
+++ b/src/views/Home.vue
@@ -61,14 +61,22 @@
size="small"
variant="search"
font="small"
- @change="handleViewChange(camera.title)"
/>
-
+
@@ -91,7 +99,8 @@ import AlarmStatistics from '../components/AlarmStatistics.vue'
import LatestAlarms from '../components/LatestAlarms.vue'
import RobotListModal from '../components/dialog/RobotListModal.vue'
import CustomSelect from '../components/common/CustomSelect.vue'
-import VideoPlayer from '../components/common/VideoPlayer.vue'
+import CarouselVideoPlayer from '../components/common/CarouselVideoPlayer.vue'
+import CarouselWebRTCPlayer from '../components/common/CarouselWebRTCPlayer.vue'
import { homeApi } from '../api/index'
import Icon1 from '../assets/img/icon1.png'
import Icon2 from '../assets/img/icon2.png'
@@ -161,13 +170,15 @@ const fetchRobotStatistics = async () => {
}
};
+// 摄像头数据
const cameras = ref([
- { title: 'A区厂区监控', src: jkA, streamUrl: '' },
- { title: 'B区厂区监控', src: jkA, streamUrl: '' },
- { title: '追随机器人监控', src: jkRobot, streamUrl: '' },
- { title: '室外机器人监控', src: jkRobot, streamUrl: '' }
+ { title: 'A区厂区监控', src: jkA },
+ { title: 'B区厂区监控', src: jkA },
+ { title: '追随机器人监控', src: jkRobot },
+ { title: '室外机器人监控', src: jkRobot }
]);
+// 选中的视角
const selectedViews = ref({
'A区厂区监控': '视角1',
'B区厂区监控': '视角1',
@@ -175,6 +186,7 @@ const selectedViews = ref({
'室外机器人监控': '视角1'
});
+// 视角选项
const viewOptions = ['视角1', '视角2'];
// 存储所有视频流数据
@@ -188,9 +200,6 @@ const fetchMonitorStreams = async () => {
if (res.code === 200) {
monitorStreams.value = res.data || {};
console.log('获取监控视频流成功:', JSON.stringify(monitorStreams.value));
-
- // 更新摄像头流URL
- updateCameraStreams();
} else {
console.error('获取监控视频流失败:', res);
}
@@ -199,30 +208,33 @@ const fetchMonitorStreams = async () => {
}
};
-// 更新摄像头流URL
-const updateCameraStreams = () => {
- cameras.value.forEach(camera => {
- const cameraData = monitorStreams.value[camera.title];
- console.log(`处理摄像头 ${camera.title}:`, cameraData);
-
- if (cameraData) {
- const viewName = selectedViews.value[camera.title];
- const newStreamUrl = cameraData[viewName] || '';
- console.log(`设置 ${camera.title} 的 ${viewName} 流URL:`, newStreamUrl);
- camera.streamUrl = newStreamUrl;
- } else {
- console.warn(`未找到 ${camera.title} 的数据`);
- }
- });
-
- // 打印更新后的摄像头数据
- console.log('更新后的摄像头数据:', JSON.stringify(cameras.value));
+// 判断流类型是否为WebRTC
+const isWebRTCStream = (url) => {
+ if (!url) return false;
+ return url.startsWith('webrtc://') || url.includes('31011500991180041301');
};
-// 监听视角选择变化
-watch(selectedViews, (newViews) => {
- updateCameraStreams();
-}, { deep: true });
+// 为摄像头获取视角列表
+const getViewsForCamera = (camera) => {
+ const cameraData = monitorStreams.value[camera.title];
+ if (!cameraData) return [];
+
+ return viewOptions.map(viewName => {
+ return {
+ name: viewName,
+ streamUrl: cameraData[viewName] || ''
+ };
+ }).filter(view => view.streamUrl); // 过滤掉没有流URL的视角
+};
+
+// 判断摄像头是否使用WebRTC
+const isWebRTCCamera = (camera) => {
+ const views = getViewsForCamera(camera);
+ if (views.length === 0) return false;
+
+ // 检查第一个视角是否为WebRTC流
+ return isWebRTCStream(views[0].streamUrl);
+};
const showRobotListModal = ref(false);
@@ -232,19 +244,6 @@ const handleStatisticClick = (item) => {
}
};
-// 处理视角切换
-const handleViewChange = (cameraTitle) => {
- const camera = cameras.value.find(c => c.title === cameraTitle);
- if (camera) {
- const cameraData = monitorStreams.value[cameraTitle];
- if (cameraData) {
- const viewName = selectedViews.value[cameraTitle];
- camera.streamUrl = cameraData[viewName] || '';
- console.log(`切换 ${cameraTitle} 到 ${viewName}, URL: ${camera.streamUrl}`);
- }
- }
-};
-
// 组件挂载时获取数据
onMounted(() => {
fetchRobotStatistics();
diff --git a/src/views/RobotDetail.vue b/src/views/RobotDetail.vue
index 0543739..378e865 100644
--- a/src/views/RobotDetail.vue
+++ b/src/views/RobotDetail.vue
@@ -34,10 +34,9 @@
@@ -45,20 +44,18 @@
@@ -76,6 +73,8 @@
@view-all="handleViewAll"
@process-all="handleProcessAll"
/>
+
+
告警事件列表
@@ -139,7 +138,7 @@
>
处理
- 已处理
@@ -159,7 +158,7 @@
v-if="filteredMeterAlarms.length === 0"
title="暂无仪表异常"
subtitle="暂无需要处理的仪表异常"
- :iconSrc="require('../assets/img/empty_list.png')"
+ :iconSrc="emptyList"
/>
处理
- 已处理
@@ -247,7 +246,7 @@ import EventDetailModal from '@/components/dialog/EventDetailModal.vue'
import TimeDisplay from '@/components/common/TimeDisplay.vue';
import { robotApi } from '@/api/detail';
import WebSocketClient from '@/utils/websocket';
-import WebRTCPlayer from '@/components/detail/WebRTCPlayer.vue';
+import CustomWebRTCPlayer from '@/components/common/CustomWebRTCPlayer.vue';
import ConfirmDialog from '@/components/dialog/ConfirmDialog.vue';
import EmptyState from '@/components/common/EmptyState.vue';
import emptyList from '@/assets/img/empty_list.png';
@@ -523,20 +522,34 @@ const meterAlarms = computed(() =>
);
// 统计数量,包括超时未处理的告警
-const pendingCount = computed(
+// 这些计算属性将不再直接用于显示,而是作为备用和调试用途
+const pendingCountComputed = computed(
() => alarmList.value.filter((a) => a.status === "pending" || a.status === "timeout").length
);
-const doneCount = computed(
+const doneCountComputed = computed(
() => alarmList.value.filter((a) => a.status === "done").length
);
-const unreadCount = computed(
+const unreadCountComputed = computed(
() => alarmList.value.filter((a) => !a.isRead).length
);
+// 添加新的告警统计数据 - 这些将用于实际显示
+const pendingCount = ref(0);
+const doneCount = ref(0);
+const unreadCount = ref(0);
+const alertCount = ref(0);
+const processedCount = ref(0);
+// 当前页面参数
+const currentPage = ref(1);
+const pageSize = ref(999);
// Tab切换
const alarmTab = ref("pending"); // "pending" | "done"
-const setAlarmTab = (tab) => {
+const setAlarmTab = async (tab) => {
alarmTab.value = tab;
+
+ // 切换标签时重新获取告警列表和数量
+ await getAlarmEventList();
+ await getAlarmEventCount();
};
// 查看全部按钮
@@ -550,59 +563,446 @@ const handleProcessAll = async () => {
confirmDialogMessage.value = '是否确认处理所有未处理的告警事件?';
};
-// 添加确认处理函数
-const handleConfirmProcess = async () => {
+// 添加一键处理OCR告警的方法
+const handleOcrAlerts = async () => {
try {
- // 获取所有未处理的告警ID
- const pendingAlarms = alarmList.value
- .filter(a => a.status === 'pending')
- .map(a => a.id);
-
- if (pendingAlarms.length === 0) {
- return;
- }
-
- const res = await robotApi.handleAlarmEvent({
- eventIds: pendingAlarms
+ const res = await robotApi.handleOcrAlerts({
+ number: robotId.value
});
if (res.code === 200) {
- // 更新本地状态
- alarmList.value = alarmList.value.map(a =>
- pendingAlarms.includes(a.id) ? { ...a, status: 'done', isRead: true } : a
- );
+ console.log('一键处理OCR告警成功');
// 重新获取告警列表
- await getAlarmEventList();
+ if (alarmTab.value === 'pending') {
+ await getUnhandledAlarmMessages();
+ } else {
+ await getHandledAlarmMessages();
+ }
+
+ // 更新告警数量
+ await getAlarmEventCount();
+
+ return true;
}
+ return false;
} catch (error) {
- console.error('一键处理失败:', error);
+ console.error('一键处理OCR告警失败:', error);
+ return false;
}
};
-// 添加获取告警事件列表的函数
-const getAlarmEventList = async () => {
+// 添加确认处理函数
+const handleConfirmProcess = async () => {
try {
- const res = await robotApi.getAlarmEventList({
- robotId: robotId.value,
- status: alarmTab.value // 'pending' 或 'done'
+ console.log('开始一键处理所有告警');
+
+ // 分类告警
+ const ocrAlarms = alarmList.value.filter(a =>
+ (a.status === 'pending' || a.status === 'timeout') &&
+ (a.group === "meter" || a.content === "日常巡检")
+ );
+
+ const eventAlarms = alarmList.value.filter(a =>
+ (a.status === 'pending' || a.status === 'timeout') &&
+ a.group === "event" && a.content !== "日常巡检"
+ );
+
+ console.log(`分类完成: OCR告警 ${ocrAlarms.length}个, 事件告警 ${eventAlarms.length}个`);
+
+ // 处理OCR告警
+ if (ocrAlarms.length > 0) {
+ const ocrSuccess = await handleOcrAlerts();
+ console.log(`OCR告警处理${ocrSuccess ? '成功' : '失败'}`);
+ }
+
+ // 处理事件告警
+ if (eventAlarms.length > 0) {
+ const eventIds = eventAlarms.map(a => a.id);
+ const res = await robotApi.handleAlarmEvent({
+ eventIds: eventIds
+ });
+
+ if (res.code === 200) {
+ console.log('事件告警处理成功');
+ // 更新本地状态
+ alarmList.value = alarmList.value.map(a =>
+ eventIds.includes(a.id) ? { ...a, status: 'done', isRead: true } : a
+ );
+ } else {
+ console.error('事件告警处理失败:', res);
+ }
+ }
+
+ // 重新获取告警列表
+ if (alarmTab.value === 'pending') {
+ await getUnhandledAlarmMessages();
+ } else {
+ await getHandledAlarmMessages();
+ }
+
+ // 更新告警数量
+ await getAlarmEventCount();
+
+ // 关闭确认对话框
+ showConfirmDialog.value = false;
+ } catch (error) {
+ console.error('一键处理失败:', error);
+ // 关闭确认对话框
+ showConfirmDialog.value = false;
+ }
+};
+
+// 添加数据加载状态
+const isLoading = ref(false);
+const loadingError = ref('');
+
+// 获取告警数量
+const getAlarmEventCount = async () => {
+ try {
+ isLoading.value = true;
+ loadingError.value = '';
+
+ console.log('开始获取告警数量,机器人ID:', robotId.value);
+ const res = await robotApi.getAlarmEventCount({
+ number: robotId.value
});
if (res.code === 200) {
- // 更新告警列表
- alarmList.value = res.data.map(item => ({
- id: item.id,
- group: 'event',
- level: item.level === 1 ? 'I级' : 'II级',
- status: item.status === 1 ? 'pending' : 'done',
- isRead: item.isRead === 1,
- type: item.type === 1 ? 'warning' : 'danger',
- content: item.content,
- time: item.createTime
- }));
+ console.log('获取告警数量成功,原始数据:', res.data);
+ alertCount.value = res.data.alert_count || 0;
+ processedCount.value = res.data.processed_count || 0;
+
+ // 直接更新显示用的计数
+ pendingCount.value = alertCount.value;
+ doneCount.value = processedCount.value;
+
+ console.log('告警数量更新:', {
+ 未处理: alertCount.value,
+ 已处理: processedCount.value,
+ 当前标签: alarmTab.value,
+ pendingCount: pendingCount.value,
+ doneCount: doneCount.value
+ });
+ } else {
+ console.error('获取告警数量失败,错误码:', res.code, '错误信息:', res.msg || res.message);
+ loadingError.value = `获取告警数量失败: ${res.msg || res.message || '未知错误'}`;
}
} catch (error) {
- console.error('获取告警事件列表失败:', error);
+ console.error('获取告警数量失败,异常:', error);
+ loadingError.value = `获取告警数量失败: ${error.message || '未知错误'}`;
+ } finally {
+ isLoading.value = false;
+ }
+};
+
+// 修改获取未处理告警消息列表函数,确保参数正确传递
+const getUnhandledAlarmMessages = async () => {
+ try {
+ isLoading.value = true;
+ loadingError.value = '';
+
+ console.log('获取未处理告警消息列表,参数:', {
+ skip: (currentPage.value - 1) * pageSize.value,
+ limit: pageSize.value,
+ number: robotId.value
+ });
+
+ const res = await robotApi.getUnhandledAlarmMessages({
+ skip: (currentPage.value - 1) * pageSize.value,
+ limit: pageSize.value,
+ number: robotId.value
+ });
+
+ if (res.code === 200) {
+ console.log('获取未处理告警消息列表成功:', res.data);
+ // 清空现有告警列表
+ alarmList.value = [];
+
+ // 处理高温感知报警
+ if (res.data.high_temperature_message && res.data.high_temperature_message.length > 0) {
+ const highTempAlarms = res.data.high_temperature_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "I级", // 高温报警为I级
+ status: "pending",
+ isRead: false,
+ type: "warning",
+ content: "高温感知报警",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || []
+ }));
+ alarmList.value.push(...highTempAlarms);
+ }
+
+ // 处理吸烟报警
+ if (res.data.smoke_message && res.data.smoke_message.length > 0) {
+ const smokeAlarms = res.data.smoke_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "I级",
+ status: "pending",
+ isRead: false,
+ type: "danger",
+ content: "吸烟报警",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || []
+ }));
+ alarmList.value.push(...smokeAlarms);
+ }
+
+ // 处理长时间滞留报警
+ if (res.data.long_stay_message && res.data.long_stay_message.length > 0) {
+ const longStayAlarms = res.data.long_stay_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "II级",
+ status: "pending",
+ isRead: false,
+ type: "warning",
+ content: "长时间滞留报警",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || []
+ }));
+ alarmList.value.push(...longStayAlarms);
+ }
+
+ // 处理空气质量报警
+ if (res.data.air_quality_message && res.data.air_quality_message.length > 0) {
+ const airQualityAlarms = res.data.air_quality_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "II级",
+ status: "pending",
+ isRead: false,
+ type: "warning",
+ content: "空气质量报警",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ temperature: item.temperature
+ }));
+ alarmList.value.push(...airQualityAlarms);
+ }
+
+ // 处理急停按下
+ if (res.data.stop_emergency_message && res.data.stop_emergency_message.length > 0) {
+ const emergencyAlarms = res.data.stop_emergency_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "I级",
+ status: "pending",
+ isRead: false,
+ type: "danger",
+ content: "急停按下",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || []
+ }));
+ alarmList.value.push(...emergencyAlarms);
+ }
+
+ // 处理语音未接通
+ if (res.data.voice_connect_message && res.data.voice_connect_message.length > 0) {
+ const voiceAlarms = res.data.voice_connect_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "II级",
+ status: "pending",
+ isRead: false,
+ type: "warning",
+ content: "语音未接通",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || []
+ }));
+ alarmList.value.push(...voiceAlarms);
+ }
+
+ // 处理日常巡检(包括读表)
+ if (res.data.daily_inspect_message && res.data.daily_inspect_message.length > 0) {
+ const inspectAlarms = res.data.daily_inspect_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "meter", // 日常巡检归类为仪表类
+ level: "II级",
+ status: "pending",
+ isRead: false,
+ type: "warning",
+ content: "日常巡检",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ temperature: item.temperature
+ }));
+ alarmList.value.push(...inspectAlarms);
+ }
+ } else {
+ console.error('获取未处理告警消息列表失败:', res);
+ loadingError.value = `获取未处理告警消息列表失败: ${res.msg || res.message || '未知错误'}`;
+ }
+ } catch (error) {
+ console.error('获取未处理告警消息列表失败:', error);
+ loadingError.value = `获取未处理告警消息列表失败: ${error.message || '未知错误'}`;
+ } finally {
+ isLoading.value = false;
+ }
+};
+
+// 获取已处理告警消息列表
+const getHandledAlarmMessages = async () => {
+ try {
+ const res = await robotApi.getHandledAlarmMessages({
+ skip: (currentPage.value - 1) * pageSize.value,
+ limit: pageSize.value,
+ number: robotId.value
+ });
+
+ if (res.code === 200) {
+ // 清空现有告警列表
+ alarmList.value = [];
+
+ // 处理高温感知报警
+ if (res.data.high_temperature_message && res.data.high_temperature_message.length > 0) {
+ const highTempAlarms = res.data.high_temperature_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "I级", // 高温报警为I级
+ status: "done",
+ isRead: true,
+ type: "warning",
+ content: "高温感知报警",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ remark: item.remark
+ }));
+ alarmList.value.push(...highTempAlarms);
+ }
+
+ // 处理吸烟报警
+ if (res.data.smoke_message && res.data.smoke_message.length > 0) {
+ const smokeAlarms = res.data.smoke_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "I级",
+ status: "done",
+ isRead: true,
+ type: "danger",
+ content: "吸烟报警",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ remark: item.remark
+ }));
+ alarmList.value.push(...smokeAlarms);
+ }
+
+ // 处理长时间滞留报警
+ if (res.data.long_stay_message && res.data.long_stay_message.length > 0) {
+ const longStayAlarms = res.data.long_stay_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "II级",
+ status: "done",
+ isRead: true,
+ type: "warning",
+ content: "长时间滞留报警",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ remark: item.remark
+ }));
+ alarmList.value.push(...longStayAlarms);
+ }
+
+ // 处理空气质量报警
+ if (res.data.air_quality_message && res.data.air_quality_message.length > 0) {
+ const airQualityAlarms = res.data.air_quality_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "II级",
+ status: "done",
+ isRead: true,
+ type: "warning",
+ content: "空气质量报警",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ temperature: item.temperature,
+ remark: item.remark
+ }));
+ alarmList.value.push(...airQualityAlarms);
+ }
+
+ // 处理急停按下
+ if (res.data.stop_emergency_message && res.data.stop_emergency_message.length > 0) {
+ const emergencyAlarms = res.data.stop_emergency_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "I级",
+ status: "done",
+ isRead: true,
+ type: "danger",
+ content: "急停按下",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ remark: item.remark
+ }));
+ alarmList.value.push(...emergencyAlarms);
+ }
+
+ // 处理语音未接通
+ if (res.data.voice_connect_message && res.data.voice_connect_message.length > 0) {
+ const voiceAlarms = res.data.voice_connect_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "event",
+ level: "II级",
+ status: "done",
+ isRead: true,
+ type: "warning",
+ content: "语音未接通",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ remark: item.remark
+ }));
+ alarmList.value.push(...voiceAlarms);
+ }
+
+ // 处理日常巡检(包括读表)
+ if (res.data.daily_inspect_message && res.data.daily_inspect_message.length > 0) {
+ const inspectAlarms = res.data.daily_inspect_message.map(item => ({
+ id: item.eventId,
+ messageId: item.messageId,
+ group: "meter", // 日常巡检归类为仪表类
+ level: "II级",
+ status: "done",
+ isRead: true,
+ type: "warning",
+ content: "日常巡检",
+ time: formatEventTime(item.createTime),
+ imageUrl: item.imageUrl || [],
+ temperature: item.temperature,
+ remark: item.remark
+ }));
+ alarmList.value.push(...inspectAlarms);
+ }
+ }
+ } catch (error) {
+ console.error('获取已处理告警消息列表失败:', error);
+ }
+};
+
+// 修改原有的获取告警事件列表函数,根据当前tab调用对应的API
+const getAlarmEventList = async () => {
+ if (alarmTab.value === 'pending') {
+ await getUnhandledAlarmMessages();
+ } else {
+ await getHandledAlarmMessages();
}
};
@@ -617,6 +1017,7 @@ const filteredEventAlarms = computed(() => {
}
return eventAlarms.value;
});
+
const filteredMeterAlarms = computed(() => {
if (alarmTab.value === "pending") {
// 未处理标签下显示待处理和超时未处理的告警
@@ -682,14 +1083,15 @@ const handleMouseLeave = (listType) => {
};
onMounted(async () => {
- login();
+ console.log('RobotDetail组件挂载,机器人ID:', robotId.value);
+
// 先登录获取token
- // const loginSuccess = await login();
- // if (loginSuccess) {
- // // 获取机器人状态
- // await getRobotStatus();
- // }
+ await login();
+ console.log('登录成功,开始初始化数据');
+
+ // 启动自动滚动
startAutoScroll();
+
// 获取任务列表DOM引用
nextTick(() => {
const taskListElement = document.querySelector('.task-list');
@@ -700,7 +1102,29 @@ onMounted(async () => {
taskListElement.addEventListener('mouseleave', () => handleMouseLeave('taskList'));
}
});
- await getAlarmEventList();
+
+ try {
+ // 先获取告警数量 - 确保在获取列表前先更新数量
+ console.log('开始初始化告警数据');
+ await getAlarmEventCount();
+ console.log('告警数量获取完成,开始获取告警列表');
+
+ // 然后获取告警列表
+ await getAlarmEventList();
+ console.log('告警列表获取完成');
+
+ // 再次确认计数正确 - 防止列表加载过程中数量变化
+ await getAlarmEventCount();
+
+ console.log('数据初始化完成,当前计数:', {
+ pendingCount: pendingCount.value,
+ doneCount: doneCount.value,
+ alertCount: alertCount.value,
+ processedCount: processedCount.value
+ });
+ } catch (error) {
+ console.error('初始化告警数据失败:', error);
+ }
});
onUnmounted(() => {
@@ -731,58 +1155,182 @@ const showMeterModal = ref(false);
const currentAlarmData = ref({});
const currentMeterData = ref({});
+// 添加获取告警详情的方法
+const fetchAlarmDetail = async (messageId) => {
+ try {
+ const res = await robotApi.getAlarmEventDetail(messageId);
+ if (res.code === 200) {
+ console.log('获取告警详情成功:', res.data);
+ return res.data;
+ }
+ return null;
+ } catch (error) {
+ console.error('获取告警详情失败:', error);
+ return null;
+ }
+};
+
// 显示告警详情
-const showAlarmDetail = (alarm) => {
+const showAlarmDetail = async (alarm) => {
+ // 如果有messageId,先获取详细信息
+ let detailData = null;
+ if (alarm.messageId) {
+ detailData = await fetchAlarmDetail(alarm.messageId);
+ }
+
+ // 合并详情数据和本地数据
currentAlarmData.value = {
title: '告警详情',
- mainImage: '../assets/img/camera-thumb1.jpg', // 替换为实际图片
- subImages: ['../assets/img/camera-thumb2.jpg', '../assets/img/camera-thumb3.jpg'], // 替换为实际图片
+ messageId: alarm.messageId,
+ mainImage: detailData?.imageUrl && detailData.imageUrl.length > 0
+ ? detailData.imageUrl[0]
+ : (alarm.imageUrl && alarm.imageUrl.length > 0 ? alarm.imageUrl[0] : '../assets/img/camera-thumb1.jpg'),
+ subImages: [],
type: alarm.content,
time: alarm.time,
- robotName: robotId.value,
- status: alarm.status === 'pending' ? '未处理' : '已处理'
+ robotName: detailData?.name || robotId.value,
+ status: alarm.status === 'pending' ? '未处理' : '已处理',
+ temperature: detailData?.temperature || alarm.temperature || '',
+ remark: detailData?.remark || alarm.remark || ''
};
+
+ // 添加视频流地址
+ if (detailData) {
+ // 添加子图片(如果有)
+ if (detailData.imageUrl && detailData.imageUrl.length > 1) {
+ currentAlarmData.value.subImages = detailData.imageUrl.slice(1);
+ } else if (alarm.imageUrl && alarm.imageUrl.length > 1) {
+ currentAlarmData.value.subImages = alarm.imageUrl.slice(1);
+ } else {
+ currentAlarmData.value.subImages = [];
+ }
+
+ // 添加视频流地址
+ currentAlarmData.value.flvPtz = detailData.flvPtz || '';
+ currentAlarmData.value.flvTherm = detailData.flvTherm || '';
+ currentAlarmData.value.flvThermLight = detailData.flvThermLight || '';
+ } else {
+ // 使用本地数据
+ currentAlarmData.value.subImages = alarm.imageUrl && alarm.imageUrl.length > 1
+ ? alarm.imageUrl.slice(1)
+ : [];
+ }
+
showAlarmModal.value = true;
};
// 显示仪表异常详情
-const showMeterDetail = (alarm) => {
+const showMeterDetail = async (alarm) => {
+ // 如果有messageId,先获取详细信息
+ let detailData = null;
+ if (alarm.messageId) {
+ detailData = await fetchAlarmDetail(alarm.messageId);
+ }
+
+ // 合并详情数据和本地数据
currentMeterData.value = {
title: '仪表异常详情',
- mainImage: '../assets/img/camera-thumb1.jpg', // 替换为实际图片
- subImages: ['../assets/img/camera-thumb2.jpg', '../assets/img/camera-thumb3.jpg'], // 替换为实际图片
+ messageId: alarm.messageId,
+ mainImage: detailData?.imageUrl && detailData.imageUrl.length > 0
+ ? detailData.imageUrl[0]
+ : (alarm.imageUrl && alarm.imageUrl.length > 0 ? alarm.imageUrl[0] : '../assets/img/camera-thumb1.jpg'),
+ subImages: [],
type: alarm.content,
time: alarm.time,
- robotName: robotId.value,
- status: alarm.status === 'pending' ? '未处理' : '已处理'
+ robotName: detailData?.name || robotId.value,
+ status: alarm.status === 'pending' ? '未处理' : '已处理',
+ temperature: detailData?.temperature || alarm.temperature || '',
+ remark: detailData?.remark || alarm.remark || ''
};
+
+ // 添加视频流地址
+ if (detailData) {
+ // 添加子图片(如果有)
+ if (detailData.imageUrl && detailData.imageUrl.length > 1) {
+ currentMeterData.value.subImages = detailData.imageUrl.slice(1);
+ } else if (alarm.imageUrl && alarm.imageUrl.length > 1) {
+ currentMeterData.value.subImages = alarm.imageUrl.slice(1);
+ } else {
+ currentMeterData.value.subImages = [];
+ }
+
+ // 添加视频流地址
+ currentMeterData.value.flvPtz = detailData.flvPtz || '';
+ currentMeterData.value.flvTherm = detailData.flvTherm || '';
+ currentMeterData.value.flvThermLight = detailData.flvThermLight || '';
+ } else {
+ // 使用本地数据
+ currentMeterData.value.subImages = alarm.imageUrl && alarm.imageUrl.length > 1
+ ? alarm.imageUrl.slice(1)
+ : [];
+ }
+
showMeterModal.value = true;
};
// 处理告警确认
-const handleAlarmConfirm = (data) => {
+const handleAlarmConfirm = async (data) => {
console.log('确认处理告警:', data);
+ // 调用处理单个告警的方法
+ await handleSingleAlarm(data);
showAlarmModal.value = false;
};
// 处理告警上报
-const handleAlarmReport = (data) => {
+const handleAlarmReport = async (data) => {
console.log('处理并上报告警:', data);
+ // 调用处理单个告警的方法
+ await handleSingleAlarm(data);
showAlarmModal.value = false;
};
// 处理仪表确认
-const handleMeterConfirm = (data) => {
+const handleMeterConfirm = async (data) => {
console.log('确认处理仪表异常:', data);
+ // 调用处理单个告警的方法
+ await handleSingleAlarm(data);
showMeterModal.value = false;
};
// 处理仪表上报
-const handleMeterReport = (data) => {
+const handleMeterReport = async (data) => {
console.log('处理并上报仪表异常:', data);
+ // 调用处理单个告警的方法
+ await handleSingleAlarm(data);
showMeterModal.value = false;
};
+// 添加处理单个告警事件的方法
+const handleSingleAlarm = async (data) => {
+ try {
+ // 获取当前告警的messageId
+ const messageId = data.messageId;
+
+ if (!messageId) {
+ console.error('缺少messageId,无法处理告警');
+ return;
+ }
+
+ const res = await robotApi.handleSingleAlarmEvent({
+ messageId: messageId,
+ remark: data.remark || '',
+ number: robotId.value
+ });
+
+ if (res.code === 200) {
+ console.log('处理单个告警成功');
+
+ // 重新获取告警列表
+ await getAlarmEventList();
+
+ // 更新告警数量
+ await getAlarmEventCount();
+ }
+ } catch (error) {
+ console.error('处理单个告警失败:', error);
+ }
+};
+
const showViewAllModal = ref(false);
const currentEvent = ref({
mainImage: '../assets/img/camera-thumb1.jpg',
@@ -1242,25 +1790,24 @@ const showConfirmDialog = ref(false);
const confirmDialogMessage = ref('');
// 监听alarmTab变化
-watch(alarmTab, async () => {
+watch(alarmTab, async (newValue) => {
+ console.log('标签切换为:', newValue);
+ // 获取告警列表
await getAlarmEventList();
+
+ // 重新获取告警数量
+ await getAlarmEventCount();
});
// 添加处理单个告警事件的函数
const handleAlarmEvent = async (alarm) => {
try {
- // 调用API处理告警事件
- const res = await robotApi.handleAlarmEvent({
- eventIds: [alarm.id]
- });
-
- if (res.code === 200) {
- // 更新本地状态
- alarm.status = 'done';
- alarm.isRead = true;
-
- // 刷新告警列表
- await getAlarmEventList();
+ // 如果是仪表类型的告警或日常巡检,直接打开详情弹窗
+ if (alarm.group === "meter" || alarm.content === "日常巡检") {
+ await showMeterDetail(alarm);
+ } else {
+ // 其他类型的告警也打开详情弹窗
+ await showAlarmDetail(alarm);
}
} catch (error) {
console.error('处理告警事件失败:', error);