|
@@ -1,137 +1,154 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="dashboard">
|
|
<div class="dashboard">
|
|
|
- <div class="bottom-bg"></div>
|
|
|
|
|
|
|
+ <!-- 背景和基础元素 -->
|
|
|
<div class="station-bg"></div>
|
|
<div class="station-bg"></div>
|
|
|
- <div class="top-title"></div>
|
|
|
|
|
- <div class="system-title">数字孪生黑林小流域</div>
|
|
|
|
|
|
|
|
|
|
- <!-- 返回按钮 -->
|
|
|
|
|
- <button class="back-btn" @click="goBack">返回</button>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 主要内容区域 -->
|
|
|
|
|
- <div class="main-content">
|
|
|
|
|
- <!-- 左侧面板区域 -->
|
|
|
|
|
- <div class="left-sidebar">
|
|
|
|
|
- <!-- 左侧面板1:水文站介绍 -->
|
|
|
|
|
- <div class="data-card station-intro">
|
|
|
|
|
- <div class="panel-header">
|
|
|
|
|
- <h3>水文站介绍</h3>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="panel-content">
|
|
|
|
|
- <div class="image-section">
|
|
|
|
|
- <div class="thumbnail-list">
|
|
|
|
|
- <img src="/src/assets/images/Heilin/图2.jpeg" alt="水文站图片2" @click="changeMainImage('/src/assets/images/Heilin/图2.jpeg')" />
|
|
|
|
|
- <img src="/src/assets/images/Heilin/图3.jpeg" alt="水文站图片3" @click="changeMainImage('/src/assets/images/Heilin/图3.jpeg')" />
|
|
|
|
|
- <img src="/src/assets/images/Heilin/图4.jpeg" alt="水文站图片4" @click="changeMainImage('/src/assets/images/Heilin/图4.jpeg')" />
|
|
|
|
|
- <img src="/src/assets/images/Heilin/图5.jpeg" alt="水文站图片5" @click="changeMainImage('/src/assets/images/Heilin/图5.jpeg')" />
|
|
|
|
|
- <img src="/src/assets/images/Heilin/图6.png" alt="水文站图片6" @click="changeMainImage('/src/assets/images/Heilin/图6.png')" />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <div class="bottom-bg"></div>
|
|
|
|
|
+ <div class="top-title"></div>
|
|
|
|
|
+ <div class="system-title">数字孪生黑林小流域</div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 返回按钮 -->
|
|
|
|
|
+ <button class="back-btn" @click="goBack">返回</button>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 主要内容区域 -->
|
|
|
|
|
+ <div class="main-content">
|
|
|
|
|
+ <!-- 左侧面板区域 -->
|
|
|
|
|
+ <div class="left-sidebar">
|
|
|
|
|
+ <!-- 左侧面板1:水文站介绍 -->
|
|
|
|
|
+ <div class="data-card station-intro">
|
|
|
|
|
+ <div class="panel-header">
|
|
|
|
|
+ <h3>水文站介绍</h3>
|
|
|
|
|
+ <span class="more-btn" @click="showDetailModal = true">更多</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="panel-content">
|
|
|
|
|
+ <div class="image-section">
|
|
|
|
|
+ <div class="thumbnail-list">
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/图2.jpeg" alt="水文站图片2" @click="changeMainImage('/src/assets/images/Heilin/图2.jpeg')" />
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/图3.jpeg" alt="水文站图片3" @click="changeMainImage('/src/assets/images/Heilin/图3.jpeg')" />
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/图4.jpeg" alt="水文站图片4" @click="changeMainImage('/src/assets/images/Heilin/图4.jpeg')" />
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/图5.jpeg" alt="水文站图片5" @click="changeMainImage('/src/assets/images/Heilin/图5.jpeg')" />
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/图6.png" alt="水文站图片6" @click="changeMainImage('/src/assets/images/Heilin/图6.png')" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="main-image">
|
|
|
|
|
+ <img :src="mainImageUrl" :alt="mainImageAlt" />
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="main-image">
|
|
|
|
|
- <img :src="mainImageUrl" :alt="mainImageAlt" />
|
|
|
|
|
|
|
+ <div class="station-text">
|
|
|
|
|
+ <p>黑林水文站位于江苏省连云港市赣榆区黑林镇,是沭河上游的重要水文监测站点。</p>
|
|
|
|
|
+ <p>建站于1958年,占地面积约2000平方米,主要负责监测沭河上游的水位、流量、降雨量等水文要素。</p>
|
|
|
|
|
+ <p>该站配备了先进的水文监测设备,包括自动水位计、流量计、雨量计等,实现了数据的自动采集和传输。</p>
|
|
|
|
|
+ <p>多年来,黑林水文站为当地的防洪抗旱、水资源管理和水环境保护提供了重要的科学依据。</p>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="station-text">
|
|
|
|
|
- <p>黑林水文站位于江苏省连云港市赣榆区黑林镇,是沭河上游的重要水文监测站点。</p>
|
|
|
|
|
- <p>建站于1958年,占地面积约2000平方米,主要负责监测沭河上游的水位、流量、降雨量等水文要素。</p>
|
|
|
|
|
- <p>该站配备了先进的水文监测设备,包括自动水位计、流量计、雨量计等,实现了数据的自动采集和传输。</p>
|
|
|
|
|
- <p>多年来,黑林水文站为当地的防洪抗旱、水资源管理和水环境保护提供了重要的科学依据。</p>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
- </div>
|
|
|
|
|
-
|
|
|
|
|
- <!-- 左侧面板2:视频监控 -->
|
|
|
|
|
- <div class="data-card mt-20 video-monitor">
|
|
|
|
|
- <div class="panel-header">
|
|
|
|
|
- <h3>视频监控</h3>
|
|
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="panel-content">
|
|
|
|
|
- <div class="video-image">
|
|
|
|
|
- <img src="/src/assets/images/Heilin/图5.jpeg" alt="视频监控画面" />
|
|
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 左侧面板 2:监控画面列表 -->
|
|
|
|
|
+ <div class="data-card mt-20 video-monitor">
|
|
|
|
|
+ <div class="panel-header">
|
|
|
|
|
+ <h3>监控画面列表</h3>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="panel-content">
|
|
|
|
|
+ <div class="monitor-select">
|
|
|
|
|
+ <div class="select-wrapper">
|
|
|
|
|
+ <span class="camera-icon">📹</span>
|
|
|
|
|
+ <select v-model="selectedMonitor">
|
|
|
|
|
+ <option value="图5">七星关区赤水河赤水河河道</option>
|
|
|
|
|
+ <option value="图2">监控点 2 - 河道</option>
|
|
|
|
|
+ <option value="图3">监控点 3 - 大坝</option>
|
|
|
|
|
+ <option value="图4">监控点 4 - 泵站</option>
|
|
|
|
|
+ <option value="图5">监控点 5 - 变电站</option>
|
|
|
|
|
+ <option value="图6">监控点 6 - 仓库</option>
|
|
|
|
|
+ </select>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="monitor-image">
|
|
|
|
|
+ <img :src="'/src/assets/images/Heilin/' + (selectedMonitor === '图6' ? selectedMonitor + '.png' : selectedMonitor + '.jpeg')" :alt="selectedMonitor" />
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<!-- 中间空间 -->
|
|
<!-- 中间空间 -->
|
|
|
- <div class="center-space"></div>
|
|
|
|
|
|
|
+ <div class="center-space">
|
|
|
|
|
+ <div class="poi-container">
|
|
|
|
|
+ <div class="poi-item" style="left: 565px; top: 741px;">
|
|
|
|
|
+ <div class="poi-label">水位自记台</div>
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/POI.png" alt="POI图标" class="poi-icon" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="poi-item" style="left: 1260px; top: 980px;">
|
|
|
|
|
+ <div class="poi-label">水土保持观测场</div>
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/POI.png" alt="POI图标" class="poi-icon" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="poi-item" style="left: 724px; top: 883px;">
|
|
|
|
|
+ <div class="poi-label">缆道房</div>
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/POI.png" alt="POI图标" class="poi-icon" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="poi-item" style="left: 979px; top: 895px;">
|
|
|
|
|
+ <div class="poi-label">降雨观测场</div>
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/POI.png" alt="POI图标" class="poi-icon" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="poi-item" style="left: 1173px; top: 851px;">
|
|
|
|
|
+ <div class="poi-label">地下水监测井</div>
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/POI.png" alt="POI图标" class="poi-icon" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
|
|
|
<!-- 右侧面板区域 -->
|
|
<!-- 右侧面板区域 -->
|
|
|
<div class="right-sidebar">
|
|
<div class="right-sidebar">
|
|
|
- <!-- 右侧面板1:历史事件 -->
|
|
|
|
|
|
|
+ <!-- 右侧面板 1:监测数据 -->
|
|
|
<div class="data-card">
|
|
<div class="data-card">
|
|
|
<div class="panel-header">
|
|
<div class="panel-header">
|
|
|
- <h3>历史事件</h3>
|
|
|
|
|
|
|
+ <h3>监测数据</h3>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="panel-content">
|
|
<div class="panel-content">
|
|
|
- <div class="event-item">
|
|
|
|
|
- <div class="event-date">2023-07-21</div>
|
|
|
|
|
- <div class="event-title">暴雨洪水</div>
|
|
|
|
|
- <div class="event-desc">受台风影响,黑林地区出现强降雨,沭河水位迅速上涨,本站测得最大流量120m³/s。</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="event-item">
|
|
|
|
|
- <div class="event-date">2022-06-15</div>
|
|
|
|
|
- <div class="event-title">设备升级</div>
|
|
|
|
|
- <div class="event-desc">完成水文监测设备升级,实现了数据的实时传输和远程监控。</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="event-item">
|
|
|
|
|
- <div class="event-date">2021-08-05</div>
|
|
|
|
|
- <div class="event-title">洪水过程</div>
|
|
|
|
|
- <div class="event-desc">沭河上游出现持续降雨,本站监测到完整的洪水过程,为防洪决策提供了重要依据。</div>
|
|
|
|
|
|
|
+ <!-- 第一行:水位卡片 -->
|
|
|
|
|
+ <div class="water-level-cards">
|
|
|
|
|
+ <div class="water-card">
|
|
|
|
|
+ <div class="card-title">实时水位</div>
|
|
|
|
|
+ <div class="card-value">36.25<span class="unit">m</span></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-card">
|
|
|
|
|
+ <div class="card-title">警戒水位</div>
|
|
|
|
|
+ <div class="card-value warning">35.00<span class="unit">m</span></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-card">
|
|
|
|
|
+ <div class="card-title">保证水位</div>
|
|
|
|
|
+ <div class="card-value danger">36.50<span class="unit">m</span></div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="water-card">
|
|
|
|
|
+ <div class="card-title">实测最高水位</div>
|
|
|
|
|
+ <div class="card-value highlight">36.46<span class="unit">m</span></div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="event-item">
|
|
|
|
|
- <div class="event-date">2020-05-10</div>
|
|
|
|
|
- <div class="event-title">水质监测</div>
|
|
|
|
|
- <div class="event-desc">开展水质监测工作,各项指标均符合国家地表水标准。</div>
|
|
|
|
|
|
|
+ <!-- 第二行:图表 -->
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <canvas ref="combinedChart"></canvas>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 右侧面板2:设备状态 -->
|
|
|
|
|
|
|
+ <!-- 右侧面板 2:蒸发量过程线 -->
|
|
|
<div class="data-card mt-20">
|
|
<div class="data-card mt-20">
|
|
|
<div class="panel-header">
|
|
<div class="panel-header">
|
|
|
- <h3>设备状态</h3>
|
|
|
|
|
|
|
+ <h3>蒸发量过程线</h3>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="panel-content">
|
|
<div class="panel-content">
|
|
|
- <div class="device-item">
|
|
|
|
|
- <span class="label">水位计</span>
|
|
|
|
|
- <span class="status online">在线</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="device-item">
|
|
|
|
|
- <span class="label">流量计</span>
|
|
|
|
|
- <span class="status online">在线</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="device-item">
|
|
|
|
|
- <span class="label">雨量计</span>
|
|
|
|
|
- <span class="status online">在线</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="device-item">
|
|
|
|
|
- <span class="label">水质监测仪</span>
|
|
|
|
|
- <span class="status online">在线</span>
|
|
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <canvas ref="evaporationChart"></canvas>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 右侧面板3:实时数据 -->
|
|
|
|
|
|
|
+ <!-- 右侧面板 3:土壤含水量过程线 -->
|
|
|
<div class="data-card mt-20">
|
|
<div class="data-card mt-20">
|
|
|
<div class="panel-header">
|
|
<div class="panel-header">
|
|
|
- <h3>实时数据</h3>
|
|
|
|
|
|
|
+ <h3>土壤含水量过程线</h3>
|
|
|
</div>
|
|
</div>
|
|
|
<div class="panel-content">
|
|
<div class="panel-content">
|
|
|
- <div class="realtime-item">
|
|
|
|
|
- <span class="label">当前水位</span>
|
|
|
|
|
- <span class="value">3.25m</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="realtime-item">
|
|
|
|
|
- <span class="label">当前流量</span>
|
|
|
|
|
- <span class="value">12.5m³/s</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="realtime-item">
|
|
|
|
|
- <span class="label">当前降雨量</span>
|
|
|
|
|
- <span class="value">0.5mm/h</span>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="realtime-item">
|
|
|
|
|
- <span class="label">水温</span>
|
|
|
|
|
- <span class="value">18.5°C</span>
|
|
|
|
|
|
|
+ <div class="chart-container">
|
|
|
|
|
+ <canvas ref="soilMoistureChart"></canvas>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -140,6 +157,137 @@
|
|
|
|
|
|
|
|
<!-- 渐变装饰层(四周暗角) -->
|
|
<!-- 渐变装饰层(四周暗角) -->
|
|
|
<GradientOverlay />
|
|
<GradientOverlay />
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 详情弹窗 -->
|
|
|
|
|
+ <div v-if="showDetailModal" class="modal-overlay" @click="closeModal">
|
|
|
|
|
+ <div class="modal-content" @click.stop>
|
|
|
|
|
+ <div class="modal-header">
|
|
|
|
|
+ <h2>水文站详细信息</h2>
|
|
|
|
|
+ <span class="close-btn" @click="closeModal">×</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="modal-body">
|
|
|
|
|
+ <!-- 基本信息区域 -->
|
|
|
|
|
+ <div class="station-info-header">
|
|
|
|
|
+ <div class="station-basic">
|
|
|
|
|
+ <h3 class="station-name">黑林水文站</h3>
|
|
|
|
|
+ <p class="station-code">测站编码:640324</p>
|
|
|
|
|
+ <p class="station-basin">所在流域:青口河</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="station-qr">
|
|
|
|
|
+ <img src="/src/assets/images/Heilin/测站二维码.png" alt="测站二维码" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 测站沿革 -->
|
|
|
|
|
+ <div class="detail-section">
|
|
|
|
|
+ <h4>测站沿革</h4>
|
|
|
|
|
+ <p>黑林水文站位于青口河中游,是集水面积 190 km²的重要控制站,也是小塔山水库入库控制断面。该站于 1976 年 7 月建站,采用黑林站冻结基面(1976 年设站时冻结,非黄海基面)。</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 监测项目 -->
|
|
|
|
|
+ <div class="detail-section">
|
|
|
|
|
+ <h4>监测项目</h4>
|
|
|
|
|
+ <p>水位、流量、降雨量、水质、蒸发量、水土流失监测、地下水</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 历史数据 -->
|
|
|
|
|
+ <div class="detail-section">
|
|
|
|
|
+ <h4>历史数据</h4>
|
|
|
|
|
+ <div class="data-grid">
|
|
|
|
|
+ <div class="data-item">
|
|
|
|
|
+ <span class="data-label">2024 年实测最大洪峰</span>
|
|
|
|
|
+ <span class="data-value">371 m³/s</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-item">
|
|
|
|
|
+ <span class="data-label">多年平均蒸发量</span>
|
|
|
|
|
+ <span class="data-value">845.7 mm</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 历史最高水位 -->
|
|
|
|
|
+ <div class="detail-section">
|
|
|
|
|
+ <h4>历史最高水位</h4>
|
|
|
|
|
+ <div class="extreme-data">
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">水位:</span>
|
|
|
|
|
+ <span class="data-value highlight">36.46 m</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">出现时间:</span>
|
|
|
|
|
+ <span class="data-text">2024 年 7 月 7 日 11 时 36 分</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">对应洪峰:</span>
|
|
|
|
|
+ <span class="data-text">371 m³/s(2024 年 7 月实测)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">背景:</span>
|
|
|
|
|
+ <span class="data-text">受沂沭河 1 号洪水与区域特大暴雨影响,为设站以来实测最高水位</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 历史最低水位 -->
|
|
|
|
|
+ <div class="detail-section">
|
|
|
|
|
+ <h4>历史最低水位</h4>
|
|
|
|
|
+ <div class="extreme-data">
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">水位:</span>
|
|
|
|
|
+ <span class="data-value low">32.10 m</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">出现时间:</span>
|
|
|
|
|
+ <span class="data-text">2019 年 12 月(枯水期)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">说明:</span>
|
|
|
|
|
+ <span class="data-text">为长期监测资料整编成果,属流域典型枯水极值</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 水位基面与说明 -->
|
|
|
|
|
+ <div class="detail-section">
|
|
|
|
|
+ <h4>水位基面与说明</h4>
|
|
|
|
|
+ <div class="base-info">
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">基面:</span>
|
|
|
|
|
+ <span class="data-text">黑林站冻结基面(1976 年设站时冻结,非黄海基面)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">警戒水位:</span>
|
|
|
|
|
+ <span class="data-value warning">35.00 m</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">保证水位:</span>
|
|
|
|
|
+ <span class="data-value danger">36.50 m</span>
|
|
|
|
|
+ <span class="data-note">(2024 年最高水位接近保证水位)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <!-- 补充信息 -->
|
|
|
|
|
+ <div class="detail-section">
|
|
|
|
|
+ <h4>补充信息</h4>
|
|
|
|
|
+ <div class="supplement-info">
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">集水面积:</span>
|
|
|
|
|
+ <span class="data-text">190 km²(青口河中游控制站,小塔山水库入库控制断面)</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">建站时间:</span>
|
|
|
|
|
+ <span class="data-text">1976 年 7 月</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="data-row">
|
|
|
|
|
+ <span class="data-label">监测项目:</span>
|
|
|
|
|
+ <span class="data-text">水位、流量、降水、含沙量、水质、气象辅助等</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
@@ -149,9 +297,13 @@ import { useRouter } from 'vue-router'
|
|
|
import GradientOverlay from '../components/gradient-overlay.vue'
|
|
import GradientOverlay from '../components/gradient-overlay.vue'
|
|
|
|
|
|
|
|
const router = useRouter()
|
|
const router = useRouter()
|
|
|
-const flowChart = ref(null)
|
|
|
|
|
-const mainImageUrl = ref('/src/assets/images/Heilin/图1.jpeg')
|
|
|
|
|
-const mainImageAlt = ref('水文站图片1')
|
|
|
|
|
|
|
+const mainImageUrl = ref('/src/assets/images/Heilin/图5.jpeg')
|
|
|
|
|
+const mainImageAlt = ref('水文站图片 5')
|
|
|
|
|
+const selectedMonitor = ref('图5')
|
|
|
|
|
+const showDetailModal = ref(false)
|
|
|
|
|
+const combinedChart = ref(null)
|
|
|
|
|
+const evaporationChart = ref(null)
|
|
|
|
|
+const soilMoistureChart = ref(null)
|
|
|
|
|
|
|
|
const goBack = () => {
|
|
const goBack = () => {
|
|
|
router.push('/')
|
|
router.push('/')
|
|
@@ -159,108 +311,308 @@ const goBack = () => {
|
|
|
|
|
|
|
|
const changeMainImage = (imageUrl) => {
|
|
const changeMainImage = (imageUrl) => {
|
|
|
mainImageUrl.value = imageUrl
|
|
mainImageUrl.value = imageUrl
|
|
|
- // 提取图片名称作为alt文本
|
|
|
|
|
|
|
+ // 提取图片名称作为 alt 文本
|
|
|
const parts = imageUrl.split('/')
|
|
const parts = imageUrl.split('/')
|
|
|
const imageName = parts[parts.length - 1]
|
|
const imageName = parts[parts.length - 1]
|
|
|
mainImageAlt.value = `水文站图片${imageName.split('.')[0]}`
|
|
mainImageAlt.value = `水文站图片${imageName.split('.')[0]}`
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-const drawFlowChart = () => {
|
|
|
|
|
- const canvas = flowChart.value
|
|
|
|
|
|
|
+const closeModal = () => {
|
|
|
|
|
+ showDetailModal.value = false
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 绘制综合图表(水位、流量、降雨量)
|
|
|
|
|
+const drawCombinedChart = () => {
|
|
|
|
|
+ const canvas = combinedChart.value
|
|
|
if (!canvas) return
|
|
if (!canvas) return
|
|
|
|
|
|
|
|
const ctx = canvas.getContext('2d')
|
|
const ctx = canvas.getContext('2d')
|
|
|
- ctx.clearRect(0, 0, canvas.width, canvas.height)
|
|
|
|
|
|
|
+ const width = canvas.width = canvas.offsetWidth
|
|
|
|
|
+ const height = canvas.height = 180
|
|
|
|
|
+ const padding = { top: 30, right: 70, bottom: 25, left: 40 }
|
|
|
|
|
+ const chartWidth = width - padding.left - padding.right
|
|
|
|
|
+ const chartHeight = height - padding.top - padding.bottom
|
|
|
|
|
|
|
|
- // 模拟流量数据
|
|
|
|
|
- const data = [8, 12, 15, 10, 18, 22, 25, 20, 15, 12, 10, 14]
|
|
|
|
|
- const labels = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
|
|
|
|
|
|
+ ctx.clearRect(0, 0, width, height)
|
|
|
|
|
|
|
|
- // 计算画布尺寸
|
|
|
|
|
- const width = canvas.width
|
|
|
|
|
- const height = canvas.height
|
|
|
|
|
- const padding = 40
|
|
|
|
|
- const chartWidth = width - 2 * padding
|
|
|
|
|
- const chartHeight = height - 2 * padding
|
|
|
|
|
|
|
+ // 模拟数据
|
|
|
|
|
+ const months = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12']
|
|
|
|
|
+ const waterLevel = [32.5, 32.8, 33.2, 33.8, 34.5, 35.2, 36.0, 35.8, 35.0, 34.2, 33.5, 32.8]
|
|
|
|
|
+ const flow = [10, 12, 15, 20, 35, 50, 80, 65, 40, 25, 18, 12]
|
|
|
|
|
+ const rainfall = [20, 25, 30, 45, 60, 85, 120, 100, 70, 40, 25, 15]
|
|
|
|
|
|
|
|
- // 计算数据范围
|
|
|
|
|
- const maxValue = Math.max(...data)
|
|
|
|
|
- const minValue = Math.min(...data)
|
|
|
|
|
- const valueRange = maxValue - minValue
|
|
|
|
|
|
|
+ const maxWaterLevel = Math.max(...waterLevel)
|
|
|
|
|
+ const minWaterLevel = Math.min(...waterLevel)
|
|
|
|
|
+ const maxFlow = Math.max(...flow)
|
|
|
|
|
+ const maxRainfall = Math.max(...rainfall)
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制主轴线
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 213, 255, 0.8)'
|
|
|
|
|
+ ctx.lineWidth = 1.5
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.moveTo(padding.left, padding.top)
|
|
|
|
|
+ ctx.lineTo(padding.left, height - padding.bottom)
|
|
|
|
|
+ ctx.lineTo(width - padding.right, height - padding.bottom)
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
|
|
|
// 绘制网格
|
|
// 绘制网格
|
|
|
- ctx.strokeStyle = 'rgba(0, 213, 255, 0.2)'
|
|
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 213, 255, 0.3)'
|
|
|
|
|
+ ctx.lineWidth = 0.8
|
|
|
|
|
+ for (let i = 0; i <= 5; i++) {
|
|
|
|
|
+ const y = padding.top + (chartHeight / 5) * i
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.moveTo(padding.left, y)
|
|
|
|
|
+ ctx.lineTo(width - padding.right, y)
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制降雨量柱状图(从上往下)
|
|
|
|
|
+ const barWidth = chartWidth / months.length * 0.5
|
|
|
|
|
+ ctx.fillStyle = 'rgba(78, 205, 196, 0.7)'
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / months.length) * i + (chartWidth / months.length - barWidth) / 2
|
|
|
|
|
+ const barHeight = (rainfall[i] / maxRainfall) * chartHeight * 0.4
|
|
|
|
|
+ ctx.fillRect(x, padding.top, barWidth, barHeight)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制水位线(蓝色)
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 100, 255, 1)'
|
|
|
|
|
+ ctx.lineWidth = 2
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / (months.length - 1)) * i
|
|
|
|
|
+ const y = padding.top + chartHeight - ((waterLevel[i] - minWaterLevel) / (maxWaterLevel - minWaterLevel)) * chartHeight
|
|
|
|
|
+ if (i === 0) {
|
|
|
|
|
+ ctx.moveTo(x, y)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ctx.lineTo(x, y)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制流量线(青蓝色)
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 255, 255, 1)'
|
|
|
|
|
+ ctx.lineWidth = 2
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / (months.length - 1)) * i
|
|
|
|
|
+ const y = padding.top + chartHeight - ((flow[i] / maxFlow) * chartHeight)
|
|
|
|
|
+ if (i === 0) {
|
|
|
|
|
+ ctx.moveTo(x, y)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ctx.lineTo(x, y)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制图例(顶部一字排开)
|
|
|
|
|
+ ctx.font = '10px Arial'
|
|
|
|
|
+ const legendItems = [
|
|
|
|
|
+ { color: 'rgba(0, 100, 255, 1)', text: '水位' },
|
|
|
|
|
+ { color: 'rgba(0, 255, 255, 1)', text: '流量' },
|
|
|
|
|
+ { color: 'rgba(78, 205, 196, 1)', text: '降雨量' }
|
|
|
|
|
+ ]
|
|
|
|
|
+ const legendX = padding.left
|
|
|
|
|
+ const legendY = 12
|
|
|
|
|
+ const legendGap = 60
|
|
|
|
|
+
|
|
|
|
|
+ legendItems.forEach((item, index) => {
|
|
|
|
|
+ const x = legendX + index * legendGap
|
|
|
|
|
+ ctx.fillStyle = item.color
|
|
|
|
|
+ ctx.fillRect(x, legendY, 8, 8)
|
|
|
|
|
+ ctx.fillText(item.text, x + 12, legendY + 7)
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制流量侧轴线
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 255, 255, 0.8)'
|
|
|
ctx.lineWidth = 1
|
|
ctx.lineWidth = 1
|
|
|
|
|
+ ctx.setLineDash([5, 5])
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.moveTo(width - padding.right - 20, padding.top)
|
|
|
|
|
+ ctx.lineTo(width - padding.right - 20, height - padding.bottom)
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+ ctx.setLineDash([])
|
|
|
|
|
|
|
|
- // 绘制水平网格线
|
|
|
|
|
|
|
+ // 流量侧轴线刻度
|
|
|
|
|
+ ctx.fillStyle = 'rgba(0, 255, 255, 0.9)'
|
|
|
|
|
+ ctx.font = '9px Arial'
|
|
|
for (let i = 0; i <= 5; i++) {
|
|
for (let i = 0; i <= 5; i++) {
|
|
|
- const y = padding + (chartHeight / 5) * i
|
|
|
|
|
|
|
+ const y = padding.top + (chartHeight / 5) * i
|
|
|
ctx.beginPath()
|
|
ctx.beginPath()
|
|
|
- ctx.moveTo(padding, y)
|
|
|
|
|
- ctx.lineTo(width - padding, y)
|
|
|
|
|
|
|
+ ctx.moveTo(width - padding.right - 20, y)
|
|
|
|
|
+ ctx.lineTo(width - padding.right - 15, y)
|
|
|
ctx.stroke()
|
|
ctx.stroke()
|
|
|
-
|
|
|
|
|
- // 绘制刻度
|
|
|
|
|
- const value = maxValue - (valueRange / 5) * i
|
|
|
|
|
- ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
|
|
|
|
|
- ctx.font = '12px Arial'
|
|
|
|
|
- ctx.textAlign = 'right'
|
|
|
|
|
- ctx.fillText(value.toFixed(1), padding - 10, y + 4)
|
|
|
|
|
|
|
+ const value = Math.round(maxFlow - (maxFlow / 5) * i)
|
|
|
|
|
+ ctx.fillText(value, width - padding.right - 50, y + 3)
|
|
|
}
|
|
}
|
|
|
|
|
+ ctx.fillText('m³/s', width - padding.right - 50, padding.top - 8)
|
|
|
|
|
|
|
|
- // 绘制垂直网格线
|
|
|
|
|
- for (let i = 0; i < labels.length; i++) {
|
|
|
|
|
- const x = padding + (chartWidth / (labels.length - 1)) * i
|
|
|
|
|
|
|
+ // 绘制 X 轴标签
|
|
|
|
|
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'
|
|
|
|
|
+ ctx.font = '9px Arial'
|
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / (months.length - 1)) * i
|
|
|
|
|
+ ctx.fillText(months[i], x, height - 8)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制 Y 轴标签
|
|
|
|
|
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.9)'
|
|
|
|
|
+ ctx.font = '9px Arial'
|
|
|
|
|
+ ctx.textAlign = 'right'
|
|
|
|
|
+ for (let i = 0; i <= 5; i++) {
|
|
|
|
|
+ const y = padding.top + (chartHeight / 5) * i
|
|
|
|
|
+ const value = Math.round(minWaterLevel + (maxWaterLevel - minWaterLevel) / 5 * (5 - i))
|
|
|
|
|
+ ctx.fillText(value, padding.left - 8, y + 3)
|
|
|
|
|
+ }
|
|
|
|
|
+ ctx.fillText('m', padding.left - 8, padding.top - 8)
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 绘制蒸发量过程线
|
|
|
|
|
+const drawEvaporationChart = () => {
|
|
|
|
|
+ const canvas = evaporationChart.value
|
|
|
|
|
+ if (!canvas) return
|
|
|
|
|
+
|
|
|
|
|
+ const ctx = canvas.getContext('2d')
|
|
|
|
|
+ const width = canvas.width = canvas.offsetWidth
|
|
|
|
|
+ const height = canvas.height = 200
|
|
|
|
|
+ const padding = { top: 20, right: 30, bottom: 30, left: 50 }
|
|
|
|
|
+ const chartWidth = width - padding.left - padding.right
|
|
|
|
|
+ const chartHeight = height - padding.top - padding.bottom
|
|
|
|
|
+
|
|
|
|
|
+ ctx.clearRect(0, 0, width, height)
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟数据
|
|
|
|
|
+ const months = ['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月']
|
|
|
|
|
+ const evaporation = [45, 52, 68, 85, 105, 120, 135, 125, 95, 72, 55, 48]
|
|
|
|
|
+ const maxEvaporation = Math.max(...evaporation)
|
|
|
|
|
+ const minEvaporation = Math.min(...evaporation)
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制网格
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 213, 255, 0.2)'
|
|
|
|
|
+ ctx.lineWidth = 1
|
|
|
|
|
+ for (let i = 0; i <= 5; i++) {
|
|
|
|
|
+ const y = padding.top + (chartHeight / 5) * i
|
|
|
ctx.beginPath()
|
|
ctx.beginPath()
|
|
|
- ctx.moveTo(x, padding)
|
|
|
|
|
- ctx.lineTo(x, height - padding)
|
|
|
|
|
|
|
+ ctx.moveTo(padding.left, y)
|
|
|
|
|
+ ctx.lineTo(width - padding.right, y)
|
|
|
ctx.stroke()
|
|
ctx.stroke()
|
|
|
-
|
|
|
|
|
- // 绘制刻度
|
|
|
|
|
- ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
|
|
|
|
|
- ctx.font = '12px Arial'
|
|
|
|
|
- ctx.textAlign = 'center'
|
|
|
|
|
- ctx.fillText(labels[i], x, height - padding + 20)
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // 绘制流量曲线
|
|
|
|
|
- ctx.strokeStyle = 'rgba(0, 255, 255, 1)'
|
|
|
|
|
|
|
+ // 绘制蒸发量线
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(255, 165, 2, 1)'
|
|
|
ctx.lineWidth = 2
|
|
ctx.lineWidth = 2
|
|
|
ctx.beginPath()
|
|
ctx.beginPath()
|
|
|
-
|
|
|
|
|
- for (let i = 0; i < data.length; i++) {
|
|
|
|
|
- const x = padding + (chartWidth / (data.length - 1)) * i
|
|
|
|
|
- const y = padding + chartHeight - ((data[i] - minValue) / valueRange) * chartHeight
|
|
|
|
|
-
|
|
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / (months.length - 1)) * i
|
|
|
|
|
+ const y = padding.top + chartHeight - ((evaporation[i] - minEvaporation) / (maxEvaporation - minEvaporation)) * chartHeight
|
|
|
if (i === 0) {
|
|
if (i === 0) {
|
|
|
ctx.moveTo(x, y)
|
|
ctx.moveTo(x, y)
|
|
|
} else {
|
|
} else {
|
|
|
ctx.lineTo(x, y)
|
|
ctx.lineTo(x, y)
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制数据点
|
|
|
|
|
+ ctx.fillStyle = 'rgba(255, 165, 2, 1)'
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / (months.length - 1)) * i
|
|
|
|
|
+ const y = padding.top + chartHeight - ((evaporation[i] - minEvaporation) / (maxEvaporation - minEvaporation)) * chartHeight
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.arc(x, y, 3, 0, Math.PI * 2)
|
|
|
|
|
+ ctx.fill()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制 X 轴标签
|
|
|
|
|
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
|
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / (months.length - 1)) * i
|
|
|
|
|
+ ctx.fillText(months[i], x, height - 10)
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 绘制土壤含水量过程线
|
|
|
|
|
+const drawSoilMoistureChart = () => {
|
|
|
|
|
+ const canvas = soilMoistureChart.value
|
|
|
|
|
+ if (!canvas) return
|
|
|
|
|
+
|
|
|
|
|
+ const ctx = canvas.getContext('2d')
|
|
|
|
|
+ const width = canvas.width = canvas.offsetWidth
|
|
|
|
|
+ const height = canvas.height = 200
|
|
|
|
|
+ const padding = { top: 20, right: 30, bottom: 30, left: 50 }
|
|
|
|
|
+ const chartWidth = width - padding.left - padding.right
|
|
|
|
|
+ const chartHeight = height - padding.top - padding.bottom
|
|
|
|
|
|
|
|
|
|
+ ctx.clearRect(0, 0, width, height)
|
|
|
|
|
+
|
|
|
|
|
+ // 模拟数据
|
|
|
|
|
+ const months = ['1 月', '2 月', '3 月', '4 月', '5 月', '6 月', '7 月', '8 月', '9 月', '10 月', '11 月', '12 月']
|
|
|
|
|
+ const soilMoisture = [18, 19, 21, 23, 25, 28, 30, 29, 26, 23, 20, 18]
|
|
|
|
|
+ const maxMoisture = Math.max(...soilMoisture)
|
|
|
|
|
+ const minMoisture = Math.min(...soilMoisture)
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制网格
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(0, 213, 255, 0.2)'
|
|
|
|
|
+ ctx.lineWidth = 1
|
|
|
|
|
+ for (let i = 0; i <= 5; i++) {
|
|
|
|
|
+ const y = padding.top + (chartHeight / 5) * i
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ ctx.moveTo(padding.left, y)
|
|
|
|
|
+ ctx.lineTo(width - padding.right, y)
|
|
|
|
|
+ ctx.stroke()
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制土壤含水量线
|
|
|
|
|
+ ctx.strokeStyle = 'rgba(139, 90, 43, 1)'
|
|
|
|
|
+ ctx.lineWidth = 2
|
|
|
|
|
+ ctx.beginPath()
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / (months.length - 1)) * i
|
|
|
|
|
+ const y = padding.top + chartHeight - ((soilMoisture[i] - minMoisture) / (maxMoisture - minMoisture)) * chartHeight
|
|
|
|
|
+ if (i === 0) {
|
|
|
|
|
+ ctx.moveTo(x, y)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ ctx.lineTo(x, y)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
ctx.stroke()
|
|
ctx.stroke()
|
|
|
|
|
|
|
|
- // 填充曲线下方区域
|
|
|
|
|
- ctx.fillStyle = 'rgba(0, 255, 255, 0.2)'
|
|
|
|
|
- ctx.lineTo(width - padding, height - padding)
|
|
|
|
|
- ctx.lineTo(padding, height - padding)
|
|
|
|
|
|
|
+ // 填充区域
|
|
|
|
|
+ ctx.fillStyle = 'rgba(139, 90, 43, 0.2)'
|
|
|
|
|
+ ctx.lineTo(padding.left + chartWidth, padding.top + chartHeight)
|
|
|
|
|
+ ctx.lineTo(padding.left, padding.top + chartHeight)
|
|
|
ctx.closePath()
|
|
ctx.closePath()
|
|
|
ctx.fill()
|
|
ctx.fill()
|
|
|
|
|
+
|
|
|
|
|
+ // 绘制 X 轴标签
|
|
|
|
|
+ ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'
|
|
|
|
|
+ ctx.textAlign = 'center'
|
|
|
|
|
+ for (let i = 0; i < months.length; i++) {
|
|
|
|
|
+ const x = padding.left + (chartWidth / (months.length - 1)) * i
|
|
|
|
|
+ ctx.fillText(months[i], x, height - 10)
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
onMounted(() => {
|
|
|
- drawFlowChart()
|
|
|
|
|
|
|
+ drawCombinedChart()
|
|
|
|
|
+ drawEvaporationChart()
|
|
|
|
|
+ drawSoilMoistureChart()
|
|
|
|
|
|
|
|
// 监听窗口 resize 事件,重新绘制图表
|
|
// 监听窗口 resize 事件,重新绘制图表
|
|
|
- window.addEventListener('resize', drawFlowChart)
|
|
|
|
|
|
|
+ window.addEventListener('resize', () => {
|
|
|
|
|
+ drawCombinedChart()
|
|
|
|
|
+ drawEvaporationChart()
|
|
|
|
|
+ drawSoilMoistureChart()
|
|
|
|
|
+ })
|
|
|
})
|
|
})
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
|
<style scoped>
|
|
<style scoped>
|
|
|
.dashboard {
|
|
.dashboard {
|
|
|
- width: 1920px;
|
|
|
|
|
- height: 1080px;
|
|
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
position: relative;
|
|
position: relative;
|
|
|
}
|
|
}
|
|
@@ -335,7 +687,8 @@ onMounted(() => {
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
font-size: 14px;
|
|
font-size: 14px;
|
|
|
transition: all 0.3s ease;
|
|
transition: all 0.3s ease;
|
|
|
- z-index: 4;
|
|
|
|
|
|
|
+ z-index: 100;
|
|
|
|
|
+ pointer-events: auto;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.back-btn:hover {
|
|
.back-btn:hover {
|
|
@@ -362,6 +715,58 @@ onMounted(() => {
|
|
|
|
|
|
|
|
.center-space {
|
|
.center-space {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.poi-container {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ pointer-events: none;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.poi-item {
|
|
|
|
|
+ position: absolute;
|
|
|
|
|
+ transform: translate(-50%, -50%);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ pointer-events: auto;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.poi-icon {
|
|
|
|
|
+ width: 40px;
|
|
|
|
|
+ height: 40px;
|
|
|
|
|
+ object-fit: contain;
|
|
|
|
|
+ animation: pulse 2s infinite;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.poi-label {
|
|
|
|
|
+ background: rgba(0, 60, 120, 0.9);
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ margin-bottom: 5px;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
|
|
+ border: 1px solid rgba(0, 213, 255, 0.5);
|
|
|
|
|
+ box-shadow: 0 0 5px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+@keyframes pulse {
|
|
|
|
|
+ 0% {
|
|
|
|
|
+ transform: scale(1);
|
|
|
|
|
+ filter: drop-shadow(0 0 5px rgba(0, 213, 255, 0.5));
|
|
|
|
|
+ }
|
|
|
|
|
+ 50% {
|
|
|
|
|
+ transform: scale(1.1);
|
|
|
|
|
+ filter: drop-shadow(0 0 10px rgba(0, 213, 255, 0.8));
|
|
|
|
|
+ }
|
|
|
|
|
+ 100% {
|
|
|
|
|
+ transform: scale(1);
|
|
|
|
|
+ filter: drop-shadow(0 0 5px rgba(0, 213, 255, 0.5));
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
.right-sidebar {
|
|
.right-sidebar {
|
|
@@ -477,6 +882,119 @@ onMounted(() => {
|
|
|
min-height: 350px;
|
|
min-height: 350px;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+.monitor-select {
|
|
|
|
|
+ margin-bottom: 15px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.select-wrapper {
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ background: linear-gradient(90deg, rgba(0, 60, 120, 0.9), rgba(0, 100, 150, 0.7));
|
|
|
|
|
+ border: 2px solid rgba(0, 213, 255, 0.5);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 6px 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ box-shadow: 0 0 10px rgba(0, 213, 255, 0.3);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.camera-icon {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.select-wrapper select {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ border: none;
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ outline: none;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ font-family: Arial, sans-serif;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.select-wrapper select option {
|
|
|
|
|
+ background: rgba(0, 60, 120, 0.95);
|
|
|
|
|
+ color: #ffffff;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.monitor-image {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 200px;
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ border: 2px solid rgba(0, 212, 255, 0.3);
|
|
|
|
|
+ box-shadow: 0 0 15px rgba(0, 212, 255, 0.2);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.monitor-image img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: cover;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.chart-container {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 180px;
|
|
|
|
|
+ position: relative;
|
|
|
|
|
+ margin-top: 15px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.water-level-cards {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(4, 1fr);
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.water-card {
|
|
|
|
|
+ background: rgba(0, 60, 120, 0.5);
|
|
|
|
|
+ border: 1px solid rgba(0, 213, 255, 0.3);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 10px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.water-card:hover {
|
|
|
|
|
+ border-color: rgba(0, 213, 255, 0.8);
|
|
|
|
|
+ box-shadow: 0 0 10px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-title {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #62f6fb;
|
|
|
|
|
+ margin-bottom: 5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-value {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #00ffff;
|
|
|
|
|
+ text-shadow: 0 0 5px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-value.warning {
|
|
|
|
|
+ color: #ffa502;
|
|
|
|
|
+ text-shadow: 0 0 8px rgba(255, 165, 2, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-value.danger {
|
|
|
|
|
+ color: #ff4757;
|
|
|
|
|
+ text-shadow: 0 0 8px rgba(255, 71, 87, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.card-value.highlight {
|
|
|
|
|
+ color: #ff6b6b;
|
|
|
|
|
+ text-shadow: 0 0 8px rgba(255, 107, 107, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.unit {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ margin-left: 2px;
|
|
|
|
|
+ opacity: 0.8;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
.video-image {
|
|
.video-image {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
height: 100%;
|
|
height: 100%;
|
|
@@ -638,4 +1156,244 @@ onMounted(() => {
|
|
|
padding: 15px;
|
|
padding: 15px;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+/* 弹窗样式 */
|
|
|
|
|
+.modal-overlay {
|
|
|
|
|
+ position: fixed;
|
|
|
|
|
+ top: 0;
|
|
|
|
|
+ left: 0;
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ background: rgba(0, 0, 0, 0.7);
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ justify-content: center;
|
|
|
|
|
+ z-index: 1000;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal-content {
|
|
|
|
|
+ background: linear-gradient(135deg, rgba(0, 26, 51, 0.95) 0%, rgba(0, 51, 102, 0.95) 100%);
|
|
|
|
|
+ border: 2px solid rgba(0, 213, 255, 0.5);
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ width: 800px;
|
|
|
|
|
+ max-height: 80vh;
|
|
|
|
|
+ overflow-y: auto;
|
|
|
|
|
+ box-shadow: 0 0 30px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+ border-bottom: 1px solid rgba(0, 213, 255, 0.3);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal-header h2 {
|
|
|
|
|
+ margin: 0;
|
|
|
|
|
+ font-size: 24px;
|
|
|
|
|
+ color: #e0fcff;
|
|
|
|
|
+ text-shadow: 0 0 10px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.close-btn {
|
|
|
|
|
+ font-size: 32px;
|
|
|
|
|
+ color: #62f6fb;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.close-btn:hover {
|
|
|
|
|
+ color: #00ffff;
|
|
|
|
|
+ text-shadow: 0 0 10px rgba(0, 213, 255, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.modal-body {
|
|
|
|
|
+ padding: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.detail-section {
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.detail-section h4 {
|
|
|
|
|
+ margin: 0 0 10px 0;
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ color: #00ffff;
|
|
|
|
|
+ text-shadow: 0 0 5px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+ border-left: 3px solid rgba(0, 213, 255, 0.8);
|
|
|
|
|
+ padding-left: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.detail-section p {
|
|
|
|
|
+ margin: 0 0 8px 0;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #e0fcff;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.detail-section p strong {
|
|
|
|
|
+ color: #62f6fb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 弹窗头部信息布局 */
|
|
|
|
|
+.station-info-header {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ padding: 15px 20px;
|
|
|
|
|
+ background: rgba(0, 60, 120, 0.5);
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ margin-bottom: 20px;
|
|
|
|
|
+ border: 1px solid rgba(0, 213, 255, 0.3);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.station-basic {
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.station-name {
|
|
|
|
|
+ margin: 0 0 10px 0;
|
|
|
|
|
+ font-size: 28px;
|
|
|
|
|
+ color: #00ffff;
|
|
|
|
|
+ text-shadow: 0 0 10px rgba(0, 213, 255, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.station-code,
|
|
|
|
|
+.station-basin {
|
|
|
|
|
+ margin: 5px 0;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #e0fcff;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.station-qr {
|
|
|
|
|
+ width: 120px;
|
|
|
|
|
+ height: 120px;
|
|
|
|
|
+ border: 2px solid rgba(0, 213, 255, 0.5);
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ padding: 5px;
|
|
|
|
|
+ background: rgba(255, 255, 255, 0.95);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.station-qr img {
|
|
|
|
|
+ width: 100%;
|
|
|
|
|
+ height: 100%;
|
|
|
|
|
+ object-fit: contain;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 数据网格布局 */
|
|
|
|
|
+.data-grid {
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: repeat(2, 1fr);
|
|
|
|
|
+ gap: 15px;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-item {
|
|
|
|
|
+ background: rgba(0, 60, 120, 0.3);
|
|
|
|
|
+ border: 1px solid rgba(0, 213, 255, 0.2);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ flex-direction: column;
|
|
|
|
|
+ gap: 5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-label {
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ color: #62f6fb;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-value {
|
|
|
|
|
+ font-size: 18px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #00ffff;
|
|
|
|
|
+ text-shadow: 0 0 5px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 极值数据样式 */
|
|
|
|
|
+.extreme-data,
|
|
|
|
|
+.base-info,
|
|
|
|
|
+.supplement-info {
|
|
|
|
|
+ background: rgba(0, 60, 120, 0.3);
|
|
|
|
|
+ border: 1px solid rgba(0, 213, 255, 0.2);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+ padding: 15px;
|
|
|
|
|
+ margin-top: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-row {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ margin-bottom: 10px;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-row:last-child {
|
|
|
|
|
+ margin-bottom: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-label {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #62f6fb;
|
|
|
|
|
+ min-width: 80px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-text {
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #e0fcff;
|
|
|
|
|
+ line-height: 1.6;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-value {
|
|
|
|
|
+ font-size: 16px;
|
|
|
|
|
+ font-weight: bold;
|
|
|
|
|
+ color: #00ffff;
|
|
|
|
|
+ text-shadow: 0 0 5px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-value.highlight {
|
|
|
|
|
+ color: #ff6b6b;
|
|
|
|
|
+ text-shadow: 0 0 8px rgba(255, 107, 107, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-value.low {
|
|
|
|
|
+ color: #4ecdc4;
|
|
|
|
|
+ text-shadow: 0 0 8px rgba(78, 205, 196, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-value.warning {
|
|
|
|
|
+ color: #ffa502;
|
|
|
|
|
+ text-shadow: 0 0 8px rgba(255, 165, 2, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-value.danger {
|
|
|
|
|
+ color: #ff4757;
|
|
|
|
|
+ text-shadow: 0 0 8px rgba(255, 71, 87, 0.8);
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.data-note {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: rgba(255, 255, 255, 0.6);
|
|
|
|
|
+ margin-left: 5px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+/* 更多按钮样式 */
|
|
|
|
|
+.more-btn {
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ color: #62f6fb;
|
|
|
|
|
+ cursor: pointer;
|
|
|
|
|
+ transition: all 0.3s ease;
|
|
|
|
|
+ padding: 4px 8px;
|
|
|
|
|
+ border: 1px solid rgba(0, 213, 255, 0.3);
|
|
|
|
|
+ border-radius: 4px;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+.more-btn:hover {
|
|
|
|
|
+ color: #00ffff;
|
|
|
|
|
+ border-color: rgba(0, 213, 255, 0.8);
|
|
|
|
|
+ box-shadow: 0 0 5px rgba(0, 213, 255, 0.5);
|
|
|
|
|
+}
|
|
|
</style>
|
|
</style>
|