|
|
|
|
<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 class="panel-actions">
|
|
|
|
|
<div class="panel-btn" @click="refreshDeviceStatus">刷新</div>
|
|
|
|
|
</div>
|
|
|
|
|
</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>
|
|
|
|
|
<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.floor }}</div>
|
|
|
|
|
<div class="floor-count">{{ floor.count }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
<!-- 右侧面板 -->
|
|
|
|
|
<div class="right-panel">
|
|
|
|
|
|
|
|
|
|
<div class="panel">
|
|
|
|
|
<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="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="center-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"
|
|
|
|
|
@click="showDeviceDetail(warning)">
|
|
|
|
|
<div class="warning-header">
|
|
|
|
|
<span :class="['warning-type', `level-${warning.displayLevel}`]">{{ warning.name }}</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="warning-time">预警时间: {{ warning.time }}</div>
|
|
|
|
|
<div class="warning-position">设备位置: {{ warning.location }}</div>
|
|
|
|
|
<div class="warning-detail">预警信息: {{ warning.ruleName }}</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.type || '服务器' }}</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 class="control-buttons">
|
|
|
|
|
<button class="control-btn btn-restart" @click="controlDevice('restart')">重启设备</button>
|
|
|
|
|
<button class="control-btn btn-shutdown" @click="controlDevice('shutdown')">关机</button>
|
|
|
|
|
<button class="control-btn btn-reset" @click="controlDevice('reset')">重置配置</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="detail-section">
|
|
|
|
|
<h3>历史数据
|
|
|
|
|
<div class="search-controls">
|
|
|
|
|
<input type="date" class="date-input" v-model="historyStartDate">
|
|
|
|
|
<span>至</span>
|
|
|
|
|
<input type="date" class="date-input" v-model="historyEndDate">
|
|
|
|
|
<button class="panel-btn" @click="loadHistoryData">查询</button>
|
|
|
|
|
</div>
|
|
|
|
|
</h3>
|
|
|
|
|
<div class="history-chart" ref="historyChart"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 房间设备列表模态框 -->
|
|
|
|
|
<div class="modal-overlay" v-if="showRoomModal">
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
<h2 class="modal-title">房间设备 - {{ selectedRoom.name }}</h2>
|
|
|
|
|
<button class="modal-close" @click="closeRoomModal">×</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
<div class="device-list">
|
|
|
|
|
<div v-for="device in getRoomDevices()"
|
|
|
|
|
: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 || 'N/A' }}</div>
|
|
|
|
|
</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: {},
|
|
|
|
|
historyStartDate: '',
|
|
|
|
|
historyEndDate: '',
|
|
|
|
|
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: [
|
|
|
|
|
{
|
|
|
|
|
level: 1,
|
|
|
|
|
name: '一层 - 数据中心',
|
|
|
|
|
totalDevices: 32,
|
|
|
|
|
onlineDevices: 30,
|
|
|
|
|
warningDevices: 2,
|
|
|
|
|
rooms: [
|
|
|
|
|
{ id: 101, name: '服务器机房A', position: { left: 5, top: 10, width: 25, height: 35 } },
|
|
|
|
|
{ id: 102, name: '服务器机房B', position: { left: 35, top: 10, width: 25, height: 35 } },
|
|
|
|
|
{ id: 103, name: '存储机房', position: { left: 65, top: 10, width: 30, height: 35 } },
|
|
|
|
|
{ id: 104, name: '网络设备间', position: { left: 5, top: 50, width: 25, height: 25 } },
|
|
|
|
|
{ id: 105, name: 'UPS电源室', position: { left: 35, top: 50, width: 25, height: 25 } },
|
|
|
|
|
{ id: 106, name: '空调机房', position: { left: 65, top: 50, width: 30, height: 25 } },
|
|
|
|
|
{ id: 107, name: '走廊', position: { left: 0, top: 80, width: 100, height: 15 } }
|
|
|
|
|
],
|
|
|
|
|
devices: [
|
|
|
|
|
{ id: 101, name: '核心服务器1', status: 'normal', room: '服务器机房A', position: { x: 15, y: 20 }, type: '服务器' },
|
|
|
|
|
{ id: 102, name: '核心服务器2', status: 'normal', room: '服务器机房A', position: { x: 15, y: 30 }, type: '服务器' },
|
|
|
|
|
{ id: 103, name: '存储阵列A', status: 'warning', room: '存储机房', position: { x: 75, y: 20 }, type: '存储设备' },
|
|
|
|
|
{ id: 104, name: '网络交换机', status: 'normal', room: '网络设备间', position: { x: 15, y: 60 }, type: '网络设备' },
|
|
|
|
|
{ id: 105, name: '安全审计系统', status: 'error', room: '服务器机房B', position: { x: 45, y: 20 }, type: '安全设备' },
|
|
|
|
|
{ id: 106, name: 'UPS电源A', status: 'normal', room: 'UPS电源室', position: { x: 45, y: 60 }, type: '电源设备' },
|
|
|
|
|
{ id: 107, name: '备份服务器', status: 'normal', room: '服务器机房B', position: { x: 45, y: 30 }, type: '服务器' },
|
|
|
|
|
{ id: 108, name: '数据库集群', status: 'normal', room: '存储机房', position: { x: 75, y: 30 }, type: '服务器' }
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
level: 2,
|
|
|
|
|
name: '二层 - 网络中心',
|
|
|
|
|
totalDevices: 28,
|
|
|
|
|
onlineDevices: 26,
|
|
|
|
|
warningDevices: 2,
|
|
|
|
|
rooms: [
|
|
|
|
|
{ id: 201, name: '核心交换机房', position: { left: 10, top: 10, width: 35, height: 40 } },
|
|
|
|
|
{ id: 202, name: '防火墙区域', position: { left: 50, top: 10, width: 40, height: 40 } },
|
|
|
|
|
{ id: 203, name: '监控区域', position: { left: 10, top: 55, width: 35, height: 30 } },
|
|
|
|
|
{ id: 204, name: '设备维护室', position: { left: 50, top: 55, width: 40, height: 30 } },
|
|
|
|
|
{ id: 205, name: '走廊', position: { left: 0, top: 90, width: 100, height: 5 } }
|
|
|
|
|
],
|
|
|
|
|
devices: [
|
|
|
|
|
{ id: 201, name: '路由器主', status: 'normal', room: '核心交换机房', position: { x: 25, y: 25 }, type: '网络设备' },
|
|
|
|
|
{ id: 202, name: '防火墙1', status: 'normal', room: '防火墙区域', position: { x: 65, y: 25 }, type: '安全设备' },
|
|
|
|
|
{ id: 203, name: '负载均衡器', status: 'warning', room: '核心交换机房', position: { x: 25, y: 35 }, type: '网络设备' },
|
|
|
|
|
{ id: 204, name: 'VPN网关', status: 'normal', room: '防火墙区域', position: { x: 65, y: 35 }, type: '安全设备' },
|
|
|
|
|
{ id: 205, name: 'IDS系统', status: 'warning', room: '监控区域', position: { x: 25, y: 70 }, type: '安全设备' },
|
|
|
|
|
{ id: 206, name: '网络监控', status: 'normal', room: '监控区域', position: { x: 25, y: 60 }, type: '监控设备' },
|
|
|
|
|
{ id: 207, name: '交换机A', status: 'normal', room: '设备维护室', position: { x: 65, y: 70 }, type: '网络设备' },
|
|
|
|
|
{ id: 208, name: '交换机B', status: 'normal', room: '设备维护室', position: { x: 75, y: 70 }, type: '网络设备' }
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
level: 3,
|
|
|
|
|
name: '三层 - 监控中心',
|
|
|
|
|
totalDevices: 24,
|
|
|
|
|
onlineDevices: 22,
|
|
|
|
|
warningDevices: 2,
|
|
|
|
|
rooms: [
|
|
|
|
|
{ id: 301, name: '大屏监控区', position: { left: 5, top: 5, width: 60, height: 45 } },
|
|
|
|
|
{ id: 302, name: '操作台区域', position: { left: 70, top: 5, width: 25, height: 45 } },
|
|
|
|
|
{ id: 303, name: '存储区', position: { left: 5, top: 55, width: 40, height: 35 } },
|
|
|
|
|
{ id: 304, name: '设备间', position: { left: 50, top: 55, width: 45, height: 35 } },
|
|
|
|
|
{ id: 305, name: '走廊', position: { left: 0, top: 95, width: 100, height: 5 } }
|
|
|
|
|
],
|
|
|
|
|
devices: [
|
|
|
|
|
{ id: 301, name: '监控服务器', status: 'normal', room: '大屏监控区', position: { x: 30, y: 25 }, type: '服务器' },
|
|
|
|
|
{ id: 302, name: '录像设备', status: 'error', room: '存储区', position: { x: 20, y: 70 }, type: '存储设备' },
|
|
|
|
|
{ id: 303, name: '分析服务器', status: 'normal', room: '大屏监控区', position: { x: 45, y: 25 }, type: '服务器' },
|
|
|
|
|
{ id: 304, name: '报警主机', status: 'normal', room: '操作台区域', position: { x: 80, y: 25 }, type: '安全设备' },
|
|
|
|
|
{ id: 305, name: '门禁控制器', status: 'normal', room: '设备间', position: { x: 65, y: 70 }, type: '安防设备' },
|
|
|
|
|
{ id: 306, name: '环境监测', status: 'warning', room: '设备间', position: { x: 75, y: 70 }, type: '监控设备' },
|
|
|
|
|
{ id: 307, name: '摄像头控制', status: 'normal', room: '操作台区域', position: { x: 80, y: 35 }, type: '安防设备' },
|
|
|
|
|
{ id: 308, name: '报警系统', status: 'normal', room: '设备间', position: { x: 65, y: 80 }, type: '安全设备' }
|
|
|
|
|
]
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
level: 4,
|
|
|
|
|
name: '四层 - 办公区域',
|
|
|
|
|
totalDevices: 20,
|
|
|
|
|
onlineDevices: 18,
|
|
|
|
|
warningDevices: 2,
|
|
|
|
|
rooms: [
|
|
|
|
|
{ id: 401, name: '办公区A', position: { left: 5, top: 10, width: 40, height: 35 } },
|
|
|
|
|
{ id: 402, name: '办公区B', position: { left: 50, top: 10, width: 45, height: 35 } },
|
|
|
|
|
{ id: 403, name: '会议室', position: { left: 5, top: 50, width: 40, height: 35 } },
|
|
|
|
|
{ id: 404, name: '打印区', position: { left: 50, top: 50, width: 20, height: 35 } },
|
|
|
|
|
{ id: 405, name: '茶水间', position: { left: 75, top: 50, width: 20, height: 35 } },
|
|
|
|
|
{ id: 406, name: '走廊', position: { left: 0, top: 90, width: 100, height: 5 } }
|
|
|
|
|
],
|
|
|
|
|
devices: [
|
|
|
|
|
{ id: 401, name: '办公服务器', status: 'normal', room: '办公区A', position: { x: 25, y: 25 }, type: '服务器' },
|
|
|
|
|
{ id: 402, name: '打印机', status: 'warning', room: '打印区', position: { x: 60, y: 65 }, type: '办公设备' },
|
|
|
|
|
{ id: 403, name: '网络终端', status: 'normal', room: '办公区B', position: { x: 70, y: 25 }, type: '网络设备' },
|
|
|
|
|
{ id: 404, name: '视频会议', status: 'normal', room: '会议室', position: { x: 25, y: 65 }, type: '会议设备' },
|
|
|
|
|
{ id: 405, name: '电话系统', status: 'warning', room: '办公区A', position: { x: 25, y: 35 }, type: '通信设备' },
|
|
|
|
|
{ id: 406, name: '考勤系统', status: 'normal', room: '办公区B', position: { x: 70, y: 35 }, type: '安防设备' },
|
|
|
|
|
{ id: 407, name: '门禁系统', status: 'normal', room: '走廊', position: { x: 50, y: 92 }, type: '安防设备' },
|
|
|
|
|
{ id: 408, name: '投影设备', status: 'normal', room: '会议室', position: { x: 25, y: 55 }, type: '会议设备' }
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
],
|
|
|
|
|
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();
|
|
|
|
|
this.historyEndDate = today.toISOString().split('T')[0];
|
|
|
|
|
const oneWeekAgo = new Date(today);
|
|
|
|
|
oneWeekAgo.setDate(today.getDate() - 7);
|
|
|
|
|
this.historyStartDate = oneWeekAgo.toISOString().split('T')[0];
|
|
|
|
|
|
|
|
|
|
this.generateDataFlows();
|
|
|
|
|
|
|
|
|
|
// 使用nextTick确保DOM已完全渲染
|
|
|
|
|
this.$nextTick(() => {
|
|
|
|
|
this.initPieChart();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 监听窗口大小变化
|
|
|
|
|
window.addEventListener('resize', this.handleResize);
|
|
|
|
|
|
|
|
|
|
// 首次进入时自动加载设备状态数据
|
|
|
|
|
this.refreshDeviceStatus();
|
|
|
|
|
|
|
|
|
|
// 加载设备类型统计数据
|
|
|
|
|
this.loadDeviceTypeData();
|
|
|
|
|
// 加载预警统计数据
|
|
|
|
|
this.loadAlertStatistics();
|
|
|
|
|
// 加载预警次数统计
|
|
|
|
|
this.loadAlertTimes();
|
|
|
|
|
// 加载楼层设备分布
|
|
|
|
|
this.loadFloorDeviceDistribution();
|
|
|
|
|
|
|
|
|
|
// 定期更新设备状态数据(每分钟更新一次)
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
this.refreshDeviceStatus();
|
|
|
|
|
}, 60000);
|
|
|
|
|
|
|
|
|
|
// 模拟WebSocket连接
|
|
|
|
|
this.simulateWebSocket();
|
|
|
|
|
},
|
|
|
|
|
beforeDestroy() {
|
|
|
|
|
// 移除事件监听
|
|
|
|
|
window.removeEventListener('resize', this.handleResize);
|
|
|
|
|
if (this.autoSwitchInterval) {
|
|
|
|
|
clearInterval(this.autoSwitchInterval);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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', {
|
|
|
|
|
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 || '-',
|
|
|
|
|
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);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
initHistoryChart() {
|
|
|
|
|
if (!this.$refs.historyChart) return;
|
|
|
|
|
|
|
|
|
|
this.historyChart = echarts.init(this.$refs.historyChart);
|
|
|
|
|
|
|
|
|
|
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: ['10-18', '10-19', '10-20', '10-21', '10-22', '10-23', '10-24'],
|
|
|
|
|
axisLine: {
|
|
|
|
|
lineStyle: {
|
|
|
|
|
color: '#4dabff'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
axisLabel: {
|
|
|
|
|
color: '#a0aec0'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
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: [45, 52, 48, 60, 55, 58, 62],
|
|
|
|
|
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: [62, 58, 65, 70, 68, 72, 75],
|
|
|
|
|
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)'
|
|
|
|
|
}]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: '网络流量',
|
|
|
|
|
type: 'line',
|
|
|
|
|
smooth: true,
|
|
|
|
|
data: [78, 82, 75, 90, 85, 88, 95],
|
|
|
|
|
itemStyle: {
|
|
|
|
|
color: '#ffa726'
|
|
|
|
|
},
|
|
|
|
|
areaStyle: {
|
|
|
|
|
color: {
|
|
|
|
|
type: 'linear',
|
|
|
|
|
x: 0,
|
|
|
|
|
y: 0,
|
|
|
|
|
x2: 0,
|
|
|
|
|
y2: 1,
|
|
|
|
|
colorStops: [{
|
|
|
|
|
offset: 0, color: 'rgba(255, 167, 38, 0.3)'
|
|
|
|
|
}, {
|
|
|
|
|
offset: 1, color: 'rgba(255, 167, 38, 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.historyQueryData = 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();
|
|
|
|
|
return currentFloor.devices.filter(device => device.room === this.selectedRoom.name);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
controlDevice(action) {
|
|
|
|
|
alert(`执行设备控制: ${action} - ${this.selectedDevice.name}`);
|
|
|
|
|
// 这里可以添加实际的设备控制逻辑
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
loadHistoryData() {
|
|
|
|
|
alert(`加载历史数据: ${this.historyStartDate} 至 ${this.historyEndDate}`);
|
|
|
|
|
// 这里可以添加实际的历史数据加载逻辑
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
filterByWarningLevel(level) {
|
|
|
|
|
if (this.warningLevelFilter === level) {
|
|
|
|
|
this.warningLevelFilter = 'all';
|
|
|
|
|
} else {
|
|
|
|
|
this.warningLevelFilter = level;
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
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 '未知';
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
simulateWebSocket() {
|
|
|
|
|
// 模拟WebSocket实时数据推送
|
|
|
|
|
setInterval(() => {
|
|
|
|
|
// 随机更新一个设备状态
|
|
|
|
|
const floorIndex = Math.floor(Math.random() * this.floors.length);
|
|
|
|
|
const deviceIndex = Math.floor(Math.random() * this.floors[floorIndex].devices.length);
|
|
|
|
|
|
|
|
|
|
const device = this.floors[floorIndex].devices[deviceIndex];
|
|
|
|
|
const oldStatus = device.status;
|
|
|
|
|
|
|
|
|
|
if (Math.random() > 0.7) {
|
|
|
|
|
device.status = Math.random() > 0.5 ? 'warning' : 'error';
|
|
|
|
|
} else if (oldStatus !== 'normal' && Math.random() > 0.8) {
|
|
|
|
|
device.status = 'normal';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}, 3000);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// 楼层设备展示相关方法
|
|
|
|
|
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];
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getCurrentFloorDevices() {
|
|
|
|
|
return this.getCurrentFloor().devices;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
<style scoped>
|
|
|
|
|
/* 全局重置样式,但使用深度选择器限制在组件内 */
|
|
|
|
|
:deep(*) {
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
box-sizing: border-box;
|
|
|
|
|
font-family: "Microsoft YaHei", sans-serif;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.index-box {
|
|
|
|
|
background-color: #0a1633;
|
|
|
|
|
color: #e6e6e6;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
padding: 1.5vh 1.5vw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.dashboard {
|
|
|
|
|
width: 100vw;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
padding: 1.5vh 1.5vw;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
position: relative;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
min-width: 1200px;
|
|
|
|
|
min-height: 700px;
|
|
|
|
|
background-color: #0a1633;
|
|
|
|
|
color: #e6e6e6;
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
height: 10vh;
|
|
|
|
|
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: 1.5vh;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.left-panel, .right-panel {
|
|
|
|
|
width: 35%;
|
|
|
|
|
min-width: 280px;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 1.5vh;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.center-panel {
|
|
|
|
|
flex: 1;
|
|
|
|
|
display: flex;
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 1.5vh;
|
|
|
|
|
min-height: 0;
|
|
|
|
|
min-width: 500px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
background: rgba(16, 31, 63, 0.7);
|
|
|
|
|
border-radius: 1vh;
|
|
|
|
|
border: 1px solid #1a3a7a;
|
|
|
|
|
box-shadow: 0 0 10px rgba(26, 58, 122, 0.3);
|
|
|
|
|
padding: 1.5vh;
|
|
|
|
|
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: space-between;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.warning-row {
|
|
|
|
|
display: flex;
|
|
|
|
|
gap: 15px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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: 30vh;
|
|
|
|
|
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;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 楼宇容器样式 */
|
|
|
|
|
.building-container {
|
|
|
|
|
flex: 1;
|
|
|
|
|
position: relative;
|
|
|
|
|
display: flex;
|
|
|
|
|
min-height: 40vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.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: 30vh;
|
|
|
|
|
min-height: 200px;
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer {
|
|
|
|
|
height: 5vh;
|
|
|
|
|
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: 1vh;
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 响应式设计 - 中等屏幕 */
|
|
|
|
|
@media screen and (max-width: 1400px) {
|
|
|
|
|
.left-panel, .right-panel {
|
|
|
|
|
width: 28%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.center-panel {
|
|
|
|
|
width: 44%;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 响应式设计 - 小屏幕 */
|
|
|
|
|
@media screen and (max-width: 1200px) {
|
|
|
|
|
.dashboard {
|
|
|
|
|
min-width: 1000px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.left-panel, .right-panel {
|
|
|
|
|
min-width: 250px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.center-panel {
|
|
|
|
|
min-width: 450px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 响应式设计 - 超小屏幕 */
|
|
|
|
|
@media screen and (max-width: 1000px) {
|
|
|
|
|
.dashboard {
|
|
|
|
|
min-width: 800px;
|
|
|
|
|
padding: 1vh 1vw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
gap: 1vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.left-panel, .right-panel, .center-panel {
|
|
|
|
|
width: 100%;
|
|
|
|
|
min-width: auto;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.left-panel, .right-panel {
|
|
|
|
|
flex-direction: row;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.panel {
|
|
|
|
|
flex: 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 响应式设计 - 移动端 */
|
|
|
|
|
@media screen and (max-width: 768px) {
|
|
|
|
|
.dashboard {
|
|
|
|
|
min-width: auto;
|
|
|
|
|
min-height: auto;
|
|
|
|
|
padding: 0.5vh 0.5vw;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.header {
|
|
|
|
|
height: 8vh;
|
|
|
|
|
padding: 0 1vw;
|
|
|
|
|
margin-bottom: 1vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.main-content {
|
|
|
|
|
gap: 1vh;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.left-panel, .right-panel {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.footer {
|
|
|
|
|
height: 4vh;
|
|
|
|
|
font-size: 0.7rem;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|