You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

547 lines
17 KiB

<template>
<div class="body">
<div class="container">
<!-- 上层三个模块并排 -->
<div class="top-section">
<!-- 预警信息统计 -->
<div class="panel">
<div class="card">
<div class="card-header">
<h2 class="card-title"><i class="fas fa-exclamation-triangle"></i> 预警信息统计</h2>
</div>
<div class="stats-container">
<div class="stat-card" :class="alertMessageData.yearCount == 0?'normal':''">
<div class="stat-label">年预警总数</div>
<div class="stat-value">{{ alertMessageData.yearCount }}</div>
</div>
<div class="stat-card" :class="alertMessageData.monthCount == 0?'normal':''">
<div class="stat-label">月预警总数</div>
<div class="stat-value">{{ alertMessageData.monthCount }}</div>
</div>
<!-- 等级预警 -->
<div class="stat-card" :class="i.count == 0?'normal':i.count>10?'warning':''" v-for="i in alertMessageData.levelCount" :key="i.id">
<div class="stat-label">{{ `等级${i.level}预警` }}</div>
<div class="stat-value">{{ i.count }}</div>
</div>
<!-- 设备预警 -->
<div class="stat-card" :class="i.count == 0?'normal':i.count>10?'warning':''" v-for="i in alertMessageData.typeCount" :key="i.id">
<div class="stat-label">{{ `${i.name}预警` }}</div>
<div class="stat-value">{{ i.count }}</div>
</div>
</div>
</div>
</div>
<!-- 检测设备信息 -->
<div class="panel">
<div class="card">
<div class="card-header">
<h2 class="card-title"><i class="fas fa-microchip"></i> 检测设备信息</h2>
</div>
<ul class="device-list">
<PieChart height="250px" :chartData="byEquTypeData"/>
</ul>
</div>
</div>
<!-- 设备预警信息 -->
<div class="panel">
<div class="card">
<div class="card-header">
<h2 class="card-title"><i class="fas fa-bell"></i> 设备预警信息</h2>
</div>
<el-empty description="暂无预警" v-if="alerts.length == 0"></el-empty>
<ul class="device-list" v-infinite-scroll="loadMore" :infinite-scroll-disabled="isLoading || noMore" infinite-scroll-distance="50" infinite-scroll-delay="300">
<li class="device-item" v-for="alert in alerts" :key="alert.id" :class="alert.status === 'UNHANDLED' ? 'warning' : 'normal'" >
<div class="device-icon">
<i :class="alert.status === 'UNHANDLED' ? 'fas fa-exclamation-circle' : 'fas fa-check-circle'"></i>
</div>
<div class="device-info">
<div class="device-name">{{ alert.name }}</div>
<div class="device-details">
<div>预警时间: {{ parseTime(alert.occurTime) }}</div>
<div>设备位置: {{ alert.location }}</div>
<div>预警信息: {{ alert.message }}</div>
</div>
</div>
<span :class="alert.status === 'UNHANDLED' ? 'alert-badge' : 'normal-badge'">
{{ '等级'+alert.level }}
</span>
</li>
<p v-if="isLoading">加载中...</p>
<p v-if="noMore">没有更多了</p>
</ul>
</div>
</div>
</div>
<!-- 下层设备状态监控独立一行 -->
<div class="bottom-section">
<div class="card full-width">
<div class="card-header">
<h2 class="card-title"><i class="fas fa-desktop"></i> 设备状态监控</h2>
<div class="last-update">最后更新: {{ lastUpdate }}</div>
</div>
<div class="table-container">
<el-empty description="暂无预警" v-if="tableHeaders.length == 0"></el-empty>
<table v-else>
<thead>
<tr>
<th v-for="header in tableHeaders" :key="header">{{ header }}</th>
</tr>
</thead>
<tbody>
<tr v-for="device in deviceStatus" :key="device.id">
<td>{{ device.name }}</td>
<td>{{ device.location }}</td>
<td>
<span :class="getCpuStatus(device.cpuUtilization || 0)"></span>
{{ device.cpuUtilization || 0 }}%
</td>
<td>
<span :class="getMemoryStatus(device.memoryUtilization || 0)"></span>
{{ device.memoryUtilization || 0 }}%
</td>
<td>{{ device.temperature || 0 }}°C</td>
<td>
<el-tag :hit="false" :type="device.health == '1'?'success':device.health == '2'?'':device.health == '3'?'danger':device.health == '4'?'warning':'info'"> {{ device.health == '1'?'健康':device.health == '2'?'良好':device.health == '3'?'一般':device.health == '4'?'需维护':'未获取' }} </el-tag>
</td>
<td>{{ formatDuration(device.contWorkPeriod) }}</td>
<td>{{ parseTime(device.gatherTime) || '--' }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
10 months ago
</template>
10 months ago
<script>
import PanelGroup from './dashboard/PanelGroup'
import LineChart from './dashboard/LineChart'
import RaddarChart from './dashboard/RaddarChart'
import PieChart from './dashboard/PieChart'
import BarChart from './dashboard/BarChart'
import digital from './dashboard/digital'
import { statistic, byEquType, listMonitor, alertMessageTimes } from "@/api/index/index"
import { getToken } from "@/utils/auth"
import Treeselect from "@riophae/vue-treeselect"
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css"
10 months ago
export default {
name: 'Index',
components: {
PanelGroup,
LineChart,
RaddarChart,
PieChart,
BarChart,
digital
},
10 months ago
data() {
return {
totalAlerts: 111,
activeAlerts: 42,
resolvedAlerts: 69,
lastUpdate: new Date().toLocaleString(),
isLoading: false, // 加载状态
noMore: false, // 是否无更多数据
page: 1, // 当前页码
devices: [
{ id: 1, name: 'Forecasts', description: '预测分析系统', status: 'normal', icon: 'fas fa-chart-line' },
{ id: 2, name: 'Gold', description: '核心处理单元', status: 'warning', icon: 'fas fa-server' },
{ id: 3, name: 'Industries', description: '工业控制系统', status: 'normal', icon: 'fas fa-industry' }
],
// 设备预警信息
alerts: [],
// 统计预警信息次数
alertMessageData: [],
byEquTypeData: [],
tableHeaders: ['设备名称', '设备位置', 'CPU使用率', '内存使用率', '温度', '健康度', '设备运行时长', '采集时间'],
deviceStatus: []
}
10 months ago
},
methods: {
formatDuration(input) {// 类型安全校验
if (typeof input !== 'string') return "0天0小时";
const match = input.match(/(\d+)\s*days?,\s*(\d+):/i);
return match ? `${match[1]}${match[2]}小时` : "0天0小时";
},
getCpuStatus(usage) {
if (usage < 50) return 'status-indicator status-good';
if (usage < 80) return 'status-indicator status-warning';
return 'status-indicator status-critical';
10 months ago
},
getMemoryStatus(usage) {
if (usage < 60) return 'status-indicator status-good';
if (usage < 85) return 'status-indicator status-warning';
return 'status-indicator status-critical';
},
getHealthStatus(health) {
if (health >= 80) return 'health-good';
if (health >= 60) return 'health-warning';
return 'health-critical';
},
getbyEquType() {
this.loading = true
byEquType().then(response => {
this.loading = false
this.byEquTypeData = response.data
}
)
},
getlistMonitor() {
this.loading = true
listMonitor({pageSize: 100}).then(response => {
this.loading = false
this.deviceStatus = response.rows
}
)
},
getalertMessageTimes() {
this.loading = true
alertMessageTimes().then(response => {
this.alertMessageData = response.data
this.loading = false
}
)
},
async loadMore() {
if (this.isLoading || this.noMore) return;
this.isLoading = true;
try {
const res = await statistic({page:this.page,status:'UNHANDLED'}); // 模拟API请求
if (res.rows.length < 10) { // 假设每页10条
this.noMore = true;
} else {
this.alerts = [...this.alerts, ...res.rows];
this.page++;
}
} catch (error) {
console.error('加载失败:', error);
} finally {
this.isLoading = false;
}
}
},
created() {
this.getbyEquType()
this.getlistMonitor()
this.getalertMessageTimes()
},
}
</script>
<style lang="scss" scoped>
.body {
background-color: #f5f7fa;
color: #333;
line-height: 1.6;
padding: 20px;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 {
color: #2c3e50;
margin-bottom: 10px;
font-size: 28px;
}
.header p {
color: #7f8c8d;
font-size: 16px;
}
.container {
display: flex;
flex-direction: column;
gap: 20px;
}
.top-section {
display: flex;
gap: 20px;
}
.bottom-section {
display: flex;
flex-direction: column;
gap: 20px;
}
.panel {
flex: 1;
display: flex;
flex-direction: column;
gap: 20px;
}
.card {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
padding: 20px;
transition: transform 0.3s ease, box-shadow 0.3s ease;
height: 100%;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.12);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 10px;
border-bottom: 1px solid #eaeaea;
}
.card-title {
font-size: 18px;
font-weight: 600;
color: #2c3e50;
display: flex;
align-items: center;
gap: 8px;
}
.card-title i {
color: #3498db;
}
.stats-container {
display: grid;
justify-content: space-between;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
overflow-y: auto;
overflow-x: hidden;
height: 255px;
}
.stat-card {
flex: 1;
background: linear-gradient(135deg, #3498db, #2c3e50);
color: white;
border-radius: 10px;
padding: 12px;
text-align: center;
box-shadow: 0 4px 8px rgba(52, 152, 219, 0.3);
}
.stat-card.warning {
background: linear-gradient(135deg, #e74c3c, #c0392b);
box-shadow: 0 4px 8px rgba(231, 76, 60, 0.3);
}
.stat-card.normal {
background: linear-gradient(135deg, #2ecc71, #27ae60);
box-shadow: 0 4px 8px rgba(46, 204, 113, 0.3);
}
.stat-value {
font-size: 32px;
font-weight: 700;
margin: 10px 0;
}
.stat-label {
font-size: 14px;
opacity: 0.9;
}
.device-list {
list-style: none;
overflow-y: auto;
overflow-x: hidden;
max-height: 255px;
padding-left: 0;
}
.device-item {
display: flex;
align-items: center;
padding: 12px 15px;
margin-bottom: 10px;
background: #f8f9fa;
border-radius: 8px;
border-left: 4px solid #3498db;
transition: all 0.2s;
}
.device-item:hover {
background: #edf2f7;
transform: translateX(5px);
}
.device-item.warning {
border-left-color: #e74c3c;
}
.device-item.normal {
border-left-color: #2ecc71;
}
.device-icon {
width: 40px;
height: 40px;
background: #3498db;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 15px;
color: white;
}
.device-item.warning .device-icon {
background: #e74c3c;
}
.device-item.normal .device-icon {
background: #2ecc71;
}
.device-info {
flex: 1;
}
.device-name {
font-weight: 600;
margin-bottom: 5px;
}
.device-details {
font-size: 13px;
color: #666;
}
.alert-badge {
background: #e74c3c;
color: white;
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.normal-badge {
background: #2ecc71;
color: white;
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
}
.table-container {
overflow-x: auto;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
}
table {
width: 100%;
border-collapse: collapse;
background: white;
}
th {
background: #2c3e50;
color: white;
padding: 15px 12px;
text-align: left;
font-weight: 600;
}
td {
padding: 12px;
border-bottom: 1px solid #eaeaea;
}
tr:hover {
background-color: #f5f7fa;
}
.status-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 5px;
}
.status-good {
background-color: #2ecc71;
}
.status-warning {
background-color: #f39c12;
}
.status-critical {
background-color: #e74c3c;
}
.health-bar {
height: 6px;
background: #ecf0f1;
border-radius: 3px;
overflow: hidden;
}
.health-fill {
height: 100%;
border-radius: 3px;
}
.health-good {
background: #2ecc71;
width: 85%;
}
.health-warning {
background: #f39c12;
width: 65%;
}
.health-critical {
background: #e74c3c;
width: 40%;
}
.full-width {
width: 100%;
}
@media (max-width: 1024px) {
.top-section {
flex-direction: column;
}
.stats-container {
flex-direction: column;
}
}
</style>