|
|
<!DOCTYPE html>
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>预警管理 - 智慧河道管理平台</title>
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
<link rel="stylesheet" href="all.min.css">
|
|
|
<script src="chart.js"></script>
|
|
|
<style>
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
background-color: #f5f9ff;
|
|
|
font-family: 'Microsoft YaHei', sans-serif;
|
|
|
color: #1a4b8c;
|
|
|
min-height: 100vh;
|
|
|
padding: 20px;
|
|
|
}
|
|
|
|
|
|
.dashboard {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 350px;
|
|
|
gap: 20px;
|
|
|
margin: 0 auto;
|
|
|
}
|
|
|
|
|
|
.main-content {
|
|
|
background: #ffffff;
|
|
|
border-radius: 15px;
|
|
|
padding: 20px;
|
|
|
box-shadow: 0 4px 20px rgba(0, 75, 150, 0.1);
|
|
|
border: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.sidebar {
|
|
|
background: #ffffff;
|
|
|
border-radius: 15px;
|
|
|
padding: 20px;
|
|
|
box-shadow: 0 4px 20px rgba(0, 75, 150, 0.1);
|
|
|
border: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
|
padding-bottom: 15px;
|
|
|
border-bottom: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.header h1 {
|
|
|
color: #1a4b8c;
|
|
|
font-size: 24px;
|
|
|
font-weight: 600;
|
|
|
}
|
|
|
|
|
|
.alert-stats {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(5, 1fr);
|
|
|
gap: 15px;
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.stat-card {
|
|
|
background: #f5f9ff;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 10px;
|
|
|
padding: 15px;
|
|
|
text-align: center;
|
|
|
box-shadow: 0 2px 8px rgba(0, 75, 150, 0.05);
|
|
|
}
|
|
|
|
|
|
.stat-number {
|
|
|
font-size: 24px;
|
|
|
font-weight: bold;
|
|
|
color: #2a7de1;
|
|
|
margin-bottom: 5px;
|
|
|
}
|
|
|
|
|
|
.stat-label {
|
|
|
font-size: 12px;
|
|
|
color: #4a6fa5;
|
|
|
}
|
|
|
|
|
|
/* 标签页样式 */
|
|
|
.tab-container {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.tab-nav {
|
|
|
display: flex;
|
|
|
gap: 2px;
|
|
|
margin-bottom: 20px;
|
|
|
background: #e1e9f5;
|
|
|
border-radius: 10px;
|
|
|
padding: 5px;
|
|
|
}
|
|
|
|
|
|
.tab-btn {
|
|
|
flex: 1;
|
|
|
background: transparent;
|
|
|
border: none;
|
|
|
color: #4a6fa5;
|
|
|
padding: 12px 20px;
|
|
|
cursor: pointer;
|
|
|
border-radius: 8px;
|
|
|
transition: all 0.3s;
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.tab-btn:hover {
|
|
|
background: rgba(42, 125, 225, 0.1);
|
|
|
}
|
|
|
|
|
|
.tab-btn.active {
|
|
|
background: #ffffff;
|
|
|
color: #1a4b8c;
|
|
|
font-weight: 600;
|
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
.tab-content {
|
|
|
display: none;
|
|
|
animation: fadeIn 0.4s ease-in-out;
|
|
|
background: #ffffff;
|
|
|
border-radius: 12px;
|
|
|
margin: 10px;
|
|
|
padding: 20px;
|
|
|
border: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.tab-content.active {
|
|
|
display: block;
|
|
|
}
|
|
|
|
|
|
.filter-section {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
margin-bottom: 20px;
|
|
|
flex-wrap: wrap;
|
|
|
}
|
|
|
|
|
|
.filter-btn {
|
|
|
background: rgba(42, 125, 225, 0.1);
|
|
|
border: 1px solid rgba(42, 125, 225, 0.3);
|
|
|
color: #2a7de1;
|
|
|
padding: 8px 16px;
|
|
|
border-radius: 20px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.filter-btn:hover,
|
|
|
.filter-btn.active {
|
|
|
background: rgba(42, 125, 225, 0.2);
|
|
|
color: #1a4b8c;
|
|
|
}
|
|
|
|
|
|
.search-box {
|
|
|
background: #f5f9ff;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 8px;
|
|
|
padding: 8px 12px;
|
|
|
color: #1a4b8c;
|
|
|
width: 200px;
|
|
|
}
|
|
|
|
|
|
.warning-list {
|
|
|
display: grid;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.warning-card {
|
|
|
background: #ffffff;
|
|
|
border-radius: 10px;
|
|
|
padding: 15px;
|
|
|
transition: all 0.3s;
|
|
|
border: 1px solid #e1e9f5;
|
|
|
box-shadow: 0 2px 10px rgba(0, 75, 150, 0.05);
|
|
|
}
|
|
|
|
|
|
.warning-card:hover {
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 8px 20px rgba(0, 75, 150, 0.1);
|
|
|
}
|
|
|
|
|
|
.warning-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
.warning-title {
|
|
|
font-size: 16px;
|
|
|
font-weight: bold;
|
|
|
color: #1a4b8c;
|
|
|
}
|
|
|
|
|
|
.warning-level {
|
|
|
padding: 4px 8px;
|
|
|
border-radius: 12px;
|
|
|
font-size: 12px;
|
|
|
font-weight: bold;
|
|
|
text-align: center;
|
|
|
min-width: 60px;
|
|
|
}
|
|
|
|
|
|
.warning-critical {
|
|
|
background: rgba(220, 53, 69, 0.1);
|
|
|
color: #dc3545;
|
|
|
border: 1px solid rgba(220, 53, 69, 0.3);
|
|
|
}
|
|
|
|
|
|
.warning-warning {
|
|
|
background: rgba(255, 193, 7, 0.1);
|
|
|
color: #ffc107;
|
|
|
border: 1px solid rgba(255, 193, 7, 0.3);
|
|
|
}
|
|
|
|
|
|
.warning-info {
|
|
|
background: rgba(23, 162, 184, 0.1);
|
|
|
color: #17a2b8;
|
|
|
border: 1px solid rgba(23, 162, 184, 0.3);
|
|
|
}
|
|
|
|
|
|
.warning-content {
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
.warning-location {
|
|
|
color: #2a7de1;
|
|
|
margin-bottom: 5px;
|
|
|
}
|
|
|
|
|
|
.warning-description {
|
|
|
color: #4a6fa5;
|
|
|
font-size: 14px;
|
|
|
line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
.warning-footer {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
font-size: 12px;
|
|
|
color: #a8c0e0;
|
|
|
}
|
|
|
|
|
|
.warning-time {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 5px;
|
|
|
}
|
|
|
|
|
|
.warning-actions {
|
|
|
display: flex;
|
|
|
gap: 5px;
|
|
|
}
|
|
|
|
|
|
.btn {
|
|
|
padding: 4px 8px;
|
|
|
border: none;
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
font-size: 12px;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.btn-primary {
|
|
|
background: rgba(42, 125, 225, 0.1);
|
|
|
color: #2a7de1;
|
|
|
border: 1px solid rgba(42, 125, 225, 0.3);
|
|
|
}
|
|
|
|
|
|
.btn-success {
|
|
|
background: rgba(40, 167, 69, 0.1);
|
|
|
color: #28a745;
|
|
|
border: 1px solid rgba(40, 167, 69, 0.3);
|
|
|
}
|
|
|
|
|
|
.btn-danger {
|
|
|
background: rgba(220, 53, 69, 0.1);
|
|
|
color: #dc3545;
|
|
|
border: 1px solid rgba(220, 53, 69, 0.3);
|
|
|
}
|
|
|
|
|
|
.btn:hover {
|
|
|
transform: translateY(-1px);
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
|
}
|
|
|
|
|
|
.sidebar-section {
|
|
|
margin-bottom: 25px;
|
|
|
padding: 15px;
|
|
|
background: #f5f9ff;
|
|
|
border-radius: 10px;
|
|
|
border: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.sidebar-title {
|
|
|
color: #1a4b8c;
|
|
|
font-size: 16px;
|
|
|
font-weight: bold;
|
|
|
margin-bottom: 15px;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
.chart-container {
|
|
|
height: 200px;
|
|
|
position: relative;
|
|
|
margin-bottom: 1.2rem;
|
|
|
background: #ffffff;
|
|
|
border-radius: 12px;
|
|
|
padding: 1.5rem;
|
|
|
border: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.alert-item {
|
|
|
padding: 8px;
|
|
|
margin-bottom: 5px;
|
|
|
background: rgba(220, 53, 69, 0.05);
|
|
|
border-left: 3px solid #dc3545;
|
|
|
border-radius: 4px;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.trend-stats {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.trend-item {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
padding: 8px 0;
|
|
|
border-bottom: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.trend-item:last-child {
|
|
|
border-bottom: none;
|
|
|
}
|
|
|
|
|
|
/* 模态框样式 */
|
|
|
.modal {
|
|
|
display: none;
|
|
|
position: fixed;
|
|
|
z-index: 1000;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
backdrop-filter: blur(5px);
|
|
|
}
|
|
|
|
|
|
.modal-content {
|
|
|
background: #ffffff;
|
|
|
margin: 5% auto;
|
|
|
padding: 30px;
|
|
|
border-radius: 15px;
|
|
|
width: 80%;
|
|
|
max-width: 600px;
|
|
|
color: #1a4b8c;
|
|
|
box-shadow: 0 10px 30px rgba(0, 75, 150, 0.2);
|
|
|
}
|
|
|
|
|
|
.close {
|
|
|
color: #4a6fa5;
|
|
|
float: right;
|
|
|
font-size: 28px;
|
|
|
font-weight: bold;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
.close:hover {
|
|
|
color: #2a7de1;
|
|
|
}
|
|
|
|
|
|
.form-group {
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.form-group label {
|
|
|
display: block;
|
|
|
margin-bottom: 5px;
|
|
|
color: #1a4b8c;
|
|
|
font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
.form-group input,
|
|
|
.form-group select,
|
|
|
.form-group textarea {
|
|
|
width: 100%;
|
|
|
padding: 10px;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 6px;
|
|
|
background: #f5f9ff;
|
|
|
color: #1a4b8c;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.form-group textarea {
|
|
|
height: 80px;
|
|
|
resize: vertical;
|
|
|
}
|
|
|
|
|
|
.header-info {
|
|
|
display: flex;
|
|
|
gap: 30px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.weather-info {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
color: #4a6fa5;
|
|
|
}
|
|
|
|
|
|
.back-button {
|
|
|
cursor: pointer;
|
|
|
font-size: 20px;
|
|
|
margin-left: 20px;
|
|
|
transition: transform 0.3s;
|
|
|
color: #4a6fa5;
|
|
|
}
|
|
|
|
|
|
.back-button:hover {
|
|
|
transform: scale(1.1);
|
|
|
color: #2a7de1;
|
|
|
}
|
|
|
|
|
|
.header-actions {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.action-btn {
|
|
|
background: linear-gradient(135deg, #2a7de1, #1a4b8c);
|
|
|
color: #fff;
|
|
|
border: none;
|
|
|
padding: 8px 16px;
|
|
|
border-radius: 6px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s ease;
|
|
|
font-size: 14px;
|
|
|
box-shadow: 0 2px 8px rgba(42, 125, 225, 0.2);
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.action-btn:hover {
|
|
|
background: linear-gradient(135deg, #1a4b8c, #2a7de1);
|
|
|
transform: translateY(-2px);
|
|
|
box-shadow: 0 4px 12px rgba(42, 125, 225, 0.3);
|
|
|
}
|
|
|
|
|
|
/* 实时监控样式 */
|
|
|
.monitor-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
|
padding-bottom: 15px;
|
|
|
border-bottom: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.monitor-controls {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.status-indicator {
|
|
|
padding: 5px 10px;
|
|
|
border-radius: 15px;
|
|
|
background: rgba(0, 255, 0, 0.1);
|
|
|
border: 1px solid #28a745;
|
|
|
font-size: 12px;
|
|
|
color: #28a745;
|
|
|
}
|
|
|
|
|
|
.monitor-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 20px;
|
|
|
}
|
|
|
|
|
|
.monitor-section {
|
|
|
background: #f5f9ff;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 10px;
|
|
|
padding: 15px;
|
|
|
}
|
|
|
|
|
|
.monitor-section h4 {
|
|
|
color: #1a4b8c;
|
|
|
margin-bottom: 15px;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.real-time-alerts {
|
|
|
max-height: 200px;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
.real-time-alert {
|
|
|
padding: 8px;
|
|
|
margin-bottom: 8px;
|
|
|
background: rgba(220, 53, 69, 0.05);
|
|
|
border-left: 3px solid #dc3545;
|
|
|
border-radius: 4px;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.monitoring-points {
|
|
|
display: grid;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.monitoring-point {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding: 8px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
border-radius: 6px;
|
|
|
font-size: 12px;
|
|
|
border: 1px solid rgba(42, 125, 225, 0.1);
|
|
|
}
|
|
|
|
|
|
.point-status {
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 10px;
|
|
|
font-size: 10px;
|
|
|
}
|
|
|
|
|
|
.status-online {
|
|
|
background: rgba(40, 167, 69, 0.1);
|
|
|
color: #28a745;
|
|
|
border: 1px solid rgba(40, 167, 69, 0.2);
|
|
|
}
|
|
|
|
|
|
.status-offline {
|
|
|
background: rgba(220, 53, 69, 0.1);
|
|
|
color: #dc3545;
|
|
|
border: 1px solid rgba(220, 53, 69, 0.2);
|
|
|
}
|
|
|
|
|
|
.data-stream {
|
|
|
max-height: 200px;
|
|
|
overflow-y: auto;
|
|
|
font-family: monospace;
|
|
|
font-size: 11px;
|
|
|
}
|
|
|
|
|
|
.data-entry {
|
|
|
padding: 4px 0;
|
|
|
border-bottom: 1px solid rgba(209, 224, 245, 0.5);
|
|
|
}
|
|
|
|
|
|
.system-status {
|
|
|
display: grid;
|
|
|
gap: 8px;
|
|
|
}
|
|
|
|
|
|
.status-item {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding: 8px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
border-radius: 6px;
|
|
|
font-size: 12px;
|
|
|
border: 1px solid rgba(42, 125, 225, 0.1);
|
|
|
}
|
|
|
|
|
|
.status-ok {
|
|
|
color: #28a745;
|
|
|
}
|
|
|
|
|
|
.status-warning {
|
|
|
color: #ffc107;
|
|
|
}
|
|
|
|
|
|
.status-error {
|
|
|
color: #dc3545;
|
|
|
}
|
|
|
|
|
|
/* 趋势分析样式 */
|
|
|
.analysis-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
|
padding-bottom: 15px;
|
|
|
border-bottom: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.time-range-selector {
|
|
|
display: flex;
|
|
|
gap: 5px;
|
|
|
}
|
|
|
|
|
|
.analysis-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: 2fr 1fr;
|
|
|
gap: 20px;
|
|
|
}
|
|
|
|
|
|
.analysis-section {
|
|
|
background: #f5f9ff;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 10px;
|
|
|
padding: 15px;
|
|
|
}
|
|
|
|
|
|
.analysis-section h4 {
|
|
|
color: #1a4b8c;
|
|
|
margin-bottom: 15px;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.chart-container-large {
|
|
|
height: 360px;
|
|
|
position: relative;
|
|
|
background: #ffffff;
|
|
|
border-radius: 12px;
|
|
|
padding: 1.5rem;
|
|
|
border: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.chart-container-medium {
|
|
|
height: 360px;
|
|
|
position: relative;
|
|
|
background: #ffffff;
|
|
|
border-radius: 12px;
|
|
|
padding: 1.5rem;
|
|
|
border: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.hotspot-map {
|
|
|
display: grid;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.hotspot-item {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr auto;
|
|
|
gap: 10px;
|
|
|
align-items: center;
|
|
|
padding: 8px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
border-radius: 6px;
|
|
|
border: 1px solid rgba(42, 125, 225, 0.1);
|
|
|
}
|
|
|
|
|
|
.hotspot-item .location {
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.hotspot-item .count {
|
|
|
font-size: 12px;
|
|
|
color: #ffc107;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.heat-bar {
|
|
|
height: 4px;
|
|
|
background: linear-gradient(90deg, #28a745, #ffc107, #dc3545);
|
|
|
border-radius: 2px;
|
|
|
grid-column: 1 / -1;
|
|
|
}
|
|
|
|
|
|
.summary-stats {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.summary-item {
|
|
|
text-align: center;
|
|
|
padding: 15px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
border-radius: 8px;
|
|
|
border: 1px solid rgba(42, 125, 225, 0.1);
|
|
|
}
|
|
|
|
|
|
.summary-label {
|
|
|
font-size: 12px;
|
|
|
color: #4a6fa5;
|
|
|
margin-bottom: 5px;
|
|
|
}
|
|
|
|
|
|
.summary-value {
|
|
|
font-size: 18px;
|
|
|
font-weight: bold;
|
|
|
color: #1a4b8c;
|
|
|
margin-bottom: 5px;
|
|
|
}
|
|
|
|
|
|
.summary-change {
|
|
|
font-size: 11px;
|
|
|
padding: 2px 6px;
|
|
|
border-radius: 10px;
|
|
|
}
|
|
|
|
|
|
.summary-change.positive {
|
|
|
background: rgba(40, 167, 69, 0.1);
|
|
|
color: #28a745;
|
|
|
}
|
|
|
|
|
|
.summary-change.negative {
|
|
|
background: rgba(220, 53, 69, 0.1);
|
|
|
color: #dc3545;
|
|
|
}
|
|
|
|
|
|
/* 预警设置样式 */
|
|
|
.settings-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
|
padding-bottom: 15px;
|
|
|
border-bottom: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.settings-actions {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.settings-grid {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 20px;
|
|
|
}
|
|
|
|
|
|
.settings-section {
|
|
|
background: #f5f9ff;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 10px;
|
|
|
padding: 15px;
|
|
|
}
|
|
|
|
|
|
.settings-section h4 {
|
|
|
color: #1a4b8c;
|
|
|
margin-bottom: 15px;
|
|
|
font-size: 14px;
|
|
|
}
|
|
|
|
|
|
.threshold-settings {
|
|
|
display: grid;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.threshold-item {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 10px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.threshold-item label {
|
|
|
font-size: 12px;
|
|
|
color: #4a6fa5;
|
|
|
}
|
|
|
|
|
|
.threshold-range {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 5px;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.threshold-input {
|
|
|
width: 60px;
|
|
|
padding: 4px 6px;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 4px;
|
|
|
background: #ffffff;
|
|
|
color: #1a4b8c;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.notification-settings {
|
|
|
display: grid;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.setting-item {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 10px;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.setting-label {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
font-size: 12px;
|
|
|
color: #4a6fa5;
|
|
|
}
|
|
|
|
|
|
.setting-input {
|
|
|
padding: 6px 8px;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 4px;
|
|
|
background: #ffffff;
|
|
|
color: #1a4b8c;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.setting-select {
|
|
|
padding: 6px 8px;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 4px;
|
|
|
background: #ffffff;
|
|
|
color: #1a4b8c;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
/* 算法模型选择样式 */
|
|
|
.model-settings {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.model-description {
|
|
|
margin-top: 5px;
|
|
|
padding: 8px 12px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
border-left: 3px solid #2a7de1;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.model-description small {
|
|
|
color: #4a6fa5;
|
|
|
font-size: 11px;
|
|
|
line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
.model-status {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
padding: 10px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
border-radius: 6px;
|
|
|
border: 1px solid rgba(42, 125, 225, 0.1);
|
|
|
}
|
|
|
|
|
|
.model-metrics {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
gap: 8px;
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
|
|
|
.metric-item {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding: 6px 10px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
border-radius: 4px;
|
|
|
border: 1px solid rgba(42, 125, 225, 0.1);
|
|
|
font-size: 11px;
|
|
|
}
|
|
|
|
|
|
.metric-value {
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.metric-value.excellent {
|
|
|
color: #28a745;
|
|
|
}
|
|
|
|
|
|
.metric-value.good {
|
|
|
color: #2a7de1;
|
|
|
}
|
|
|
|
|
|
.metric-value.warning {
|
|
|
color: #ffc107;
|
|
|
}
|
|
|
|
|
|
.metric-value.danger {
|
|
|
color: #dc3545;
|
|
|
}
|
|
|
|
|
|
.update-strategy {
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
gap: 6px;
|
|
|
margin-top: 10px;
|
|
|
}
|
|
|
|
|
|
.radio-label {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 8px;
|
|
|
padding: 4px 8px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
border-radius: 4px;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s ease;
|
|
|
font-size: 11px;
|
|
|
}
|
|
|
|
|
|
.radio-label:hover {
|
|
|
background: rgba(42, 125, 225, 0.1);
|
|
|
}
|
|
|
|
|
|
.radio-label input[type="radio"] {
|
|
|
accent-color: #2a7de1;
|
|
|
}
|
|
|
|
|
|
.btn-secondary {
|
|
|
background: rgba(108, 117, 125, 0.1);
|
|
|
color: #6c757d;
|
|
|
border: 1px solid rgba(108, 117, 125, 0.3);
|
|
|
padding: 4px 8px;
|
|
|
font-size: 11px;
|
|
|
}
|
|
|
|
|
|
.btn-secondary:hover {
|
|
|
background: rgba(108, 117, 125, 0.2);
|
|
|
transform: translateY(-1px);
|
|
|
}
|
|
|
|
|
|
/* 动画效果 */
|
|
|
@keyframes pulse {
|
|
|
0% {
|
|
|
transform: scale(1);
|
|
|
}
|
|
|
|
|
|
50% {
|
|
|
transform: scale(1.05);
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
transform: scale(1);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.model-description {
|
|
|
animation: fadeIn 0.3s ease-in-out;
|
|
|
}
|
|
|
|
|
|
@keyframes fadeIn {
|
|
|
from {
|
|
|
opacity: 0;
|
|
|
transform: translateY(-10px);
|
|
|
}
|
|
|
|
|
|
to {
|
|
|
opacity: 1;
|
|
|
transform: translateY(0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.rules-settings {
|
|
|
display: grid;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.rule-item {
|
|
|
border: 1px solid rgba(42, 125, 225, 0.1);
|
|
|
border-radius: 8px;
|
|
|
padding: 12px;
|
|
|
background: rgba(42, 125, 225, 0.05);
|
|
|
}
|
|
|
|
|
|
.rule-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
.rule-header span {
|
|
|
font-size: 12px;
|
|
|
color: #4a6fa5;
|
|
|
}
|
|
|
|
|
|
.rule-select {
|
|
|
width: 100%;
|
|
|
padding: 6px 8px;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 4px;
|
|
|
background: #ffffff;
|
|
|
color: #1a4b8c;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.system-settings {
|
|
|
display: grid;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
/* 开关样式 */
|
|
|
.switch {
|
|
|
position: relative;
|
|
|
display: inline-block;
|
|
|
width: 40px;
|
|
|
height: 20px;
|
|
|
}
|
|
|
|
|
|
.switch input {
|
|
|
opacity: 0;
|
|
|
width: 0;
|
|
|
height: 0;
|
|
|
}
|
|
|
|
|
|
.slider {
|
|
|
position: absolute;
|
|
|
cursor: pointer;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
bottom: 0;
|
|
|
background-color: rgba(42, 125, 225, 0.1);
|
|
|
transition: .4s;
|
|
|
border-radius: 20px;
|
|
|
border: 1px solid rgba(42, 125, 225, 0.3);
|
|
|
}
|
|
|
|
|
|
.slider:before {
|
|
|
position: absolute;
|
|
|
content: "";
|
|
|
height: 14px;
|
|
|
width: 14px;
|
|
|
left: 2px;
|
|
|
bottom: 2px;
|
|
|
background-color: #2a7de1;
|
|
|
transition: .4s;
|
|
|
border-radius: 50%;
|
|
|
}
|
|
|
|
|
|
input:checked+.slider {
|
|
|
background-color: rgba(42, 125, 225, 0.3);
|
|
|
}
|
|
|
|
|
|
input:checked+.slider:before {
|
|
|
transform: translateX(20px);
|
|
|
}
|
|
|
|
|
|
/* 自定义滚动条样式 - 与页面风格匹配 */
|
|
|
::-webkit-scrollbar {
|
|
|
width: 5px;
|
|
|
height: 8px;
|
|
|
}
|
|
|
|
|
|
::-webkit-scrollbar-track {
|
|
|
background: rgba(209, 224, 245, 0.3);
|
|
|
/* 浅蓝色轨道 */
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
|
background: rgba(42, 125, 225, 0.5);
|
|
|
/* 主色调蓝色 */
|
|
|
border-radius: 4px;
|
|
|
transition: all 0.3s;
|
|
|
}
|
|
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
|
background: rgba(42, 125, 225, 0.7);
|
|
|
/* 悬停时加深颜色 */
|
|
|
}
|
|
|
|
|
|
/* 特定模块的滚动容器样式 */
|
|
|
.settings-section {
|
|
|
scrollbar-width: 5px;
|
|
|
/* Firefox */
|
|
|
scrollbar-color: rgba(42, 125, 225, 0.5) rgba(209, 224, 245, 0.3);
|
|
|
/* Firefox */
|
|
|
}
|
|
|
|
|
|
.rules-settings,
|
|
|
.notification-settings {
|
|
|
max-height: 300px;
|
|
|
/* 控制滚动区域高度 */
|
|
|
overflow-y: auto;
|
|
|
padding-right: 5px;
|
|
|
/* 避免内容被滚动条遮挡 */
|
|
|
}
|
|
|
|
|
|
/* 紧急预警样式 */
|
|
|
.urgent-alert {
|
|
|
border-left: 3px solid #dc3545;
|
|
|
background: rgba(220, 53, 69, 0.05);
|
|
|
margin-bottom: 12px;
|
|
|
padding: 12px;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.warning-alert {
|
|
|
border-left: 3px solid #ffc107;
|
|
|
background: rgba(255, 193, 7, 0.05);
|
|
|
margin-bottom: 12px;
|
|
|
padding: 12px;
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
.alert-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
margin-bottom: 8px;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.alert-time {
|
|
|
color: #6c757d;
|
|
|
}
|
|
|
|
|
|
.alert-level {
|
|
|
font-weight: bold;
|
|
|
padding: 2px 8px;
|
|
|
border-radius: 10px;
|
|
|
font-size: 11px;
|
|
|
}
|
|
|
|
|
|
.alert-level.critical {
|
|
|
background: rgba(220, 53, 69, 0.1);
|
|
|
color: #dc3545;
|
|
|
}
|
|
|
|
|
|
.alert-level.warning {
|
|
|
background: rgba(255, 193, 7, 0.1);
|
|
|
color: #ffc107;
|
|
|
}
|
|
|
|
|
|
.alert-content {
|
|
|
margin-bottom: 10px;
|
|
|
}
|
|
|
|
|
|
.alert-content strong {
|
|
|
color: #1a4b8c;
|
|
|
display: block;
|
|
|
margin-bottom: 5px;
|
|
|
}
|
|
|
|
|
|
.alert-content p {
|
|
|
color: #4a6fa5;
|
|
|
font-size: 13px;
|
|
|
margin: 0;
|
|
|
line-height: 1.4;
|
|
|
}
|
|
|
|
|
|
.alert-footer {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
.alert-location {
|
|
|
color: #6c757d;
|
|
|
}
|
|
|
|
|
|
.btn-sm {
|
|
|
padding: 3px 10px;
|
|
|
font-size: 12px;
|
|
|
}
|
|
|
|
|
|
/* 滚动条样式 */
|
|
|
#urgentAlerts::-webkit-scrollbar {
|
|
|
width: 5px;
|
|
|
}
|
|
|
|
|
|
#urgentAlerts::-webkit-scrollbar-thumb {
|
|
|
background: rgba(42, 125, 225, 0.3);
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
|
|
|
#urgentAlerts::-webkit-scrollbar-thumb:hover {
|
|
|
background: rgba(42, 125, 225, 0.5);
|
|
|
}
|
|
|
|
|
|
/* 新增预警详情模态框样式 */
|
|
|
.alert-detail-modal {
|
|
|
display: none;
|
|
|
position: fixed;
|
|
|
z-index: 1000;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
backdrop-filter: blur(5px);
|
|
|
}
|
|
|
|
|
|
.alert-detail-content {
|
|
|
background: #ffffff;
|
|
|
margin: 5% auto;
|
|
|
padding: 30px;
|
|
|
border-radius: 15px;
|
|
|
width: 80%;
|
|
|
max-width: 600px;
|
|
|
color: #1a4b8c;
|
|
|
box-shadow: 0 10px 30px rgba(0, 75, 150, 0.2);
|
|
|
}
|
|
|
|
|
|
.alert-detail-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
|
padding-bottom: 15px;
|
|
|
border-bottom: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.alert-detail-title {
|
|
|
font-size: 20px;
|
|
|
font-weight: bold;
|
|
|
color: #1a4b8c;
|
|
|
}
|
|
|
|
|
|
.alert-detail-level {
|
|
|
padding: 6px 12px;
|
|
|
border-radius: 15px;
|
|
|
font-size: 14px;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.alert-detail-critical {
|
|
|
background: rgba(220, 53, 69, 0.1);
|
|
|
color: #dc3545;
|
|
|
border: 1px solid rgba(220, 53, 69, 0.3);
|
|
|
}
|
|
|
|
|
|
.alert-detail-warning {
|
|
|
background: rgba(255, 193, 7, 0.1);
|
|
|
color: #ffc107;
|
|
|
border: 1px solid rgba(255, 193, 7, 0.3);
|
|
|
}
|
|
|
|
|
|
.alert-detail-info {
|
|
|
background: rgba(23, 162, 184, 0.1);
|
|
|
color: #17a2b8;
|
|
|
border: 1px solid rgba(23, 162, 184, 0.3);
|
|
|
}
|
|
|
|
|
|
.alert-detail-body {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.alert-detail-row {
|
|
|
display: flex;
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.alert-detail-label {
|
|
|
width: 100px;
|
|
|
font-weight: bold;
|
|
|
color: #4a6fa5;
|
|
|
}
|
|
|
|
|
|
.alert-detail-value {
|
|
|
flex: 1;
|
|
|
color: #1a4b8c;
|
|
|
}
|
|
|
|
|
|
.alert-detail-footer {
|
|
|
display: flex;
|
|
|
justify-content: flex-end;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.alert-detail-actions {
|
|
|
display: flex;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
/* 新增批量处理模态框样式 */
|
|
|
.batch-process-modal {
|
|
|
display: none;
|
|
|
position: fixed;
|
|
|
z-index: 1000;
|
|
|
left: 0;
|
|
|
top: 0;
|
|
|
width: 100%;
|
|
|
height: 100%;
|
|
|
background-color: rgba(0, 0, 0, 0.5);
|
|
|
backdrop-filter: blur(5px);
|
|
|
}
|
|
|
|
|
|
.batch-process-content {
|
|
|
background: #ffffff;
|
|
|
margin: 5% auto;
|
|
|
padding: 30px;
|
|
|
border-radius: 15px;
|
|
|
width: 80%;
|
|
|
max-width: 600px;
|
|
|
color: #1a4b8c;
|
|
|
box-shadow: 0 10px 30px rgba(0, 75, 150, 0.2);
|
|
|
}
|
|
|
|
|
|
.batch-process-header {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
margin-bottom: 20px;
|
|
|
padding-bottom: 15px;
|
|
|
border-bottom: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.batch-process-title {
|
|
|
font-size: 20px;
|
|
|
font-weight: bold;
|
|
|
color: #1a4b8c;
|
|
|
}
|
|
|
|
|
|
.batch-process-body {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.batch-process-footer {
|
|
|
display: flex;
|
|
|
justify-content: flex-end;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.batch-alerts-list {
|
|
|
max-height: 300px;
|
|
|
overflow-y: auto;
|
|
|
margin-bottom: 20px;
|
|
|
border: 1px solid #e1e9f5;
|
|
|
border-radius: 8px;
|
|
|
padding: 10px;
|
|
|
}
|
|
|
|
|
|
.batch-alert-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
padding: 8px;
|
|
|
border-bottom: 1px solid #e1e9f5;
|
|
|
}
|
|
|
|
|
|
.batch-alert-item:last-child {
|
|
|
border-bottom: none;
|
|
|
}
|
|
|
|
|
|
.batch-alert-checkbox {
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
.batch-alert-info {
|
|
|
flex: 1;
|
|
|
}
|
|
|
|
|
|
.batch-alert-title {
|
|
|
font-weight: bold;
|
|
|
margin-bottom: 3px;
|
|
|
}
|
|
|
|
|
|
.batch-alert-meta {
|
|
|
display: flex;
|
|
|
font-size: 12px;
|
|
|
color: #4a6fa5;
|
|
|
}
|
|
|
|
|
|
.batch-alert-location {
|
|
|
margin-right: 10px;
|
|
|
}
|
|
|
|
|
|
.batch-process-options {
|
|
|
margin-bottom: 20px;
|
|
|
}
|
|
|
|
|
|
.batch-option {
|
|
|
margin-bottom: 15px;
|
|
|
}
|
|
|
|
|
|
.batch-option-label {
|
|
|
display: block;
|
|
|
margin-bottom: 5px;
|
|
|
font-weight: bold;
|
|
|
}
|
|
|
|
|
|
.batch-option-select {
|
|
|
width: 100%;
|
|
|
padding: 8px;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 6px;
|
|
|
background: #f5f9ff;
|
|
|
color: #1a4b8c;
|
|
|
}
|
|
|
|
|
|
.batch-option-textarea {
|
|
|
width: 100%;
|
|
|
padding: 8px;
|
|
|
border: 1px solid #d1e0f5;
|
|
|
border-radius: 6px;
|
|
|
background: #f5f9ff;
|
|
|
color: #1a4b8c;
|
|
|
height: 80px;
|
|
|
resize: vertical;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
<div class="dashboard">
|
|
|
<div class="main-content">
|
|
|
<div class="header">
|
|
|
<h1>⚠️ 预警管理系统</h1>
|
|
|
<div class="header-actions">
|
|
|
<button class="action-btn" onclick="location.href='index.html'">
|
|
|
<i class="fas fa-home-alt" style="color: orange;"></i> 回到首页
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="alert-stats">
|
|
|
<div class="stat-card">
|
|
|
<div class="stat-number" id="totalAlerts">45</div>
|
|
|
<div class="stat-label">总预警数</div>
|
|
|
</div>
|
|
|
<div class="stat-card">
|
|
|
<div class="stat-number" id="criticalAlerts">8</div>
|
|
|
<div class="stat-label">严重预警</div>
|
|
|
</div>
|
|
|
<div class="stat-card">
|
|
|
<div class="stat-number" id="warningAlerts">23</div>
|
|
|
<div class="stat-label">一般预警</div>
|
|
|
</div>
|
|
|
<div class="stat-card">
|
|
|
<div class="stat-number" id="infoAlerts">14</div>
|
|
|
<div class="stat-label">提示信息</div>
|
|
|
</div>
|
|
|
<div class="stat-card">
|
|
|
<div class="stat-number" id="todayAlerts">12</div>
|
|
|
<div class="stat-label">今日新增</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="tab-container">
|
|
|
<div class="tab-nav">
|
|
|
<button class="tab-btn active" onclick="switchTab('warning-list')">预警列表</button>
|
|
|
<button class="tab-btn" onclick="switchTab('warning-monitor')">实时监控</button>
|
|
|
<button class="tab-btn" onclick="switchTab('warning-analysis')">趋势分析</button>
|
|
|
<button class="tab-btn" onclick="switchTab('warning-settings')">预警设置</button>
|
|
|
</div>
|
|
|
|
|
|
<div id="warning-list" class="tab-content active">
|
|
|
<div class="filter-section">
|
|
|
<button class="filter-btn active" data-level="all">全部预警</button>
|
|
|
<button class="filter-btn" data-level="critical">严重</button>
|
|
|
<button class="filter-btn" data-level="warning">一般</button>
|
|
|
<button class="filter-btn" data-level="info">提示</button>
|
|
|
<select class="filter-btn" id="typeFilter">
|
|
|
<option value="all">全部类型</option>
|
|
|
<option value="water-quality">水质异常</option>
|
|
|
<option value="water-level">水位异常</option>
|
|
|
<option value="equipment">设备故障</option>
|
|
|
<option value="pollution">污染事件</option>
|
|
|
</select>
|
|
|
<input type="text" class="search-box" placeholder="搜索预警..." id="searchInput">
|
|
|
<button class="filter-btn" onclick="openNewAlertModal()">📝 新建预警</button>
|
|
|
<button class="filter-btn" onclick="exportAlerts()">📊 导出报表</button>
|
|
|
<button class="filter-btn" onclick="batchProcess()">📋 批量处理</button>
|
|
|
</div>
|
|
|
|
|
|
<div class="warning-list" id="warningList">
|
|
|
<!-- 预警数据将通过JS动态加载 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div id="warning-monitor" class="tab-content">
|
|
|
<div class="monitor-header">
|
|
|
<h3>🔴 实时监控</h3>
|
|
|
<div class="monitor-controls">
|
|
|
<button class="filter-btn" onclick="refreshMonitor()">🔄 刷新</button>
|
|
|
<button class="filter-btn" onclick="toggleAutoRefresh()">⏱️ 自动刷新</button>
|
|
|
<span class="status-indicator" id="connectionStatus">🟢 连接正常</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="monitor-grid">
|
|
|
<div class="monitor-section">
|
|
|
<h4>实时预警状态</h4>
|
|
|
<div class="real-time-alerts" id="realTimeAlerts">
|
|
|
<!-- 实时预警数据 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="monitor-section">
|
|
|
<h4>监测点状态</h4>
|
|
|
<div class="monitoring-points" id="monitoringPoints">
|
|
|
<!-- 监测点状态 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="monitor-section">
|
|
|
<h4>实时数据流</h4>
|
|
|
<div class="data-stream" id="dataStream">
|
|
|
<!-- 数据流显示 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="monitor-section">
|
|
|
<h4>系统状态</h4>
|
|
|
<div class="system-status">
|
|
|
<div class="status-item">
|
|
|
<span>数据库连接</span>
|
|
|
<span class="status-ok">正常</span>
|
|
|
</div>
|
|
|
<div class="status-item">
|
|
|
<span>网络状态</span>
|
|
|
<span class="status-ok">正常</span>
|
|
|
</div>
|
|
|
<div class="status-item">
|
|
|
<span>预警引擎</span>
|
|
|
<span class="status-ok">运行中</span>
|
|
|
</div>
|
|
|
<div class="status-item">
|
|
|
<span>数据采集</span>
|
|
|
<span class="status-warning">延迟</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div id="warning-analysis" class="tab-content">
|
|
|
<div class="analysis-header">
|
|
|
<h3>📊 趋势分析</h3>
|
|
|
<div class="time-range-selector">
|
|
|
<button class="filter-btn" data-range="7d" onclick="updateAnalysisData('7d')">近7天</button>
|
|
|
<button class="filter-btn active" data-range="30d"
|
|
|
onclick="updateAnalysisData('30d')">近30天</button>
|
|
|
<button class="filter-btn" data-range="90d"
|
|
|
onclick="updateAnalysisData('90d')">近3个月</button>
|
|
|
<button class="filter-btn" data-range="1y" onclick="updateAnalysisData('1y')">近1年</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="analysis-grid">
|
|
|
<div class="analysis-section">
|
|
|
<h4>预警趋势图</h4>
|
|
|
<div class="chart-container-large">
|
|
|
<canvas id="warningTrendChart"></canvas>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="analysis-section">
|
|
|
<h4>预警类型分布</h4>
|
|
|
<div class="chart-container-medium">
|
|
|
<canvas id="warningTypeDistChart"></canvas>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 响应时间分析 - 现在铺满整个宽度 -->
|
|
|
<div class="analysis-section" style="grid-column: 1 / -1;">
|
|
|
<h4>响应时间分析</h4>
|
|
|
<div class="chart-container-large">
|
|
|
<canvas id="responseTimeChart"></canvas>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="analysis-section">
|
|
|
<h4>预警热点区域</h4>
|
|
|
<div class="hotspot-map" id="hotspotMap">
|
|
|
<!-- 动态内容将通过JS更新 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 统计摘要 - 现在放在响应时间分析下面 -->
|
|
|
<div class="analysis-section">
|
|
|
<h4>统计摘要</h4>
|
|
|
<div class="summary-stats" id="summaryStats">
|
|
|
<!-- 动态内容将通过JS更新 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div id="warning-settings" class="tab-content">
|
|
|
<div class="settings-header">
|
|
|
<h3>⚙️ 预警设置</h3>
|
|
|
<div class="settings-actions">
|
|
|
<button class="filter-btn" onclick="saveSettings()">💾 保存设置</button>
|
|
|
<button class="filter-btn" onclick="resetSettings()">🔄 重置默认</button>
|
|
|
<button class="filter-btn" onclick="exportSettings()">📤 导出配置</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="settings-grid" style="grid-template-columns: 1fr 1fr; align-items: start;">
|
|
|
<!-- 左侧列 -->
|
|
|
<div style="display: grid; gap: 20px;">
|
|
|
<!-- 预警阈值设置 -->
|
|
|
<div class="settings-section" style="height: 211px;">
|
|
|
<h4>预警阈值设置</h4>
|
|
|
<div class="threshold-settings">
|
|
|
<div class="threshold-item">
|
|
|
<label>水质pH值</label>
|
|
|
<div class="threshold-range">
|
|
|
<input type="number" value="6.5" step="0.1" class="threshold-input"> -
|
|
|
<input type="number" value="8.5" step="0.1" class="threshold-input">
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="threshold-item">
|
|
|
<label>溶解氧 (mg/L)</label>
|
|
|
<div class="threshold-range">
|
|
|
<input type="number" value="5.0" step="0.1" class="threshold-input"> 以上
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="threshold-item">
|
|
|
<label>水位 (m)</label>
|
|
|
<div class="threshold-range">
|
|
|
<input type="number" value="28.5" step="0.1" class="threshold-input"> 警戒水位
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="threshold-item">
|
|
|
<label>浊度 (NTU)</label>
|
|
|
<div class="threshold-range">
|
|
|
<input type="number" value="20" step="1" class="threshold-input"> 以下
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 预警规则 -->
|
|
|
<div class="settings-section" style="height: 100%;">
|
|
|
<h4>预警规则</h4>
|
|
|
<div class="rules-settings">
|
|
|
<div class="rule-item">
|
|
|
<div class="rule-header">
|
|
|
<span>水质异常规则</span>
|
|
|
<label class="switch">
|
|
|
<input type="checkbox" checked>
|
|
|
<span class="slider"></span>
|
|
|
</label>
|
|
|
</div>
|
|
|
<div class="rule-config">
|
|
|
<select class="rule-select">
|
|
|
<option>连续3次检测异常</option>
|
|
|
<option>单次严重异常</option>
|
|
|
<option>趋势异常</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="rule-item">
|
|
|
<div class="rule-header">
|
|
|
<span>设备故障规则</span>
|
|
|
<label class="switch">
|
|
|
<input type="checkbox" checked>
|
|
|
<span class="slider"></span>
|
|
|
</label>
|
|
|
</div>
|
|
|
<div class="rule-config">
|
|
|
<select class="rule-select">
|
|
|
<option>设备离线超过5分钟</option>
|
|
|
<option>数据异常</option>
|
|
|
<option>通信中断</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="rule-item">
|
|
|
<div class="rule-header">
|
|
|
<span>水位预警规则</span>
|
|
|
<label class="switch">
|
|
|
<input type="checkbox" checked>
|
|
|
<span class="slider"></span>
|
|
|
</label>
|
|
|
</div>
|
|
|
<div class="rule-config">
|
|
|
<select class="rule-select">
|
|
|
<option>超过警戒水位</option>
|
|
|
<option>水位变化过快</option>
|
|
|
<option>预测超标</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 系统配置 -->
|
|
|
<div class="settings-section">
|
|
|
<h4>系统配置</h4>
|
|
|
<div class="system-settings">
|
|
|
<div class="setting-item">
|
|
|
<label class="setting-label">数据刷新间隔</label>
|
|
|
<select class="setting-select">
|
|
|
<option value="30">30秒</option>
|
|
|
<option value="60" selected>1分钟</option>
|
|
|
<option value="300">5分钟</option>
|
|
|
<option value="600">10分钟</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label class="setting-label">预警保留时间</label>
|
|
|
<select class="setting-select">
|
|
|
<option value="30">30天</option>
|
|
|
<option value="90" selected>90天</option>
|
|
|
<option value="180">180天</option>
|
|
|
<option value="365">1年</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label class="setting-label">自动解除预警</label>
|
|
|
<label class="switch">
|
|
|
<input type="checkbox">
|
|
|
<span class="slider"></span>
|
|
|
</label>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label class="setting-label">预警升级机制</label>
|
|
|
<label class="switch">
|
|
|
<input type="checkbox" checked>
|
|
|
<span class="slider"></span>
|
|
|
</label>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 右侧列 -->
|
|
|
<div style="display: grid; gap: 20px;">
|
|
|
<!-- 通知设置 -->
|
|
|
<div class="settings-section" style="height: 100%;">
|
|
|
<h4>通知设置</h4>
|
|
|
<div class="notification-settings">
|
|
|
<div class="setting-item">
|
|
|
<label class="setting-label">
|
|
|
<input type="checkbox" checked> 邮件通知
|
|
|
</label>
|
|
|
<input type="email" placeholder="邮箱地址" class="setting-input">
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label class="setting-label">
|
|
|
<input type="checkbox" checked> 短信通知
|
|
|
</label>
|
|
|
<input type="tel" placeholder="手机号码" class="setting-input">
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label class="setting-label">
|
|
|
<input type="checkbox"> 微信通知
|
|
|
</label>
|
|
|
<input type="text" placeholder="微信号" class="setting-input">
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label class="setting-label">
|
|
|
<input type="checkbox" checked> 系统推送
|
|
|
</label>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 算法模型选择 -->
|
|
|
<div class="settings-section">
|
|
|
<h4>🤖 算法模型选择</h4>
|
|
|
<div class="model-settings">
|
|
|
<div class="setting-item">
|
|
|
<label>水质预警算法</label>
|
|
|
<select class="setting-select" id="waterQualityModel"
|
|
|
onchange="updateModelDescription('waterQuality', this.value)">
|
|
|
<option value="traditional">传统阈值模型</option>
|
|
|
<option value="ml_regression" selected>机器学习回归模型</option>
|
|
|
<option value="lstm">LSTM时序预测模型</option>
|
|
|
<option value="ensemble">集成学习模型</option>
|
|
|
</select>
|
|
|
<div class="model-description" id="waterQualityDesc">
|
|
|
<small>🎯 机器学习回归模型:基于历史数据训练,预测准确率85%,适用于多参数水质预警</small>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label>水位预警算法</label>
|
|
|
<select class="setting-select" id="waterLevelModel"
|
|
|
onchange="updateModelDescription('waterLevel', this.value)">
|
|
|
<option value="traditional">传统阈值模型</option>
|
|
|
<option value="arima">ARIMA时间序列模型</option>
|
|
|
<option value="neural_network" selected>神经网络模型</option>
|
|
|
<option value="hybrid">混合预测模型</option>
|
|
|
</select>
|
|
|
<div class="model-description" id="waterLevelDesc">
|
|
|
<small>🧠 神经网络模型:深度学习算法,适用于复杂水位变化预测,准确率92%</small>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label>异常检测算法</label>
|
|
|
<select class="setting-select" id="anomalyModel"
|
|
|
onchange="updateModelDescription('anomaly', this.value)">
|
|
|
<option value="statistical">统计学方法</option>
|
|
|
<option value="isolation_forest" selected>孤立森林算法</option>
|
|
|
<option value="autoencoder">自编码器</option>
|
|
|
<option value="one_class_svm">单类支持向量机</option>
|
|
|
</select>
|
|
|
<div class="model-description" id="anomalyDesc">
|
|
|
<small>🌲 孤立森林算法:无监督学习,擅长检测数据异常点,误报率低于5%</small>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label>污染事件识别</label>
|
|
|
<select class="setting-select" id="pollutionModel"
|
|
|
onchange="updateModelDescription('pollution', this.value)">
|
|
|
<option value="rule_based">规则引擎</option>
|
|
|
<option value="random_forest" selected>随机森林</option>
|
|
|
<option value="cnn">卷积神经网络</option>
|
|
|
<option value="transformer">Transformer模型</option>
|
|
|
</select>
|
|
|
<div class="model-description" id="pollutionDesc">
|
|
|
<small>🌳 随机森林:集成学习算法,多特征融合识别污染事件,F1分数89%</small>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label>模型训练状态</label>
|
|
|
<div class="model-status">
|
|
|
<span class="status-indicator status-online">●</span>
|
|
|
<span>所有模型运行正常</span>
|
|
|
<button class="btn btn-primary" onclick="retrainModels()">🔄 重新训练</button>
|
|
|
<button class="btn btn-secondary" onclick="viewModelLogs()">📊 查看日志</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label>模型性能指标</label>
|
|
|
<div class="model-metrics">
|
|
|
<div class="metric-item">
|
|
|
<span>准确率:</span>
|
|
|
<span class="metric-value good">87.5%</span>
|
|
|
</div>
|
|
|
<div class="metric-item">
|
|
|
<span>召回率:</span>
|
|
|
<span class="metric-value excellent">92.3%</span>
|
|
|
</div>
|
|
|
<div class="metric-item">
|
|
|
<span>F1分数:</span>
|
|
|
<span class="metric-value good">89.8%</span>
|
|
|
</div>
|
|
|
<div class="metric-item">
|
|
|
<span>响应时间:</span>
|
|
|
<span class="metric-value excellent">
|
|
|
< 100ms</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="setting-item">
|
|
|
<label>模型更新策略</label>
|
|
|
<div class="update-strategy">
|
|
|
<label class="radio-label">
|
|
|
<input type="radio" name="updateStrategy" value="auto" checked>
|
|
|
<span>自动更新(每周)</span>
|
|
|
</label>
|
|
|
<label class="radio-label">
|
|
|
<input type="radio" name="updateStrategy" value="manual">
|
|
|
<span>手动更新</span>
|
|
|
</label>
|
|
|
<label class="radio-label">
|
|
|
<input type="radio" name="updateStrategy" value="scheduled">
|
|
|
<span>定时更新(每月)</span>
|
|
|
</label>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="sidebar">
|
|
|
<div class="sidebar-section">
|
|
|
<div class="sidebar-title" style="color: #dc3545; border-left: 3px solid #dc3545; padding-left: 10px;">
|
|
|
<i class="fas fa-exclamation-triangle"></i> 紧急预警
|
|
|
</div>
|
|
|
<div id="urgentAlerts" style="max-height: 300px; overflow-y: auto;">
|
|
|
<!-- 紧急预警条目 -->
|
|
|
<div class="alert-item urgent-alert">
|
|
|
<div class="alert-header">
|
|
|
<span class="alert-time">今天 14:30</span>
|
|
|
<span class="alert-level critical">严重</span>
|
|
|
</div>
|
|
|
<div class="alert-content">
|
|
|
<strong>湘江橘子洲段水质异常</strong>
|
|
|
<p>检测到重金属含量超标,pH值异常,请立即处理!</p>
|
|
|
</div>
|
|
|
<div class="alert-footer">
|
|
|
<span class="alert-location"><i class="fas fa-map-marker-alt"></i> 监测点 #A-1024</span>
|
|
|
<button class="btn btn-sm btn-danger">立即处理</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="alert-item urgent-alert">
|
|
|
<div class="alert-header">
|
|
|
<span class="alert-time">今天 11:45</span>
|
|
|
<span class="alert-level critical">严重</span>
|
|
|
</div>
|
|
|
<div class="alert-content">
|
|
|
<strong>水位超过警戒线</strong>
|
|
|
<p>湘江猴子石段水位已达38.5米,超过警戒水位1.2米!</p>
|
|
|
</div>
|
|
|
<div class="alert-footer">
|
|
|
<span class="alert-location"><i class="fas fa-map-marker-alt"></i> 监测点 #B-2048</span>
|
|
|
<button class="btn btn-sm btn-danger">立即处理</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="alert-item warning-alert">
|
|
|
<div class="alert-header">
|
|
|
<span class="alert-time">今天 09:20</span>
|
|
|
<span class="alert-level warning">警告</span>
|
|
|
</div>
|
|
|
<div class="alert-content">
|
|
|
<strong>污水处理设备故障</strong>
|
|
|
<p>风光带污水处理站主泵异常,可能影响水质监测数据</p>
|
|
|
</div>
|
|
|
<div class="alert-footer">
|
|
|
<span class="alert-location"><i class="fas fa-map-marker-alt"></i> 设备 #EQ-3072</span>
|
|
|
<button class="btn btn-sm btn-warning">查看详情</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="sidebar-section">
|
|
|
<div class="sidebar-title">预警级别分布</div>
|
|
|
<div class="chart-container">
|
|
|
<canvas id="levelChart"></canvas>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="sidebar-section">
|
|
|
<div class="sidebar-title">本周预警趋势</div>
|
|
|
<div class="chart-container">
|
|
|
<canvas id="trendChart"></canvas>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<div class="sidebar-section">
|
|
|
<div class="sidebar-title">预警类型分析</div>
|
|
|
<div class="chart-container">
|
|
|
<canvas id="typeChart"></canvas>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="sidebar-section">
|
|
|
<div class="sidebar-title">处理统计</div>
|
|
|
<div class="trend-stats">
|
|
|
<div class="trend-item">
|
|
|
<span>平均响应时间</span>
|
|
|
<span id="avgResponseTime">15分钟</span>
|
|
|
</div>
|
|
|
<div class="trend-item">
|
|
|
<span>处理完成率</span>
|
|
|
<span id="completionRate">92.3%</span>
|
|
|
</div>
|
|
|
<div class="trend-item">
|
|
|
<span>误报率</span>
|
|
|
<span id="falseAlarmRate">3.2%</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 新建预警模态框 -->
|
|
|
<div id="newAlertModal" class="modal">
|
|
|
<div class="modal-content">
|
|
|
<span class="close" onclick="closeNewAlertModal()">×</span>
|
|
|
<h2>新建预警</h2>
|
|
|
<form id="newAlertForm">
|
|
|
<div class="form-group">
|
|
|
<label for="alertType">预警类型:</label>
|
|
|
<select id="alertType" required>
|
|
|
<option value="">请选择类型</option>
|
|
|
<option value="water-quality">水质异常</option>
|
|
|
<option value="water-level">水位异常</option>
|
|
|
<option value="equipment">设备故障</option>
|
|
|
<option value="pollution">污染事件</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label for="alertLevel">预警级别:</label>
|
|
|
<select id="alertLevel" required>
|
|
|
<option value="info">提示</option>
|
|
|
<option value="warning">一般</option>
|
|
|
<option value="critical">严重</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label for="alertLocation">预警位置:</label>
|
|
|
<input type="text" id="alertLocation" required>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label for="alertDescription">预警描述:</label>
|
|
|
<textarea id="alertDescription" required></textarea>
|
|
|
</div>
|
|
|
<button type="submit" class="btn btn-primary">创建预警</button>
|
|
|
</form>
|
|
|
</div>
|
|
|
</div>
|
|
|
<!-- 新增预警详情模态框 -->
|
|
|
<div id="alertDetailModal" class="alert-detail-modal">
|
|
|
<div class="alert-detail-content">
|
|
|
<div class="alert-detail-header">
|
|
|
<div class="alert-detail-title" id="alertDetailTitle">预警详情</div>
|
|
|
<div class="alert-detail-level" id="alertDetailLevel">严重</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-body">
|
|
|
<div class="alert-detail-row">
|
|
|
<div class="alert-detail-label">预警ID:</div>
|
|
|
<div class="alert-detail-value" id="alertDetailId">AL-2025001</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-row">
|
|
|
<div class="alert-detail-label">预警类型:</div>
|
|
|
<div class="alert-detail-value" id="alertDetailType">水质异常</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-row">
|
|
|
<div class="alert-detail-label">预警位置:</div>
|
|
|
<div class="alert-detail-value" id="alertDetailLocation">湘江橘子洲段</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-row">
|
|
|
<div class="alert-detail-label">发生时间:</div>
|
|
|
<div class="alert-detail-value" id="alertDetailTime">2025-01-03 09:30</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-row">
|
|
|
<div class="alert-detail-label">当前状态:</div>
|
|
|
<div class="alert-detail-value" id="alertDetailStatus">未处理</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-row">
|
|
|
<div class="alert-detail-label">详细描述:</div>
|
|
|
<div class="alert-detail-value" id="alertDetailDescription">检测到水质pH值异常,溶解氧含量严重不足,疑似工业废水排放</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-footer">
|
|
|
<div class="alert-detail-actions">
|
|
|
<button class="btn btn-primary" onclick="processCurrentAlert()">处理</button>
|
|
|
<button class="btn btn-danger" onclick="resolveCurrentAlert()">解除</button>
|
|
|
<button class="btn btn-secondary" onclick="closeAlertDetailModal()">关闭</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 新增批量处理模态框 -->
|
|
|
<div id="batchProcessModal" class="batch-process-modal">
|
|
|
<div class="batch-process-content">
|
|
|
<div class="batch-process-header">
|
|
|
<div class="batch-process-title">批量处理预警</div>
|
|
|
<span class="close" onclick="closeBatchProcessModal()">×</span>
|
|
|
</div>
|
|
|
<div class="batch-process-body">
|
|
|
<div class="batch-alerts-list" id="batchAlertsList">
|
|
|
<!-- 预警列表将通过JS动态加载 -->
|
|
|
</div>
|
|
|
<div class="batch-process-options">
|
|
|
<div class="batch-option">
|
|
|
<label class="batch-option-label">处理操作:</label>
|
|
|
<select class="batch-option-select" id="batchAction">
|
|
|
<option value="process">标记为处理中</option>
|
|
|
<option value="resolve">标记为已解决</option>
|
|
|
<option value="assign">分配给处理人员</option>
|
|
|
<option value="priority">调整优先级</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="batch-option" id="assignOption" style="display: none;">
|
|
|
<label class="batch-option-label">分配给:</label>
|
|
|
<select class="batch-option-select" id="assignTo">
|
|
|
<option value="operator1">操作员1</option>
|
|
|
<option value="operator2">操作员2</option>
|
|
|
<option value="operator3">操作员3</option>
|
|
|
<option value="team">应急小组</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="batch-option" id="priorityOption" style="display: none;">
|
|
|
<label class="batch-option-label">优先级:</label>
|
|
|
<select class="batch-option-select" id="priorityLevel">
|
|
|
<option value="low">低</option>
|
|
|
<option value="medium" selected>中</option>
|
|
|
<option value="high">高</option>
|
|
|
<option value="urgent">紧急</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="batch-option">
|
|
|
<label class="batch-option-label">处理备注:</label>
|
|
|
<textarea class="batch-option-textarea" id="batchNote" placeholder="请输入处理备注..."></textarea>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="batch-process-footer">
|
|
|
<button class="btn btn-secondary" onclick="closeBatchProcessModal()">取消</button>
|
|
|
<button class="btn btn-primary" onclick="confirmBatchProcess()">确认处理</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 新增处理预警模态框 -->
|
|
|
<div id="processAlertModal" class="alert-detail-modal">
|
|
|
<div class="alert-detail-content">
|
|
|
<div class="alert-detail-header">
|
|
|
<div class="alert-detail-title">处理预警</div>
|
|
|
<span class="close" onclick="closeProcessAlertModal()">×</span>
|
|
|
</div>
|
|
|
<div class="alert-detail-body">
|
|
|
<div class="alert-detail-row">
|
|
|
<div class="alert-detail-label">预警ID:</div>
|
|
|
<div class="alert-detail-value" id="processAlertId">AL-2025001</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-row">
|
|
|
<div class="alert-detail-label">预警标题:</div>
|
|
|
<div class="alert-detail-value" id="processAlertTitle">水质严重污染预警</div>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label for="assignToSingle">分配给:</label>
|
|
|
<select class="setting-select" id="assignToSingle">
|
|
|
<option value="operator1">操作员1</option>
|
|
|
<option value="operator2">操作员2</option>
|
|
|
<option value="operator3">操作员3</option>
|
|
|
<option value="team">应急小组</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label for="prioritySingle">优先级:</label>
|
|
|
<select class="setting-select" id="prioritySingle">
|
|
|
<option value="low">低</option>
|
|
|
<option value="medium" selected>中</option>
|
|
|
<option value="high">高</option>
|
|
|
<option value="urgent">紧急</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
<div class="form-group">
|
|
|
<label for="processNote">处理备注:</label>
|
|
|
<textarea class="setting-input" id="processNote" placeholder="请输入处理备注..." rows="3"></textarea>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="alert-detail-footer">
|
|
|
<button class="btn btn-secondary" onclick="closeProcessAlertModal()">取消</button>
|
|
|
<button class="btn btn-primary" onclick="confirmProcessAlert()">确认处理</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
<script>
|
|
|
// 标签页切换功能
|
|
|
function switchTab(tabId) {
|
|
|
// 隐藏所有标签页内容
|
|
|
const tabContents = document.querySelectorAll('.tab-content');
|
|
|
tabContents.forEach(content => {
|
|
|
content.classList.remove('active');
|
|
|
});
|
|
|
|
|
|
// 移除所有标签按钮的激活状态
|
|
|
const tabBtns = document.querySelectorAll('.tab-btn');
|
|
|
tabBtns.forEach(btn => {
|
|
|
btn.classList.remove('active');
|
|
|
});
|
|
|
|
|
|
// 显示选中的标签页内容
|
|
|
document.getElementById(tabId).classList.add('active');
|
|
|
|
|
|
// 激活对应的标签按钮
|
|
|
event.target.classList.add('active');
|
|
|
|
|
|
// 根据切换的标签页执行特定初始化
|
|
|
switch (tabId) {
|
|
|
case 'warning-monitor':
|
|
|
refreshMonitor();
|
|
|
break;
|
|
|
case 'warning-analysis':
|
|
|
//setTimeout(initAnalysisCharts, 100); // 延迟初始化图表
|
|
|
break;
|
|
|
case 'warning-settings':
|
|
|
// 设置页面无需特殊初始化
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 修改预警数据结构,添加notes字段
|
|
|
const warnings = [
|
|
|
{
|
|
|
id: 'AL-2025001',
|
|
|
type: 'water-quality',
|
|
|
level: 'critical',
|
|
|
title: '水质严重污染预警',
|
|
|
location: '湘江橘子洲段',
|
|
|
description: '检测到水质pH值异常,溶解氧含量严重不足,疑似工业废水排放',
|
|
|
time: '2025-01-03 09:30',
|
|
|
status: 'active',
|
|
|
notes: []
|
|
|
},
|
|
|
{
|
|
|
id: 'AL-2025002',
|
|
|
type: 'water-level',
|
|
|
level: 'warning',
|
|
|
title: '水位上涨预警',
|
|
|
location: '湘江猴子石段',
|
|
|
description: '受上游降雨影响,水位持续上涨,预计6小时内达到警戒水位',
|
|
|
time: '2025-01-03 10:15',
|
|
|
status: 'active',
|
|
|
notes: []
|
|
|
},
|
|
|
{
|
|
|
id: 'AL-2025003',
|
|
|
type: 'equipment',
|
|
|
level: 'info',
|
|
|
title: '监测设备离线',
|
|
|
location: '湘江风光带监测点',
|
|
|
description: '水质监测设备通信中断,可能是网络故障或设备故障',
|
|
|
time: '2025-01-03 11:20',
|
|
|
status: 'resolved',
|
|
|
notes: []
|
|
|
}
|
|
|
];
|
|
|
|
|
|
// 级别映射
|
|
|
const levelMap = {
|
|
|
'critical': { text: '严重', class: 'warning-critical' },
|
|
|
'warning': { text: '一般', class: 'warning-warning' },
|
|
|
'info': { text: '提示', class: 'warning-info' }
|
|
|
};
|
|
|
|
|
|
// 类型映射
|
|
|
const typeMap = {
|
|
|
'water-quality': '水质异常',
|
|
|
'water-level': '水位异常',
|
|
|
'equipment': '设备故障',
|
|
|
'pollution': '污染事件'
|
|
|
};
|
|
|
|
|
|
// 修改loadWarnings函数,显示分配信息和优先级
|
|
|
function loadWarnings() {
|
|
|
updateStats(); // 更新统计数字
|
|
|
|
|
|
const warningList = document.getElementById('warningList');
|
|
|
warningList.innerHTML = warnings.map(warning => `
|
|
|
<div class="warning-card">
|
|
|
<div class="warning-header">
|
|
|
<div class="warning-title">${warning.title}</div>
|
|
|
<div class="warning-level ${levelMap[warning.level].class}">${levelMap[warning.level].text}</div>
|
|
|
</div>
|
|
|
<div class="warning-content">
|
|
|
<div class="warning-location">📍 ${warning.location}</div>
|
|
|
${warning.assignedTo ? `<div class="warning-assigned">👤 分配给: ${warning.assignedTo}</div>` : ''}
|
|
|
${warning.priority ? `<div class="warning-priority">⚠️ 优先级: ${warning.priority}</div>` : ''}
|
|
|
<div class="warning-description">${warning.description}</div>
|
|
|
</div>
|
|
|
<div class="warning-footer">
|
|
|
<div class="warning-time">
|
|
|
<i class="fas fa-clock"></i>
|
|
|
<span>${warning.time}</span>
|
|
|
</div>
|
|
|
<div class="warning-actions">
|
|
|
<button class="btn btn-primary" onclick="viewWarning('${warning.id}')">查看</button>
|
|
|
${warning.status === 'active' ? `
|
|
|
<button class="btn btn-success" onclick="processWarning('${warning.id}')">处理</button>
|
|
|
<button class="btn btn-danger" onclick="resolveWarning('${warning.id}')">解除</button>
|
|
|
` : warning.status === 'processing' ? `
|
|
|
<button class="btn btn-warning" disabled>处理中</button>
|
|
|
` : `
|
|
|
<button class="btn btn-secondary" disabled>已解决</button>
|
|
|
`}
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
// 模态框操作
|
|
|
function openNewAlertModal() {
|
|
|
document.getElementById('newAlertModal').style.display = 'block';
|
|
|
}
|
|
|
|
|
|
function closeNewAlertModal() {
|
|
|
document.getElementById('newAlertModal').style.display = 'none';
|
|
|
}
|
|
|
|
|
|
// 新增变量存储当前查看的预警ID
|
|
|
let currentAlertId = null;
|
|
|
|
|
|
// 查看预警详情
|
|
|
function viewWarning(id) {
|
|
|
currentAlertId = id;
|
|
|
const warning = warnings.find(w => w.id === id);
|
|
|
if (!warning) return;
|
|
|
|
|
|
// 填充模态框内容
|
|
|
document.getElementById('alertDetailTitle').textContent = warning.title;
|
|
|
document.getElementById('alertDetailId').textContent = warning.id;
|
|
|
document.getElementById('alertDetailType').textContent = typeMap[warning.type];
|
|
|
document.getElementById('alertDetailLocation').textContent = warning.location;
|
|
|
document.getElementById('alertDetailTime').textContent = warning.time;
|
|
|
document.getElementById('alertDetailDescription').textContent = warning.description;
|
|
|
|
|
|
// 设置预警级别样式
|
|
|
const levelElement = document.getElementById('alertDetailLevel');
|
|
|
levelElement.textContent = levelMap[warning.level].text;
|
|
|
levelElement.className = 'alert-detail-level ' + levelMap[warning.level].class.replace('warning-', 'alert-detail-');
|
|
|
|
|
|
// 设置状态
|
|
|
document.getElementById('alertDetailStatus').textContent = warning.status === 'active' ? '未处理' : '已处理';
|
|
|
|
|
|
// 显示模态框
|
|
|
document.getElementById('alertDetailModal').style.display = 'block';
|
|
|
}
|
|
|
|
|
|
// 关闭预警详情模态框
|
|
|
function closeAlertDetailModal() {
|
|
|
document.getElementById('alertDetailModal').style.display = 'none';
|
|
|
currentAlertId = null;
|
|
|
}
|
|
|
|
|
|
// 处理当前查看的预警
|
|
|
function processCurrentAlert() {
|
|
|
if (currentAlertId) {
|
|
|
processWarning(currentAlertId);
|
|
|
closeAlertDetailModal();
|
|
|
}
|
|
|
}
|
|
|
// 修改processWarning和resolveWarning函数,使其更新预警状态
|
|
|
// function processWarning(id) {
|
|
|
// const warning = warnings.find(w => w.id === id);
|
|
|
// if (warning) {
|
|
|
// warning.status = 'processing';
|
|
|
// alert(`正在处理预警: ${id}`);
|
|
|
// loadWarnings(); // 刷新列表
|
|
|
// }
|
|
|
// }
|
|
|
// 存储当前处理的预警ID
|
|
|
let processingAlertId = null;
|
|
|
|
|
|
// 打开处理预警模态框
|
|
|
function openProcessAlertModal(id) {
|
|
|
processingAlertId = id;
|
|
|
const warning = warnings.find(w => w.id === id);
|
|
|
if (!warning) return;
|
|
|
|
|
|
// 填充模态框内容
|
|
|
document.getElementById('processAlertId').textContent = warning.id;
|
|
|
document.getElementById('processAlertTitle').textContent = warning.title;
|
|
|
|
|
|
// 显示模态框
|
|
|
document.getElementById('processAlertModal').style.display = 'block';
|
|
|
}
|
|
|
|
|
|
// 关闭处理预警模态框
|
|
|
function closeProcessAlertModal() {
|
|
|
document.getElementById('processAlertModal').style.display = 'none';
|
|
|
processingAlertId = null;
|
|
|
}
|
|
|
|
|
|
// 确认处理预警
|
|
|
function confirmProcessAlert() {
|
|
|
if (!processingAlertId) return;
|
|
|
|
|
|
const assignTo = document.getElementById('assignToSingle').value;
|
|
|
const priority = document.getElementById('prioritySingle').value;
|
|
|
const note = document.getElementById('processNote').value;
|
|
|
|
|
|
const warning = warnings.find(w => w.id === processingAlertId);
|
|
|
if (warning) {
|
|
|
warning.status = 'processing';
|
|
|
warning.assignedTo = assignTo;
|
|
|
warning.priority = priority;
|
|
|
|
|
|
// 添加处理备注
|
|
|
if (note) {
|
|
|
if (!warning.notes) warning.notes = [];
|
|
|
warning.notes.push({
|
|
|
time: new Date().toLocaleString(),
|
|
|
action: 'assigned',
|
|
|
note: note
|
|
|
});
|
|
|
}
|
|
|
|
|
|
alert(`预警 ${warning.id} 已分配给 ${assignTo},优先级设为 ${priority}`);
|
|
|
loadWarnings(); // 刷新列表
|
|
|
}
|
|
|
|
|
|
closeProcessAlertModal();
|
|
|
}
|
|
|
|
|
|
// 修改processWarning函数,改为打开处理模态框
|
|
|
function processWarning(id) {
|
|
|
openProcessAlertModal(id);
|
|
|
}
|
|
|
|
|
|
// 修改processCurrentAlert函数
|
|
|
function processCurrentAlert() {
|
|
|
if (currentAlertId) {
|
|
|
openProcessAlertModal(currentAlertId);
|
|
|
closeAlertDetailModal();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function resolveWarning(id) {
|
|
|
const warning = warnings.find(w => w.id === id);
|
|
|
if (warning) {
|
|
|
warning.status = 'resolved';
|
|
|
alert(`已解除预警: ${id}`);
|
|
|
loadWarnings(); // 刷新列表
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 解除当前查看的预警
|
|
|
function resolveCurrentAlert() {
|
|
|
if (currentAlertId) {
|
|
|
resolveWarning(currentAlertId);
|
|
|
closeAlertDetailModal();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function exportAlerts() {
|
|
|
alert('导出预警报表');
|
|
|
}
|
|
|
|
|
|
// 批量处理功能
|
|
|
function batchProcess() {
|
|
|
// 显示模态框
|
|
|
document.getElementById('batchProcessModal').style.display = 'block';
|
|
|
|
|
|
// 加载预警列表
|
|
|
loadBatchAlertsList();
|
|
|
|
|
|
// 监听操作类型变化
|
|
|
document.getElementById('batchAction').addEventListener('change', function () {
|
|
|
const action = this.value;
|
|
|
document.getElementById('assignOption').style.display = action === 'assign' ? 'block' : 'none';
|
|
|
document.getElementById('priorityOption').style.display = action === 'priority' ? 'block' : 'none';
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 关闭批量处理模态框
|
|
|
function closeBatchProcessModal() {
|
|
|
document.getElementById('batchProcessModal').style.display = 'none';
|
|
|
}
|
|
|
|
|
|
// 加载批量处理预警列表
|
|
|
function loadBatchAlertsList() {
|
|
|
const container = document.getElementById('batchAlertsList');
|
|
|
|
|
|
container.innerHTML = warnings.map(warning => `
|
|
|
<div class="batch-alert-item">
|
|
|
<input type="checkbox" class="batch-alert-checkbox" id="batch-${warning.id}" checked>
|
|
|
<div class="batch-alert-info">
|
|
|
<div class="batch-alert-title">${warning.title}</div>
|
|
|
<div class="batch-alert-meta">
|
|
|
<span class="batch-alert-location">📍 ${warning.location}</span>
|
|
|
<span class="batch-alert-time">🕒 ${warning.time}</span>
|
|
|
</div>
|
|
|
</div>
|
|
|
<div class="warning-level ${levelMap[warning.level].class}">${levelMap[warning.level].text}</div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
// 确认批量处理
|
|
|
function confirmBatchProcess() {
|
|
|
const action = document.getElementById('batchAction').value;
|
|
|
const note = document.getElementById('batchNote').value;
|
|
|
const selectedAlerts = [];
|
|
|
|
|
|
// 获取选中的预警
|
|
|
warnings.forEach(warning => {
|
|
|
const checkbox = document.getElementById(`batch-${warning.id}`);
|
|
|
if (checkbox && checkbox.checked) {
|
|
|
selectedAlerts.push(warning.id);
|
|
|
}
|
|
|
});
|
|
|
|
|
|
if (selectedAlerts.length === 0) {
|
|
|
alert('请至少选择一个预警进行处理!');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 根据操作类型处理预警
|
|
|
switch (action) {
|
|
|
case 'process':
|
|
|
selectedAlerts.forEach(id => {
|
|
|
const warning = warnings.find(w => w.id === id);
|
|
|
if (warning) warning.status = 'processing';
|
|
|
});
|
|
|
alert(`已将 ${selectedAlerts.length} 个预警标记为处理中`);
|
|
|
break;
|
|
|
|
|
|
case 'resolve':
|
|
|
selectedAlerts.forEach(id => {
|
|
|
const warning = warnings.find(w => w.id === id);
|
|
|
if (warning) warning.status = 'resolved';
|
|
|
});
|
|
|
alert(`已将 ${selectedAlerts.length} 个预警标记为已解决`);
|
|
|
break;
|
|
|
|
|
|
case 'assign':
|
|
|
const assignTo = document.getElementById('assignTo').value;
|
|
|
selectedAlerts.forEach(id => {
|
|
|
const warning = warnings.find(w => w.id === id);
|
|
|
if (warning) warning.assignedTo = assignTo;
|
|
|
});
|
|
|
alert(`已将 ${selectedAlerts.length} 个预警分配给 ${assignTo}`);
|
|
|
break;
|
|
|
|
|
|
case 'priority':
|
|
|
const priority = document.getElementById('priorityLevel').value;
|
|
|
selectedAlerts.forEach(id => {
|
|
|
const warning = warnings.find(w => w.id === id);
|
|
|
if (warning) warning.priority = priority;
|
|
|
});
|
|
|
alert(`已将 ${selectedAlerts.length} 个预警优先级调整为 ${priority}`);
|
|
|
break;
|
|
|
}
|
|
|
|
|
|
// 添加处理备注
|
|
|
if (note) {
|
|
|
selectedAlerts.forEach(id => {
|
|
|
const warning = warnings.find(w => w.id === id);
|
|
|
if (warning) {
|
|
|
if (!warning.notes) warning.notes = [];
|
|
|
warning.notes.push({
|
|
|
time: new Date().toLocaleString(),
|
|
|
action: action,
|
|
|
note: note
|
|
|
});
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
// 刷新预警列表
|
|
|
loadWarnings();
|
|
|
|
|
|
// 关闭模态框
|
|
|
closeBatchProcessModal();
|
|
|
}
|
|
|
|
|
|
|
|
|
// 实时监控功能
|
|
|
let autoRefreshEnabled = false;
|
|
|
let refreshInterval;
|
|
|
|
|
|
function refreshMonitor() {
|
|
|
loadRealTimeAlerts();
|
|
|
loadMonitoringPoints();
|
|
|
loadDataStream();
|
|
|
updateConnectionStatus();
|
|
|
}
|
|
|
|
|
|
function toggleAutoRefresh() {
|
|
|
autoRefreshEnabled = !autoRefreshEnabled;
|
|
|
const btn = event.target;
|
|
|
if (autoRefreshEnabled) {
|
|
|
btn.textContent = '⏸️ 停止刷新';
|
|
|
refreshInterval = setInterval(refreshMonitor, 5000);
|
|
|
} else {
|
|
|
btn.textContent = '⏱️ 自动刷新';
|
|
|
clearInterval(refreshInterval);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function loadRealTimeAlerts() {
|
|
|
const container = document.getElementById('realTimeAlerts');
|
|
|
const alerts = [
|
|
|
{ time: '14:32:15', message: '湘江橘子洲段水质pH值异常', level: 'critical' },
|
|
|
{ time: '14:30:42', message: '监测点#3通信中断', level: 'warning' },
|
|
|
{ time: '14:28:33', message: '水位上涨趋势预警', level: 'info' },
|
|
|
{ time: '14:25:18', message: '设备#7离线超时', level: 'warning' }
|
|
|
];
|
|
|
|
|
|
container.innerHTML = alerts.map(alert => `
|
|
|
<div class="real-time-alert">
|
|
|
<strong>${alert.time}</strong> - ${alert.message}
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
function loadMonitoringPoints() {
|
|
|
const container = document.getElementById('monitoringPoints');
|
|
|
const points = [
|
|
|
{ name: '橘子洲监测点', status: 'online' },
|
|
|
{ name: '猴子石监测点', status: 'online' },
|
|
|
{ name: '风光带监测点', status: 'offline' },
|
|
|
{ name: '大桥监测点', status: 'online' },
|
|
|
{ name: '码头监测点', status: 'online' }
|
|
|
];
|
|
|
|
|
|
container.innerHTML = points.map(point => `
|
|
|
<div class="monitoring-point">
|
|
|
<span>${point.name}</span>
|
|
|
<span class="point-status status-${point.status}">
|
|
|
${point.status === 'online' ? '在线' : '离线'}
|
|
|
</span>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
function loadDataStream() {
|
|
|
const container = document.getElementById('dataStream');
|
|
|
const now = new Date();
|
|
|
const entries = [];
|
|
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
const time = new Date(now.getTime() - i * 30000);
|
|
|
entries.push(`
|
|
|
<div class="data-entry">
|
|
|
${time.toLocaleTimeString()} - pH:7.2 DO:6.8mg/L 浊度:15NTU 水位:27.8m
|
|
|
</div>
|
|
|
`);
|
|
|
}
|
|
|
|
|
|
container.innerHTML = entries.join('');
|
|
|
}
|
|
|
|
|
|
function updateConnectionStatus() {
|
|
|
const status = document.getElementById('connectionStatus');
|
|
|
const isConnected = Math.random() > 0.1; // 90%概率连接正常
|
|
|
|
|
|
if (isConnected) {
|
|
|
status.textContent = '🟢 连接正常';
|
|
|
status.style.background = 'rgba(40, 167, 69, 0.1)';
|
|
|
status.style.borderColor = '#28a745';
|
|
|
} else {
|
|
|
status.textContent = '🔴 连接异常';
|
|
|
status.style.background = 'rgba(220, 53, 69, 0.1)';
|
|
|
status.style.borderColor = '#dc3545';
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 时间范围选择
|
|
|
function selectTimeRange(range) {
|
|
|
document.querySelectorAll('.time-range-selector .filter-btn').forEach(btn => {
|
|
|
btn.classList.remove('active');
|
|
|
});
|
|
|
event.target.classList.add('active');
|
|
|
|
|
|
// 根据选择的时间范围更新图表数据
|
|
|
console.log('选择时间范围:', range);
|
|
|
}
|
|
|
|
|
|
// 设置功能
|
|
|
function saveSettings() {
|
|
|
// 收集所有设置数据
|
|
|
const settings = {
|
|
|
thresholds: {
|
|
|
ph: {
|
|
|
min: document.querySelector('.threshold-settings input[value="6.5"]').value,
|
|
|
max: document.querySelector('.threshold-settings input[value="8.5"]').value
|
|
|
}
|
|
|
},
|
|
|
notifications: {
|
|
|
email: document.querySelector('input[type="email"]').value,
|
|
|
phone: document.querySelector('input[type="tel"]').value
|
|
|
},
|
|
|
refreshInterval: document.querySelector('.setting-select').value
|
|
|
};
|
|
|
|
|
|
console.log('保存设置:', settings);
|
|
|
alert('设置已保存!');
|
|
|
}
|
|
|
|
|
|
function resetSettings() {
|
|
|
if (confirm('确定要重置为默认设置吗?')) {
|
|
|
// 重置所有表单值
|
|
|
document.querySelectorAll('.threshold-input').forEach(input => {
|
|
|
input.value = input.defaultValue;
|
|
|
});
|
|
|
alert('设置已重置为默认值!');
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function exportSettings() {
|
|
|
const settings = {
|
|
|
timestamp: new Date().toISOString(),
|
|
|
thresholds: {},
|
|
|
notifications: {},
|
|
|
rules: {},
|
|
|
system: {},
|
|
|
models: {
|
|
|
waterQuality: document.getElementById('waterQualityModel').value,
|
|
|
waterLevel: document.getElementById('waterLevelModel').value,
|
|
|
anomaly: document.getElementById('anomalyModel').value,
|
|
|
pollution: document.getElementById('pollutionModel').value
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const blob = new Blob([JSON.stringify(settings, null, 2)], {
|
|
|
type: 'application/json'
|
|
|
});
|
|
|
|
|
|
const url = URL.createObjectURL(blob);
|
|
|
const a = document.createElement('a');
|
|
|
a.href = url;
|
|
|
a.download = 'warning-settings.json';
|
|
|
a.click();
|
|
|
URL.revokeObjectURL(url);
|
|
|
}
|
|
|
|
|
|
// 算法模型相关功能
|
|
|
const modelDescriptions = {
|
|
|
waterQuality: {
|
|
|
traditional: '🔧 传统阈值模型:基于固定阈值判断,响应快速,适用于简单场景',
|
|
|
ml_regression: '🎯 机器学习回归模型:基于历史数据训练,预测准确率85%,适用于多参数水质预警',
|
|
|
lstm: '🧠 LSTM时序预测模型:深度学习时间序列模型,能捕捉长期依赖关系,准确率90%',
|
|
|
ensemble: '🎭 集成学习模型:多算法融合,综合准确率最高,计算复杂度较高'
|
|
|
},
|
|
|
waterLevel: {
|
|
|
traditional: '🔧 传统阈值模型:基于固定水位线预警,简单可靠',
|
|
|
arima: '📈 ARIMA时间序列模型:统计学方法,适用于平稳时间序列预测',
|
|
|
neural_network: '🧠 神经网络模型:深度学习算法,适用于复杂水位变化预测,准确率92%',
|
|
|
hybrid: '🔄 混合预测模型:结合多种算法优势,预测精度高,鲁棒性强'
|
|
|
},
|
|
|
anomaly: {
|
|
|
statistical: '📊 统计学方法:基于统计分布检测异常,计算简单,适用于正态分布数据',
|
|
|
isolation_forest: '🌲 孤立森林算法:无监督学习,擅长检测数据异常点,误报率低于5%',
|
|
|
autoencoder: '🔄 自编码器:神经网络重构误差检测异常,适用于高维数据',
|
|
|
one_class_svm: '🎯 单类支持向量机:边界检测方法,适用于小样本异常检测'
|
|
|
},
|
|
|
pollution: {
|
|
|
rule_based: '📋 规则引擎:基于专家知识的规则系统,可解释性强',
|
|
|
random_forest: '🌳 随机森林:集成学习算法,多特征融合识别污染事件,F1分数89%',
|
|
|
cnn: '🖼️ 卷积神经网络:适用于图像和空间数据的污染检测',
|
|
|
transformer: '🔄 Transformer模型:注意力机制,处理复杂时空关系,准确率95%'
|
|
|
}
|
|
|
};
|
|
|
|
|
|
function updateModelDescription(modelType, value) {
|
|
|
const descElement = document.getElementById(modelType + 'Desc');
|
|
|
if (descElement && modelDescriptions[modelType] && modelDescriptions[modelType][value]) {
|
|
|
descElement.innerHTML = `<small>${modelDescriptions[modelType][value]}</small>`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function retrainModels() {
|
|
|
if (confirm('确定要重新训练所有模型吗?这个过程可能需要几分钟时间。')) {
|
|
|
// 模拟训练过程
|
|
|
const statusElement = document.querySelector('.model-status span:nth-child(2)');
|
|
|
const originalText = statusElement.textContent;
|
|
|
|
|
|
statusElement.textContent = '正在训练模型...';
|
|
|
statusElement.style.color = '#ffc107';
|
|
|
|
|
|
// 模拟训练进度
|
|
|
let progress = 0;
|
|
|
const interval = setInterval(() => {
|
|
|
progress += 10;
|
|
|
statusElement.textContent = `训练进度: ${progress}%`;
|
|
|
|
|
|
if (progress >= 100) {
|
|
|
clearInterval(interval);
|
|
|
statusElement.textContent = '模型训练完成!';
|
|
|
statusElement.style.color = '#28a745';
|
|
|
|
|
|
// 更新性能指标
|
|
|
updateModelMetrics();
|
|
|
|
|
|
setTimeout(() => {
|
|
|
statusElement.textContent = originalText;
|
|
|
statusElement.style.color = '';
|
|
|
}, 3000);
|
|
|
}
|
|
|
}, 500);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function updateModelMetrics() {
|
|
|
// 模拟更新性能指标
|
|
|
const metrics = document.querySelectorAll('.metric-value');
|
|
|
const newValues = ['89.2%', '94.1%', '91.5%', '< 80ms'];
|
|
|
|
|
|
metrics.forEach((metric, index) => {
|
|
|
if (newValues[index]) {
|
|
|
metric.textContent = newValues[index];
|
|
|
metric.style.animation = 'pulse 1s ease-in-out';
|
|
|
}
|
|
|
});
|
|
|
}
|
|
|
|
|
|
function viewModelLogs() {
|
|
|
// 模拟查看模型日志
|
|
|
const logData = {
|
|
|
timestamp: new Date().toISOString(),
|
|
|
models: {
|
|
|
waterQuality: { status: 'running', accuracy: 0.875, lastTrained: '2024-01-15' },
|
|
|
waterLevel: { status: 'running', accuracy: 0.923, lastTrained: '2024-01-14' },
|
|
|
anomaly: { status: 'running', accuracy: 0.898, lastTrained: '2024-01-16' },
|
|
|
pollution: { status: 'running', accuracy: 0.915, lastTrained: '2024-01-15' }
|
|
|
}
|
|
|
};
|
|
|
|
|
|
console.log('模型运行日志:', logData);
|
|
|
alert('模型日志已输出到控制台,请按F12查看详细信息。');
|
|
|
}
|
|
|
|
|
|
// 初始化图表
|
|
|
function initializeCharts() {
|
|
|
// 预警级别分布图表
|
|
|
const levelCtx = document.getElementById('levelChart').getContext('2d');
|
|
|
new Chart(levelCtx, {
|
|
|
type: 'doughnut',
|
|
|
data: {
|
|
|
labels: ['严重预警', '一般预警', '提示信息'],
|
|
|
datasets: [{
|
|
|
data: [8, 23, 14], // 使用页面中的统计数据
|
|
|
backgroundColor: [
|
|
|
'rgba(220, 53, 69, 0.8)',
|
|
|
'rgba(255, 193, 7, 0.8)',
|
|
|
'rgba(23, 162, 184, 0.8)'
|
|
|
],
|
|
|
borderColor: [
|
|
|
'rgba(220, 53, 69, 1)',
|
|
|
'rgba(255, 193, 7, 1)',
|
|
|
'rgba(23, 162, 184, 1)'
|
|
|
],
|
|
|
borderWidth: 1
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
position: 'bottom',
|
|
|
labels: {
|
|
|
color: '#1a4b8c',
|
|
|
font: {
|
|
|
weight: 'bold'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 本周预警趋势图表
|
|
|
const trendCtx = document.getElementById('trendChart').getContext('2d');
|
|
|
new Chart(trendCtx, {
|
|
|
type: 'line',
|
|
|
data: {
|
|
|
labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
|
|
datasets: [{
|
|
|
label: '预警数量',
|
|
|
data: [5, 8, 6, 10, 7, 4, 3], // 模拟数据
|
|
|
backgroundColor: 'rgba(42, 125, 225, 0.1)',
|
|
|
borderColor: '#2a7de1',
|
|
|
borderWidth: 2,
|
|
|
tension: 0.4,
|
|
|
fill: true
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
display: false
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
grid: {
|
|
|
color: 'rgba(209, 224, 245, 0.5)'
|
|
|
},
|
|
|
ticks: {
|
|
|
color: '#4a6fa5'
|
|
|
}
|
|
|
},
|
|
|
y: {
|
|
|
beginAtZero: true,
|
|
|
grid: {
|
|
|
color: 'rgba(209, 224, 245, 0.5)'
|
|
|
},
|
|
|
ticks: {
|
|
|
color: '#4a6fa5'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// 预警类型分析图表
|
|
|
const typeCtx = document.getElementById('typeChart').getContext('2d');
|
|
|
new Chart(typeCtx, {
|
|
|
type: 'bar',
|
|
|
data: {
|
|
|
labels: ['水质异常', '水位异常', '设备故障', '污染事件'],
|
|
|
datasets: [{
|
|
|
label: '预警数量',
|
|
|
data: [12, 18, 8, 7], // 模拟数据
|
|
|
backgroundColor: [
|
|
|
'rgba(42, 125, 225, 0.7)',
|
|
|
'rgba(26, 75, 140, 0.7)',
|
|
|
'rgba(108, 117, 125, 0.7)',
|
|
|
'rgba(40, 167, 69, 0.7)'
|
|
|
],
|
|
|
borderColor: [
|
|
|
'rgba(42, 125, 225, 1)',
|
|
|
'rgba(26, 75, 140, 1)',
|
|
|
'rgba(108, 117, 125, 1)',
|
|
|
'rgba(40, 167, 69, 1)'
|
|
|
],
|
|
|
borderWidth: 1
|
|
|
}]
|
|
|
},
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
display: false
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
grid: {
|
|
|
display: false
|
|
|
},
|
|
|
ticks: {
|
|
|
color: '#4a6fa5'
|
|
|
}
|
|
|
},
|
|
|
y: {
|
|
|
beginAtZero: true,
|
|
|
grid: {
|
|
|
color: 'rgba(209, 224, 245, 0.5)'
|
|
|
},
|
|
|
ticks: {
|
|
|
color: '#4a6fa5'
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
});
|
|
|
|
|
|
// // 预警趋势图
|
|
|
// const trendCtx2 = document.getElementById('warningTrendChart').getContext('2d');
|
|
|
// new Chart(trendCtx2, {
|
|
|
// type: 'line',
|
|
|
// data: {
|
|
|
// labels: ['1月', '2月', '3月', '4月', '5月', '6月'],
|
|
|
// datasets: [{
|
|
|
// label: '严重预警',
|
|
|
// data: [12, 8, 15, 10, 18, 14],
|
|
|
// borderColor: '#dc3545',
|
|
|
// backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
|
|
// tension: 0.4
|
|
|
// }, {
|
|
|
// label: '一般预警',
|
|
|
// data: [25, 32, 28, 35, 30, 38],
|
|
|
// borderColor: '#ffc107',
|
|
|
// backgroundColor: 'rgba(255, 193, 7, 0.1)',
|
|
|
// tension: 0.4
|
|
|
// }, {
|
|
|
// label: '提示信息',
|
|
|
// data: [45, 52, 48, 55, 50, 58],
|
|
|
// borderColor: '#17a2b8',
|
|
|
// backgroundColor: 'rgba(23, 162, 184, 0.1)',
|
|
|
// tension: 0.4
|
|
|
// }]
|
|
|
// },
|
|
|
// options: {
|
|
|
// responsive: true,
|
|
|
// maintainAspectRatio: false,
|
|
|
// plugins: {
|
|
|
// legend: {
|
|
|
// labels: { color: '#1a4b8c' }
|
|
|
// }
|
|
|
// },
|
|
|
// scales: {
|
|
|
// x: {
|
|
|
// ticks: { color: '#1a4b8c' },
|
|
|
// grid: { color: 'rgba(209, 224, 245, 0.5)' }
|
|
|
// },
|
|
|
// y: {
|
|
|
// ticks: { color: '#1a4b8c' },
|
|
|
// grid: { color: 'rgba(209, 224, 245, 0.5)' }
|
|
|
// }
|
|
|
// }
|
|
|
// }
|
|
|
// });
|
|
|
|
|
|
// 预警类型分布图
|
|
|
// const typeCtx2 = document.getElementById('warningTypeDistChart').getContext('2d');
|
|
|
// typeChart = new Chart(typeCtx2, {
|
|
|
// type: 'doughnut',
|
|
|
// data: {
|
|
|
// labels: ['水质异常', '水位异常', '设备故障', '污染事件'],
|
|
|
// datasets: [{
|
|
|
// data: [35, 25, 30, 10],
|
|
|
// backgroundColor: [
|
|
|
// 'rgba(220, 53, 69, 0.8)',
|
|
|
// 'rgba(255, 193, 7, 0.8)',
|
|
|
// 'rgba(23, 162, 184, 0.8)',
|
|
|
// 'rgba(40, 167, 69, 0.8)'
|
|
|
// ],
|
|
|
// borderColor: '#ffffff',
|
|
|
// borderWidth: 1
|
|
|
// }]
|
|
|
// },
|
|
|
// options: {
|
|
|
// responsive: true,
|
|
|
// maintainAspectRatio: false,
|
|
|
// plugins: {
|
|
|
// legend: {
|
|
|
// labels: { color: '#1a4b8c' }
|
|
|
// }
|
|
|
// }
|
|
|
// }
|
|
|
// });
|
|
|
|
|
|
// // 响应时间分析图
|
|
|
// const responseCtx = document.getElementById('responseTimeChart').getContext('2d');
|
|
|
// new Chart(responseCtx, {
|
|
|
// type: 'bar',
|
|
|
// data: {
|
|
|
// labels: ['<5分钟', '5-15分钟', '15-30分钟', '30-60分钟', '>60分钟'],
|
|
|
// datasets: [{
|
|
|
// label: '预警数量',
|
|
|
// data: [45, 32, 18, 8, 3],
|
|
|
// backgroundColor: 'rgba(42, 125, 225, 0.6)',
|
|
|
// borderColor: '#2a7de1',
|
|
|
// borderWidth: 1
|
|
|
// }]
|
|
|
// },
|
|
|
// options: {
|
|
|
// responsive: true,
|
|
|
// maintainAspectRatio: false,
|
|
|
// plugins: {
|
|
|
// legend: {
|
|
|
// labels: { color: '#1a4b8c' }
|
|
|
// }
|
|
|
// },
|
|
|
// scales: {
|
|
|
// x: {
|
|
|
// ticks: { color: '#1a4b8c' },
|
|
|
// grid: { color: 'rgba(209, 224, 245, 0.5)' }
|
|
|
// },
|
|
|
// y: {
|
|
|
// ticks: { color: '#1a4b8c' },
|
|
|
// grid: { color: 'rgba(209, 224, 245, 0.5)' }
|
|
|
// }
|
|
|
// }
|
|
|
// }
|
|
|
// });
|
|
|
}
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
|
loadWarnings();
|
|
|
initializeCharts();
|
|
|
refreshMonitor();
|
|
|
|
|
|
// 时间范围选择器事件
|
|
|
document.addEventListener('click', function (e) {
|
|
|
if (e.target.matches('.time-range-selector .filter-btn')) {
|
|
|
selectTimeRange(e.target.dataset.range);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
// 点击模态框外部关闭
|
|
|
window.onclick = function (event) {
|
|
|
const modal = document.getElementById('newAlertModal');
|
|
|
if (event.target === modal) {
|
|
|
closeNewAlertModal();
|
|
|
}
|
|
|
|
|
|
const detailModal = document.getElementById('alertDetailModal');
|
|
|
if (event.target === detailModal) {
|
|
|
closeAlertDetailModal();
|
|
|
}
|
|
|
|
|
|
const batchModal = document.getElementById('batchProcessModal');
|
|
|
if (event.target === batchModal) {
|
|
|
closeBatchProcessModal();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 存储图表实例
|
|
|
let trendChart, typeChart, responseTimeChart;
|
|
|
// 初始化图表
|
|
|
function initCharts() {
|
|
|
// 预警趋势图
|
|
|
const trendCtx = document.getElementById('warningTrendChart').getContext('2d');
|
|
|
trendChart = new Chart(trendCtx, {
|
|
|
type: 'line',
|
|
|
data: getTrendData('30d'), // 默认显示30天数据
|
|
|
options: getChartOptions('预警趋势')
|
|
|
});
|
|
|
|
|
|
// 预警类型分布
|
|
|
const typeCtx = document.getElementById('warningTypeDistChart').getContext('2d');
|
|
|
typeChart = new Chart(typeCtx, {
|
|
|
type: 'doughnut',
|
|
|
data: getTypeData('30d'),
|
|
|
options: getChartOptions2('类型分布')
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 响应时间分析
|
|
|
const responseCtx = document.getElementById('responseTimeChart').getContext('2d');
|
|
|
responseTimeChart = new Chart(responseCtx, {
|
|
|
type: 'bar',
|
|
|
data: getResponseTimeData('30d'),
|
|
|
options: getChartOptions('响应时间(分钟)')
|
|
|
});
|
|
|
}
|
|
|
// 更新所有分析数据
|
|
|
function updateAnalysisData(range) {
|
|
|
// 更新按钮状态
|
|
|
document.querySelectorAll('.time-range-selector .filter-btn').forEach(btn => {
|
|
|
btn.classList.remove('active');
|
|
|
if (btn.dataset.range === range) btn.classList.add('active');
|
|
|
});
|
|
|
|
|
|
// 更新图表数据
|
|
|
trendChart.data = getTrendData(range);
|
|
|
trendChart.update();
|
|
|
|
|
|
typeChart.data = getTypeData(range);
|
|
|
typeChart.update();
|
|
|
|
|
|
responseTimeChart.data = getResponseTimeData(range);
|
|
|
responseTimeChart.update();
|
|
|
|
|
|
// 更新热点区域数据
|
|
|
updateHotspotData(range);
|
|
|
|
|
|
// 更新统计摘要
|
|
|
updateSummaryStats(range);
|
|
|
}
|
|
|
|
|
|
// 模拟数据获取函数
|
|
|
function getTrendData(range) {
|
|
|
// 根据范围返回不同的数据集
|
|
|
const dataMap = {
|
|
|
'7d': {
|
|
|
labels: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
|
|
|
datasets: [
|
|
|
{ label: '严重预警', data: [3, 5, 2, 4, 6, 1, 2], borderColor: '#dc3545', backgroundColor: 'rgba(220, 53, 69, 0.1)', tension: 0.4 },
|
|
|
{ label: '一般预警', data: [8, 12, 9, 11, 15, 7, 10], borderColor: '#ffc107', backgroundColor: 'rgba(255, 193, 7, 0.1)', tension: 0.4 },
|
|
|
{ label: '提示信息', data: [15, 18, 14, 20, 22, 12, 16], borderColor: '#17a2b8', backgroundColor: 'rgba(23, 162, 184, 0.1)', tension: 0.4 }
|
|
|
]
|
|
|
},
|
|
|
'30d': {
|
|
|
labels: ['第1周', '第2周', '第3周', '第4周'],
|
|
|
datasets: [
|
|
|
{ label: '严重预警', data: [12, 8, 15, 10], borderColor: '#dc3545', backgroundColor: 'rgba(220, 53, 69, 0.1)', tension: 0.4 },
|
|
|
{ label: '一般预警', data: [25, 32, 28, 35], borderColor: '#ffc107', backgroundColor: 'rgba(255, 193, 7, 0.1)', tension: 0.4 },
|
|
|
{ label: '提示信息', data: [45, 52, 48, 55], borderColor: '#17a2b8', backgroundColor: 'rgba(23, 162, 184, 0.1)', tension: 0.4 }
|
|
|
]
|
|
|
},
|
|
|
'90d': {
|
|
|
labels: ['1月', '2月', '3月'],
|
|
|
datasets: [
|
|
|
{ label: '严重预警', data: [42, 38, 45], borderColor: '#dc3545', backgroundColor: 'rgba(220, 53, 69, 0.1)', tension: 0.4 },
|
|
|
{ label: '一般预警', data: [125, 132, 128], borderColor: '#ffc107', backgroundColor: 'rgba(255, 193, 7, 0.1)', tension: 0.4 },
|
|
|
{ label: '提示信息', data: [145, 152, 148], borderColor: '#17a2b8', backgroundColor: 'rgba(23, 162, 184, 0.1)', tension: 0.4 }
|
|
|
]
|
|
|
},
|
|
|
'1y': {
|
|
|
labels: ['Q1', 'Q2', 'Q3', 'Q4'],
|
|
|
datasets: [
|
|
|
{ label: '严重预警', data: [120, 85, 95, 110], borderColor: '#dc3545', backgroundColor: 'rgba(220, 53, 69, 0.1)', tension: 0.4 },
|
|
|
{ label: '一般预警', data: [325, 312, 328, 335], borderColor: '#ffc107', backgroundColor: 'rgba(255, 193, 7, 0.1)', tension: 0.4 },
|
|
|
{ label: '提示信息', data: [445, 452, 448, 455], borderColor: '#17a2b8', backgroundColor: 'rgba(23, 162, 184, 0.1)', tension: 0.4 }
|
|
|
]
|
|
|
}
|
|
|
};
|
|
|
return dataMap[range] || dataMap['30d'];
|
|
|
}
|
|
|
|
|
|
function getTypeData(range) {
|
|
|
const dataMap = {
|
|
|
'7d': {
|
|
|
labels: ['水质异常', '水位异常', '设备故障', '污染事件'],
|
|
|
datasets: [{
|
|
|
data: [15, 25, 20, 5],
|
|
|
backgroundColor: ['#dc3545', '#ffc107', '#17a2b8', '#28a745']
|
|
|
}]
|
|
|
},
|
|
|
'30d': {
|
|
|
labels: ['水质异常', '水位异常', '设备故障', '污染事件'],
|
|
|
datasets: [{
|
|
|
data: [35, 25, 30, 10],
|
|
|
backgroundColor: ['#dc3545', '#ffc107', '#17a2b8', '#28a745']
|
|
|
}]
|
|
|
},
|
|
|
'90d': {
|
|
|
labels: ['水质异常', '水位异常', '设备故障', '污染事件'],
|
|
|
datasets: [{
|
|
|
data: [105, 75, 90, 30],
|
|
|
backgroundColor: ['#dc3545', '#ffc107', '#17a2b8', '#28a745']
|
|
|
}]
|
|
|
},
|
|
|
'1y': {
|
|
|
labels: ['水质异常', '水位异常', '设备故障', '污染事件'],
|
|
|
datasets: [{
|
|
|
data: [420, 300, 360, 120],
|
|
|
backgroundColor: ['#dc3545', '#ffc107', '#17a2b8', '#28a745']
|
|
|
}]
|
|
|
}
|
|
|
};
|
|
|
return dataMap[range] || dataMap['30d'];
|
|
|
}
|
|
|
|
|
|
function getResponseTimeData(range) {
|
|
|
const dataMap = {
|
|
|
'7d': {
|
|
|
labels: ['<5分钟', '5-15分钟', '15-30分钟', '30-60分钟', '>60分钟'],
|
|
|
datasets: [{
|
|
|
label: '响应数量',
|
|
|
data: [15, 22, 18, 8, 3],
|
|
|
backgroundColor: 'rgba(42, 125, 225, 0.6)'
|
|
|
}]
|
|
|
},
|
|
|
'30d': {
|
|
|
labels: ['<5分钟', '5-15分钟', '15-30分钟', '30-60分钟', '>60分钟'],
|
|
|
datasets: [{
|
|
|
label: '响应数量',
|
|
|
data: [45, 32, 18, 8, 3],
|
|
|
backgroundColor: 'rgba(42, 125, 225, 0.6)'
|
|
|
}]
|
|
|
},
|
|
|
'90d': {
|
|
|
labels: ['<5分钟', '5-15分钟', '15-30分钟', '30-60分钟', '>60分钟'],
|
|
|
datasets: [{
|
|
|
label: '响应数量',
|
|
|
data: [135, 96, 54, 24, 9],
|
|
|
backgroundColor: 'rgba(42, 125, 225, 0.6)'
|
|
|
}]
|
|
|
},
|
|
|
'1y': {
|
|
|
labels: ['<5分钟', '5-15分钟', '15-30分钟', '30-60分钟', '>60分钟'],
|
|
|
datasets: [{
|
|
|
label: '响应数量',
|
|
|
data: [540, 384, 216, 96, 36],
|
|
|
backgroundColor: 'rgba(42, 125, 225, 0.6)'
|
|
|
}]
|
|
|
}
|
|
|
};
|
|
|
return dataMap[range] || dataMap['30d'];
|
|
|
}
|
|
|
|
|
|
// 更新热点区域数据
|
|
|
function updateHotspotData(range) {
|
|
|
const hotspotData = {
|
|
|
'7d': [
|
|
|
{ location: '湘江橘子洲段', count: '8次', width: '65%' },
|
|
|
{ location: '湘江猴子石段', count: '6次', width: '50%' },
|
|
|
{ location: '湘江风光带', count: '4次', width: '35%' }
|
|
|
],
|
|
|
'30d': [
|
|
|
{ location: '湘江橘子洲段', count: '23次', width: '90%' },
|
|
|
{ location: '湘江猴子石段', count: '18次', width: '70%' },
|
|
|
{ location: '湘江风光带', count: '15次', width: '60%' },
|
|
|
{ location: '湘江大桥段', count: '12次', width: '48%' }
|
|
|
],
|
|
|
'90d': [
|
|
|
{ location: '湘江橘子洲段', count: '68次', width: '95%' },
|
|
|
{ location: '湘江猴子石段', count: '54次', width: '80%' },
|
|
|
{ location: '湘江风光带', count: '45次', width: '70%' },
|
|
|
{ location: '湘江大桥段', count: '36次', width: '55%' }
|
|
|
],
|
|
|
'1y': [
|
|
|
{ location: '湘江橘子洲段', count: '247次', width: '100%' },
|
|
|
{ location: '湘江猴子石段', count: '198次', width: '85%' },
|
|
|
{ location: '湘江风光带', count: '165次', width: '75%' },
|
|
|
{ location: '湘江大桥段', count: '132次', width: '60%' }
|
|
|
]
|
|
|
};
|
|
|
|
|
|
const currentData = hotspotData[range] || hotspotData['30d'];
|
|
|
const hotspotMap = document.getElementById('hotspotMap');
|
|
|
|
|
|
hotspotMap.innerHTML = currentData.map(item => `
|
|
|
<div class="hotspot-item">
|
|
|
<span class="location">${item.location}</span>
|
|
|
<span class="count">${item.count}</span>
|
|
|
<div class="heat-bar" style="width: ${item.width}"></div>
|
|
|
</div>
|
|
|
`).join('');
|
|
|
}
|
|
|
|
|
|
// 更新统计摘要数据
|
|
|
function updateSummaryStats(range) {
|
|
|
const statsData = {
|
|
|
'7d': {
|
|
|
total: '87',
|
|
|
avgResponse: '8.2分钟',
|
|
|
completionRate: '96.5%',
|
|
|
falseAlarmRate: '2.8%',
|
|
|
totalChange: '+5.3%',
|
|
|
responseChange: '-3.2%',
|
|
|
rateChange: '+1.5%',
|
|
|
falseChange: '-0.7%'
|
|
|
},
|
|
|
'30d': {
|
|
|
total: '1,247',
|
|
|
avgResponse: '15.2分钟',
|
|
|
completionRate: '94.7%',
|
|
|
falseAlarmRate: '3.2%',
|
|
|
totalChange: '+12.3%',
|
|
|
responseChange: '-8.5%',
|
|
|
rateChange: '+2.1%',
|
|
|
falseChange: '-1.8%'
|
|
|
},
|
|
|
'90d': {
|
|
|
total: '3,741',
|
|
|
avgResponse: '14.8分钟',
|
|
|
completionRate: '95.2%',
|
|
|
falseAlarmRate: '2.9%',
|
|
|
totalChange: '+15.6%',
|
|
|
responseChange: '-10.2%',
|
|
|
rateChange: '+3.4%',
|
|
|
falseChange: '-2.3%'
|
|
|
},
|
|
|
'1y': {
|
|
|
total: '14,964',
|
|
|
avgResponse: '13.5分钟',
|
|
|
completionRate: '96.1%',
|
|
|
falseAlarmRate: '2.5%',
|
|
|
totalChange: '+18.9%',
|
|
|
responseChange: '-12.7%',
|
|
|
rateChange: '+4.2%',
|
|
|
falseChange: '-3.1%'
|
|
|
}
|
|
|
};
|
|
|
|
|
|
const currentData = statsData[range] || statsData['30d'];
|
|
|
const summaryStats = document.getElementById('summaryStats');
|
|
|
|
|
|
summaryStats.innerHTML = `
|
|
|
<div class="summary-item">
|
|
|
<div class="summary-label">总预警数</div>
|
|
|
<div class="summary-value">${currentData.total}</div>
|
|
|
<div class="summary-change ${currentData.totalChange.includes('+') ? 'positive' : 'negative'}">${currentData.totalChange}</div>
|
|
|
</div>
|
|
|
<div class="summary-item">
|
|
|
<div class="summary-label">平均响应时间</div>
|
|
|
<div class="summary-value">${currentData.avgResponse}</div>
|
|
|
<div class="summary-change ${currentData.responseChange.includes('+') ? 'negative' : 'positive'}">${currentData.responseChange}</div>
|
|
|
</div>
|
|
|
<div class="summary-item">
|
|
|
<div class="summary-label">解决率</div>
|
|
|
<div class="summary-value">${currentData.completionRate}</div>
|
|
|
<div class="summary-change positive">${currentData.rateChange}</div>
|
|
|
</div>
|
|
|
<div class="summary-item">
|
|
|
<div class="summary-label">误报率</div>
|
|
|
<div class="summary-value">${currentData.falseAlarmRate}</div>
|
|
|
<div class="summary-change positive">${currentData.falseChange}</div>
|
|
|
</div>
|
|
|
`;
|
|
|
}
|
|
|
|
|
|
// 通用图表配置
|
|
|
function getChartOptions(title) {
|
|
|
return {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
labels: { color: '#1a4b8c' }
|
|
|
}
|
|
|
},
|
|
|
scales: {
|
|
|
x: {
|
|
|
ticks: { color: '#1a4b8c' },
|
|
|
grid: { color: 'rgba(209, 224, 245, 0.5)' }
|
|
|
},
|
|
|
y: {
|
|
|
ticks: { color: '#1a4b8c' },
|
|
|
grid: { color: 'rgba(209, 224, 245, 0.5)' }
|
|
|
}
|
|
|
}
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function getChartOptions2() {
|
|
|
|
|
|
return {
|
|
|
options: {
|
|
|
responsive: true,
|
|
|
maintainAspectRatio: false,
|
|
|
plugins: {
|
|
|
legend: {
|
|
|
labels: { color: '#1a4b8c' }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
// 页面加载完成后初始化
|
|
|
document.addEventListener('DOMContentLoaded', function () {
|
|
|
initCharts();
|
|
|
updateHotspotData('30d');
|
|
|
updateSummaryStats('30d');
|
|
|
|
|
|
// 时间范围选择器事件
|
|
|
document.addEventListener('click', function (e) {
|
|
|
if (e.target.matches('.time-range-selector .filter-btn')) {
|
|
|
const range = e.target.dataset.range;
|
|
|
updateAnalysisData(range);
|
|
|
}
|
|
|
});
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 添加表单提交事件监听
|
|
|
document.getElementById('newAlertForm').addEventListener('submit', function (e) {
|
|
|
e.preventDefault(); // 阻止表单默认提交行为
|
|
|
|
|
|
// 获取表单数据
|
|
|
const newAlert = {
|
|
|
id: 'AL-' + new Date().getTime(), // 生成唯一ID
|
|
|
type: document.getElementById('alertType').value,
|
|
|
level: document.getElementById('alertLevel').value,
|
|
|
title: `${typeMap[document.getElementById('alertType').value]}预警`,
|
|
|
location: document.getElementById('alertLocation').value,
|
|
|
description: document.getElementById('alertDescription').value,
|
|
|
time: new Date().toLocaleString(),
|
|
|
status: 'active'
|
|
|
};
|
|
|
|
|
|
// 添加到预警数组
|
|
|
warnings.unshift(newAlert);
|
|
|
|
|
|
// 更新统计数字
|
|
|
updateStats();
|
|
|
|
|
|
// 刷新预警列表
|
|
|
loadWarnings();
|
|
|
|
|
|
// 关闭模态框
|
|
|
closeNewAlertModal();
|
|
|
|
|
|
// 重置表单
|
|
|
this.reset();
|
|
|
});
|
|
|
function updateStats() {
|
|
|
// 更新统计数字
|
|
|
document.getElementById('totalAlerts').textContent = warnings.length;
|
|
|
document.getElementById('criticalAlerts').textContent = warnings.filter(w => w.level === 'critical').length;
|
|
|
document.getElementById('warningAlerts').textContent = warnings.filter(w => w.level === 'warning').length;
|
|
|
document.getElementById('infoAlerts').textContent = warnings.filter(w => w.level === 'info').length;
|
|
|
|
|
|
// 今日新增统计(简化处理)
|
|
|
const todayAlerts = warnings.filter(w => {
|
|
|
const alertDate = new Date(w.time);
|
|
|
const today = new Date();
|
|
|
return alertDate.toDateString() === today.toDateString();
|
|
|
}).length;
|
|
|
document.getElementById('todayAlerts').textContent = todayAlerts;
|
|
|
}
|
|
|
</script>
|
|
|
</body>
|
|
|
|
|
|
</html> |