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 @@ @@ -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 @@ @@ -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 @@

状态:

-

{{ 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);