Files
Visualize-electronic-fences/src/layout/components/notice/index.vue
2026-05-27 15:53:27 +08:00

313 lines
8.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div v-loading="state.loading" class="layout-navbars-breadcrumb-user-news">
<div class="head-box">
<div class="head-box-title"><p style="color: red; font-size: 16px; font-weight: 800">入侵警告</p></div>
<div class="head-box-btn" @click="readAll">全部已读</div>
</div>
<div v-loading="state.loading" class="content-box">
<template v-if="newsList.length > 0">
<!-- <div v-for="(v, k) in newsList" :key="k" class="content-box-item" @click="onNewsClick(k)"> -->
<div v-for="(v, k) in newsList" :key="k" class="content-box-item">
<div class="item-conten">
<div>{{ v.message }}</div>
<div class="content-box-msg"></div>
<!-- <div class="content-box-time">{{ v.time }}</div> -->
</div>
<!-- 已读/未读 -->
<!-- <span v-if="v.read" class="el-tag el-tag--success el-tag--mini read">已读</span> -->
<button v-if="!v.read" class="el-tag el-tag--danger el-tag--mini read" @click="handleSee(v)">查看画面</button>
</div>
</template>
<el-empty v-else :description="'消息为空'"></el-empty>
</div>
<!-- <div v-if="newsList.length > 0" class="foot-box" @click="onGoToGiteeClick">前往gitee</div> -->
<!-- 添加或修改测试单对话框 -->
<el-dialog
v-model="dialog.visible"
:title="dialog.title"
width="500px"
class="monitor-dialog"
:before-close="handleMonitorDialogClose"
fullscreen
destroy-on-close
>
<template #header>
<div class="flex w-full items-center justify-between gap-2 min-w-0 pr-8">
<span class="truncate">实时监控</span>
</div>
</template>
<div class="monitor-video-wrap">
<div class="monitor-video-stack">
<video ref="monitorVideoRef" class="monitor-video" controls preload="metadata" :src="monitorVideoUrl">您的浏览器不支持视频播放</video>
</div>
</div>
</el-dialog>
</div>
</template>
<script setup lang="ts" name="layoutBreadcrumbUserNews">
import { useNoticeStore } from '@/store/modules/notice';
import { listNotice } from '@/api/system/notice';
import { initHls, destroyHls } from '@/utils/video';
import { onBeforeRouteLeave } from 'vue-router';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { nextTick } from 'vue';
import { useVideosStore } from '@/store/modules/video';
const videosStore = useVideosStore();
const noticeStore = useNoticeStore();
const { readAll } = useNoticeStore();
const monitorVideoRef = ref<HTMLVideoElement>();
// 定义变量内容
const state = reactive({
loading: false
});
const newsList = ref([]) as any;
const monitorVideoUrl = ref();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const parseNoticeItem = (notice: any) => {
const rawMessage = String(notice.message ?? '');
const [messagePart, videoIdPart] = rawMessage.split('|', 2);
return {
...notice,
rawMessage,
message: messagePart?.trim() ?? rawMessage,
videoId: videoIdPart?.trim() ?? ''
};
};
//查看画面
const handleSee = async (row: any) => {
const videoId = row?.videoId;
console.log('查看画面', row, 'videoId', videoId);
if (!videoId) {
proxy?.$modal.msgError('视频ID不存在');
return;
}
await videosStore.getVideos(videoId);
monitorVideoUrl.value = videosStore.hisUrl;
dialog.visible = true;
await nextTick();
initHls(monitorVideoUrl, monitorVideoRef);
};
const handleMonitorDialogClose = (done: () => void) => {
// 卸载 Hls 与清理 video
destroyHls(monitorVideoRef);
done();
};
/**
* 过滤消息只保留最新的5条
* @param messages 消息列表
* @returns 过滤后的消息列表
*/
const filterMessages = (messages: any[]) => {
// 如果消息超过5条则只保留最新的5条
if (messages.length > 5) {
return messages.slice(-5); // 只取最后5条最新消息
}
return messages;
};
/**
* 初始化数据
* @returns
*/
const getTableData = async () => {
state.loading = true;
const allNotices = [...noticeStore.state.notices]; // 复制数组避免直接修改原数据
// 过滤消息并解析 message/videoId
const filteredNotices = filterMessages(allNotices).map(parseNoticeItem);
newsList.value = filteredNotices;
state.loading = false;
};
// 监听通知变化,自动过滤
watch(
() => noticeStore.state.notices,
(newNotices) => {
const filteredNotices = filterMessages([...newNotices]).map(parseNoticeItem);
newsList.value = filteredNotices;
console.log('监听消息队列', newsList.value);
},
{ deep: true }
);
//点击消息,写入已读
const onNewsClick = (item: any) => {
if (newsList.value[item]) {
newsList.value[item].read = true;
//并且写入pinia
noticeStore.state.notices = noticeStore.state.notices.map((notice: any, index: number) => {
if (index === findOriginalIndex(item)) {
return { ...notice, read: true };
}
return notice;
});
}
};
// 辅助函数:找到原始索引
const findOriginalIndex = (filteredIndex: number) => {
const filteredNotices = filterMessages([...noticeStore.state.notices]).map(parseNoticeItem);
const currentMessage = filteredNotices[filteredIndex];
// 在原始数组中查找对应消息的索引
return noticeStore.state.notices.findIndex(
(notice: any) => String(notice.message) === currentMessage.rawMessage && notice.time === currentMessage.time
);
};
// 前往通知中心点击
const onGoToGiteeClick = () => {
window.open('https://gitee.com/dromara/RuoYi-Vue-Plus/tree/5.X/');
};
onMounted(() => {
nextTick(() => {
getTableData();
console.log('消息列表:', newsList.value);
});
});
onBeforeRouteLeave(() => {
dialog.visible = false;
monitorVideoUrl.value = '';
});
</script>
<style lang="scss" scoped>
/* 全屏监控弹窗:占满视口、禁止外层滚动,视频在可视区内尽可能放大 */
:deep(.monitor-dialog.el-dialog) {
display: flex;
flex-direction: column;
height: 100vh;
max-height: 100vh;
margin: 0;
overflow: hidden;
}
:deep(.monitor-dialog .el-dialog__header) {
flex-shrink: 0;
margin-right: 0;
}
:deep(.monitor-dialog .el-dialog__body) {
flex: 1;
min-height: 0;
overflow: hidden;
padding: 0;
display: flex;
flex-direction: column;
}
.monitor-video-wrap {
flex: 1;
min-height: 0;
width: 100%;
display: flex;
align-items: stretch;
justify-content: center;
background: #000;
}
.monitor-video-stack {
position: relative;
flex: 1;
min-height: 0;
width: 100%;
}
.monitor-video {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
max-width: 100%;
max-height: 100%;
object-fit: contain; //cover裁剪
background: #000;
}
.monitor-draw-overlay {
position: absolute;
inset: 0;
z-index: 2;
cursor: crosshair;
background: transparent;
}
.layout-navbars-breadcrumb-user-news {
.head-box {
display: flex;
border-bottom: 1px solid var(--el-border-color-lighter);
box-sizing: border-box;
color: var(--el-text-color-primary);
justify-content: space-between;
height: 35px;
align-items: center;
.head-box-btn {
color: var(--el-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
&:hover {
opacity: 1;
}
}
}
.content-box {
height: 300px;
overflow: auto;
font-size: 13px;
.content-box-item {
padding-top: 12px;
display: flex;
&:last-of-type {
padding-bottom: 12px;
}
.content-box-msg {
color: var(--el-text-color-secondary);
margin-top: 5px;
margin-bottom: 5px;
}
.content-box-time {
color: var(--el-text-color-secondary);
}
.item-conten {
width: 100%;
display: flex;
flex-direction: column;
}
}
}
.foot-box {
height: 35px;
color: var(--el-color-primary);
font-size: 13px;
cursor: pointer;
opacity: 0.8;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid var(--el-border-color-lighter);
&:hover {
opacity: 1;
}
}
:deep(.el-empty__description p) {
font-size: 13px;
}
}
</style>