【web】 首页设备状态监控

dev
Wangxin 8 months ago
parent 1da2ecdbe8
commit fcd437e0a0
  1. 2
      ALOps_sys_fe/alops-ui/src/router/index.js
  2. 771
      ALOps_sys_fe/alops-ui/src/views/index.vue

@ -70,7 +70,7 @@ export const constantRoutes = [
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true, fullscreen: true }
meta: { title: '首页', icon: 'dashboard' }
}
]
},

@ -1,5 +1,5 @@
<template>
<div id="app" class="dashboard">
<div id="app" class="dashboard" style="overflow-x: hidden;">
<div class="header">
<h1>唐山市设备监控中心大屏 - 智能管理版</h1>
@ -11,177 +11,112 @@
<div class="left-panel">
<div class="panel">
<div class="panel-title">
预警信息统计
设备状态监控
<div class="panel-actions">
<div class="panel-btn" @click="exportWarningData">导出</div>
<div class="panel-btn" @click="showWarningHistory = !showWarningHistory">历史</div>
<div class="panel-btn" @click="refreshDeviceStatus">刷新</div>
</div>
</div>
<div class="warning-stats">
<div class="stat-item" @click="filterByWarningLevel('total')">
<div class="stat-value stat-total">{{ warningStats.total }}</div>
<div class="stat-label">总预警数</div>
</div>
<div class="stat-item" @click="filterByWarningLevel('high')">
<div class="stat-value stat-high">{{ warningStats.highLevel }}</div>
<div class="stat-label">高级别预警</div>
</div>
<div class="stat-item" @click="filterByWarningLevel('medium')">
<div class="stat-value stat-medium">{{ warningStats.mediumLevel }}</div>
<div class="stat-label">中级别预警</div>
</div>
<div class="stat-item" @click="filterByWarningLevel('low')">
<div class="stat-value stat-low">{{ warningStats.lowLevel }}</div>
<div class="stat-label">低级别预警</div>
<div class="device-list" style="height: calc(100% - 50px); overflow-y: auto;">
<div v-for="device in deviceStatus || []"
:key="device.id || Math.random()"
class="device-item"
@click="showDeviceDetail(device)">
<div :class="['device-status', `status-${device.status || 'normal'}`]"></div>
<div class="device-name">{{ device.equManuName || device.name || '未知设备' }}</div>
<div class="device-value">{{ device.cpuUtilization || 0 }}%</div>
</div>
</div>
</div>
<div class="panel">
<div class="panel-title">
设备状态监控
<div class="panel-actions">
<div class="panel-btn" @click="refreshDeviceStatus">刷新</div>
</div>
楼层设备分布情况
</div>
<div class="device-list">
<div v-for="device in filteredDeviceStatus"
:key="device.id"
class="device-item"
@click="showDeviceDetail(device)">
<div :class="['device-status', `status-${device.status}`]"></div>
<div class="device-name">{{ device.name }}</div>
<div class="device-value">{{ device.value }}</div>
<div class="floor-device-container">
<div class="floor-device-grid">
<div v-for="floor in floorDeviceDistribution"
:key="floor.level"
class="floor-device-card">
<div class="floor-level">{{ floor.level }}</div>
<div class="floor-count">{{ floor.count }}</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧面板 -->
<div class="right-panel">
<!-- 中间面板 - 楼层房间布局展示 -->
<div class="center-panel">
<div class="panel">
<div class="panel-title">
楼层房间设备分布监控
<div class="panel-actions">
<div class="panel-btn" @click="toggleAutoSwitch">{{ autoSwitch ? '停止' : '自动' }}切换</div>
<div class="panel-title">设备类型分布</div>
<div class="device-stats-container">
<div class="device-stat-item">
<div class="device-stat-value">{{ totalDevices }}</div>
<div class="device-stat-label">设备总数</div>
</div>
<div class="device-stat-item">
<div class="device-stat-value">{{ onlineDevices }}</div>
<div class="device-stat-label">在线设备数</div>
</div>
</div>
<div class="building-container">
<!-- 楼层平面图容器 -->
<div class="floor-plans-container">
<div v-for="floor in floors"
:key="floor.level"
:class="['floor-plan', { active: currentFloor === floor.level }]">
<h2 class="floor-title">{{ floor.name }}</h2>
<div class="floor-layout">
<!-- 房间布局 -->
<div v-for="room in floor.rooms"
:key="room.id"
class="room"
:style="{
left: `${room.position.left}%`,
top: `${room.position.top}%`,
width: `${room.position.width}%`,
height: `${room.position.height}%`
}"
@click="showRoomDevices(room)">
<div class="room-label">{{ room.name }}</div>
</div>
<!-- 设备标记 -->
<div v-for="device in floor.devices"
:key="device.id"
class="device-marker"
:class="`device-${device.status}`"
:style="{
left: `${device.position.x}%`,
top: `${device.position.y}%`
}"
:title="device.name"
@click="showDeviceDetail(device)">
</div>
</div>
<div class="chart-container" ref="pieChart"></div>
</div>
<div class="panel">
<div class="panel-title">
预警信息统计
</div>
<div class="warning-stats">
<div class="warning-row">
<div class="stat-item time-item" @click="filterByWarningLevel('annual')">
<div class="stat-value stat-annual">{{ warningStats.annual }}</div>
<div class="stat-label">年度预警</div>
</div>
<div class="stat-item time-item" @click="filterByWarningLevel('monthly')">
<div class="stat-value stat-monthly">{{ warningStats.monthly }}</div>
<div class="stat-label">月度预警</div>
</div>
</div>
<!-- 楼层信息面板 -->
<div class="floor-info">
<div class="floor-selector">
<div class="floor-btn"
v-for="floor in floors"
:key="floor.level"
:class="{ active: currentFloor === floor.level }"
@click="switchFloor(floor.level)">
{{ floor.name }}
</div>
<div class="warning-row">
<div class="stat-item level-item" @click="filterByWarningLevel('level1')">
<div class="stat-value stat-level1">{{ warningStats.level1 }}</div>
<div class="stat-label">1级预警</div>
</div>
<div class="stat-item level-item" @click="filterByWarningLevel('level2')">
<div class="stat-value stat-level2">{{ warningStats.level2 }}</div>
<div class="stat-label">2级预警</div>
</div>
<div class="floor-details">
<h3>{{ getCurrentFloor().name }} - 设备分布</h3>
<div class="floor-stats">
<div class="floor-stat">
<span class="stat-label">设备总数:</span>
<span class="stat-value">{{ getCurrentFloor().totalDevices }}</span>
</div>
<div class="floor-stat">
<span class="stat-label">在线设备:</span>
<span class="stat-value online">{{ getCurrentFloor().onlineDevices }}</span>
</div>
<div class="floor-stat">
<span class="stat-label">异常设备:</span>
<span class="stat-value warning">{{ getCurrentFloor().warningDevices }}</span>
</div>
</div>
<div class="device-list-mini">
<div v-for="device in getCurrentFloorDevices()"
:key="device.id"
class="device-item-mini"
@mouseenter="highlightDevice(device.id)"
@mouseleave="resetDeviceHighlight"
@click="showDeviceDetail(device)">
<div :class="['device-dot', `status-${device.status}`]"></div>
<span class="device-name-mini">{{ device.name }}</span>
<span class="device-position">{{ device.room }}</span>
</div>
</div>
<div class="stat-item level-item" @click="filterByWarningLevel('level3')">
<div class="stat-value stat-level3">{{ warningStats.level3 }}</div>
<div class="stat-label">3级预警</div>
</div>
</div>
</div>
</div>
</div>
<!-- 右侧面板 -->
<div class="right-panel">
<!-- 中间面板 - 楼层房间布局展示 -->
<div class="center-panel">
<div class="panel">
<div class="panel-title">
设备预警信息
<div class="panel-actions">
<div class="panel-btn" @click="toggleSoundAlert">
{{ soundAlert ? '关闭' : '开启' }}声音
</div>
</div>
近期预警信息
</div>
<div class="device-list">
<el-scrollbar class="warning-scrollbar">
<div v-for="warning in filteredWarnings"
:key="warning.id"
class="device-item"
class="warning-item"
@click="showDeviceDetail(warning)">
<div :class="['device-status', `status-${warning.level}`]"></div>
<div class="device-name">{{ warning.name }}</div>
<div class="device-value">{{ warning.time }}</div>
<div class="warning-header">
<span :class="['warning-type', `level-${warning.level}`]">{{ warning.name }}</span>
</div>
<div class="warning-time">预警时间: {{ warning.time }}</div>
<div class="warning-position">设备位置: {{ warning.location }}</div>
<div class="warning-detail">设备编号 {{ warning.deviceId }} 设备名称{{ warning.deviceName }}{{ warning.description }}</div>
</div>
</div>
</div>
<div class="panel">
<div class="panel-title">检测设备信息统计</div>
<div class="chart-container" ref="chart"></div>
</div>
<div class="panel">
<div class="panel-title">设备类型分布</div>
<div class="chart-container" ref="pieChart"></div>
</el-scrollbar>
</div>
</div>
</div>
<div class="footer">
@ -211,7 +146,7 @@
</div>
<div class="detail-item">
<span class="detail-label">所在位置:</span>
<span class="detail-value">{{ selectedDevice.room || '未知' }}</span>
<span class="detail-value">{{ selectedDevice.location || '未知' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">设备类型:</span>
@ -219,42 +154,42 @@
</div>
<div class="detail-item">
<span class="detail-label">设备状态:</span>
<span class="detail-value" :style="{color: getStatusColor(selectedDevice.status)}">
<span class="detail-value" :style="{color: selectedDevice.status === '1' ? 'green' : 'red'}">
{{ getStatusText(selectedDevice.status) }}
</span>
</div>
<div class="detail-item">
<!-- <div class="detail-item">
<span class="detail-label">当前值:</span>
<span class="detail-value">{{ selectedDevice.value || 'N/A' }}</span>
</div>
</div> -->
</div>
<div class="detail-section">
<h3>运行状态</h3>
<div class="detail-item">
<span class="detail-label">运行时间:</span>
<span class="detail-value">{{ selectedDevice.uptime || '156天' }}</span>
<span class="detail-value">{{ formatDuration(selectedDevice.contWorkPeriod) || '0天' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">CPU使用率:</span>
<span class="detail-value">{{ selectedDevice.cpu || '45%' }}</span>
<span class="detail-value">{{ selectedDevice.cpuUtilization || '0' }}%</span>
</div>
<div class="detail-item">
<span class="detail-label">内存使用率:</span>
<span class="detail-value">{{ selectedDevice.memory || '62%' }}</span>
<span class="detail-value">{{ selectedDevice.memoryUtilization || '0' }}%</span>
</div>
<div class="detail-item">
<span class="detail-label">网络流量:</span>
<span class="detail-value">{{ selectedDevice.network || '125MB/s' }}</span>
<span class="detail-label">风扇转速:</span>
<span class="detail-value">{{ selectedDevice.fanSpeed || '0' }}'RPM/s'</span>
</div>
<div class="detail-item">
<span class="detail-label">温度:</span>
<span class="detail-value">{{ selectedDevice.temperature || '42°C' }}</span>
<span class="detail-value">{{ selectedDevice.temperature || '0' }}°C</span>
</div>
<div class="detail-item">
<!-- <div class="detail-item">
<span class="detail-label">最后维护:</span>
<span class="detail-value">{{ selectedDevice.lastMaintenance || '2024-09-15' }}</span>
</div>
</div> -->
<div class="control-buttons">
<button class="control-btn btn-restart" @click="controlDevice('restart')">重启设备</button>
@ -304,9 +239,11 @@
</template>
<script>
import { getInfo, historyQuery } from "@/api/inMonitoring/devicesState"
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
import "splitpanes/dist/splitpanes.css"
import * as echarts from 'echarts'
import { monitorpageList } from "@/api/inMonitoring/devicesState"
require('echarts/theme/macarons') // echarts theme
export default {
@ -329,30 +266,36 @@ export default {
historyEndDate: '',
warningLevelFilter: 'all',
warningStats: {
total: 18,
highLevel: 4,
mediumLevel: 7,
lowLevel: 7
annual: 156,
monthly: 18,
level1: 4,
level2: 7,
level3: 7
},
deviceStatus: [
{ id: 1, name: '一层核心服务器', status: 'normal', value: '98%', type: '服务器', uptime: '156天', cpu: '45%', memory: '62%', network: '125MB/s', temperature: '42°C', lastMaintenance: '2024-09-15' },
{ id: 2, name: '二层数据库集群', status: 'normal', value: '92%', type: '服务器', uptime: '142天', cpu: '38%', memory: '75%', network: '89MB/s', temperature: '45°C', lastMaintenance: '2024-09-20' },
{ id: 3, name: '三层网络交换机', status: 'warning', value: '75%', type: '网络设备', uptime: '89天', cpu: '25%', memory: '48%', network: '210MB/s', temperature: '52°C', lastMaintenance: '2024-08-30' },
{ id: 4, name: '四层防火墙', status: 'normal', value: '88%', type: '安全设备', uptime: '201天', cpu: '18%', memory: '32%', network: '95MB/s', temperature: '38°C', lastMaintenance: '2024-07-15' },
{ id: 5, name: '一层存储设备', status: 'error', value: '45%', type: '存储设备', uptime: '67天', cpu: '62%', memory: '85%', network: '156MB/s', temperature: '58°C', lastMaintenance: '2024-10-05' },
{ id: 6, name: '三层备份系统', status: 'normal', value: '96%', type: '备份设备', uptime: '134天', cpu: '28%', memory: '41%', network: '78MB/s', temperature: '41°C', lastMaintenance: '2024-09-10' },
{ id: 7, name: '二层负载均衡', status: 'warning', value: '68%', type: '网络设备', uptime: '98天', cpu: '52%', memory: '66%', network: '185MB/s', temperature: '49°C', lastMaintenance: '2024-09-25' },
{ id: 8, name: '四层应用服务器', status: 'normal', value: '91%', type: '服务器', uptime: '178天', cpu: '41%', memory: '58%', network: '112MB/s', temperature: '43°C', lastMaintenance: '2024-08-20' }
],
deviceStatus: [],
deviceWarnings: [
{ id: 1, name: '一层存储设备容量不足', level: 'error', time: '10:23', deviceId: 5 },
{ id: 2, name: '三层网络交换机负载高', level: 'warning', time: '09:45', deviceId: 3 },
{ id: 3, name: '二层负载均衡器异常', level: 'warning', time: '09:12', deviceId: 7 },
{ id: 4, name: '二层数据库连接数超限', level: 'error', time: '08:56', deviceId: 2 },
{ id: 5, name: '四层备份任务失败', level: 'warning', time: '08:30', deviceId: 6 },
{ id: 6, name: '一层安全审计异常', level: 'error', time: '07:45', deviceId: 1 }
{ id: 1, name: 'test1', level: 'error', time: '2025-10-10 20:32:56', deviceId: 11, deviceName: 'test1设备端口21', location: '华峰', description: '区域状态为22' },
{ id: 2, name: 'test1', level: 'error', time: '2025-10-10 20:32:56', deviceId: 11, deviceName: 'test1设备端口', location: '华峰', description: '状态异常,需要立即处理' },
{ id: 3, name: '网络设备异常', level: 'error', time: '2025-10-10 18:15:32', deviceId: 23, deviceName: '交换机端口', location: '数据中心', description: '状态异常,连接数超过阈值' },
{ id: 4, name: '服务器负载过高', level: 'error', time: '2025-10-10 14:23:07', deviceId: 67, deviceName: '核心服务器', location: '服务器机房A', description: 'CPU使用率达到95%,需要优化' },
{ id: 5, name: '网络延迟异常', level: 'warning', time: '2025-10-10 12:05:41', deviceId: 89, deviceName: '路由器网络', location: '网络中心', description: '延迟超过阈值,影响数据传输' },
{ id: 6, name: '安全设备告警', level: 'warning', time: '2025-10-10 10:30:15', deviceId: 56, deviceName: '防火墙', location: '安全区域', description: '检测到异常访问尝试' }
],
currentFloor: 1,
floorDeviceDistribution: [
{ level: 1, count: 15 },
{ level: 2, count: 18 },
{ level: 3, count: 22 },
{ level: 4, count: 14 },
{ level: 5, count: 16 },
{ level: 6, count: 13 },
{ level: 7, count: 17 },
{ level: 8, count: 11 },
{ level: 9, count: 12 },
{ level: 10, count: 9 },
{ level: 11, count: 14 },
{ level: 12, count: 16 }
],
floors: [
{
level: 1,
@ -461,12 +404,8 @@ export default {
highlightedDevice: null
}},
computed: {
filteredDeviceStatus() {
if (this.warningLevelFilter === 'all') {
return this.deviceStatus;
}
return this.deviceStatus.filter(device => device.status === this.warningLevelFilter);
},
// 使deviceStatus
// filteredDeviceStatus使deviceStatus
filteredWarnings() {
if (this.warningLevelFilter === 'all') {
return this.deviceWarnings;
@ -492,15 +431,19 @@ export default {
// 使nextTickDOM
this.$nextTick(() => {
this.initBarChart();
this.initPieChart();
});
//
window.addEventListener('resize', this.handleResize);
//
setInterval(this.updateData, 5000);
//
this.refreshDeviceStatus();
//
setInterval(() => {
this.refreshDeviceStatus();
}, 60000);
// WebSocket
this.simulateWebSocket();
@ -513,6 +456,13 @@ export default {
}
},
methods: {
formatDuration(input) {
const regex = /(\d+)\s*days,\s*(\d+):/;
const match = input.match(regex);
return match
? `${match[1]}${match[2]}小时`
: "格式不匹配";
},
updateTimeDisplay() {
const now = new Date();
this.currentTime = now.toLocaleString('zh-CN', {
@ -530,101 +480,6 @@ export default {
}
},
initBarChart() {
this.barChart = echarts.init(this.$refs.chart);
const option = {
backgroundColor: 'transparent',
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
legend: {
data: ['正常设备', '预警设备', '故障设备'],
textStyle: {
color: '#a0aec0'
},
top: 10
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: ['服务器', '网络设备', '存储设备', '安全设备', '备份设备'],
axisLine: {
lineStyle: {
color: '#4dabff'
}
},
axisLabel: {
color: '#a0aec0'
}
},
yAxis: {
type: 'value',
axisLine: {
lineStyle: {
color: '#4dabff'
}
},
axisLabel: {
color: '#a0aec0'
},
splitLine: {
lineStyle: {
color: 'rgba(77, 171, 255, 0.2)'
}
}
},
series: [
{
name: '正常设备',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series'
},
data: [32, 28, 15, 12, 8],
itemStyle: {
color: '#4CAF50'
}
},
{
name: '预警设备',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series'
},
data: [5, 7, 3, 2, 1],
itemStyle: {
color: '#FF9800'
}
},
{
name: '故障设备',
type: 'bar',
stack: 'total',
emphasis: {
focus: 'series'
},
data: [2, 1, 1, 0, 0],
itemStyle: {
color: '#F44336'
}
}
]
};
this.barChart.setOption(option);
},
initPieChart() {
this.pieChart = echarts.init(this.$refs.pieChart);
@ -850,68 +705,36 @@ export default {
}
},
updateData() {
//
this.warningStats.total = Math.floor(15 + Math.random() * 10);
this.warningStats.highLevel = Math.floor(3 + Math.random() * 5);
this.warningStats.mediumLevel = Math.floor(5 + Math.random() * 8);
this.warningStats.lowLevel = this.warningStats.total - this.warningStats.highLevel - this.warningStats.mediumLevel;
this.onlineDevices = Math.floor(90 + Math.random() * 15);
//
this.deviceStatus.forEach(device => {
if (device.status === 'normal' && Math.random() > 0.7) {
device.status = Math.random() > 0.5 ? 'warning' : 'error';
} else if (device.status !== 'normal' && Math.random() > 0.8) {
device.status = 'normal';
}
//
if (device.status === 'normal') {
device.value = Math.floor(85 + Math.random() * 15) + '%';
} else if (device.status === 'warning') {
device.value = Math.floor(60 + Math.random() * 20) + '%';
} else {
device.value = Math.floor(30 + Math.random() * 30) + '%';
}
});
//
this.floors.forEach(floor => {
floor.devices.forEach(device => {
if (device.status === 'normal' && Math.random() > 0.8) {
device.status = Math.random() > 0.5 ? 'warning' : 'error';
} else if (device.status !== 'normal' && Math.random() > 0.9) {
device.status = 'normal';
}
});
//
floor.onlineDevices = floor.devices.filter(d => d.status === 'normal').length;
floor.warningDevices = floor.devices.filter(d => d.status !== 'normal').length;
});
//
if (this.barChart) {
const option = this.barChart.getOption();
option.series[0].data = option.series[0].data.map(() => Math.floor(20 + Math.random() * 20));
option.series[1].data = option.series[1].data.map(() => Math.floor(1 + Math.random() * 8));
option.series[2].data = option.series[2].data.map(() => Math.floor(0 + Math.random() * 3));
this.barChart.setOption(option);
}
this.updateTime = this.currentTime;
},
//
showDeviceDetail(device) {
this.selectedDevice = device;
this.showDeviceModal = true;
this.loading = true;
//
getInfo(device.id).then(response => {
this.selectedDevice = { ...this.selectedDevice, ...response.data };
this.loading = false;
}).catch(() => {
this.loading = false;
});
//
this.$nextTick(() => {
this.initHistoryChart();
//
const now = new Date();
const oneMonthAgo = new Date();
oneMonthAgo.setMonth(now.getMonth() - 1);
const historyQueryList = {
maxTime: now.toISOString().replace('T', ' ').substring(0, 19),
minTime: oneMonthAgo.toISOString().replace('T', ' ').substring(0, 19),
pageNum: 1,
pageSize: 1000
};
historyQuery({ id: device.id, ...historyQueryList }).then(response => {
this.historyQueryData = response.data;
this.$nextTick(() => {
this.initHistoryChart();
});
});
},
@ -954,40 +777,42 @@ export default {
},
refreshDeviceStatus() {
this.updateData();
alert('设备状态已刷新');
},
toggleSoundAlert() {
this.soundAlert = !this.soundAlert;
if (this.soundAlert) {
//
this.playAlertSound();
}
// monitorpageList
monitorpageList().then(response => {
console.log("%c 🍑: refreshDeviceStatus -> response ", "font-size:16px;background-color:#0dcaac;color:white;", response)
if (response.code === 200 && response.rows) {
// 使
this.deviceStatus = response.rows.map(device => ({
...device,
//
equManuName: device.equManuName || device.name || '未知设备',
cpuUtilization: device.cpuUtilization || 0,
status: (() => {
// health
const health = parseInt(device.health);
if (health === 1 || health === 2) {
return 'normal';
} else if (health === 3) {
return 'warning';
} else if (health === 4) {
return 'error';
}
return 'normal'; //
})(),
type: device.equTypeName || '其他设备',
location: device.location || '未知位置'
}));
console.log("%c 🇧🇴: refreshDeviceStatus -> this.deviceStatus ", "font-size:16px;background-color:#a56846;color:white;", this.deviceStatus)
} else {
console.error('获取设备状态数据失败', response);
}
}).catch(error => {
console.error('获取设备状态数据异常', error);
});
},
playAlertSound() {
//
try {
const audioContext = new (window.AudioContext || window.webkitAudioContext)();
const oscillator = audioContext.createOscillator();
const gainNode = audioContext.createGain();
// updateFloorDevicesBasedOnApi使
oscillator.connect(gainNode);
gainNode.connect(audioContext.destination);
oscillator.frequency.value = 800;
oscillator.type = 'sine';
gainNode.gain.setValueAtTime(0.3, audioContext.currentTime);
gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + 0.5);
oscillator.start(audioContext.currentTime);
oscillator.stop(audioContext.currentTime + 0.5);
} catch (e) {
console.log('音频播放不支持');
}
},
toggleAutoSwitch() {
this.autoSwitch = !this.autoSwitch;
@ -1020,9 +845,8 @@ export default {
getStatusText(status) {
switch(status) {
case 'normal': return '正常';
case 'warning': return '警告';
case 'error': return '故障';
case '1': return '正常';
case '0': return '故障';
default: return '未知';
}
},
@ -1043,10 +867,6 @@ export default {
device.status = 'normal';
}
//
if (this.soundAlert && oldStatus !== device.status) {
this.playAlertSound();
}
}, 3000);
},
@ -1074,15 +894,16 @@ export default {
}
</script>
<style>
* {
<style scoped>
/* 全局重置样式,但使用深度选择器限制在组件内 */
:deep(*) {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: "Microsoft YaHei", sans-serif;
}
body {
.dashboard {
background-color: #0a1633;
color: #e6e6e6;
overflow: hidden;
@ -1160,7 +981,7 @@ export default {
}
.left-panel, .right-panel {
width: 25%;
width: 35%;
min-width: 280px;
display: flex;
flex-direction: column;
@ -1246,16 +1067,101 @@ export default {
.warning-stats {
display: flex;
flex-wrap: wrap;
gap: 1.5vh;
flex-direction: column;
gap: 15px;
}
.stat-item {
.warning-row {
display: flex;
gap: 15px;
}
.time-item {
flex: 1;
min-width: 45%;
}
.level-item {
flex: 1;
}
.floor-device-container {
max-height: 400px;
overflow-y: auto;
padding-right: 4px;
}
.floor-device-container::-webkit-scrollbar {
width: 6px;
}
.floor-device-container::-webkit-scrollbar-track {
background: rgba(26, 58, 122, 0.3);
border-radius: 0.8vh;
padding: 1.2vh;
border-radius: 3px;
}
.floor-device-container::-webkit-scrollbar-thumb {
background: rgba(125, 211, 252, 0.6);
border-radius: 3px;
}
.floor-device-container::-webkit-scrollbar-thumb:hover {
background: rgba(125, 211, 252, 0.8);
}
.floor-device-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.floor-device-card {
background: linear-gradient(135deg, rgba(26, 58, 122, 0.7), rgba(17, 39, 77, 0.7));
border: 1px solid #2a4a94;
border-radius: 8px;
padding: 16px 12px;
position: relative;
overflow: hidden;
transition: all 0.3s ease;
backdrop-filter: blur(5px);
display: flex;
justify-content: space-between;
align-items: center;
}
.floor-device-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: linear-gradient(90deg, #4dabff, #62f7ff);
box-shadow: 0 0 10px rgba(98, 247, 255, 0.7);
}
.floor-device-card:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(77, 171, 255, 0.3);
border-color: #4dabff;
}
.floor-level {
font-size: 16px;
color: #ffffff;
font-weight: 500;
}
.floor-count {
font-size: 16px;
color: #62f7ff;
font-weight: bold;
text-shadow: 0 0 10px rgba(98, 247, 255, 0.5);
}
.stat-item {
background: rgba(26, 58, 122, 0.3);
border-radius: 8px;
padding: 15px;
border: 1px solid #2a4a94;
display: flex;
flex-direction: column;
@ -1276,19 +1182,23 @@ export default {
margin: 0.5vh 0;
}
.stat-total {
color: #ff6b6b;
.stat-annual {
color: #62f7ff;
}
.stat-high {
.stat-monthly {
color: #4dabff;
}
.stat-level1 {
color: #ff6b6b;
}
.stat-medium {
.stat-level2 {
color: #ffa726;
}
.stat-low {
.stat-level3 {
color: #4dabff;
}
@ -1334,6 +1244,72 @@ export default {
margin-right: 10px;
}
.warning-scrollbar {
height: 100%;
min-height: 100px;
flex-shrink: 0;
}
.warning-scrollbar .el-scrollbar__wrap {
padding-right: 10px;
}
.warning-scrollbar .el-scrollbar__thumb {
background: #4dabff;
border-radius: 10px;
}
.warning-item {
padding: 15px;
margin-bottom: 10px;
background: linear-gradient(135deg, rgba(26, 58, 122, 0.2), rgba(17, 39, 77, 0.2));
border: 1px solid rgba(42, 74, 148, 0.5);
border-radius: 8px;
cursor: pointer;
transition: all 0.3s;
}
.warning-item:hover {
background: linear-gradient(135deg, rgba(26, 58, 122, 0.4), rgba(17, 39, 77, 0.4));
border-color: #4dabff;
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(77, 171, 255, 0.3);
}
.warning-header {
margin-bottom: 8px;
}
.warning-type {
font-weight: bold;
color: #ff6b6b;
display: inline-flex;
align-items: center;
}
.warning-type::before {
content: '•';
margin-right: 6px;
font-size: 18px;
}
.warning-type.level-error {
color: #ff6b6b;
}
.warning-type.level-warning {
color: #ffa726;
}
.warning-time,
.warning-position,
.warning-detail {
font-size: 13px;
color: rgba(255, 255, 255, 0.85);
margin-bottom: 5px;
line-height: 1.5;
}
.status-normal {
background: #4CAF50;
box-shadow: 0 0 5px #4CAF50;
@ -1601,6 +1577,33 @@ export default {
font-size: 0.7rem;
}
.device-stats-container {
display: flex;
gap: 15px;
margin-bottom: 15px;
}
.device-stat-item {
flex: 1;
background: linear-gradient(135deg, rgba(26, 58, 122, 0.7), rgba(17, 39, 77, 0.7));
border: 1px solid #2a4a94;
border-radius: 8px;
padding: 15px 10px;
text-align: center;
}
.device-stat-value {
font-size: 24px;
font-weight: bold;
color: #62f7ff;
margin-bottom: 5px;
}
.device-stat-label {
font-size: 14px;
color: #a0aec0;
}
.chart-container {
height: 30vh;
min-height: 200px;

Loading…
Cancel
Save