|
|
<template class="">
|
|
|
<div class="index-box">
|
|
|
<div id="app" class="dashboard" style="overflow-x: hidden;">
|
|
|
|
|
|
<div class="header">
|
|
|
<h1>唐山烟草网络设备智能监管平台</h1>
|
|
|
<div class="time-display">{{ currentTime }}</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="main-content">
|
|
|
<!-- 左侧面板 -->
|
|
|
<div class="left-panel">
|
|
|
<div class="panel">
|
|
|
<div class="panel-title">
|
|
|
设备状态监控
|
|
|
</div>
|
|
|
<div class="device-list" style="height: calc(100% - 50px); overflow-y: auto; overflow-x: hidden;">
|
|
|
<div v-for="device in deviceStatus || []"
|
|
|
:key="device.id || Math.random()"
|
|
|
class="device-item"
|
|
|
@click="showDeviceDetail(device)">
|
|
|
<div :class="['device-status', `status-${Number(device.health||0)}`]"></div>
|
|
|
<div class="device-name">{{ device.name || '未知设备' }}</div>
|
|
|
<div class="device-value">
|
|
|
<span :class="['health-icon', `health-${Number(device.health||0)}`]">
|
|
|
{{ device.health == 1?'健康':device.health == 2?'良好':device.health == 3?'一般':device.health == 4?'需维护':'未知' }}
|
|
|
</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="panel">
|
|
|
<div class="panel-title">
|
|
|
楼层设备分布情况
|
|
|
</div>
|
|
|
<div class="floor-device-container">
|
|
|
<div class="floor-device-grid">
|
|
|
<div v-for="floor in floorDeviceDistribution"
|
|
|
:key="floor.level"
|
|
|
class="floor-device-card" @click="jumpToDeviceFiles(floor.floor)">
|
|
|
<div class="floor-level">{{ floor.floor }}</div>
|
|
|
<div class="floor-count">{{ floor.count }}</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
<!-- 右侧面板 -->
|
|
|
<div class="center-panel">
|
|
|
|
|
|
<div class="panel">
|
|
|
<div class="panel-title">设备信息统计</div>
|
|
|
<div class="device-stats-container">
|
|
|
<div class="device-stat-item clickable-item" @click="goToDeviceFiles">
|
|
|
<div class="device-stat-value">{{ totalDevices }}</div>
|
|
|
<div class="device-stat-label">设备总数</div>
|
|
|
</div>
|
|
|
<div class="device-stat-item clickable-item" @click="goToDeviceStateMonitor">
|
|
|
<div class="device-stat-value">{{ onlineDevices }}</div>
|
|
|
<div class="device-stat-label">在线设备数</div>
|
|
|
</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="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="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="panel">
|
|
|
<div class="panel-title">
|
|
|
近期预警信息
|
|
|
</div>
|
|
|
<el-scrollbar class="warning-scrollbar">
|
|
|
<div v-for="warning in filteredWarnings"
|
|
|
:key="warning.id"
|
|
|
class="warning-item" >
|
|
|
<div class="warning-header">
|
|
|
<span :class="['warning-type', `level-${warning.displayLevel}`]">{{ warning.name }}</span>
|
|
|
<span :class="['warning-status-indicator', warning.status]">
|
|
|
{{ getStatusText2(warning.status) }}
|
|
|
</span>
|
|
|
</div>
|
|
|
<div class="warning-time">预警时间: {{ warning.time }}</div>
|
|
|
<div class="warning-position">设备位置: {{ warning.location }}</div>
|
|
|
<div class="warning-detail">预警规则: {{ warning.ruleName }}</div>
|
|
|
<div class="warning-detail">预警详情: {{ warning.message }}</div>
|
|
|
</div>
|
|
|
</el-scrollbar>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="footer">
|
|
|
<div>监控系统 v4.0 - 智能管理版</div>
|
|
|
<div>最后更新: <span class="update-time">{{ updateTime }}</span></div>
|
|
|
<div>监控设备总数: {{ totalDevices }} 台 | 在线: {{ onlineDevices }} 台</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 设备详情模态框 -->
|
|
|
<div class="modal-overlay" v-if="showDeviceModal">
|
|
|
<div class="modal-content">
|
|
|
<div class="modal-header">
|
|
|
<h2 class="modal-title">设备详情 - {{ selectedDevice.name }}</h2>
|
|
|
<button class="modal-close" @click="closeDeviceModal">×</button>
|
|
|
</div>
|
|
|
<div class="modal-body">
|
|
|
<div class="device-detail-grid">
|
|
|
<div class="detail-section">
|
|
|
<h3>基本信息</h3>
|
|
|
<div class="detail-item">
|
|
|
<span class="detail-label">设备名称:</span>
|
|
|
<span class="detail-value">{{ selectedDevice.name }}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<span class="detail-label">设备ID:</span>
|
|
|
<span class="detail-value">{{ selectedDevice.id }}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<span class="detail-label">所在位置:</span>
|
|
|
<span class="detail-value">{{ selectedDevice.location || '未知' }}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<span class="detail-label">设备类型:</span>
|
|
|
<span class="detail-value">{{ selectedDevice.equTypeName || '未知' }}</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<span class="detail-label">设备状态:</span>
|
|
|
<span class="detail-value" :style="{color: selectedDevice.status === '1' ? 'green' : 'red'}">
|
|
|
{{ getStatusText(selectedDevice.status) }}
|
|
|
</span>
|
|
|
</div>
|
|
|
<!-- <div class="detail-item">
|
|
|
<span class="detail-label">当前值:</span>
|
|
|
<span class="detail-value">{{ selectedDevice.value || 'N/A' }}</span>
|
|
|
</div> -->
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-section">
|
|
|
<h3>运行状态</h3>
|
|
|
<div class="detail-item">
|
|
|
<span class="detail-label">运行时间:</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.cpuUtilization || '0' }}%</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<span class="detail-label">内存使用率:</span>
|
|
|
<span class="detail-value">{{ selectedDevice.memoryUtilization || '0' }}%</span>
|
|
|
</div>
|
|
|
<div class="detail-item">
|
|
|
<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 || '0' }}°C</span>
|
|
|
</div>
|
|
|
<!-- <div class="detail-item">
|
|
|
<span class="detail-label">最后维护:</span>
|
|
|
<span class="detail-value">{{ selectedDevice.lastMaintenance || '2024-09-15' }}</span>
|
|
|
</div> -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="detail-section">
|
|
|
<div class="detail-section-tit">
|
|
|
<h3>历史数据</h3>
|
|
|
<div class="search-controls">
|
|
|
<el-date-picker
|
|
|
v-model="dateRange"
|
|
|
type="daterange"
|
|
|
range-separator="至"
|
|
|
start-placeholder="开始日期"
|
|
|
end-placeholder="结束日期"
|
|
|
value-format="yyyy-MM-dd HH:mm:ss"
|
|
|
@change="handleDateRangeChange"
|
|
|
class="date-picker"
|
|
|
/>
|
|
|
<el-button type="primary" @click="loadHistoryData">查询</el-button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="history-chart" ref="historyChart"></div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
import { getInfo, historyQuery } from "@/api/inMonitoring/devicesState"
|
|
|
import { byEquType, statistic, alertMessageTimes, floorStatistic } from "@/api/index/index"
|
|
|
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 {
|
|
|
name: 'Index',
|
|
|
data() {
|
|
|
return {
|
|
|
currentTime: '',
|
|
|
updateTime: '',
|
|
|
totalDevices: 104,
|
|
|
onlineDevices: 96,
|
|
|
soundAlert: true,
|
|
|
autoSwitch: false,
|
|
|
autoSwitchInterval: null,
|
|
|
showDeviceModal: false,
|
|
|
showRoomModal: false,
|
|
|
showWarningHistory: false,
|
|
|
selectedDevice: {},
|
|
|
selectedRoom: {},
|
|
|
dateRange: [],
|
|
|
maxTime: '',
|
|
|
minTime: '',
|
|
|
historyData: null,
|
|
|
warningLevelFilter: 'all',
|
|
|
warningStats: {
|
|
|
annual: 156,
|
|
|
monthly: 18,
|
|
|
level1: 4,
|
|
|
level2: 7,
|
|
|
level3: 7
|
|
|
},
|
|
|
deviceStatus: [],
|
|
|
deviceWarnings: [
|
|
|
{ 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: [
|
|
|
{ floor: "1层", count: 6 },
|
|
|
],
|
|
|
floors: [],
|
|
|
dataFlows: [],
|
|
|
barChart: null,
|
|
|
pieChart: null,
|
|
|
historyChart: null,
|
|
|
highlightedDevice: null,
|
|
|
totalDevices: 0,
|
|
|
onlineDevices: 0,
|
|
|
deviceTypeData: [],
|
|
|
alertStatistics: {},
|
|
|
alertTimes: {},
|
|
|
floorDeviceDistribution: []
|
|
|
}},
|
|
|
computed: {
|
|
|
// 直接使用deviceStatus,不再需要过滤计算属性
|
|
|
// 移除filteredDeviceStatus计算属性,直接使用deviceStatus
|
|
|
filteredWarnings() {
|
|
|
if (this.warningLevelFilter === 'all') {
|
|
|
return this.deviceWarnings;
|
|
|
}
|
|
|
|
|
|
// 根据不同的过滤条件进行处理
|
|
|
switch (this.warningLevelFilter) {
|
|
|
case 'level1':
|
|
|
// 过滤1级预警(level为1)
|
|
|
return this.deviceWarnings.filter(warning => warning.level === 1 || warning.level === '1');
|
|
|
case 'level2':
|
|
|
// 过滤2级预警(level为2)
|
|
|
return this.deviceWarnings.filter(warning => warning.level === 2 || warning.level === '2');
|
|
|
case 'level3':
|
|
|
// 过滤3级预警(level为3)
|
|
|
return this.deviceWarnings.filter(warning => warning.level === 3 || warning.level === '3' || warning.level === 'error');
|
|
|
case 'annual':
|
|
|
case 'monthly':
|
|
|
// 按时间范围过滤(这里可以根据实际需求实现时间过滤逻辑)
|
|
|
// 暂时返回所有数据
|
|
|
return this.deviceWarnings;
|
|
|
case 'error':
|
|
|
// 过滤错误级别的预警
|
|
|
return this.deviceWarnings.filter(warning => warning.level > 2 || warning.level === 'error');
|
|
|
case 'warning':
|
|
|
// 过滤警告级别的预警
|
|
|
return this.deviceWarnings.filter(warning => warning.level <= 2 || warning.level === 'warning');
|
|
|
default:
|
|
|
return this.deviceWarnings;
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
this.updateTimeDisplay();
|
|
|
setInterval(this.updateTimeDisplay, 1000);
|
|
|
|
|
|
// 初始化日期范围
|
|
|
// const today = new Date();
|
|
|
// const oneWeekAgo = new Date(today);
|
|
|
// oneWeekAgo.setDate(today.getDate() - 7);
|
|
|
|
|
|
// // 格式化日期为YYYY-MM-DD HH:mm:ss格式
|
|
|
// const formatDate = (date) => {
|
|
|
// const year = date.getFullYear();
|
|
|
// const month = String(date.getMonth() + 1).padStart(2, '0');
|
|
|
// const day = String(date.getDate()).padStart(2, '0');
|
|
|
// const hours = String(date.getHours()).padStart(2, '0');
|
|
|
// const minutes = String(date.getMinutes()).padStart(2, '0');
|
|
|
// const seconds = String(date.getSeconds()).padStart(2, '0');
|
|
|
// return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
|
|
// };
|
|
|
|
|
|
// this.dateRange = [
|
|
|
// formatDate(oneWeekAgo),
|
|
|
// formatDate(today)
|
|
|
// ];
|
|
|
// console.log("%c 🤸♀️: mounted -> this.dateRange ", "font-size:16px;background-color:#1a9bd6;color:white;", this.dateRange)
|
|
|
// // 初始化maxTime和minTime
|
|
|
// this.minTime = this.dateRange[0];
|
|
|
// this.maxTime = this.dateRange[1];
|
|
|
|
|
|
this.generateDataFlows();
|
|
|
|
|
|
// 使用nextTick确保DOM已完全渲染
|
|
|
this.$nextTick(() => {
|
|
|
this.initPieChart();
|
|
|
// 初始化历史图表
|
|
|
// this.initHistoryChart();
|
|
|
});
|
|
|
|
|
|
// 监听窗口大小变化
|
|
|
window.addEventListener('resize', this.handleResize);
|
|
|
|
|
|
// 首次进入时自动加载设备状态数据
|
|
|
this.refreshDeviceStatus();
|
|
|
|
|
|
// 加载设备类型统计数据
|
|
|
this.loadDeviceTypeData();
|
|
|
// 加载预警统计数据
|
|
|
this.loadAlertStatistics();
|
|
|
// 加载预警次数统计
|
|
|
this.loadAlertTimes();
|
|
|
// 加载楼层设备分布
|
|
|
this.loadFloorDeviceDistribution();
|
|
|
|
|
|
// 定期更新设备状态数据(每分钟更新一次)
|
|
|
setInterval(() => {
|
|
|
this.refreshDeviceStatus();
|
|
|
}, 600000);
|
|
|
},
|
|
|
activated() {
|
|
|
// 每次进入首页时刷新数据
|
|
|
this.refreshDeviceStatus();
|
|
|
this.loadDeviceTypeData();
|
|
|
this.loadAlertStatistics();
|
|
|
this.loadAlertTimes();
|
|
|
this.loadFloorDeviceDistribution();
|
|
|
this.updateTimeDisplay();
|
|
|
|
|
|
// 更新最后刷新时间
|
|
|
this.updateTime = this.currentTime;
|
|
|
},
|
|
|
|
|
|
beforeDestroy() {
|
|
|
// 移除事件监听
|
|
|
window.removeEventListener('resize', this.handleResize);
|
|
|
if (this.autoSwitchInterval) {
|
|
|
clearInterval(this.autoSwitchInterval);
|
|
|
}
|
|
|
},
|
|
|
methods: {
|
|
|
getStatusText2(status) {
|
|
|
console.log("%c 🤨: data -> status ", "font-size:16px;background-color:#df83a6;color:white;", status)
|
|
|
if (status === 'HANDLED') return '已处理';
|
|
|
if (status === 'UNHANDLED') return '未处理';
|
|
|
return status;
|
|
|
},
|
|
|
// 跳转到设备档案页面
|
|
|
goToDeviceFiles() {
|
|
|
this.$router.push('/device/files');
|
|
|
},
|
|
|
// 跳转到设备状态监控页面
|
|
|
goToDeviceStateMonitor() {
|
|
|
this.$router.push('/inMonitoring/devicesState');
|
|
|
},
|
|
|
// 处理日期范围变化,将结束时间设置为23:59:59
|
|
|
handleDateRangeChange(range) {
|
|
|
if (range && range.length === 2) {
|
|
|
// 保留开始时间不变,将结束时间设置为当天23:59:59
|
|
|
const endDate = new Date(range[1]);
|
|
|
endDate.setHours(23, 59, 59, 999);
|
|
|
|
|
|
// 格式化为yyyy-MM-dd HH:mm:ss
|
|
|
const endDateTime = endDate.getFullYear() + '-' +
|
|
|
String(endDate.getMonth() + 1).padStart(2, '0') + '-' +
|
|
|
String(endDate.getDate()).padStart(2, '0') + ' ' +
|
|
|
'23:59:59';
|
|
|
|
|
|
// 更新dateRange
|
|
|
this.dateRange = [range[0], endDateTime];
|
|
|
|
|
|
// 更新minTime和maxTime
|
|
|
this.minTime = this.dateRange[0];
|
|
|
this.maxTime = this.dateRange[1];
|
|
|
}
|
|
|
},
|
|
|
|
|
|
formatDuration(input) {
|
|
|
if (!input) {
|
|
|
return "无数据";
|
|
|
}
|
|
|
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', {
|
|
|
year: 'numeric',
|
|
|
month: '2-digit',
|
|
|
day: '2-digit',
|
|
|
hour: '2-digit',
|
|
|
minute: '2-digit',
|
|
|
second: '2-digit',
|
|
|
hour12: false
|
|
|
});
|
|
|
|
|
|
if (!this.updateTime) {
|
|
|
this.updateTime = this.currentTime;
|
|
|
}
|
|
|
},
|
|
|
|
|
|
loadDeviceTypeData() {
|
|
|
byEquType().then(response => {
|
|
|
const data = response.data || {};
|
|
|
|
|
|
// 直接从返回数据中获取设备总数和在线设备数
|
|
|
this.totalDevices = data.totalCount || 0;
|
|
|
this.onlineDevices = data.onlineCount || 0;
|
|
|
|
|
|
// 准备饼图数据,从list数组中提取
|
|
|
const colors = ['#4dabff', '#62f7ff', '#ffa726', '#4CAF50', '#a0aec0', '#ff6b6b', '#95e1d3'];
|
|
|
this.deviceTypeData = (data.list || []).map((item, index) => ({
|
|
|
name: item.equTypeName || '未知类型',
|
|
|
value: item.count || 0,
|
|
|
itemStyle: { color: colors[index % colors.length] }
|
|
|
}));
|
|
|
|
|
|
// 更新饼图
|
|
|
this.initPieChart();
|
|
|
}).catch(error => {
|
|
|
console.error('获取设备类型数据失败:', error);
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 加载预警统计数据
|
|
|
loadAlertStatistics() {
|
|
|
statistic().then(response => {
|
|
|
this.alertStatistics = response.data || {};
|
|
|
|
|
|
// 处理rows数据并更新deviceWarnings
|
|
|
if (response.rows && Array.isArray(response.rows)) {
|
|
|
this.deviceWarnings = response.rows.map((row, index) => ({
|
|
|
id: row.id || index + 1,
|
|
|
name: row.name || '未命名设备',
|
|
|
level: row.level, // 保留原始的level值
|
|
|
displayLevel: row.level > 2 ? 'error' : 'warning', // 用于显示的级别
|
|
|
time: row.occurTime ? this.formatOccurTime(row.occurTime) : '',
|
|
|
location: row.location || '未知位置',
|
|
|
ruleName: row.ruleName || '-',
|
|
|
status: row.status || '-',
|
|
|
message: row.message || '-'
|
|
|
}));
|
|
|
}
|
|
|
}).catch(error => {
|
|
|
console.error('获取预警统计数据失败:', error);
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 格式化occurTime时间
|
|
|
formatOccurTime(timeString) {
|
|
|
if (!timeString) return '';
|
|
|
const date = new Date(timeString);
|
|
|
if (isNaN(date.getTime())) return timeString;
|
|
|
return date.toLocaleString('zh-CN', {
|
|
|
year: 'numeric',
|
|
|
month: '2-digit',
|
|
|
day: '2-digit',
|
|
|
hour: '2-digit',
|
|
|
minute: '2-digit',
|
|
|
second: '2-digit',
|
|
|
hour12: false
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 加载预警次数统计
|
|
|
loadAlertTimes() {
|
|
|
alertMessageTimes().then(response => {
|
|
|
this.alertTimes = response.data || {};
|
|
|
|
|
|
// 根据接口返回的数据更新warningStats
|
|
|
if (response.code === 200 && response.data) {
|
|
|
const data = response.data;
|
|
|
|
|
|
// 更新年度和月度预警数据
|
|
|
this.warningStats.annual = data.yearCount || 0;
|
|
|
this.warningStats.monthly = data.monthCount || 0;
|
|
|
|
|
|
// 从levelCount中提取1-3级预警数据
|
|
|
if (data.levelCount && Array.isArray(data.levelCount)) {
|
|
|
// 初始化1-3级预警数据为0
|
|
|
this.warningStats.level1 = 0;
|
|
|
this.warningStats.level2 = 0;
|
|
|
this.warningStats.level3 = 0;
|
|
|
|
|
|
// 根据level字段更新对应级别的数据
|
|
|
data.levelCount.forEach(item => {
|
|
|
if (item.level === '1') {
|
|
|
this.warningStats.level1 = item.count || 0;
|
|
|
} else if (item.level === '2') {
|
|
|
this.warningStats.level2 = item.count || 0;
|
|
|
} else if (item.level === '3') {
|
|
|
this.warningStats.level3 = item.count || 0;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
}).catch(error => {
|
|
|
console.error('获取预警次数数据失败:', error);
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 加载楼层设备分布
|
|
|
loadFloorDeviceDistribution() {
|
|
|
floorStatistic().then(response => {
|
|
|
// 创建1-12层的默认数组,初始count为0,使用floor字段存储楼层名称
|
|
|
const floorData = [];
|
|
|
for (let i = 1; i <= 12; i++) {
|
|
|
floorData.push({ floor: i + '层', count: 0 });
|
|
|
}
|
|
|
|
|
|
// 如果API返回了数据,更新对应楼层的count值
|
|
|
if (response && response.code === 200 && response.data && Array.isArray(response.data)) {
|
|
|
response.data.forEach(item => {
|
|
|
// 查找对应的楼层索引
|
|
|
const index = floorData.findIndex(f => f.floor === item.floor);
|
|
|
if (index !== -1) {
|
|
|
floorData[index].count = item.count || 0;
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
this.floorDeviceDistribution = floorData;
|
|
|
}).catch(error => {
|
|
|
console.error('获取楼层设备分布失败:', error);
|
|
|
// 出错时仍显示1-12层,count为0
|
|
|
const floorData = [];
|
|
|
for (let i = 1; i <= 12; i++) {
|
|
|
floorData.push({ floor: i + '层', count: 0 });
|
|
|
}
|
|
|
this.floorDeviceDistribution = floorData;
|
|
|
});
|
|
|
},
|
|
|
|
|
|
initPieChart() {
|
|
|
if (!this.$refs.pieChart) return;
|
|
|
|
|
|
this.pieChart = echarts.init(this.$refs.pieChart);
|
|
|
|
|
|
const option = {
|
|
|
backgroundColor: 'transparent',
|
|
|
tooltip: {
|
|
|
trigger: 'item',
|
|
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
|
|
},
|
|
|
legend: {
|
|
|
orient: 'vertical',
|
|
|
right: 10,
|
|
|
top: 'center',
|
|
|
textStyle: {
|
|
|
color: '#a0aec0'
|
|
|
}
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
name: '设备类型',
|
|
|
type: 'pie',
|
|
|
radius: ['40%', '70%'],
|
|
|
avoidLabelOverlap: false,
|
|
|
itemStyle: {
|
|
|
borderRadius: 10,
|
|
|
borderColor: '#0a1633',
|
|
|
borderWidth: 2
|
|
|
},
|
|
|
label: {
|
|
|
show: false,
|
|
|
position: 'center'
|
|
|
},
|
|
|
emphasis: {
|
|
|
label: {
|
|
|
show: true,
|
|
|
fontSize: '18',
|
|
|
fontWeight: 'bold',
|
|
|
color: '#62f7ff'
|
|
|
}
|
|
|
},
|
|
|
labelLine: {
|
|
|
show: false
|
|
|
},
|
|
|
data: this.deviceTypeData.length > 0 ? this.deviceTypeData : [
|
|
|
{ value: 0, name: '暂无数据', itemStyle: { color: '#a0aec0' } }
|
|
|
]
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
|
|
|
this.pieChart.setOption(option);
|
|
|
},
|
|
|
|
|
|
// 加载历史数据
|
|
|
loadHistoryData() {
|
|
|
if (!this.dateRange || this.dateRange.length !== 2) {
|
|
|
this.$message.warning('请选择日期范围');
|
|
|
return;
|
|
|
}
|
|
|
// 将组件获取的时间赋值给maxTime、minTime
|
|
|
this.minTime = this.dateRange[0];
|
|
|
this.maxTime = this.dateRange[1];
|
|
|
|
|
|
const params = {
|
|
|
id: this.selectedDevice.id,
|
|
|
minTime: this.minTime,
|
|
|
maxTime: this.maxTime,
|
|
|
pageNum: 1,
|
|
|
pageSize: 1000
|
|
|
};
|
|
|
|
|
|
historyQuery(params).then(response => {
|
|
|
if (response.code === 200 && response.data) {
|
|
|
this.historyData = response.data;
|
|
|
this.initHistoryChart();
|
|
|
}
|
|
|
}).catch(error => {
|
|
|
console.error('获取历史数据失败:', error);
|
|
|
this.$message.error('获取历史数据失败');
|
|
|
});
|
|
|
},
|
|
|
|
|
|
initHistoryChart() {
|
|
|
if (!this.$refs.historyChart) return;
|
|
|
|
|
|
this.historyChart = echarts.init(this.$refs.historyChart);
|
|
|
|
|
|
// 使用接口返回的数据或默认数据
|
|
|
const timeList = this.historyData?.timeList || ['10-18', '10-19', '10-20', '10-21', '10-22', '10-23', '10-24'];
|
|
|
const cpuList = this.historyData?.cpuList || [45, 52, 48, 60, 55, 58, 62];
|
|
|
const memoryList = this.historyData?.memoryList || [62, 58, 65, 70, 68, 72, 75];
|
|
|
|
|
|
const option = {
|
|
|
backgroundColor: 'transparent',
|
|
|
tooltip: {
|
|
|
trigger: 'axis'
|
|
|
},
|
|
|
legend: {
|
|
|
data: ['CPU使用率', '内存使用率'],
|
|
|
textStyle: {
|
|
|
color: '#a0aec0'
|
|
|
}
|
|
|
},
|
|
|
grid: {
|
|
|
left: '3%',
|
|
|
right: '4%',
|
|
|
bottom: '3%',
|
|
|
containLabel: true
|
|
|
},
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
boundaryGap: false,
|
|
|
data: timeList,
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#4dabff'
|
|
|
}
|
|
|
},
|
|
|
axisLabel: {
|
|
|
color: '#a0aec0',
|
|
|
// 格式化时间戳为更友好的显示格式
|
|
|
formatter: function(value) {
|
|
|
if (typeof value === 'string' && value.includes(' ')) {
|
|
|
return value.split(' ')[1].substring(0, 5); // 只显示时分
|
|
|
}
|
|
|
return value;
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#4dabff'
|
|
|
}
|
|
|
},
|
|
|
axisLabel: {
|
|
|
color: '#a0aec0',
|
|
|
formatter: '{value}%'
|
|
|
},
|
|
|
splitLine: {
|
|
|
lineStyle: {
|
|
|
color: 'rgba(77, 171, 255, 0.2)'
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
name: 'CPU使用率',
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
data: cpuList,
|
|
|
itemStyle: {
|
|
|
color: '#4dabff'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
color: {
|
|
|
type: 'linear',
|
|
|
x: 0,
|
|
|
y: 0,
|
|
|
x2: 0,
|
|
|
y2: 1,
|
|
|
colorStops: [{
|
|
|
offset: 0, color: 'rgba(77, 171, 255, 0.3)'
|
|
|
}, {
|
|
|
offset: 1, color: 'rgba(77, 171, 255, 0.1)'
|
|
|
}]
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: '内存使用率',
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
data: memoryList,
|
|
|
itemStyle: {
|
|
|
color: '#62f7ff'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
color: {
|
|
|
type: 'linear',
|
|
|
x: 0,
|
|
|
y: 0,
|
|
|
x2: 0,
|
|
|
y2: 1,
|
|
|
colorStops: [{
|
|
|
offset: 0, color: 'rgba(98, 247, 255, 0.3)'
|
|
|
}, {
|
|
|
offset: 1, color: 'rgba(98, 247, 255, 0.1)'
|
|
|
}]
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
|
|
|
this.historyChart.setOption(option);
|
|
|
},
|
|
|
|
|
|
generateDataFlows() {
|
|
|
// 生成数据流动画元素
|
|
|
const flows = [];
|
|
|
const width = window.innerWidth;
|
|
|
const height = window.innerHeight;
|
|
|
|
|
|
for (let i = 0; i < 20; i++) {
|
|
|
const startX = Math.random() * width;
|
|
|
const startY = Math.random() * height;
|
|
|
const targetX = Math.random() * width;
|
|
|
const targetY = Math.random() * height;
|
|
|
|
|
|
flows.push({
|
|
|
x: startX,
|
|
|
y: startY,
|
|
|
tx: targetX - startX,
|
|
|
ty: targetY - startY,
|
|
|
delay: Math.random() * 5
|
|
|
});
|
|
|
}
|
|
|
|
|
|
this.dataFlows = flows;
|
|
|
},
|
|
|
|
|
|
handleResize() {
|
|
|
// 重新生成数据流
|
|
|
this.generateDataFlows();
|
|
|
|
|
|
// 重新调整图表大小
|
|
|
if (this.barChart) {
|
|
|
this.barChart.resize();
|
|
|
}
|
|
|
if (this.pieChart) {
|
|
|
this.pieChart.resize();
|
|
|
}
|
|
|
if (this.historyChart) {
|
|
|
this.historyChart.resize();
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 新增功能方法
|
|
|
showDeviceDetail(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;
|
|
|
});
|
|
|
|
|
|
// 获取设备历史数据
|
|
|
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.historyData = response.data;
|
|
|
this.$nextTick(() => {
|
|
|
this.initHistoryChart();
|
|
|
});
|
|
|
});
|
|
|
},
|
|
|
|
|
|
closeDeviceModal() {
|
|
|
this.showDeviceModal = false;
|
|
|
this.selectedDevice = {};
|
|
|
},
|
|
|
|
|
|
showRoomDevices(room) {
|
|
|
this.selectedRoom = room;
|
|
|
this.showRoomModal = true;
|
|
|
},
|
|
|
|
|
|
closeRoomModal() {
|
|
|
this.showRoomModal = false;
|
|
|
this.selectedRoom = {};
|
|
|
},
|
|
|
|
|
|
getRoomDevices() {
|
|
|
const currentFloor = this.getCurrentFloor();
|
|
|
},
|
|
|
|
|
|
controlDevice(action) {
|
|
|
alert(`执行设备控制: ${action} - ${this.selectedDevice.name}`);
|
|
|
// 这里可以添加实际的设备控制逻辑
|
|
|
},
|
|
|
filterByWarningLevel(level) {
|
|
|
if (this.warningLevelFilter === level) {
|
|
|
this.warningLevelFilter = 'all';
|
|
|
} else {
|
|
|
this.warningLevelFilter = level;
|
|
|
}
|
|
|
|
|
|
// 年度预警跳转到预警信息页面
|
|
|
if (level === 'annual') {
|
|
|
this.$router.push('/inMonitoring/inMonitoring/message');
|
|
|
}
|
|
|
// 1-3级预警跳转到预警信息页面并传递级别参数
|
|
|
else if (level === 'level1') {
|
|
|
this.$router.push({
|
|
|
path: '/inMonitoring/inMonitoring/message',
|
|
|
query: { level: '1' }
|
|
|
});
|
|
|
}
|
|
|
else if (level === 'level2') {
|
|
|
this.$router.push({
|
|
|
path: '/inMonitoring/inMonitoring/message',
|
|
|
query: { level: '2' }
|
|
|
});
|
|
|
}
|
|
|
else if (level === 'level3') {
|
|
|
this.$router.push({
|
|
|
path: '/inMonitoring/inMonitoring/message',
|
|
|
query: { level: '3' }
|
|
|
});
|
|
|
}
|
|
|
},
|
|
|
// 跳转到设备档案页面并传递楼层参数
|
|
|
jumpToDeviceFiles(floor) {
|
|
|
// 移除'层'字并转换为数字
|
|
|
const floorNumber = Number(String(floor).replace('层', ''));
|
|
|
this.$router.push({
|
|
|
path: '/device/files',
|
|
|
query: { floor: floorNumber }
|
|
|
});
|
|
|
},
|
|
|
|
|
|
refreshDeviceStatus() {
|
|
|
// 调用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);
|
|
|
});
|
|
|
},
|
|
|
|
|
|
// 移除updateFloorDevicesBasedOnApi方法,简化为直接使用接口数据
|
|
|
|
|
|
|
|
|
toggleAutoSwitch() {
|
|
|
this.autoSwitch = !this.autoSwitch;
|
|
|
|
|
|
if (this.autoSwitch) {
|
|
|
this.autoSwitchInterval = setInterval(() => {
|
|
|
this.currentFloor = this.currentFloor % this.floors.length + 1;
|
|
|
}, 5000);
|
|
|
} else {
|
|
|
if (this.autoSwitchInterval) {
|
|
|
clearInterval(this.autoSwitchInterval);
|
|
|
this.autoSwitchInterval = null;
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
|
|
|
exportWarningData() {
|
|
|
alert('导出预警数据');
|
|
|
// 这里可以添加实际的数据导出逻辑
|
|
|
},
|
|
|
|
|
|
getStatusColor(status) {
|
|
|
switch(status) {
|
|
|
case 'normal': return '#4CAF50';
|
|
|
case 'warning': return '#FF9800';
|
|
|
case 'error': return '#F44336';
|
|
|
default: return '#a0aec0';
|
|
|
}
|
|
|
},
|
|
|
|
|
|
getStatusText(status) {
|
|
|
switch(status) {
|
|
|
case '1': return '正常';
|
|
|
case '0': return '故障';
|
|
|
default: return '未知';
|
|
|
}
|
|
|
},
|
|
|
|
|
|
// 楼层设备展示相关方法
|
|
|
switchFloor(floorLevel) {
|
|
|
this.currentFloor = floorLevel;
|
|
|
},
|
|
|
|
|
|
highlightDevice(deviceId) {
|
|
|
this.highlightedDevice = deviceId;
|
|
|
},
|
|
|
|
|
|
resetDeviceHighlight() {
|
|
|
this.highlightedDevice = null;
|
|
|
},
|
|
|
|
|
|
getCurrentFloor() {
|
|
|
return this.floors.find(floor => floor.level === this.currentFloor) || this.floors[0];
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
</script>
|
|
|
<style scoped>
|
|
|
/* 全局重置样式,但使用深度选择器限制在组件内 */
|
|
|
:deep(*) {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
font-family: "Microsoft YaHei", sans-serif;
|
|
|
}
|
|
|
|
|
|
html, body {
|
|
|
height: 100%;
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
overflow: auto;
|
|
|
}
|
|
|
|
|
|
.index-box {
|
|
|
background-color: #0a1633;
|
|
|
color: #e6e6e6;
|
|
|
overflow: auto;
|
|
|
padding: 16px;
|
|
|
min-height: 100%;
|
|
|
position: relative;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
/* 可点击元素的样式 */
|
|
|
.clickable-item {
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.clickable-item:hover {
|
|
|
background-color: #f0f9ff;
|
|
|
}
|
|
|
|
|
|
.dashboard {
|
|
|
width: 100%;
|
|
|
min-height: 100%;
|
|
|
padding: 16px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
position: relative;
|
|
|
min-width: 1200px;
|
|
|
min-height: 700px;
|
|
|
background-color: #0a1633;
|
|
|
color: #e6e6e6;
|
|
|
}
|
|
|
|
|
|
.header {
|
|
|
height: 10%;
|
|
|
min-height: 70px;
|
|
|
max-height: 100px;
|
|
|
background: linear-gradient(90deg, rgba(10, 26, 58, 0.8), rgba(13, 36, 82, 0.8), rgba(10, 26, 58, 0.8));
|
|
|
border-radius: 1vh;
|
|
|
margin-bottom: 1.5vh;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
padding: 0 2vw;
|
|
|
border: 1px solid #1a3a7a;
|
|
|
box-shadow: 0 0 15px rgba(26, 58, 122, 0.5);
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
backdrop-filter: blur(5px);
|
|
|
}
|
|
|
|
|
|
.header::before {
|
|
|
content: '';
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
left: -100%;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.1), transparent);
|
|
|
animation: headerShine 8s infinite;
|
|
|
}
|
|
|
|
|
|
@keyframes headerShine {
|
|
|
0% { left: -100%; }
|
|
|
100% { left: 100%; }
|
|
|
}
|
|
|
|
|
|
.header h1 {
|
|
|
font-size: clamp(1.5rem, 2.5vw, 2.5rem);
|
|
|
background: linear-gradient(90deg, #4dabff, #62f7ff);
|
|
|
-webkit-background-clip: text;
|
|
|
-webkit-text-fill-color: transparent;
|
|
|
text-shadow: 0 0 10px rgba(77, 171, 255, 0.5);
|
|
|
letter-spacing: 2px;
|
|
|
}
|
|
|
|
|
|
.time-display {
|
|
|
font-size: clamp(1rem, 1.5vw, 1.5rem);
|
|
|
color: #62f7ff;
|
|
|
text-shadow: 0 0 5px rgba(98, 247, 255, 0.7);
|
|
|
}
|
|
|
|
|
|
.main-content {
|
|
|
flex: 1;
|
|
|
display: flex;
|
|
|
gap: 16px;
|
|
|
min-height: 0;
|
|
|
height: 100%;
|
|
|
align-items: stretch;
|
|
|
}
|
|
|
|
|
|
.left-panel, .right-panel, .center-panel {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 16px;
|
|
|
min-height: 100%;
|
|
|
max-height: 100vh;
|
|
|
box-sizing: border-box;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.left-panel, .right-panel {
|
|
|
width: 30%;
|
|
|
}
|
|
|
|
|
|
.center-panel {
|
|
|
flex: 1;
|
|
|
|
|
|
}
|
|
|
|
|
|
.panel {
|
|
|
background: rgba(16, 31, 63, 0.7);
|
|
|
border-radius: 8px;
|
|
|
border: 1px solid #1a3a7a;
|
|
|
box-shadow: 0 0 10px rgba(26, 58, 122, 0.3);
|
|
|
padding: 16px;
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
backdrop-filter: blur(5px);
|
|
|
flex: 1;
|
|
|
}
|
|
|
|
|
|
.panel::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);
|
|
|
}
|
|
|
|
|
|
.panel-title {
|
|
|
font-size: clamp(0.9rem, 1.2vw, 1.3rem);
|
|
|
color: #62f7ff;
|
|
|
margin-bottom: 1.5vh;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: left;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.panel-title::before {
|
|
|
content: '';
|
|
|
display: inline-block;
|
|
|
width: 4px;
|
|
|
height: 18px;
|
|
|
background: #4dabff;
|
|
|
margin-right: 8px;
|
|
|
border-radius: 2px;
|
|
|
}
|
|
|
|
|
|
.panel-actions {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.panel-btn {
|
|
|
background: rgba(26, 58, 122, 0.5);
|
|
|
border: 1px solid #2a4a94;
|
|
|
border-radius: 4px;
|
|
|
padding: 4px 8px;
|
|
|
font-size: 0.7rem;
|
|
|
color: #a0aec0;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.panel-btn:hover {
|
|
|
background: rgba(77, 171, 255, 0.3);
|
|
|
border-color: #4dabff;
|
|
|
color: #62f7ff;
|
|
|
}
|
|
|
|
|
|
.warning-stats {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 15px;
|
|
|
height: 100%;
|
|
|
}
|
|
|
|
|
|
.warning-row {
|
|
|
display: flex;
|
|
|
gap: 15px;
|
|
|
height: 100%;
|
|
|
}
|
|
|
|
|
|
.time-item {
|
|
|
flex: 1;
|
|
|
}
|
|
|
|
|
|
.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: 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;
|
|
|
align-items: center;
|
|
|
transition: all 0.3s;
|
|
|
cursor: pointer;
|
|
|
justify-content: center;
|
|
|
}
|
|
|
|
|
|
.stat-item:hover {
|
|
|
transform: translateY(-3px);
|
|
|
box-shadow: 0 5px 15px rgba(26, 58, 122, 0.5);
|
|
|
border-color: #4dabff;
|
|
|
}
|
|
|
|
|
|
.stat-value {
|
|
|
font-size: clamp(1.5rem, 2.5vw, 2.2rem);
|
|
|
font-weight: bold;
|
|
|
margin: 0.5vh 0;
|
|
|
}
|
|
|
|
|
|
.stat-annual {
|
|
|
color: #62f7ff;
|
|
|
}
|
|
|
|
|
|
.stat-monthly {
|
|
|
color: #4dabff;
|
|
|
}
|
|
|
|
|
|
.stat-level1 {
|
|
|
color: #ff6b6b;
|
|
|
}
|
|
|
|
|
|
.stat-level2 {
|
|
|
color: #ffa726;
|
|
|
}
|
|
|
|
|
|
.stat-level3 {
|
|
|
color: #4dabff;
|
|
|
}
|
|
|
|
|
|
.stat-label {
|
|
|
font-size: clamp(0.7rem, 0.9vw, 0.9rem);
|
|
|
color: #a0aec0;
|
|
|
}
|
|
|
|
|
|
.device-list {
|
|
|
height: 30%;
|
|
|
min-height: 200px;
|
|
|
overflow-y: auto;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.device-list::-webkit-scrollbar {
|
|
|
width: 5px;
|
|
|
}
|
|
|
|
|
|
.device-list::-webkit-scrollbar-thumb {
|
|
|
background: #4dabff;
|
|
|
border-radius: 10px;
|
|
|
}
|
|
|
|
|
|
.device-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding: 1.2vh 1vh;
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
transition: all 0.3s;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.device-item:hover {
|
|
|
background: rgba(26, 58, 122, 0.3);
|
|
|
transform: translateX(5px);
|
|
|
}
|
|
|
|
|
|
.device-status {
|
|
|
width: 10px;
|
|
|
height: 10px;
|
|
|
border-radius: 50%;
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
.warning-scrollbar {
|
|
|
height: 100%;
|
|
|
min-height: 100px;
|
|
|
max-height: 100vh;
|
|
|
/* flex-shrink: 0; */
|
|
|
/* height: cale(100vh - 200px); */
|
|
|
}
|
|
|
|
|
|
.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;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
|
}
|
|
|
|
|
|
.warning-status-indicator {
|
|
|
font-size: 0.8rem;
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 4px;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.warning-status-indicator.HANDLED {
|
|
|
background-color: #4CAF50;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.warning-status-indicator.UNHANDLED {
|
|
|
background-color: #F44336;
|
|
|
color: white;
|
|
|
}
|
|
|
|
|
|
.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-0 {
|
|
|
background: #9E9E9E;
|
|
|
box-shadow: 0 0 5px #9E9E9E;
|
|
|
}
|
|
|
.status-1 {
|
|
|
background: #4CAF50;
|
|
|
box-shadow: 0 0 5px #4CAF50;
|
|
|
}
|
|
|
.status-2 {
|
|
|
background: #FF9800;
|
|
|
box-shadow: 0 0 5px #FF9800;
|
|
|
}
|
|
|
.status-4 {
|
|
|
background: #F44336;
|
|
|
box-shadow: 0 0 5px #F44336;
|
|
|
}
|
|
|
.status-3 {
|
|
|
background: #FFEB3B;
|
|
|
box-shadow: 0 0 5px #FFEB3B;
|
|
|
}
|
|
|
|
|
|
|
|
|
.status-normal {
|
|
|
background: #4CAF50;
|
|
|
box-shadow: 0 0 5px #4CAF50;
|
|
|
}
|
|
|
|
|
|
.status-warning {
|
|
|
background: #FF9800;
|
|
|
box-shadow: 0 0 5px #FF9800;
|
|
|
}
|
|
|
|
|
|
.status-error {
|
|
|
background: #F44336;
|
|
|
box-shadow: 0 0 5px #F44336;
|
|
|
animation: blink 1.5s infinite;
|
|
|
}
|
|
|
|
|
|
@keyframes blink {
|
|
|
0%, 50% { opacity: 1; }
|
|
|
51%, 100% { opacity: 0.3; }
|
|
|
}
|
|
|
|
|
|
.device-name {
|
|
|
flex: 1;
|
|
|
font-size: clamp(0.8rem, 1vw, 1rem);
|
|
|
}
|
|
|
|
|
|
.device-value {
|
|
|
color: #62f7ff;
|
|
|
font-weight: bold;
|
|
|
font-size: clamp(0.8rem, 1vw, 1rem);
|
|
|
}
|
|
|
.health-1{
|
|
|
color: #4CAF50;
|
|
|
}
|
|
|
.health-2{
|
|
|
color: #FF9800;
|
|
|
}
|
|
|
.health-4{
|
|
|
color: #F44336;
|
|
|
}
|
|
|
.health-3{
|
|
|
color: #FFEB3B;
|
|
|
}
|
|
|
.health-0{
|
|
|
color: #9E9E9E;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* 楼宇容器样式 */
|
|
|
.building-container {
|
|
|
flex: 1;
|
|
|
position: relative;
|
|
|
display: flex;
|
|
|
min-height: 40%;
|
|
|
}
|
|
|
|
|
|
.floor-plans-container {
|
|
|
width: 70%;
|
|
|
height: 100%;
|
|
|
background: rgba(10, 22, 51, 0.8);
|
|
|
border-radius: 8px;
|
|
|
position: relative;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.floor-plan {
|
|
|
position: absolute;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
transition: opacity 0.5s ease;
|
|
|
opacity: 0;
|
|
|
}
|
|
|
|
|
|
.floor-plan.active {
|
|
|
opacity: 1;
|
|
|
}
|
|
|
|
|
|
.floor-title {
|
|
|
font-size: 1.5rem;
|
|
|
color: #62f7ff;
|
|
|
margin-bottom: 20px;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.floor-layout {
|
|
|
width: 90%;
|
|
|
height: 80%;
|
|
|
position: relative;
|
|
|
background: rgba(16, 31, 63, 0.5);
|
|
|
border-radius: 8px;
|
|
|
border: 1px solid #2a4a94;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.room {
|
|
|
position: absolute;
|
|
|
background: rgba(26, 58, 122, 0.3);
|
|
|
border: 1px solid #2a4a94;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
transition: all 0.3s;
|
|
|
overflow: hidden;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.room:hover {
|
|
|
background: rgba(26, 58, 122, 0.5);
|
|
|
border-color: #4dabff;
|
|
|
}
|
|
|
|
|
|
.room-label {
|
|
|
font-size: 0.7rem;
|
|
|
color: #a0aec0;
|
|
|
text-align: center;
|
|
|
padding: 2px;
|
|
|
}
|
|
|
|
|
|
.device-marker {
|
|
|
position: absolute;
|
|
|
width: 16px;
|
|
|
height: 16px;
|
|
|
border-radius: 50%;
|
|
|
transform: translate(-50%, -50%);
|
|
|
box-shadow: 0 0 6px currentColor;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
font-size: 8px;
|
|
|
color: white;
|
|
|
z-index: 10;
|
|
|
transition: all 0.3s;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.device-marker:hover {
|
|
|
transform: translate(-50%, -50%) scale(1.4);
|
|
|
z-index: 20;
|
|
|
}
|
|
|
|
|
|
.device-normal {
|
|
|
background: #4CAF50;
|
|
|
color: #4CAF50;
|
|
|
}
|
|
|
|
|
|
.device-warning {
|
|
|
background: #FF9800;
|
|
|
color: #FF9800;
|
|
|
}
|
|
|
|
|
|
.device-error {
|
|
|
background: #F44336;
|
|
|
color: #F44336;
|
|
|
animation: pulse 2s infinite;
|
|
|
}
|
|
|
|
|
|
@keyframes pulse {
|
|
|
0% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0.7); }
|
|
|
70% { box-shadow: 0 0 0 8px rgba(244, 67, 54, 0); }
|
|
|
100% { box-shadow: 0 0 0 0 rgba(244, 67, 54, 0); }
|
|
|
}
|
|
|
|
|
|
.floor-info {
|
|
|
width: 30%;
|
|
|
padding-left: 15px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.floor-selector {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 8px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.floor-btn {
|
|
|
padding: 10px 15px;
|
|
|
background: rgba(26, 58, 122, 0.5);
|
|
|
border: 1px solid #2a4a94;
|
|
|
border-radius: 6px;
|
|
|
cursor: pointer;
|
|
|
text-align: center;
|
|
|
transition: all 0.3s;
|
|
|
color: #a0aec0;
|
|
|
}
|
|
|
|
|
|
.floor-btn:hover {
|
|
|
background: rgba(26, 58, 122, 0.8);
|
|
|
border-color: #4dabff;
|
|
|
color: #62f7ff;
|
|
|
}
|
|
|
|
|
|
.floor-btn.active {
|
|
|
background: rgba(77, 171, 255, 0.3);
|
|
|
border-color: #4dabff;
|
|
|
color: #62f7ff;
|
|
|
box-shadow: 0 0 10px rgba(77, 171, 255, 0.5);
|
|
|
}
|
|
|
|
|
|
.floor-details h3 {
|
|
|
color: #62f7ff;
|
|
|
margin-bottom: 15px;
|
|
|
font-size: 1.1rem;
|
|
|
}
|
|
|
|
|
|
.floor-stats {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 10px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.floor-stat {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
padding: 8px 0;
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
}
|
|
|
|
|
|
.stat-label {
|
|
|
color: #a0aec0;
|
|
|
}
|
|
|
|
|
|
.stat-value {
|
|
|
color: #4dabff;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.stat-value.online {
|
|
|
color: #4CAF50;
|
|
|
}
|
|
|
|
|
|
.stat-value.warning {
|
|
|
color: #FF9800;
|
|
|
}
|
|
|
|
|
|
.device-list-mini {
|
|
|
max-height: 200px;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
.device-item-mini {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding: 6px 8px;
|
|
|
margin-bottom: 5px;
|
|
|
background: rgba(26, 58, 122, 0.3);
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
font-size: 0.8rem;
|
|
|
}
|
|
|
|
|
|
.device-item-mini:hover {
|
|
|
background: rgba(77, 171, 255, 0.2);
|
|
|
transform: translateX(3px);
|
|
|
}
|
|
|
|
|
|
.device-dot {
|
|
|
width: 8px;
|
|
|
height: 8px;
|
|
|
border-radius: 50%;
|
|
|
margin-right: 8px;
|
|
|
}
|
|
|
|
|
|
.device-name-mini {
|
|
|
flex: 1;
|
|
|
color: #e6e6e6;
|
|
|
}
|
|
|
|
|
|
.device-position {
|
|
|
color: #a0aec0;
|
|
|
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: 30%;
|
|
|
min-height: 200px;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.footer {
|
|
|
height: 5%;
|
|
|
min-height: 30px;
|
|
|
max-height: 50px;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
color: #a0aec0;
|
|
|
font-size: clamp(0.7rem, 0.9vw, 0.9rem);
|
|
|
margin-top: 8px;
|
|
|
flex-shrink: 0;
|
|
|
}
|
|
|
|
|
|
.update-time {
|
|
|
color: #62f7ff;
|
|
|
}
|
|
|
|
|
|
.data-flow {
|
|
|
position: absolute;
|
|
|
width: 4px;
|
|
|
height: 4px;
|
|
|
border-radius: 50%;
|
|
|
background: #62f7ff;
|
|
|
box-shadow: 0 0 8px #62f7ff;
|
|
|
animation: flow 3s linear infinite;
|
|
|
z-index: 1;
|
|
|
}
|
|
|
|
|
|
@keyframes flow {
|
|
|
0% { transform: translate(0, 0); opacity: 0; }
|
|
|
10% { opacity: 1; }
|
|
|
90% { opacity: 1; }
|
|
|
100% { transform: translate(var(--tx), var(--ty)); opacity: 0; }
|
|
|
}
|
|
|
|
|
|
/* 模态框样式 */
|
|
|
.modal-overlay {
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
background: rgba(0, 0, 0, 0.7);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
z-index: 1000;
|
|
|
backdrop-filter: blur(5px);
|
|
|
}
|
|
|
|
|
|
.modal-content {
|
|
|
background: rgba(16, 31, 63, 0.95);
|
|
|
border-radius: 1vh;
|
|
|
border: 1px solid #1a3a7a;
|
|
|
box-shadow: 0 0 30px rgba(26, 58, 122, 0.8);
|
|
|
width: 80%;
|
|
|
max-width: 800px;
|
|
|
max-height: 80%;
|
|
|
overflow: hidden;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
}
|
|
|
|
|
|
.modal-header {
|
|
|
padding: 1.5vh 2vw;
|
|
|
border-bottom: 1px solid #1a3a7a;
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
background: rgba(10, 26, 58, 0.8);
|
|
|
}
|
|
|
|
|
|
.modal-title {
|
|
|
font-size: 1.3rem;
|
|
|
color: #62f7ff;
|
|
|
}
|
|
|
|
|
|
.modal-close {
|
|
|
background: none;
|
|
|
border: none;
|
|
|
color: #a0aec0;
|
|
|
font-size: 1.5rem;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.modal-close:hover {
|
|
|
color: #62f7ff;
|
|
|
}
|
|
|
|
|
|
.modal-body {
|
|
|
padding: 2vh 2vw;
|
|
|
flex: 1;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
.device-detail-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 20px;
|
|
|
}
|
|
|
|
|
|
.detail-section {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.detail-section h3 {
|
|
|
color: #62f7ff;
|
|
|
margin-bottom: 10px;
|
|
|
font-size: 1.1rem;
|
|
|
border-bottom: 1px solid #1a3a7a;
|
|
|
padding-bottom: 5px;
|
|
|
}
|
|
|
|
|
|
.detail-item {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
margin-bottom: 8px;
|
|
|
padding-bottom: 8px;
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
}
|
|
|
|
|
|
.detail-label {
|
|
|
color: #a0aec0;
|
|
|
}
|
|
|
|
|
|
.detail-value {
|
|
|
color: #e6e6e6;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.control-buttons {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
margin-top: 15px;
|
|
|
}
|
|
|
|
|
|
.control-btn {
|
|
|
padding: 8px 15px;
|
|
|
border: none;
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.btn-restart {
|
|
|
background: #ffa726;
|
|
|
color: #fff;
|
|
|
}
|
|
|
|
|
|
.btn-restart:hover {
|
|
|
background: #ff9800;
|
|
|
}
|
|
|
|
|
|
.btn-shutdown {
|
|
|
background: #f44336;
|
|
|
color: #fff;
|
|
|
}
|
|
|
|
|
|
.btn-shutdown:hover {
|
|
|
background: #d32f2f;
|
|
|
}
|
|
|
|
|
|
.btn-reset {
|
|
|
background: #4dabff;
|
|
|
color: #fff;
|
|
|
}
|
|
|
|
|
|
.btn-reset:hover {
|
|
|
background: #2196f3;
|
|
|
}
|
|
|
|
|
|
.history-chart {
|
|
|
height: 200px;
|
|
|
margin-top: 15px;
|
|
|
}
|
|
|
|
|
|
.search-controls {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.search-input {
|
|
|
flex: 1;
|
|
|
background: rgba(26, 58, 122, 0.3);
|
|
|
border: 1px solid #2a4a94;
|
|
|
border-radius: 4px;
|
|
|
padding: 8px 12px;
|
|
|
color: #e6e6e6;
|
|
|
}
|
|
|
|
|
|
.search-input:focus {
|
|
|
outline: none;
|
|
|
border-color: #4dabff;
|
|
|
}
|
|
|
|
|
|
.date-input {
|
|
|
background: rgba(26, 58, 122, 0.3);
|
|
|
border: 1px solid #2a4a94;
|
|
|
border-radius: 4px;
|
|
|
padding: 8px 12px;
|
|
|
color: #e6e6e6;
|
|
|
}
|
|
|
|
|
|
</style> |