robot_bigScreen/src/components/dialog/AlarmDetailModal.vue
2025-06-09 12:05:24 +08:00

548 lines
12 KiB
Vue

<template>
<div class="alarm-modal" v-if="visible">
<div class="modal-content">
<!-- 标题 -->
<div class="modal-header">
<span class="title">{{ alarmData.title || "告警详情" }}</span>
<img
src="../../assets/img/close.png"
class="close-icon"
@click="handleClose"
/>
</div>
<!-- 左侧图片区域 -->
<div class="modal-body">
<div class="image-section">
<TitleBlock title="监控视图">监控视图</TitleBlock>
<div class="images-container">
<div class="main-camera">
<div class="camera-wrapper">
<div class="camera-title">主监控</div>
<div class="camera-view">
<template v-if="alarmData.mainImage">
<img
:src="alarmData.mainImage"
alt=""
@error="imgLoadError.main = true"
v-show="!imgLoadError.main"
/>
<EmptyState
v-if="imgLoadError.main"
subtitle="暂无信息"
title=""
iconSrc="empty"
class="video-empty-state"
/>
</template>
<EmptyState
v-else
subtitle="暂无信息"
title=""
:iconSrc="empty"
class="video-empty-state"
/>
</div>
</div>
</div>
<div class="sub-cameras">
<div class="camera-item">
<div class="camera-wrapper">
<div class="camera-title">云台监控</div>
<div class="camera-view">
<template
v-if="
alarmData.subImages && alarmData.subImages.length > 0
"
>
<img
:src="alarmData.subImages[0]"
alt=""
@error="imgLoadError.sub1 = true"
v-show="!imgLoadError.sub1"
/>
<EmptyState
v-if="imgLoadError.sub1"
subtitle="暂无信息"
title=""
:iconSrc="empty"
class="video-empty-state"
/>
</template>
<EmptyState
v-else
subtitle="暂无信息"
title=""
:iconSrc="empty"
class="video-empty-state"
/>
</div>
</div>
</div>
<div class="camera-item">
<div class="camera-wrapper">
<div class="camera-title">热成像</div>
<div class="camera-view">
<template
v-if="
alarmData.subImages && alarmData.subImages.length > 1
"
>
<img
:src="alarmData.subImages[1]"
alt=""
@error="imgLoadError.sub2 = true"
v-show="!imgLoadError.sub2"
/>
<EmptyState
v-if="imgLoadError.sub2"
title=""
subtitle="暂无信息"
:iconSrc="empty"
class="video-empty-state"
/>
</template>
<EmptyState
v-else
title=""
subtitle="暂无信息"
:iconSrc="empty"
class="video-empty-state"
/>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧信息区域 -->
<div class="info-section">
<div class="info-group">
<TitleBlock title="事件信息">事件信息</TitleBlock>
<div class="info-content">
<div class="info-item">
<p class="label">事件类型:</p>
<p class="value">{{ alarmData.type }}</p>
</div>
<div class="info-item">
<p class="label">上报时间:</p>
<p class="value">{{ alarmData.time }}</p>
</div>
<div class="info-item">
<p class="label">机器人名称:</p>
<p class="value">{{ alarmData.robotName }}</p>
</div>
<div class="info-item">
<p class="label">状态:</p>
<p class="value" :class="alarmData.status">
{{ alarmData.status }}
</p>
</div>
<div class="info-item" v-if="alarmData.temperature">
<p class="label">温度:</p>
<p class="value">{{ alarmData.temperature }}</p>
</div>
</div>
</div>
<div class="info-group">
<TitleBlock title="备注">备注</TitleBlock>
<div class="remark-content">
<div
class="remark-box"
contenteditable="true"
data-placeholder="请输入备注信息......"
@input="updateRemark"
v-html="alarmData.remark || ''"
></div>
</div>
</div>
</div>
</div>
<!-- 底部按钮 -->
<div class="modal-footer">
<button class="btn cancel" @click="handleClose">取消</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>
</template>
<script setup>
import { ref, onMounted, watch } from "vue";
import TitleBlock from "../common/TitleBlock.vue";
import EmptyState from "../common/EmptyState.vue";
import empty from "../../assets/img/empty.png";
const props = defineProps({
visible: {
type: Boolean,
default: false,
},
alarmData: {
type: Object,
default: () => ({
title: "",
mainImage: "",
subImages: [],
type: "",
time: "",
robotName: "",
status: "",
messageId: "",
remark: "",
}),
},
});
// 追踪图片加载错误状态
const imgLoadError = ref({
main: false,
sub1: false,
sub2: false,
});
// 重置图片加载错误状态
const resetImgLoadError = () => {
imgLoadError.value = {
main: false,
sub1: false,
sub2: false,
};
};
// 监听模态框可见性变化
watch(
() => props.visible,
(newValue) => {
if (newValue) {
// 当模态框显示时重置图片加载错误状态
resetImgLoadError();
}
}
);
// 当模态框显示时重置图片加载错误状态
onMounted(() => {
if (props.visible) {
resetImgLoadError();
}
});
const emit = defineEmits(["update:visible", "confirm", "report"]);
const remarkText = ref("");
// 监听alarmData中的remark变化
watch(
() => props.alarmData.remark,
(newValue) => {
remarkText.value = newValue || "";
},
{ immediate: true }
);
const handleClose = () => {
emit("update:visible", false);
};
const handleConfirm = () => {
emit("confirm", {
...props.alarmData,
remark: remarkText.value,
});
};
const handleReport = () => {
emit("report", {
...props.alarmData,
remark: remarkText.value,
});
};
const updateRemark = (e) => {
remarkText.value = e.target.innerText;
};
</script>
<style scoped>
.alarm-modal {
position: fixed;
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: 900px;
height: 600px;
background: url("../../assets/img/alert.png") no-repeat;
background-size: 100% 100%;
border-radius: 4px;
padding: 20px;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.title {
color: #b9e8ff;
font-size: 28px;
letter-spacing: 4px;
padding-left: 20px;
}
.close-icon {
width: 20px;
height: 20px;
cursor: pointer;
}
.modal-body {
height: 80%;
display: flex;
gap: 20px;
/* padding-top: 20px; */
}
.image-section {
height: 100%;
flex: 1;
display: flex;
flex-direction: column;
/* margin-top: 20px; */
gap: 10px;
border-radius: 4px;
border: 1px solid rgba(0, 206, 234, 0.7);
}
.images-container {
display: flex;
flex-direction: column;
gap: 10px;
padding: 10px;
}
.main-camera {
width: 100%;
}
.camera-wrapper {
position: relative;
min-height: 240px;
/* border: 1px solid rgba(0,206,234,0.7); */
border-radius: 4px;
background: #033347; /* 设置底部矩形的背景色 */
}
.sub-cameras {
display: flex;
gap: 10px;
}
.camera-item {
flex: 1;
}
.camera-item .camera-wrapper {
min-height: 120px;
}
.camera-title {
width: 100%;
position: absolute;
top: 0px;
left: 0px;
z-index: 1;
color: #b9e8ff;
font-size: 12px;
padding: 4px 8px;
background: rgba(0, 21, 31, 0.5);
border-radius: 2px;
text-align: left;
}
.camera-view {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
.camera-view img {
width: 100%;
height: 100%;
/* object-fit: cover; */
}
.info-section {
width: 400px;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 20px;
}
.info-group {
background: rgba(0, 21, 31, 0.5);
border-radius: 4px;
border: 1px solid rgba(0, 206, 234, 0.7);
}
.info-group h3 {
color: #b9e8ff;
font-size: 14px;
margin-bottom: 15px;
}
.info-content {
padding: 15px;
background: rgba(0, 21, 31, 0.2);
/* border: 1px solid rgba(0,206,234,0.7); */
border-radius: 4px;
}
.info-item {
display: flex;
align-items: center;
justify-content: space-between;
/* background: rgba(0, 21, 31, 0.2); */
border-bottom: 1px solid #244c60;
padding: 10px;
/* padding: 15px 20px; */
}
.info-item:last-child {
border-bottom: none;
}
.info-item .label {
color: #b9e8ff;
font-size: 14px;
margin: 0;
}
.info-item .label:before {
content: "";
display: inline-block;
width: 10px;
height: 10px;
background: url("../../assets/img/bt.png") no-repeat;
background-size: 100% 100%;
margin-right: 5px;
}
.info-item .value {
color: #b9e8ff;
font-size: 14px;
margin: 0;
text-align: right;
}
.info-item .value.未处理 {
color: #f33f3f;
}
.info-item .value.已处理 {
color: #00ff84;
}
.remark-content {
/* background: rgba(0, 21, 31, 0.2); */
/* border: 1px solid rgba(0,206,234,0.7); */
border-radius: 4px;
padding: 0;
}
.remark-box {
width: 100%;
min-height: 130px;
background: none;
border-radius: 4px;
color: #b9e8ff;
padding: 10px;
outline: none;
}
.remark-box:empty:before {
content: attr(data-placeholder);
color: rgba(185, 232, 255, 0.4);
}
.modal-footer {
margin-top: 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn {
padding: 0 20px;
height: 32px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 14px;
}
.btn.cancel {
background: url("../../assets/img/cancel.png") no-repeat;
background-size: 100% 100%;
color: #c6f4ff;
}
.btn.confirm {
background: url("../../assets/img/confirm.png") no-repeat;
background-size: 100% 100%;
color: #c6f4ff;
}
.btn.report {
background: url("../../assets/img/report.png") no-repeat;
background-size: 100% 100%;
color: #c6f4ff;
}
.video-empty-state {
padding-top: 30px;
background: #033347;
}
:deep(.empty-icon) {
/* width: 100px; */
height: 46px !important;
}
:deep(.empty-title) {
display: none;
}
:deep(.empty-subtitle) {
color: #fff;
font-size: 10px;
letter-spacing: 1px;
margin-top: 5px;
}
</style>