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