首页完善
This commit is contained in:
parent
36aef50419
commit
0b23ae04bb
@ -102,6 +102,24 @@ export const robotApi = {
|
||||
url: `/api/v1/events/alertFront/${messageId}`,
|
||||
method: 'get',
|
||||
})
|
||||
},
|
||||
// 查看全部告警事件接口
|
||||
getAlarmEventList: (number) => {
|
||||
return service({
|
||||
url: `/api/v1/events/getAllAlertMessage/${number}`,
|
||||
method: 'get',
|
||||
})
|
||||
},
|
||||
// 查看全部告警事件接口(支持分页)
|
||||
getAlarmDetailList: (params) => {
|
||||
return service({
|
||||
url: `/api/v1/events/getAllAlertMessage/${params.number}`,
|
||||
method: 'get',
|
||||
params: {
|
||||
offset: params.offset,
|
||||
limit: params.limit
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
BIN
src/assets/img/empty_robot.png
Normal file
BIN
src/assets/img/empty_robot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
@ -7,9 +7,9 @@
|
||||
type="text"
|
||||
placeholder="请输入机器人搜索"
|
||||
v-model="searchText"
|
||||
@input="handleSearchInput"
|
||||
@keyup.enter="handleSearchInput"
|
||||
>
|
||||
<img src="../assets/img/fdj.png" alt="搜索" class="search-icon" />
|
||||
<img src="../assets/img/fdj.png" alt="搜索" class="search-icon" @click="handleSearchInput"/>
|
||||
</div>
|
||||
<CustomSelect
|
||||
v-model="selectedFilter"
|
||||
@ -32,9 +32,11 @@
|
||||
<button class="retry-button" @click="fetchRobotList">重试</button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="groupedRobots.length === 0" class="empty-state">
|
||||
<div class="empty-icon">🔍</div>
|
||||
<div class="empty-text">没有找到机器人</div>
|
||||
<div v-else-if="groupedRobots.length === 0" class="empty-state-container">
|
||||
<EmptyState
|
||||
subtitle="暂无机器人信息"
|
||||
:iconSrc="emptyRobot"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<template v-else>
|
||||
@ -68,10 +70,23 @@ import { ref, onMounted, onUnmounted, computed, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import BatteryIndicator from './common/BatteryIndicator.vue'
|
||||
import CustomSelect from './common/CustomSelect.vue'
|
||||
import EmptyState from './common/EmptyState.vue'
|
||||
import { homeApi } from '../api/index'
|
||||
import emptyRobot from '../assets/img/empty_robot.png'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
// 防抖函数
|
||||
const debounce = (fn, delay) => {
|
||||
let timer = null
|
||||
return function(...args) {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(() => {
|
||||
fn.apply(this, args)
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索和筛选
|
||||
const searchText = ref('')
|
||||
const filterOptions = ['全部', '在线', '离线', '故障']
|
||||
@ -83,14 +98,14 @@ const loading = ref(false)
|
||||
const error = ref(null)
|
||||
|
||||
// 获取机器人列表
|
||||
const fetchRobotList = async () => {
|
||||
const fetchRobotList = async (searchNumber = null) => {
|
||||
loading.value = true
|
||||
error.value = null
|
||||
|
||||
try {
|
||||
const res = await homeApi.getRobotList({
|
||||
tenantInfoId: '4fff5d4bcc4b4239941ff077a0da8958', // 租户id
|
||||
number: null, // 机器人名
|
||||
number: searchNumber, // 机器人名,用于搜索
|
||||
status: null, // 是否故障
|
||||
onlineStatus: null // 在线状态
|
||||
})
|
||||
@ -210,8 +225,9 @@ const groupedRobots = computed(() => {
|
||||
return grouped.filter(group => group.robots.length > 0)
|
||||
})
|
||||
|
||||
const handleSearchInput = (e) => {
|
||||
searchText.value = e.target.value
|
||||
const handleSearchInput = () => {
|
||||
console.log('点击搜索:', searchText.value)
|
||||
searchRobots()
|
||||
}
|
||||
|
||||
const handleWheel = (e) => {
|
||||
@ -258,7 +274,14 @@ onUnmounted(() => {
|
||||
// 监听筛选条件变化,重新获取数据
|
||||
watch(selectedFilter, () => {
|
||||
console.log('筛选条件变化:', selectedFilter.value)
|
||||
searchRobots() // 重新搜索,应用筛选条件
|
||||
})
|
||||
|
||||
// 搜索机器人
|
||||
const searchRobots = () => {
|
||||
console.log('搜索机器人:', searchText.value)
|
||||
fetchRobotList(searchText.value)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -558,4 +581,20 @@ watch(selectedFilter, () => {
|
||||
.robot-status.待机中 {
|
||||
color: #00ffff;
|
||||
}
|
||||
|
||||
.empty-state-container {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
}
|
||||
:deep(.empty-icon) {
|
||||
width: 213px !important;
|
||||
height: 150px !important;
|
||||
}
|
||||
:deep(.empty-subtitle) {
|
||||
color: #fff!important;
|
||||
font-size: 16px!important;
|
||||
}
|
||||
</style>
|
||||
@ -53,6 +53,10 @@ const props = defineProps({
|
||||
variant: {
|
||||
type: String,
|
||||
default: 'default' // 可选值:'default', 'search'
|
||||
},
|
||||
font: {
|
||||
type: String,
|
||||
default: 'default' // 可选值:'default', 'small'
|
||||
}
|
||||
})
|
||||
|
||||
@ -108,6 +112,7 @@ const selectOption = (option) => {
|
||||
.select-text {
|
||||
color: #B9E8FF;
|
||||
margin-right: 25px;
|
||||
font-size: v-bind("font === 'small' ? '12px' : '14px'");
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
@ -137,7 +142,7 @@ const selectOption = (option) => {
|
||||
color: #B9E8FF;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
font-size: v-bind("size === 'small' ? '12px' : '12px'");
|
||||
font-size: v-bind("font === 'small' ? '12px' : '12px'");
|
||||
line-height: v-bind("size === 'small' ? '18px' : '20px'");
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import small_title from '../../assets/img/small_title.png';
|
||||
const props = defineProps({
|
||||
title: String
|
||||
});
|
||||
console.log(props.title,"--------------");
|
||||
|
||||
const titleBgSrc = computed(() => {
|
||||
if (props.title === '最新告警') {
|
||||
return title2;
|
||||
|
||||
@ -164,8 +164,8 @@
|
||||
<!-- 底部按钮 -->
|
||||
<div class="modal-footer">
|
||||
<button class="btn cancel" @click="handleClose">取消</button>
|
||||
<button class="btn confirm" @click="handleConfirm">确认处理</button>
|
||||
<button class="btn report" @click="handleReport">处理并上报</button>
|
||||
<button class="btn confirm" @click="handleConfirm" v-if="alarmData.status !== '已处理'">确认处理</button>
|
||||
<button class="btn report" @click="handleReport" v-if="alarmData.status !== '已处理'">处理并上报</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -271,15 +271,17 @@ const updateRemark = (e) => {
|
||||
<style scoped>
|
||||
.alarm-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
/* background: rgba(0, 0, 0, 0.7); */
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
|
||||
@ -49,15 +49,17 @@ const handleCancel = () => {
|
||||
<style scoped>
|
||||
.confirm-dialog-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.confirm-dialog {
|
||||
|
||||
@ -13,10 +13,10 @@
|
||||
<div class="left-content">
|
||||
<!-- 主监控 -->
|
||||
<div class="main-monitor">
|
||||
<div class="monitor-title">主监控</div>
|
||||
<div class="monitor-title">{{ selectedEvent ? selectedEvent.etypeName : '主监控' }}</div>
|
||||
<div class="monitor-view">
|
||||
<template v-if="eventData.mainImage">
|
||||
<img :src="eventData.mainImage" alt="" @error="imgLoadError.main = true" v-show="!imgLoadError.main" />
|
||||
<template v-if="selectedImage">
|
||||
<img :src="selectedImage" alt="" @error="imgLoadError.main = true" v-show="!imgLoadError.main" />
|
||||
<EmptyState
|
||||
v-if="imgLoadError.main"
|
||||
subtitle="暂无图片"
|
||||
@ -35,33 +35,18 @@
|
||||
|
||||
<!-- 云台监控和热成像 -->
|
||||
<div class="sub-monitors">
|
||||
<div class="monitor-item">
|
||||
<div class="monitor-title">云台监控</div>
|
||||
<div class="monitor-item" v-for="index in 2" :key="index-1">
|
||||
<div class="monitor-title">{{ index === 1 ? '云台监控' : '热成像' }}</div>
|
||||
<div class="monitor-view">
|
||||
<template v-if="eventData.subImages && eventData.subImages.length > 0">
|
||||
<img :src="eventData.subImages[0]" alt="" @error="imgLoadError.sub1 = true" v-show="!imgLoadError.sub1" />
|
||||
<EmptyState
|
||||
v-if="imgLoadError.sub1"
|
||||
subtitle="暂无图片"
|
||||
:iconSrc="empty"
|
||||
class="video-empty-state"
|
||||
<template v-if="selectedEvent && selectedEvent.imageList && selectedEvent.imageList.length >= index">
|
||||
<img
|
||||
:src="selectedEvent.imageList[index-1]"
|
||||
alt=""
|
||||
@error="imgLoadError['sub'+(index-1)] = true"
|
||||
v-show="!imgLoadError['sub'+(index-1)]"
|
||||
/>
|
||||
</template>
|
||||
<EmptyState
|
||||
v-else
|
||||
subtitle="暂无图片"
|
||||
:iconSrc="empty"
|
||||
class="video-empty-state"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="monitor-item">
|
||||
<div class="monitor-title">热成像</div>
|
||||
<div class="monitor-view">
|
||||
<template v-if="eventData.subImages && eventData.subImages.length > 1">
|
||||
<img :src="eventData.subImages[1]" alt="" @error="imgLoadError.sub2 = true" v-show="!imgLoadError.sub2" />
|
||||
<EmptyState
|
||||
v-if="imgLoadError.sub2"
|
||||
v-if="imgLoadError['sub'+(index-1)]"
|
||||
subtitle="暂无图片"
|
||||
:iconSrc="empty"
|
||||
class="video-empty-state"
|
||||
@ -89,11 +74,12 @@
|
||||
<th>状态</th>
|
||||
</tr>
|
||||
<tr class="table-content">
|
||||
<td>{{ eventData.type }}</td>
|
||||
<td>{{ eventData.time }}</td>
|
||||
<td>{{ eventData.robotName }}</td>
|
||||
<td :class="eventData.status">{{ eventData.status }}</td>
|
||||
<td>{{ selectedEvent ? selectedEvent.etypeName : '' }}</td>
|
||||
<td>{{ rawCreateTime }}</td>
|
||||
<td>{{ selectedEvent ? selectedEvent.name : '' }}</td>
|
||||
<td :class="handleStatus">{{ handleStatus }}</td>
|
||||
</tr>
|
||||
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@ -102,20 +88,32 @@
|
||||
<div class="remark-section">
|
||||
<TitleBlock>备注</TitleBlock>
|
||||
<div class="remark-content">
|
||||
<div class="remark-box" contenteditable="true" data-placeholder="请输入备注信息..." @input="updateRemark"></div>
|
||||
<div
|
||||
class="remark-box"
|
||||
contenteditable="true"
|
||||
data-placeholder="请输入备注信息..."
|
||||
@input="updateRemark"
|
||||
v-text="selectedEvent && selectedEvent.remark ? selectedEvent.remark : ''"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧监控视图列表 -->
|
||||
<div class="right-content">
|
||||
<TitleBlock>监控视图</TitleBlock>
|
||||
<TitleBlock>告警列表</TitleBlock>
|
||||
<div class="monitor-list">
|
||||
<div v-for="(monitor, index) in monitorList" :key="index" class="monitor-item">
|
||||
<div class="monitor-title">{{ monitor.title }}</div>
|
||||
<div
|
||||
v-for="(event, index) in eventList"
|
||||
:key="event.messageId"
|
||||
class="monitor-item"
|
||||
:class="{ 'active': selectedEventIndex === index }"
|
||||
@click="selectEvent(event, index)"
|
||||
>
|
||||
<div class="monitor-title">{{ event.etypeName }}</div>
|
||||
<div class="monitor-view">
|
||||
<template v-if="monitor.image">
|
||||
<img :src="monitor.image" alt="" @error="setMonitorError(index)" v-show="!monitorErrors[index]" />
|
||||
<template v-if="event.imagePreview">
|
||||
<img :src="event.imagePreview" alt="" @error="setMonitorError(index)" v-show="!monitorErrors[index]" />
|
||||
<EmptyState
|
||||
v-if="monitorErrors[index]"
|
||||
subtitle="暂无图片"
|
||||
@ -138,15 +136,23 @@
|
||||
<!-- 底部按钮 -->
|
||||
<div class="modal-footer">
|
||||
<button class="btn cancel" @click="handleClose">取消</button>
|
||||
<button class="btn confirm" @click="handleConfirm">确认处理</button>
|
||||
<button class="btn report" @click="handleReport">处理并上报</button>
|
||||
<button
|
||||
class="btn confirm"
|
||||
@click="handleConfirm"
|
||||
v-if="selectedEvent && selectedEvent.handle !== '1'"
|
||||
>确认处理</button>
|
||||
<button
|
||||
class="btn report"
|
||||
@click="handleReport"
|
||||
v-if="selectedEvent && selectedEvent.handle !== '1'"
|
||||
>处理并上报</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import { ref, onMounted, watch, computed } from 'vue';
|
||||
import TitleBlock from '../common/TitleBlock.vue';
|
||||
import EmptyState from '../common/EmptyState.vue';
|
||||
import empty from '../../assets/img/empty.png';
|
||||
@ -170,14 +176,79 @@ const props = defineProps({
|
||||
monitorList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
eventList: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
});
|
||||
|
||||
// 当前选中的事件和索引
|
||||
const selectedEventIndex = ref(0);
|
||||
const selectedEvent = ref(null);
|
||||
const selectedImage = ref('');
|
||||
|
||||
// 格式化时间
|
||||
const formatTime = computed(() => {
|
||||
if (!selectedEvent.value || !selectedEvent.value.createTime) {
|
||||
return '';
|
||||
}
|
||||
// 如果已经是格式化的时间,直接返回
|
||||
if (typeof selectedEvent.value.createTime === 'string' && selectedEvent.value.createTime.includes('-')) {
|
||||
return selectedEvent.value.createTime;
|
||||
}
|
||||
// 有T换成" "
|
||||
if (selectedEvent.value.createTime.includes('T')) {
|
||||
selectedEvent.value.createTime = selectedEvent.value.createTime.replace('T', ' ');
|
||||
}
|
||||
// 否则尝试格式化
|
||||
try {
|
||||
const date = new Date(Number(selectedEvent.value.createTime));
|
||||
return date.toLocaleString();
|
||||
} catch (e) {
|
||||
return selectedEvent.value.createTime || '';
|
||||
}
|
||||
});
|
||||
|
||||
// 新增:原始createTime显示(去掉T)
|
||||
const rawCreateTime = computed(() => {
|
||||
if (!selectedEvent.value || !selectedEvent.value.createTime) return '-';
|
||||
return selectedEvent.value.createTime.replace('T', ' ');
|
||||
});
|
||||
|
||||
// 处理状态计算属性
|
||||
const handleStatus = computed(() => {
|
||||
if (!selectedEvent.value) {
|
||||
return '未处理';
|
||||
}
|
||||
return selectedEvent.value.handle === '1' ? '已处理' : '未处理';
|
||||
});
|
||||
|
||||
// 选择事件
|
||||
const selectEvent = (event, index) => {
|
||||
selectedEventIndex.value = index;
|
||||
selectedEvent.value = event;
|
||||
selectedImage.value = event.imagePreview || (event.imageList && event.imageList.length > 0 ? event.imageList[0] : '');
|
||||
|
||||
// 重置图片加载错误状态
|
||||
resetImgLoadError();
|
||||
};
|
||||
|
||||
// 初始化选择第一个事件
|
||||
watch(() => props.eventList, (newList) => {
|
||||
if (newList && newList.length > 0) {
|
||||
selectEvent(newList[0], 0);
|
||||
} else {
|
||||
selectedEvent.value = null;
|
||||
selectedImage.value = '';
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
// 追踪图片加载错误状态
|
||||
const imgLoadError = ref({
|
||||
main: false,
|
||||
sub1: false,
|
||||
sub2: false
|
||||
sub0: false,
|
||||
sub1: false
|
||||
});
|
||||
|
||||
// 监控视图列表的错误状态
|
||||
@ -198,8 +269,8 @@ const setMonitorError = (index) => {
|
||||
const resetImgLoadError = () => {
|
||||
imgLoadError.value = {
|
||||
main: false,
|
||||
sub1: false,
|
||||
sub2: false
|
||||
sub0: false,
|
||||
sub1: false
|
||||
};
|
||||
monitorErrors.value = [];
|
||||
};
|
||||
@ -236,14 +307,14 @@ const handleClose = () => {
|
||||
|
||||
const handleConfirm = () => {
|
||||
emit('confirm', {
|
||||
...props.eventData,
|
||||
...selectedEvent.value,
|
||||
remark: remarkText.value
|
||||
});
|
||||
};
|
||||
|
||||
const handleReport = () => {
|
||||
emit('report', {
|
||||
...props.eventData,
|
||||
...selectedEvent.value,
|
||||
remark: remarkText.value
|
||||
});
|
||||
};
|
||||
@ -252,20 +323,22 @@ const handleReport = () => {
|
||||
<style scoped>
|
||||
.event-detail-modal {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 1000;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
width: 890px;
|
||||
height: 900px;
|
||||
height: 800px;
|
||||
background: url("../../assets/img/alert2.png") no-repeat;
|
||||
background-size: 100% 100%;
|
||||
border-radius: 4px;
|
||||
@ -287,6 +360,7 @@ const handleReport = () => {
|
||||
font-size: 28px;
|
||||
letter-spacing: 4px;
|
||||
padding-left: 20px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
@ -327,8 +401,8 @@ const handleReport = () => {
|
||||
}
|
||||
|
||||
.main-monitor {
|
||||
min-height: 300px;
|
||||
height: 300px;
|
||||
min-height: 215px;
|
||||
height: 215px;
|
||||
}
|
||||
|
||||
.monitor-title {
|
||||
@ -337,7 +411,7 @@ const handleReport = () => {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 5px 10px;
|
||||
/* background: rgba(0, 21, 31, 0.2); */
|
||||
background: rgba(0, 21, 31, 0.5);
|
||||
color: #B9E8FF;
|
||||
font-size: 12px;
|
||||
z-index: 1;
|
||||
@ -410,7 +484,7 @@ const handleReport = () => {
|
||||
.remark-section{
|
||||
border:1px solid rgba(0,206,234,0.7);
|
||||
border-radius: 4px;
|
||||
height: 135px;
|
||||
height: 116px;
|
||||
}
|
||||
.remark-content {
|
||||
padding:10px;
|
||||
@ -434,7 +508,7 @@ const handleReport = () => {
|
||||
}
|
||||
|
||||
.monitor-list {
|
||||
padding:10px;
|
||||
padding:10px;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
@ -455,6 +529,16 @@ padding:10px;
|
||||
.monitor-list .monitor-item {
|
||||
margin-bottom: 10px;
|
||||
height: 150px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.monitor-list .monitor-item:hover {
|
||||
transform: scale(1.02);
|
||||
}
|
||||
|
||||
.monitor-list .monitor-item.active {
|
||||
border: 2px solid #00FFFF;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
</div>
|
||||
<CustomSelect
|
||||
v-model="selectedViews[camera.title]"
|
||||
:options="viewOptions"
|
||||
:options="getViewOptionsForCamera(camera)"
|
||||
size="small"
|
||||
variant="search"
|
||||
font="small"
|
||||
@ -108,6 +108,10 @@ import Icon3 from '../assets/img/icon3.png'
|
||||
import Icon4 from '../assets/img/icon4.png'
|
||||
import jkA from '../assets/img/jkA.png'
|
||||
import jkRobot from '../assets/img/jkRobot.png'
|
||||
import VideoPlayer from '../components/common/VideoPlayer.vue'
|
||||
import EmptyState from '../components/common/EmptyState.vue'
|
||||
import CustomWebRTCPlayer from '../components/common/CustomWebRTCPlayer.vue'
|
||||
import empty from '../assets/img/empty.png'
|
||||
|
||||
// 统计数据
|
||||
const statistics = ref([
|
||||
@ -180,14 +184,20 @@ const cameras = ref([
|
||||
|
||||
// 选中的视角
|
||||
const selectedViews = ref({
|
||||
'A区厂区监控': '视角1',
|
||||
'B区厂区监控': '视角1',
|
||||
'追随机器人监控': '视角1',
|
||||
'室外机器人监控': '视角1'
|
||||
'A区厂区监控': '',
|
||||
'B区厂区监控': '',
|
||||
'追随机器人监控': '',
|
||||
'室外机器人监控': ''
|
||||
});
|
||||
|
||||
// 视角选项
|
||||
const viewOptions = ['视角1', '视角2'];
|
||||
// 获取特定摄像头的视角选项
|
||||
const getViewOptionsForCamera = (camera) => {
|
||||
const cameraData = monitorStreams.value[camera.title];
|
||||
if (!cameraData) return [];
|
||||
|
||||
// 返回该摄像头所有可用的视角名称,无论是否有URL
|
||||
return Object.keys(cameraData);
|
||||
};
|
||||
|
||||
// 存储所有视频流数据
|
||||
const monitorStreams = ref({});
|
||||
@ -200,6 +210,15 @@ const fetchMonitorStreams = async () => {
|
||||
if (res.code === 200) {
|
||||
monitorStreams.value = res.data || {};
|
||||
console.log('获取监控视频流成功:', JSON.stringify(monitorStreams.value));
|
||||
|
||||
// 初始化每个摄像头的默认视角
|
||||
cameras.value.forEach(camera => {
|
||||
const viewOptions = getViewOptionsForCamera(camera);
|
||||
if (viewOptions.length > 0) {
|
||||
// 设置为第一个可用视角
|
||||
selectedViews.value[camera.title] = viewOptions[0];
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error('获取监控视频流失败:', res);
|
||||
}
|
||||
@ -211,7 +230,7 @@ const fetchMonitorStreams = async () => {
|
||||
// 判断流类型是否为WebRTC
|
||||
const isWebRTCStream = (url) => {
|
||||
if (!url) return false;
|
||||
return url.startsWith('webrtc://') || url.includes('31011500991180041301');
|
||||
return url.startsWith('webrtc://') || url.includes('31011500991180041301') || url.includes('34020000001320000');
|
||||
};
|
||||
|
||||
// 为摄像头获取视角列表
|
||||
@ -219,12 +238,13 @@ const getViewsForCamera = (camera) => {
|
||||
const cameraData = monitorStreams.value[camera.title];
|
||||
if (!cameraData) return [];
|
||||
|
||||
return viewOptions.map(viewName => {
|
||||
// 获取所有视角并转换为视图对象数组
|
||||
return Object.entries(cameraData).map(([viewName, streamUrl]) => {
|
||||
return {
|
||||
name: viewName,
|
||||
streamUrl: cameraData[viewName] || ''
|
||||
streamUrl: streamUrl || '' // 保留空字符串URL,不替换为"empty"标识符
|
||||
};
|
||||
}).filter(view => view.streamUrl); // 过滤掉没有流URL的视角
|
||||
}); // 不过滤掉没有流URL的视角,保留所有视角
|
||||
};
|
||||
|
||||
// 判断摄像头是否使用WebRTC
|
||||
@ -232,8 +252,8 @@ const isWebRTCCamera = (camera) => {
|
||||
const views = getViewsForCamera(camera);
|
||||
if (views.length === 0) return false;
|
||||
|
||||
// 检查第一个视角是否为WebRTC流
|
||||
return isWebRTCStream(views[0].streamUrl);
|
||||
// 检查任意视角是否为WebRTC流
|
||||
return views.some(view => isWebRTCStream(view.streamUrl));
|
||||
};
|
||||
|
||||
const showRobotListModal = ref(false);
|
||||
|
||||
@ -69,7 +69,7 @@
|
||||
v-model="alarmTab"
|
||||
:pending-count="pendingCount"
|
||||
:done-count="doneCount"
|
||||
:unread-count="unreadCount"
|
||||
:unread-count="allCount"
|
||||
@view-all="handleViewAll"
|
||||
@process-all="handleProcessAll"
|
||||
/>
|
||||
@ -215,6 +215,7 @@
|
||||
v-model:visible="showViewAllModal"
|
||||
:event-data="currentEvent"
|
||||
:monitor-list="monitorList"
|
||||
:event-list="eventListData"
|
||||
@confirm="handleConfirm"
|
||||
@report="handleReport"
|
||||
/>
|
||||
@ -537,6 +538,7 @@ const unreadCountComputed = computed(
|
||||
const pendingCount = ref(0);
|
||||
const doneCount = ref(0);
|
||||
const unreadCount = ref(0);
|
||||
const allCount = ref(0); // 添加allCount变量,用于存储全部告警数量
|
||||
const alertCount = ref(0);
|
||||
const processedCount = ref(0);
|
||||
// 当前页面参数
|
||||
@ -553,8 +555,51 @@ const setAlarmTab = async (tab) => {
|
||||
};
|
||||
|
||||
// 查看全部按钮
|
||||
const handleViewAll = () => {
|
||||
showViewAllModal.value = true;
|
||||
const handleViewAll = async () => {
|
||||
try {
|
||||
isLoading.value = true;
|
||||
|
||||
// 使用新API获取告警事件列表,每页10条
|
||||
const res = await robotApi.getAlarmDetailList({
|
||||
number: robotId.value,
|
||||
offset: 0,
|
||||
limit: 10
|
||||
});
|
||||
|
||||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||||
console.log('获取告警事件列表成功:', res.data);
|
||||
|
||||
// 将API返回的数据传递给EventDetailModal组件
|
||||
showViewAllModal.value = true;
|
||||
|
||||
// 更新告警事件列表数据
|
||||
currentEvent.value = {
|
||||
mainImage: res.data[0].imagePreview || '',
|
||||
subImages: res.data[0].imageList || [],
|
||||
type: res.data[0].etypeName || '',
|
||||
time: res.data[0].createTime || '',
|
||||
robotName: res.data[0].name || robotId.value,
|
||||
status: res.data[0].handle === '1' ? '已处理' : '未处理'
|
||||
};
|
||||
|
||||
// 更新monitorList,右侧显示所有告警事件
|
||||
monitorList.value = res.data.map(item => ({
|
||||
title: item.etypeName || '未知告警',
|
||||
image: item.imagePreview || ''
|
||||
}));
|
||||
|
||||
// 将完整数据传递给弹窗组件
|
||||
eventListData.value = res.data;
|
||||
} else {
|
||||
console.error('获取告警事件列表失败或列表为空');
|
||||
window.$message && window.$message.error('获取告警事件列表失败或列表为空');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取告警事件列表异常:', error);
|
||||
window.$message && window.$message.error('获取告警事件列表失败');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 一键处理按钮
|
||||
@ -672,6 +717,7 @@ const getAlarmEventCount = async () => {
|
||||
console.log('获取告警数量成功,原始数据:', res.data);
|
||||
alertCount.value = res.data.alert_count || 0;
|
||||
processedCount.value = res.data.processed_count || 0;
|
||||
allCount.value = res.data.all_count || 0; // 从接口返回的all_count中获取全部告警数量
|
||||
|
||||
// 直接更新显示用的计数
|
||||
pendingCount.value = alertCount.value;
|
||||
@ -680,6 +726,7 @@ const getAlarmEventCount = async () => {
|
||||
console.log('告警数量更新:', {
|
||||
未处理: alertCount.value,
|
||||
已处理: processedCount.value,
|
||||
全部: allCount.value, // 添加日志输出
|
||||
当前标签: alarmTab.value,
|
||||
pendingCount: pendingCount.value,
|
||||
doneCount: doneCount.value
|
||||
@ -1350,14 +1397,46 @@ const monitorList = ref([
|
||||
{ title: '监控视图6', image: '../assets/img/camera-thumb3.jpg' }
|
||||
]);
|
||||
|
||||
const handleConfirm = () => {
|
||||
console.log('确认处理');
|
||||
showViewAllModal.value = false;
|
||||
const handleConfirm = async (data) => {
|
||||
console.log('确认处理告警事件:', data);
|
||||
|
||||
// 如果有messageId,处理单个告警事件
|
||||
if (data.messageId) {
|
||||
try {
|
||||
const res = await robotApi.handleSingleAlarmEvent({
|
||||
messageId: data.messageId,
|
||||
remark: data.remark || '',
|
||||
number: robotId.value
|
||||
});
|
||||
|
||||
if (res.code === 200) {
|
||||
console.log('处理单个告警成功');
|
||||
|
||||
// 更新告警列表
|
||||
await getAlarmEventList();
|
||||
|
||||
// 更新告警数量
|
||||
await getAlarmEventCount();
|
||||
|
||||
// 关闭弹窗
|
||||
showViewAllModal.value = false;
|
||||
} else {
|
||||
console.error('处理单个告警失败:', res);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理单个告警异常:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('缺少messageId,无法处理告警');
|
||||
showViewAllModal.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const handleReport = () => {
|
||||
console.log('处理并上报');
|
||||
showViewAllModal.value = false;
|
||||
const handleReport = async (data) => {
|
||||
console.log('处理并上报告警事件:', data);
|
||||
|
||||
// 处理逻辑与handleConfirm相同,可能后续会有不同
|
||||
await handleConfirm(data);
|
||||
};
|
||||
|
||||
// 登录接口调用
|
||||
@ -1802,17 +1881,34 @@ watch(alarmTab, async (newValue) => {
|
||||
// 添加处理单个告警事件的函数
|
||||
const handleAlarmEvent = async (alarm) => {
|
||||
try {
|
||||
// 如果是仪表类型的告警或日常巡检,直接打开详情弹窗
|
||||
// 如果是仪表类型的告警或日常巡检,直接处理,不弹窗
|
||||
if (alarm.group === "meter" || alarm.content === "日常巡检") {
|
||||
await showMeterDetail(alarm);
|
||||
// 检查 messageId 是否存在
|
||||
if (!alarm.messageId) {
|
||||
window.$message && window.$message.error('缺少 messageId,无法处理该告警');
|
||||
return;
|
||||
}
|
||||
// 可选:添加 loading 状态
|
||||
isLoading.value = true;
|
||||
try {
|
||||
await handleSingleAlarm({ messageId: alarm.messageId });
|
||||
} catch (err) {
|
||||
window.$message && window.$message.error('处理仪表识别告警失败');
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
} else {
|
||||
// 其他类型的告警也打开详情弹窗
|
||||
// 其他类型的告警弹窗详情
|
||||
await showAlarmDetail(alarm);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('处理告警事件失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
// 在RobotDetail.vue中添加eventListData变量,用于存储完整的告警事件列表数据
|
||||
// 在currentEvent和monitorList之后添加
|
||||
const eventListData = ref([]);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user