|
|
@@ -0,0 +1,242 @@
|
|
|
+<template>
|
|
|
+ <el-container>
|
|
|
+ <el-header>
|
|
|
+ <el-card shadow="hover" style="margin-top: 10px">
|
|
|
+ <el-row justify="end" align="middle">
|
|
|
+ <el-col :span="19" style="text-align: right">
|
|
|
+ <el-radio-group v-model="timeRadio" size="large" @change="handleDateRangeChange">
|
|
|
+ <el-radio-button label="今日" value="today" />
|
|
|
+ <el-radio-button label="本周" value="week" />
|
|
|
+ <el-radio-button label="本月" value="month" />
|
|
|
+ </el-radio-group>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="5" style="text-align: right">
|
|
|
+ <el-date-picker
|
|
|
+ v-model="dateRange"
|
|
|
+ type="daterange"
|
|
|
+ range-separator="-"
|
|
|
+ start-placeholder="开始日期"
|
|
|
+ end-placeholder="结束日期"
|
|
|
+ style="margin-left: 10px; margin-right: 20px"
|
|
|
+ />
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-card>
|
|
|
+ </el-header>
|
|
|
+
|
|
|
+ <el-main style="margin-top: 20px">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="6" v-for="(item, index) in stats" :key="index">
|
|
|
+ <el-card shadow="hover" class="stat-card">
|
|
|
+ <h3>{{ item.label }}</h3>
|
|
|
+ <p :class="['number', item.class]">{{ item.value }}</p>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20" style="margin-top: 20px">
|
|
|
+ <el-col :span="18">
|
|
|
+ <el-row :gutter="20">
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <h3>类型占比</h3>
|
|
|
+ <div ref="typePie" class="chart-placeholder"></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <h3>告警级别</h3>
|
|
|
+ <div class="chart-placeholder"></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="8">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <h3>告警问题统计</h3>
|
|
|
+ <div class="chart-placeholder"></div>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ <el-row :gutter="20" style="margin-top: 20px">
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <h3>在线时长排行</h3>
|
|
|
+ <el-table :data="onlineTimeRanking" style="width: 100%">
|
|
|
+ <el-table-column prop="rank" label="排名" width="60"></el-table-column>
|
|
|
+ <el-table-column prop="device" label="设备名称"></el-table-column>
|
|
|
+ <el-table-column prop="value" label="时长"></el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <h3>告警数量排行</h3>
|
|
|
+ <el-table :data="alarmRanking" style="width: 100%">
|
|
|
+ <el-table-column prop="rank" label="排名" width="60"></el-table-column>
|
|
|
+ <el-table-column prop="device" label="设备名称"></el-table-column>
|
|
|
+ <el-table-column prop="value" label="告警数量"></el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="6">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <h3>告警清单</h3>
|
|
|
+ <el-table :data="alarmList" style="width: 100%">
|
|
|
+ <el-table-column prop="time" label="时间" width="160"></el-table-column>
|
|
|
+ <el-table-column prop="device" label="设备名称"></el-table-column>
|
|
|
+ <el-table-column prop="type" label="告警类型"></el-table-column>
|
|
|
+ <el-table-column prop="level" label="告警级别"></el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </el-card>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
+ </el-main>
|
|
|
+ </el-container>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts">
|
|
|
+import { reactive } from 'vue';
|
|
|
+import { deviceStatistics } from '@/api/smsb/device/device';
|
|
|
+import * as echarts from 'echarts';
|
|
|
+
|
|
|
+const timeRadio = ref('today');
|
|
|
+const dateRange = ref(['2025-01-01', '2025-01-01']);
|
|
|
+const typePie = ref();
|
|
|
+const stats = reactive([
|
|
|
+ { label: '设备总量', value: 0, class: '' },
|
|
|
+ { label: '已上线设备', value: 0, class: 'success' },
|
|
|
+ { label: '离线设备', value: 0, class: 'danger' },
|
|
|
+ { label: '待接入设备', value: 0, class: 'warning' }
|
|
|
+]);
|
|
|
+
|
|
|
+const onlineTimeRanking = reactive([
|
|
|
+ { rank: 1, device: '设备A', value: '6000' },
|
|
|
+ { rank: 2, device: '设备B', value: '5900' },
|
|
|
+ { rank: 3, device: '设备C', value: '5800' }
|
|
|
+]);
|
|
|
+
|
|
|
+const alarmRanking = reactive([
|
|
|
+ { rank: 1, device: '设备A', value: '6000' },
|
|
|
+ { rank: 2, device: '设备B', value: '5900' },
|
|
|
+ { rank: 3, device: '设备C', value: '5800' }
|
|
|
+]);
|
|
|
+
|
|
|
+const alarmList = reactive([
|
|
|
+ { time: '2025-02-20', device: '摄像头01', type: '紧急SOS', level: '设备报警' },
|
|
|
+ { time: '2025-02-20', device: '摄像头02', type: '紧急SOS', level: '设备报警' },
|
|
|
+ { time: '2025-02-20', device: '摄像头03', type: '紧急SOS', level: '设备报警' }
|
|
|
+]);
|
|
|
+
|
|
|
+const handleDateRangeChange = () => {
|
|
|
+ const rangeType = timeRadio.value;
|
|
|
+ const today = new Date();
|
|
|
+ const startDate = new Date();
|
|
|
+ const endDate = new Date();
|
|
|
+ switch (rangeType) {
|
|
|
+ case 'today':
|
|
|
+ break;
|
|
|
+ case 'week':
|
|
|
+ startDate.setDate(today.getDate() - 7);
|
|
|
+ break;
|
|
|
+ case 'month':
|
|
|
+ startDate.setMonth(today.getMonth() - 1);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new Error('Invalid range type');
|
|
|
+ }
|
|
|
+ dateRange.value = [formatDate(startDate), formatDate(endDate)];
|
|
|
+};
|
|
|
+
|
|
|
+const formatDate = (date: Date) => {
|
|
|
+ const year = date.getFullYear();
|
|
|
+ const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
+ const day = String(date.getDate()).padStart(2, '0');
|
|
|
+ return `${year}-${month}-${day}`;
|
|
|
+};
|
|
|
+const getDeviceStatistics = async () => {
|
|
|
+ const res = await deviceStatistics();
|
|
|
+ stats.forEach((item) => {
|
|
|
+ switch (item.label) {
|
|
|
+ case '设备总量':
|
|
|
+ item.value = res.data.totalNum;
|
|
|
+ break;
|
|
|
+ case '已上线设备':
|
|
|
+ item.value = res.data.onlineNum;
|
|
|
+ break;
|
|
|
+ case '离线设备':
|
|
|
+ item.value = res.data.offlineNum;
|
|
|
+ break;
|
|
|
+ case '待接入设备':
|
|
|
+ item.value = res.data.initNum;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const typePieInstance = echarts.init(typePie.value, 'macaroons');
|
|
|
+ typePieInstance.setOption({
|
|
|
+ title: {
|
|
|
+ text: '',
|
|
|
+ subtext: '',
|
|
|
+ left: 'center'
|
|
|
+ },
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'item'
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ orient: 'vertical',
|
|
|
+ left: 'left'
|
|
|
+ },
|
|
|
+ series: [
|
|
|
+ {
|
|
|
+ name: '类型占比',
|
|
|
+ type: 'pie',
|
|
|
+ radius: '60%',
|
|
|
+ data: [
|
|
|
+ { value: (res.data.onlineNum / res.data.totalNum) * 100, name: '在线' },
|
|
|
+ { value: (res.data.offlineNum / res.data.totalNum) * 100, name: '离线' },
|
|
|
+ { value: (res.data.initNum / res.data.totalNum) * 100, name: '待接入' }
|
|
|
+ ],
|
|
|
+ emphasis: {
|
|
|
+ itemStyle: {
|
|
|
+ shadowBlur: 10,
|
|
|
+ shadowOffsetX: 0,
|
|
|
+ shadowColor: 'rgba(0, 0, 0, 0.5)'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ });
|
|
|
+};
|
|
|
+onMounted(() => {
|
|
|
+ handleDateRangeChange();
|
|
|
+ getDeviceStatistics();
|
|
|
+});
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+.stat-card {
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.number {
|
|
|
+ font-size: 24px;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.success {
|
|
|
+ color: green;
|
|
|
+}
|
|
|
+
|
|
|
+.danger {
|
|
|
+ color: red;
|
|
|
+}
|
|
|
+
|
|
|
+.warning {
|
|
|
+ color: orange;
|
|
|
+}
|
|
|
+
|
|
|
+.chart-placeholder {
|
|
|
+ height: 250px;
|
|
|
+ /*background: #f5f5f5;*/
|
|
|
+ border-radius: 8px;
|
|
|
+}
|
|
|
+</style>
|