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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<!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>