告警弹窗

This commit is contained in:
龚皓 2024-11-26 16:24:48 +08:00
parent 9081420148
commit 7551409394
1 changed files with 418 additions and 400 deletions

View File

@ -1,433 +1,451 @@
<template> <template>
<div class="alert-container"> <div class="alert-container">
<div class="title-count"> <div class="title-count">
<!-- <el-row class="title-row"> <!-- <el-row class="title-row">
今日事件列表 今日事件列表
</el-row> --> </el-row> -->
<el-row class="total-row"> <el-row class="total-row">
<div class="total-text"> <div class="total-text">
今日告警 今日告警
</div>
<div class="total-text">
{{ totalItems }}
</div>
</el-row>
</div> </div>
<div class="total-text">
<el-row class="table-row"> {{ totalItems }}
<el-col :span="24" class="table-col"> </div>
<div class="table-container"> </el-row>
<el-table :data="tableData" @row-click="handleRowClick" class="table-part"> </div>
<el-table-column v-show="false" prop="id" label="告警编号" v-if="false"></el-table-column>
<el-table-column label="告警类型" :width="adjustedWidths[0]" align="center" <el-row class="table-row">
:show-overflow-tooltip="true"> <el-col :span="24" class="table-col">
<template v-slot="scope"> <div class="table-container">
{{ typeMapping[scope.row.types] }} <el-table :data="tableData" @row-click="handleRowClick" class="table-part">
</template> <el-table-column v-show="false" prop="id" label="告警编号" v-if="false"></el-table-column>
</el-table-column> <el-table-column label="告警类型" :width="adjustedWidths[0]" align="center" :show-overflow-tooltip="true">
<el-table-column prop="camera.name" label="告警位置" :width="adjustedWidths[1]" align="center" <template v-slot="scope">
:show-overflow-tooltip="true"></el-table-column> {{ typeMapping[scope.row.types] }}
<el-table-column label="告警时间" :width="adjustedWidths[2]" align="center" </template>
:show-overflow-tooltip="true"> </el-table-column>
<template v-slot="scope"> <el-table-column prop="camera.name" label="告警位置" :width="adjustedWidths[1]" align="center"
{{ formatDateTime(scope.row.ended_at) }} :show-overflow-tooltip="true"></el-table-column>
</template> <el-table-column label="告警时间" :width="adjustedWidths[2]" align="center" :show-overflow-tooltip="true">
</el-table-column> <template v-slot="scope">
<el-table-column prop="status" label="告警状态" v-if="false"> {{ formatDateTime(scope.row.ended_at) }}
<template v-slot="scope"> </template>
<el-tag :type="scope.row.status === 'pending' ? 'warning' : 'success'">{{ </el-table-column>
statusMapping[scope.row.status] <el-table-column prop="status" label="告警状态" v-if="false">
}}</el-tag> <template v-slot="scope">
</template> <el-tag :type="scope.row.status === 'pending' ? 'warning' : 'success'">{{
</el-table-column> statusMapping[scope.row.status]
</el-table> }}</el-tag>
</div> </template>
</el-col> </el-table-column>
</el-row> </el-table>
<!-- <el-row class="table-row"> </div>
</el-col>
</el-row>
<!-- <el-row class="table-row">
<el-col :span="24" class="table-col"> <el-col :span="24" class="table-col">
<div class="table-container"> <div class="table-container">
<dv-scroll-board :config="tableConfig" class="table-part" @row-click="handleRowClick"></dv-scroll-board> <dv-scroll-board :config="tableConfig" class="table-part" @row-click="handleRowClick"></dv-scroll-board>
</div> </div>
</el-col> </el-col>
</el-row> --> </el-row> -->
<!-- <div class="adjusted-widths-display"> <!-- <div class="adjusted-widths-display">
当前列宽: {{ adjustedWidths.join(', ') }} 当前列宽: {{ adjustedWidths.join(', ') }}
</div> --> </div> -->
<el-dialog v-model="dialogVisible" title="告警详情" width="50%" class="dialog-container"> <el-dialog v-model="dialogVisible" title="告警详情" width="50%" class="dialog-container">
<div> <div>
<el-row class="dialog-row"> <el-row class="dialog-row">
<el-col :span="12" class="dialog-left"> <el-col :span="12" class="dialog-left">
<el-row gutter class="dialog-image-container"> <el-row gutter class="dialog-image-container">
<template v-if="hasSnapshot"> <template v-if="hasSnapshot">
<el-image :src="snapshotFile"></el-image> <el-image :src="snapshotFile" @click="handleImageClick(snapshotFile)" style="cursor: pointer;"></el-image>
</template> </template>
<!-- <template v-if="hasVideo"> <!-- <template v-if="hasVideo">
<video :src="videoFile" controls></video> <video :src="videoFile" controls></video>
</template> --> </template> -->
</el-row> </el-row>
</el-col> </el-col>
<el-col :span="11" class="dialog-right"> <el-col :span="11" class="dialog-right">
<el-row> <el-row>
<el-col :span="24"> <el-col :span="24">
<p>告警编号: {{ selectedRow.id }}</p> <p>告警编号: {{ selectedRow.id }}</p>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<p>告警类型: {{ typeMapping[selectedRow.types] }}</p> <p>告警类型: {{ typeMapping[selectedRow.types] }}</p>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<p>告警位置: {{ selectedRow.camera.name }}</p> <p>告警位置: {{ selectedRow.camera.name }}</p>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<p>告警时间: {{ selectedRow.formatted_started_at }}</p> <p>告警时间: {{ selectedRow.formatted_started_at }}</p>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<p>告警状态: <el-tag :type="selectedRow.status === 'pending' ? 'warning' : 'success'">{{ <p>告警状态: <el-tag :type="selectedRow.status === 'pending' ? 'warning' : 'success'">{{
statusMapping[selectedRow.status] statusMapping[selectedRow.status]
}}</el-tag></p> }}</el-tag></p>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<p>摄像头编号: {{ selectedRow.camera_id }}</p> <p>摄像头编号: {{ selectedRow.camera_id }}</p>
</el-col> </el-col>
<el-col :span="24"> <el-col :span="24">
<p>持续时间: {{ duration }}</p> <p>持续时间: {{ duration }}</p>
</el-col> </el-col>
<!-- <el-col :span="24"> <!-- <el-col :span="24">
<p>备注: {{ selectedRow.remark }}</p> <p>备注: {{ selectedRow.remark }}</p>
</el-col> --> </el-col> -->
<!-- <el-col :span="24"> <!-- <el-col :span="24">
<el-form-item style="width: 90%;"> <el-form-item style="width: 90%;">
<el-input v-model="remark" placeholder="请描述事件原因" type="textarea" rows="5" :disabled="true"></el-input> <el-input v-model="remark" placeholder="请描述事件原因" type="textarea" rows="5" :disabled="true"></el-input>
</el-form-item> </el-form-item>
</el-col> --> </el-col> -->
</el-row> </el-row>
</el-col> </el-col>
</el-row> </el-row>
</div> </div>
</el-dialog> </el-dialog>
</div>
</template> <el-dialog v-model="previewVisible" width="60%" custom-class="image-preview-dialog" :close-on-click-modal="true">
<img :src="previewImage" alt="预览图片" style="width: 100%; height: auto; display: block; margin: auto;" />
</el-dialog>
</div>
</template>
<script setup> <script setup>
import { ref, reactive, onMounted, onBeforeUnmount } from 'vue'; import { ref, reactive, onMounted, onBeforeUnmount } from 'vue';
import { BoxApi } from '@/utils/boxApi.ts'; import { BoxApi } from '@/utils/boxApi.ts';
import { useGlobalTimerStore } from '@/stores/globalTimerStore';
const boxApi = new BoxApi();
const tableData = ref([]); const boxApi = new BoxApi();
const dialogVisible = ref(false); const tableData = ref([]);
const remark = ref(""); const dialogVisible = ref(false);
const selectedRow = ref({}); const remark = ref("");
const typeMapping = reactive({}); const selectedRow = ref({});
const currentPage = ref(1); const typeMapping = reactive({});
const pageSize = ref(20); const currentPage = ref(1);
const totalItems = ref(0); const pageSize = ref(20);
const displayTotalItems = ref(0); const totalItems = ref(0);
const token = ref(null); const displayTotalItems = ref(0);
const apiInstance = new BoxApi(); const token = ref(null);
const statusMapping = { const apiInstance = new BoxApi();
'pending': '待处理', const statusMapping = {
'assigned': '处理中', 'pending': '待处理',
'closed': '已处理' 'assigned': '处理中',
}; 'closed': '已处理'
const duration = ref(''); };
const duration = ref('');
const hasSnapshot = ref(false);
const hasVideo = ref(false); const hasSnapshot = ref(false);
const snapshotFile = ref(""); const hasVideo = ref(false);
const videoFile = ref(""); const snapshotFile = ref("");
const originalWidths = [97, 150, 160]; // const videoFile = ref("");
const adjustedWidths = ref([...originalWidths]); const originalWidths = [97, 150, 160]; //
const baseWidth = 2150; const adjustedWidths = ref([...originalWidths]);
const baseWidth = 2150;
const formatDateTimeToISO = (datetime) => { const previewVisible = ref(false); //
const previewImage = ref(''); //
const handleImageClick = (imagePath) => {
previewImage.value = imagePath; //
previewVisible.value = true; //
};
const formatDateTimeToISO = (datetime) => {
return new Date(datetime).toISOString().replace('.000', ''); return new Date(datetime).toISOString().replace('.000', '');
}; };
const adjustColumnWidths = () => { const adjustColumnWidths = () => {
const currentWidth = window.innerWidth; const currentWidth = window.innerWidth;
// console.log(">>>>>>>>>>", currentWidth); // console.log(">>>>>>>>>>", currentWidth);
const scale = currentWidth / baseWidth; const scale = currentWidth / baseWidth;
// console.log(">>>>>>>>>>", scale); // console.log(">>>>>>>>>>", scale);
// const adjustedScale = Math.max(scale, 0.5); // const adjustedScale = Math.max(scale, 0.5);
adjustedWidths.value = originalWidths.map(width => { adjustedWidths.value = originalWidths.map(width => {
return currentWidth < baseWidth return currentWidth < baseWidth
? width * scale // ? width * scale //
: width * scale; // : width * scale; //
}); });
// nextTick(() => { // nextTick(() => {
// });
};
// const tableConfig = ref({
// header: ['', '', ''],
// data: [],
// rowNum: 5,
// columnWidth: [100, 160, 190],
// carousel: 'single',
// }); // });
};
const fetchTypeMapping = async (token) => {
try { // const tableConfig = ref({
const algorithms = await boxApi.getAlgorithms(token); // header: ['', '', ''],
// console.log("Algorithms:", algorithms); // data: [],
algorithms.forEach((algorithm) => { // rowNum: 5,
typeMapping[algorithm.code_name] = algorithm.name; // columnWidth: [100, 160, 190],
}); // carousel: 'single',
} catch (error) { // });
console.error("Error fetching algorithms:", error);
} const fetchTypeMapping = async (token) => {
}; try {
const algorithms = await boxApi.getAlgorithms(token);
const fetchEvents = async () => { // console.log("Algorithms:", algorithms);
try { algorithms.forEach((algorithm) => {
const token = localStorage.getItem('alertToken'); typeMapping[algorithm.code_name] = algorithm.name;
const limit = 1000;
let allEvents = [];
let currentPage = 1;
const { endOfToday, startOfToday } = getDateParams();
const timeBefore = formatDateTimeToISO(endOfToday);
const timeAfter = formatDateTimeToISO(startOfToday);
// console.log("Start of today:", timeAfter);
// const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage);
const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage,timeBefore, timeAfter);
// console.log("Total items:", firstResponse);
const total = firstResponse.count;
totalItems.value = total;
allEvents = firstResponse.results;
while (allEvents.length < total) {
currentPage += 1;
const response = await apiInstance.getEventsByParams(token, limit, currentPage);
allEvents = allEvents.concat(response.results);
}
tableData.value = allEvents;
// tableConfig.value.data = allEvents.map(event => [
// typeMapping[event.types],
// event.camera.name,
// formatDateTime(event.ended_at)
// ]);
// console.log(">>>>>>>>>>events IDs:", allEvents.map(event => event.id));
} catch (error) {
console.error("Error fetching events data:", error);
}
};
const getDateParams = () => {
const today = new Date();
const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate());
const endOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
return {
startOfToday,
endOfToday,
};
};
const formatDateTime = (datetime) => {
const date = new Date(datetime);
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
const minutes = date.getMinutes().toString().padStart(2, '0');
const seconds = date.getSeconds().toString().padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
const calculateDuration = (started_at, ended_at) => {
const start = new Date(started_at);
const end = new Date(ended_at);
const durationMs = end - start;
const minutes = Math.floor(durationMs / 60000);
const seconds = ((durationMs % 60000) / 1000).toFixed(0);
return `${minutes}${seconds < 10 ? '0' : ''}${seconds}`;
};
const handleRowClick = (row) => {
selectedRow.value = row;
duration.value = calculateDuration(row.started_at, row.ended_at);
row.formatted_started_at = formatDateTime(row.started_at);
//
hasSnapshot.value = false;
hasVideo.value = false;
snapshotFile.value = "";
videoFile.value = "";
//
row.mediums.forEach((medium) => {
if (medium.name === "snapshot") {
hasSnapshot.value = true;
snapshotFile.value = medium.file;
} else if (medium.name === "video") {
hasVideo.value = true;
videoFile.value = medium.file;
}
}); });
dialogVisible.value = true; } catch (error) {
console.error("Error fetching algorithms:", error);
}
};
const fetchEvents = async () => {
try {
const token = localStorage.getItem('alertToken');
const limit = 1000;
let allEvents = [];
let currentPage = 1;
const { endOfToday, startOfToday } = getDateParams();
const timeBefore = formatDateTimeToISO(endOfToday);
const timeAfter = formatDateTimeToISO(startOfToday);
// console.log("Start of today:", timeAfter);
// const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage);
const firstResponse = await apiInstance.getEventsByParams(token, limit, currentPage, timeBefore, timeAfter);
// console.log("Total items:", firstResponse);
const total = firstResponse.count;
totalItems.value = total;
allEvents = firstResponse.results;
while (allEvents.length < total) {
currentPage += 1;
const response = await apiInstance.getEventsByParams(token, limit, currentPage);
allEvents = allEvents.concat(response.results);
}
tableData.value = allEvents;
// tableConfig.value.data = allEvents.map(event => [
// typeMapping[event.types],
// event.camera.name,
// formatDateTime(event.ended_at)
// ]);
// console.log(">>>>>>>>>>events IDs:", allEvents.map(event => event.id));
} catch (error) {
console.error("Error fetching events data:", error);
}
};
const getDateParams = () => {
const today = new Date();
const startOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate());
const endOfToday = new Date(today.getFullYear(), today.getMonth(), today.getDate() + 1);
return {
startOfToday,
endOfToday,
}; };
};
const formatDateTime = (datetime) => {
const handlePageChange = (page) => { const date = new Date(datetime);
currentPage.value = page; const year = date.getFullYear();
fetchEvents(); const month = (date.getMonth() + 1).toString().padStart(2, '0');
}; const day = date.getDate().toString().padStart(2, '0');
const hours = date.getHours().toString().padStart(2, '0');
// const calculateColumnWidths = () => { const minutes = date.getMinutes().toString().padStart(2, '0');
// const containerWidth = document.querySelector('.table-container').clientWidth; const seconds = date.getSeconds().toString().padStart(2, '0');
// }; return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
onMounted(async () => { const calculateDuration = (started_at, ended_at) => {
token.value = localStorage.getItem('alertToken'); const start = new Date(started_at);
await fetchTypeMapping(token.value); const end = new Date(ended_at);
await fetchEvents(); const durationMs = end - start;
await adjustColumnWidths(); const minutes = Math.floor(durationMs / 60000);
window.addEventListener('resize', adjustColumnWidths); const seconds = ((durationMs % 60000) / 1000).toFixed(0);
return `${minutes}${seconds < 10 ? '0' : ''}${seconds}`;
};
const handleRowClick = (row) => {
selectedRow.value = row;
duration.value = calculateDuration(row.started_at, row.ended_at);
row.formatted_started_at = formatDateTime(row.started_at);
//
hasSnapshot.value = false;
hasVideo.value = false;
snapshotFile.value = "";
videoFile.value = "";
//
row.mediums.forEach((medium) => {
if (medium.name === "snapshot") {
hasSnapshot.value = true;
snapshotFile.value = medium.file;
} else if (medium.name === "video") {
hasVideo.value = true;
videoFile.value = medium.file;
}
}); });
dialogVisible.value = true;
onBeforeUnmount(() => { };
window.removeEventListener('resize', adjustColumnWidths);
});
</script> const handlePageChange = (page) => {
currentPage.value = page;
fetchEvents();
};
// const calculateColumnWidths = () => {
// const containerWidth = document.querySelector('.table-container').clientWidth;
// };
onMounted(async () => {
token.value = localStorage.getItem('alertToken');
await fetchTypeMapping(token.value);
await fetchEvents();
await adjustColumnWidths();
window.addEventListener('resize', adjustColumnWidths);
const globalTimerStore = useGlobalTimerStore();
globalTimerStore.registerCallback(fetchEvents);
globalTimerStore.startTimer();
});
onBeforeUnmount(() => {
window.removeEventListener('resize', adjustColumnWidths);
const globalTimerStore = useGlobalTimerStore();
globalTimerStore.unregisterCallback(fetchEvents);
});
</script>
<style scoped> <style scoped>
.alert-container { .alert-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 90%; width: 90%;
height: 100%; height: 100%;
margin: 0 0 0 1vw; margin: 0 0 0 1vw;
box-sizing: border-box; box-sizing: border-box;
background-color: transparent; background-color: transparent;
} }
.table-continer { .table-continer {
width: 100%; width: 100%;
height: 100%; height: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
.table-row:hover { .table-row:hover {
cursor: pointer; cursor: pointer;
} }
.title-count { .title-count {
width: 80%; width: 80%;
text-align: center; text-align: center;
/* background-color: transparent; */ /* background-color: transparent; */
} }
/* .total-text{ /* .total-text{
padding-right: 1vw; padding-right: 1vw;
} */ } */
/* title-row内容水平上下居中 */ /* title-row内容水平上下居中 */
.title-row { .title-row {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
/* total-row左右等分分别靠左和靠右 */ /* total-row左右等分分别靠左和靠右 */
.total-row { .total-row {
display: flex; display: flex;
/* justify-content: space-between; */ /* justify-content: space-between; */
justify-content: right; justify-content: right;
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
padding-right: 0vw; padding-right: 0vw;
} }
.dialog-row { .dialog-row {
gap: 30px; gap: 30px;
display: flex; display: flex;
text-align: left; text-align: left;
flex-direction: row; flex-direction: row;
} }
.dialog-image-container { .dialog-image-container {
gap: 20px; gap: 20px;
} }
.el-dialog .el-image, .el-dialog .el-image,
.el-dialog video { .el-dialog video {
width: 100%; width: 100%;
} }
/* 基础表格样式 */ /* 基础表格样式 */
::v-deep .el-table { ::v-deep .el-table {
color: white; color: white;
font-size: 13px; font-size: 13px;
/* font-weight: bold; */ /* font-weight: bold; */
background-color: transparent; background-color: transparent;
padding: 0; padding: 0;
margin: 0; margin: 0;
height: 14vh; height: 14vh;
} }
/* 表头和单元格基本样式 */ /* 表头和单元格基本样式 */
::v-deep .el-table td { ::v-deep .el-table td {
border: none; border: none;
background-color: #001529; background-color: #001529;
transition: border 0.3s ease, background-color 0.3s ease; transition: border 0.3s ease, background-color 0.3s ease;
color: white; color: white;
padding: 0; padding: 0;
/* height: 10px; */ /* height: 10px; */
/* white-space: nowrap; */ /* white-space: nowrap; */
/* overflow: hidden; */ /* overflow: hidden; */
/* text-overflow: ellipsis; */ /* text-overflow: ellipsis; */
} }
/* 去掉中间数据的分割线 */ /* 去掉中间数据的分割线 */
/* ::v-deep .el-table .el-table__row>td{ /* ::v-deep .el-table .el-table__row>td{
border: none; border: none;
} */ } */
.table-container >>> .el-table__row>td{ .table-container>>>.el-table__row>td {
border: none; border: none;
} }
.table-container >>> .el-table th.is-leaf{
border: none; .table-container>>>.el-table th.is-leaf {
} border: none;
::v-deep .el-table__inner-wrapper::before{ }
height: 0;
} ::v-deep .el-table__inner-wrapper::before {
height: 0;
::v-deep .el-table thead th { }
color: white;
font-weight: bold; ::v-deep .el-table thead th {
background-color: #001529; color: white;
padding: 0; font-weight: bold;
} background-color: #001529;
padding: 0;
::v-deep .el-table .el-table__cell:hover { }
background-color: transparent;
color: rgb(238, 150, 49); ::v-deep .el-table .el-table__cell:hover {
/* font-size: 26px; */ background-color: transparent;
/* 保持文本为白色 */ color: rgb(238, 150, 49);
} /* font-size: 26px; */
/* 保持文本为白色 */
.el-tooltip__popper { }
font-size: 26px;
max-width:50% .el-tooltip__popper {
} font-size: 26px;
</style> max-width: 50%
}
</style>