Compare commits

...

4 Commits

Author SHA1 Message Date
龚皓 3fac89b635 主页布局 2024-07-26 15:58:52 +08:00
龚皓 f1d2740689 封装请求方法 2024-07-26 15:58:11 +08:00
龚皓 7b5bb1aea7 类型分布 2024-07-26 15:57:32 +08:00
龚皓 43526edb32 路径指向 2024-07-26 15:55:45 +08:00
5 changed files with 324 additions and 106 deletions

View File

@ -16,7 +16,7 @@
<el-menu-item index="/real-time-monitoring">实时监测</el-menu-item> <el-menu-item index="/real-time-monitoring">实时监测</el-menu-item>
<el-menu-item index="/statistics-analysis">统计分析</el-menu-item> <el-menu-item index="/statistics-analysis">统计分析</el-menu-item>
<!-- <el-menu-item index="/system-settings">系统设置</el-menu-item> --> <!-- <el-menu-item index="/system-settings">系统设置</el-menu-item> -->
<el-menu-item index="/test">系统设置</el-menu-item> <el-menu-item index="/news">系统设置</el-menu-item>
</el-menu> </el-menu>
</div> </div>
<div class="user-info"> <div class="user-info">

View File

@ -3,58 +3,188 @@
<el-card class="alert-card"> <el-card class="alert-card">
<div class="alert-header">告警趋势</div> <div class="alert-header">告警趋势</div>
<el-tabs v-model="activeName" @tab-click="handleClick" class="alert-tabs"> <el-tabs v-model="activeName" @tab-click="handleClick" class="alert-tabs">
<el-tab-pane label="24小时" name="first">24小时数据展示</el-tab-pane> <el-tab-pane label="前一天" name="first">前一天数据</el-tab-pane>
<el-tab-pane label="30天" name="second">30天数据展示</el-tab-pane> <el-tab-pane label="3天" name="second">近3天数据展示</el-tab-pane>
<el-tab-pane label="12月" name="third">12月数据展示</el-tab-pane>
</el-tabs> </el-tabs>
<div id="alert-chart" class="chart-container"></div> <div id="alertChart" class="chart-container"></div>
</el-card> </el-card>
</div> </div>
</template> </template>
<script> <script>
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import { login, getEvents } from '../../superbox.js';
const username = 'turingvideo';
const password = '1234qwer!';
export default { export default {
name: 'AlertChart', name: 'AlertChart',
data() { data() {
return { return {
activeName: 'first' activeName: 'first',
eventsData: []
}; };
}, },
mounted() { async created() {
try {
const token = await login(username, password);
this.eventsData = await getEvents(token);
this.initChart(); this.initChart();
} catch (error) {
console.error("Error fetching events:", error);
}
}, },
methods: { methods: {
handleClick(tab, event) { handleClick(tab, event) {
console.log(tab, event);
//
this.initChart(); this.initChart();
}, },
initChart() { initChart() {
var chartDom = document.getElementById('alert-chart'); var chartDom = document.getElementById('alertChart');
var myChart = echarts.init(chartDom); var myChart = echarts.init(chartDom);
var option; var option;
option = { if (this.activeName === 'first') {
option = this.get24HourChartOption();
} else if (this.activeName === 'second') {
option = this.get3DayChartOption();
}
option && myChart.setOption(option);
},
get24HourChartOption() {
const now = new Date();
const todayMidnight = new Date(now);
todayMidnight.setHours(0, 0, 0, 0);
const yesterdayMidnight = new Date(todayMidnight);
yesterdayMidnight.setDate(todayMidnight.getDate() - 1);
const timePoints = ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00', '24:00'];
const alertCounts = Array(timePoints.length).fill(0);
this.eventsData.forEach(event => {
const eventTime = new Date(event.started_at);
if (eventTime >= yesterdayMidnight && eventTime < todayMidnight) {
const hours = eventTime.getHours();
if (hours < 4) alertCounts[1]++;
else if (hours < 8) alertCounts[2]++;
else if (hours < 12) alertCounts[3]++;
else if (hours < 16) alertCounts[4]++;
else if (hours < 20) alertCounts[5]++;
else if (hours < 24) alertCounts[6]++;
}
});
// Accumulate the alert counts
for (let i = 1; i < alertCounts.length; i++) {
alertCounts[i] += alertCounts[i - 1];
}
return {
title: {
text: '24小时内告警数量变化',
left: 'center',
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
formatter: '{b0}: {c0}'
},
xAxis: { xAxis: {
type: 'category', type: 'category',
boundaryGap: false, boundaryGap: false,
data: ['2024-07-08 00:00', '2024-07-08 08:00', '2024-07-08 16:00', '2024-07-09 00:00', '2024-07-09 08:00', '2024-07-09 16:00'] data: timePoints
}, },
yAxis: { yAxis: {
type: 'value' type: 'value'
}, },
series: [ series: [
{ {
data: [10, 22, 28, 43, 49, 62], data: alertCounts,
type: 'line', type: 'line',
areaStyle: {} areaStyle: {}
} }
] ]
}; };
},
option && myChart.setOption(option); get3DayChartOption() {
const now = new Date();
const day1Midnight = new Date(now);
day1Midnight.setHours(0, 0, 0, 0);
const day2Midnight = new Date(day1Midnight);
day2Midnight.setDate(day1Midnight.getDate() - 1);
const day3Midnight = new Date(day2Midnight);
day3Midnight.setDate(day2Midnight.getDate() - 1);
//
const adjustedDay1 = new Date(day1Midnight);
adjustedDay1.setDate(day1Midnight.getDate() + 1);
const adjustedDay2 = new Date(day2Midnight);
adjustedDay2.setDate(day2Midnight.getDate() + 1);
const adjustedDay3 = new Date(day3Midnight);
adjustedDay3.setDate(day3Midnight.getDate() + 1);
// 使
const timePoints = [
adjustedDay3.toISOString().slice(0, 10),
adjustedDay2.toISOString().slice(0, 10),
adjustedDay1.toISOString().slice(0, 10),
];
const alertCounts = Array(timePoints.length).fill(0);
this.eventsData.forEach(event => {
const eventTime = new Date(event.started_at);
if (eventTime >= day3Midnight && eventTime < day2Midnight) {
alertCounts[0]++;
} else if (eventTime >= day2Midnight && eventTime < day1Midnight) {
alertCounts[1]++;
} else if (eventTime >= day1Midnight && eventTime <= now) {
alertCounts[2]++;
}
});
return {
title: {
text: '近3天内告警数量变化',
left: 'center',
textStyle: {
color: '#fff'
}
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross'
},
formatter: '{b0}: {c0}'
},
xAxis: {
type: 'category',
boundaryGap: false,
data: timePoints
},
yAxis: {
type: 'value'
},
series: [
{
data: alertCounts,
type: 'line',
areaStyle: {}
}
]
};
} }
} }
}; };

View File

@ -24,7 +24,7 @@
</el-table-column> </el-table-column>
<el-table-column prop="status" label="告警状态" width="140"> <el-table-column prop="status" label="告警状态" width="140">
<template v-slot="scope"> <template v-slot="scope">
<el-tag :type="scope.row.status === '已处理' ? 'success' : 'warning'">{{ scope.row.status }}</el-tag> <el-tag :type="scope.row.status === 'pending' ? 'warning' : 'success'">{{ statusMapping[scope.row.status] }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="140"> <el-table-column label="操作" width="140">
@ -41,6 +41,17 @@
</el-pagination> </el-pagination>
</div> </div>
</el-col> </el-col>
<el-col :span="8">
<div class="right-panel">
<div class="panel-section">
<statistics />
</div>
<div class="panel-section">
<alertChart />
</div>
</div>
</el-col>
</el-row> </el-row>
<el-dialog v-model="dialogVisible" title="告警详情" width="80%"> <el-dialog v-model="dialogVisible" title="告警详情" width="80%">
<div> <div>
@ -61,7 +72,8 @@
</el-row> </el-row>
<el-row> <el-row>
<el-col :span="6"> <el-col :span="6">
<p>告警状态: <el-tag :type="selectedRow.status === '已处理' ? 'success' : 'warning'">{{ selectedRow.status }}</el-tag></p> <p>告警状态: <el-tag :type="selectedRow.status === 'pending' ? 'warning' : 'success'">{{ statusMapping[selectedRow.status]
}}</el-tag></p>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<p>摄像头编号: {{ selectedRow.camera_id }}</p> <p>摄像头编号: {{ selectedRow.camera_id }}</p>
@ -97,7 +109,10 @@
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import Statistics from '../components/Statistics.vue'; import Statistics from '../components/Statistics.vue';
import AlertChart from '../components/AlertChart.vue'; import AlertChart from '../components/AlertChart.vue';
import { login, getEvents, initCodeNameMap, codenameTranslate } from '../../superbox.js'; import { login, getEvents, initCodeNameMap, codenameTranslate, getAlgorithms } from '../../superbox.js';
const username = 'turingvideo';
const password = '1234qwer!';
export default { export default {
name: 'Home', name: 'Home',
@ -113,38 +128,39 @@ export default {
mediums: {}, mediums: {},
duration: '', duration: '',
typeMapping: { typeMapping: {
'abnormal:525': '黑屏检测', // 'abnormal:525': '',
'car_blocking:512': '车辆拥塞', // 'car_blocking:512': '',
'car_blocking:514': '违规停车', // 'car_blocking:514': '',
'escalator_status:518': '扶梯运行状态', // 'escalator_status:518': '',
'gathering:520': '人员密集', // 'gathering:520': '',
'gathering:521': '保洁点名', // 'gathering:521': '',
'intrude:513': '入侵检测', // 'intrude:513': '',
'long_term_door_status:526': '长期门状态检测', // 'long_term_door_status:526': '',
'lying_down:527': '人员倒地', // 'lying_down:527': '',
'minizawu:531': '杂物堆积', // 'minizawu:531': '',
'minizawu:532': '杂物堆积', // 'minizawu:532': '',
'personnel_stay:535': '人员逗留', // 'personnel_stay:535': '',
'vacant:524': '人员离岗', // 'vacant:524': '',
'zawu:516': '饮料垃圾检测', // 'zawu:516': '',
'zawu:517': '垃圾桶满溢', // 'zawu:517': '',
'zawu:519': '违规放置', // 'zawu:519': '',
'zawu:523': '绿化带垃圾检测', // 'zawu:523': '绿',
}, },
statusMapping: { statusMapping: {
'': '待处理', 'pending': '待处理',
'': '处理中', 'closed': '已处理'
'': '已处理'
}, },
currentPage: 1, currentPage: 1,
pageSize: 20, pageSize: 20,
} }
}, },
async created() { async created() {
try { try {
const token = await login('turingvideo', '1234qwer!'); const token = await login(username, password);
// await initCodeNameMap(token); // await initCodeNameMap(token);
this.tableData = await getEvents(token); this.tableData = await getEvents(token);
this.typeMapping = await this.fetchTypeMapping(token);
console.log(this.tableData); console.log(this.tableData);
} catch (error) { } catch (error) {
console.error("Error fetching events:", error); console.error("Error fetching events:", error);
@ -158,6 +174,23 @@ export default {
} }
}, },
methods: { methods: {
async fetchTypeMapping(token) {
let algorithms = await getAlgorithms(token);
const additionalMappings = [
{ code_name: 'minizawu:532', name: '杂物堆积' },
];
algorithms = algorithms.concat(additionalMappings);
let mapping = {};
algorithms.forEach((algorithm) => {
mapping[algorithm.code_name] = algorithm.name;
});
// console.log("mapping: ", mapping);
return mapping;
},
handleView(row) { handleView(row) {
this.selectedRow = row; this.selectedRow = row;
this.duration = this.calculateDuration(row.started_at, row.ended_at); this.duration = this.calculateDuration(row.started_at, row.ended_at);
@ -232,6 +265,24 @@ export default {
padding-left: 7%; padding-left: 7%;
} }
.right-panel {
display: flex;
flex-direction: column;
gap: 20px;
padding: 10px 10px 400px 10px;
background-color: #f5f5f5;
height: 600px;
overflow-y: auto;
}
.panel-section {
flex: 1;
background-color: #fff;
padding: 20px;
box-shadow: 0 20px 8px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
/* 弹窗> 显示左右等分*/ /* 弹窗> 显示左右等分*/
.event-media { .event-media {
/* display: flex; /* display: flex;

View File

@ -1,48 +1,10 @@
<template> <template>
<div> <div>
<el-card class="stats-card"> <el-card class="stats-card">
<div class="stats-header">控情况</div> <div class="stats-header">告警数量和类型分</div>
<el-row :gutter="20" class="stats-row"> <el-row :gutter="20" class="stats-row">
<el-col :span="8"> <el-col :span="24">
<div class="stats-item"> <div ref="chart" class="chart"></div>
<div class="stats-title">布控区域数</div>
<div class="stats-value">15</div>
</div>
</el-col>
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">布控站点数</div>
<div class="stats-value">54</div>
<div class="stats-subvalue">7 异常站点</div>
</div>
</el-col>
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">布控摄像头数</div>
<div class="stats-value">2000</div>
</div>
</el-col>
</el-row>
<div class="stats-divider"></div>
<div class="stats-header">告警情况</div>
<el-row :gutter="20" class="stats-row">
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">告警站点</div>
<div class="stats-value">25</div>
</div>
</el-col>
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">未完成告警</div>
<div class="stats-value">1511</div>
</div>
</el-col>
<el-col :span="8">
<div class="stats-item">
<div class="stats-title">已完成告警</div>
<div class="stats-value">123916</div>
</div>
</el-col> </el-col>
</el-row> </el-row>
</el-card> </el-card>
@ -50,9 +12,100 @@
</template> </template>
<script> <script>
import * as echarts from 'echarts';
import { login, getAlgorithms,getEvents} from '../../superbox.js';
const username = 'turingvideo';
const password = '1234qwer!';
export default { export default {
name: 'Statistics' name: 'Statistics',
data() {
return {
chart: null,
seriesData: [
],
};
},
// mounted() {
// this.initChart();
// },
async created(){
try{
const token = await login(username, password);
await this.fetchTypeMapping(token);
await this.updateSeriesData(token);
this.initChart();
}catch(error){
console.error("Error fetching algorithms:", error);
} }
},
methods: {
async fetchTypeMapping(token) {
const algorithms = await getAlgorithms(token);
let mapping = algorithms.map(algorithm => ({
value: 0, // 10
code_name: algorithm.code_name,
name: algorithm.name
}));
const newMapping = [
{code_name: "minizawu:532",name: "杂物堆积",value: 0},
]
mapping = mapping.concat(newMapping);
this.seriesData = mapping;
},
async updateSeriesData(token){
const events = await getEvents(token);
events.forEach(event => {
const matchAlgorithm = this.seriesData.find(item => item.code_name === event.types);
if (matchAlgorithm){
matchAlgorithm.value += 1;
}
})
},
initChart() {
this.chart = echarts.init(this.$refs.chart);
const option = {
tooltip: {
trigger: 'item',
},
legend: {
orient: 'horizontal',
bottom: 10,
textStyle: {
color: '#fff',
},
itemGap: 20,
data: this.seriesData.map(item => item.name),
},
series: [
{
name: '告警类型',
type: 'pie',
radius: '50%',
center: ['50%', '150px'],
data: this.seriesData,
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
},
label: {
show: true
},
stillShowZeroSum: false,
}
]
};
this.chart.setOption(option);
}
}
};
</script> </script>
<style scoped> <style scoped>
@ -72,30 +125,12 @@ export default {
} }
.stats-row { .stats-row {
margin-bottom: 20px; /* margin-top: 20px; */
}
.stats-item {
text-align: center;
}
.stats-title {
font-size: 16px;
margin-bottom: 10px; margin-bottom: 10px;
} }
.stats-value { .chart {
font-size: 24px; width: 100%;
font-weight: bold; height: 500px;
}
.stats-subvalue {
font-size: 14px;
color: #f39c12;
}
.stats-divider {
border-top: 1px solid #3a4b5c;
margin: 20px 0;
} }
</style> </style>

View File

@ -6,12 +6,13 @@ export const getSuperboxApiConfig = (address) => {
const LOGIN_ROUTE = BASE_ROUTE + "/auth/login"; const LOGIN_ROUTE = BASE_ROUTE + "/auth/login";
const LOGOUT_ROUTE = BASE_ROUTE + "/auth/logout"; const LOGOUT_ROUTE = BASE_ROUTE + "/auth/logout";
const CAMERA_ROUTE = BASE_ROUTE + "/camera/cameras"; const CAMERA_ROUTE = BASE_ROUTE + "/camera/cameras";
const EVENTS_ROUTE = BASE_ROUTE + "/event/events"; const EVENTS_ROUTE = BASE_ROUTE + "/event/events?limit=300";
const ALGORITHM_ROUTE = BASE_ROUTE + "/algorithms"; const ALGORITHM_ROUTE = BASE_ROUTE + "/algorithms";
let addr = address; let addr = address;
if (!addr) { if (!addr) {
addr = window.location.host.replace(/:\d+/, ""); addr = window.location.host.replace(/:\d+/, "");
console.log("No address provided, using " + addr);
} }
const superboxAddress = "http://" + addr + ":" + PORT.toString(); const superboxAddress = "http://" + addr + ":" + PORT.toString();
@ -155,4 +156,5 @@ export default {
getSuperboxApiConfig, getSuperboxApiConfig,
initCodeNameMap, initCodeNameMap,
codenameTranslate, codenameTranslate,
getAlgorithms,
}; };