Compare commits
4 Commits
283eedbcaa
...
3fac89b635
Author | SHA1 | Date |
---|---|---|
|
3fac89b635 | |
|
f1d2740689 | |
|
7b5bb1aea7 | |
|
43526edb32 |
|
@ -16,7 +16,7 @@
|
|||
<el-menu-item index="/real-time-monitoring">实时监测</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="/test">系统设置</el-menu-item>
|
||||
<el-menu-item index="/news">系统设置</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
<div class="user-info">
|
||||
|
|
|
@ -3,58 +3,188 @@
|
|||
<el-card class="alert-card">
|
||||
<div class="alert-header">告警趋势</div>
|
||||
<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="30天" name="second">30天数据展示</el-tab-pane>
|
||||
<el-tab-pane label="12月" name="third">12月数据展示</el-tab-pane>
|
||||
<el-tab-pane label="前一天" name="first">前一天数据</el-tab-pane>
|
||||
<el-tab-pane label="3天" name="second">近3天数据展示</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div id="alert-chart" class="chart-container"></div>
|
||||
<div id="alertChart" class="chart-container"></div>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
import { login, getEvents } from '../../superbox.js';
|
||||
|
||||
const username = 'turingvideo';
|
||||
const password = '1234qwer!';
|
||||
|
||||
export default {
|
||||
name: 'AlertChart',
|
||||
data() {
|
||||
return {
|
||||
activeName: 'first'
|
||||
activeName: 'first',
|
||||
eventsData: []
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.initChart();
|
||||
async created() {
|
||||
try {
|
||||
const token = await login(username, password);
|
||||
this.eventsData = await getEvents(token);
|
||||
this.initChart();
|
||||
} catch (error) {
|
||||
console.error("Error fetching events:", error);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick(tab, event) {
|
||||
console.log(tab, event);
|
||||
// 根据选中的标签刷新图表数据
|
||||
this.initChart();
|
||||
},
|
||||
initChart() {
|
||||
var chartDom = document.getElementById('alert-chart');
|
||||
var chartDom = document.getElementById('alertChart');
|
||||
var myChart = echarts.init(chartDom);
|
||||
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: {
|
||||
type: 'category',
|
||||
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: {
|
||||
type: 'value'
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [10, 22, 28, 43, 49, 62],
|
||||
data: alertCounts,
|
||||
type: 'line',
|
||||
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: {}
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
</el-table-column>
|
||||
<el-table-column prop="status" label="告警状态" width="140">
|
||||
<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>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140">
|
||||
|
@ -41,6 +41,17 @@
|
|||
</el-pagination>
|
||||
</div>
|
||||
</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-dialog v-model="dialogVisible" title="告警详情" width="80%">
|
||||
<div>
|
||||
|
@ -61,7 +72,8 @@
|
|||
</el-row>
|
||||
<el-row>
|
||||
<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 :span="6">
|
||||
<p>摄像头编号: {{ selectedRow.camera_id }}</p>
|
||||
|
@ -97,7 +109,10 @@
|
|||
import * as echarts from 'echarts';
|
||||
import Statistics from '../components/Statistics.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 {
|
||||
name: 'Home',
|
||||
|
@ -113,38 +128,39 @@ export default {
|
|||
mediums: {},
|
||||
duration: '',
|
||||
typeMapping: {
|
||||
'abnormal:525': '黑屏检测',
|
||||
'car_blocking:512': '车辆拥塞',
|
||||
'car_blocking:514': '违规停车',
|
||||
'escalator_status:518': '扶梯运行状态',
|
||||
'gathering:520': '人员密集',
|
||||
'gathering:521': '保洁点名',
|
||||
'intrude:513': '入侵检测',
|
||||
'long_term_door_status:526': '长期门状态检测',
|
||||
'lying_down:527': '人员倒地',
|
||||
'minizawu:531': '杂物堆积',
|
||||
'minizawu:532': '杂物堆积',
|
||||
'personnel_stay:535': '人员逗留',
|
||||
'vacant:524': '人员离岗',
|
||||
'zawu:516': '饮料垃圾检测',
|
||||
'zawu:517': '垃圾桶满溢',
|
||||
'zawu:519': '违规放置',
|
||||
'zawu:523': '绿化带垃圾检测',
|
||||
// 'abnormal:525': '黑屏检测',
|
||||
// 'car_blocking:512': '车辆拥塞',
|
||||
// 'car_blocking:514': '违规停车',
|
||||
// 'escalator_status:518': '扶梯运行状态',
|
||||
// 'gathering:520': '人员密集',
|
||||
// 'gathering:521': '保洁点名',
|
||||
// 'intrude:513': '入侵检测',
|
||||
// 'long_term_door_status:526': '长期门状态检测',
|
||||
// 'lying_down:527': '人员倒地',
|
||||
// 'minizawu:531': '杂物堆积',
|
||||
// 'minizawu:532': '杂物堆积',
|
||||
// 'personnel_stay:535': '人员逗留',
|
||||
// 'vacant:524': '人员离岗',
|
||||
// 'zawu:516': '饮料垃圾检测',
|
||||
// 'zawu:517': '垃圾桶满溢',
|
||||
// 'zawu:519': '违规放置',
|
||||
// 'zawu:523': '绿化带垃圾检测',
|
||||
},
|
||||
statusMapping: {
|
||||
'': '待处理',
|
||||
'': '处理中',
|
||||
'': '已处理'
|
||||
'pending': '待处理',
|
||||
'closed': '已处理'
|
||||
},
|
||||
currentPage: 1,
|
||||
pageSize: 20,
|
||||
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
try {
|
||||
const token = await login('turingvideo', '1234qwer!');
|
||||
const token = await login(username, password);
|
||||
// await initCodeNameMap(token);
|
||||
this.tableData = await getEvents(token);
|
||||
this.typeMapping = await this.fetchTypeMapping(token);
|
||||
console.log(this.tableData);
|
||||
} catch (error) {
|
||||
console.error("Error fetching events:", error);
|
||||
|
@ -158,6 +174,23 @@ export default {
|
|||
}
|
||||
},
|
||||
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) {
|
||||
this.selectedRow = row;
|
||||
this.duration = this.calculateDuration(row.started_at, row.ended_at);
|
||||
|
@ -221,7 +254,7 @@ export default {
|
|||
}
|
||||
|
||||
.top-scroll-content {
|
||||
/* 高度滚动条显示出来默认值 */
|
||||
/* 高度滚动条显示出来默认值 */
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
|
@ -232,6 +265,24 @@ export default {
|
|||
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 {
|
||||
/* display: flex;
|
||||
|
|
|
@ -1,48 +1,10 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-card class="stats-card">
|
||||
<div class="stats-header">布控情况</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">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 :span="24">
|
||||
<div ref="chart" class="chart"></div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-card>
|
||||
|
@ -50,9 +12,100 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts';
|
||||
import { login, getAlgorithms,getEvents} from '../../superbox.js';
|
||||
|
||||
const username = 'turingvideo';
|
||||
const password = '1234qwer!';
|
||||
|
||||
|
||||
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>
|
||||
|
||||
<style scoped>
|
||||
|
@ -72,30 +125,12 @@ export default {
|
|||
}
|
||||
|
||||
.stats-row {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.stats-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.stats-title {
|
||||
font-size: 16px;
|
||||
/* margin-top: 20px; */
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.stats-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.stats-subvalue {
|
||||
font-size: 14px;
|
||||
color: #f39c12;
|
||||
}
|
||||
|
||||
.stats-divider {
|
||||
border-top: 1px solid #3a4b5c;
|
||||
margin: 20px 0;
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,12 +6,13 @@ export const getSuperboxApiConfig = (address) => {
|
|||
const LOGIN_ROUTE = BASE_ROUTE + "/auth/login";
|
||||
const LOGOUT_ROUTE = BASE_ROUTE + "/auth/logout";
|
||||
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";
|
||||
|
||||
let addr = address;
|
||||
if (!addr) {
|
||||
addr = window.location.host.replace(/:\d+/, "");
|
||||
console.log("No address provided, using " + addr);
|
||||
}
|
||||
const superboxAddress = "http://" + addr + ":" + PORT.toString();
|
||||
|
||||
|
@ -155,4 +156,5 @@ export default {
|
|||
getSuperboxApiConfig,
|
||||
initCodeNameMap,
|
||||
codenameTranslate,
|
||||
getAlgorithms,
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue