You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3472 lines
129 KiB
HTML

1 week ago
<!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()">&times;</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()">&times;</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()">&times;</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>