|
|
<!DOCTYPE html>
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
<meta charset="UTF-8">
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
<title>海洋数据成果展示</title>
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.min.js"></script>
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.0/echarts.min.js"></script>
|
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts-gl/2.0.0/echarts-gl.min.js"></script>
|
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
|
|
<style>
|
|
|
* {
|
|
|
margin: 0;
|
|
|
padding: 0;
|
|
|
box-sizing: border-box;
|
|
|
font-family: 'Segoe UI', Arial, sans-serif;
|
|
|
}
|
|
|
|
|
|
body {
|
|
|
background: linear-gradient(135deg, #0c1a2d 0%, #0d2b4a 100%);
|
|
|
color: #e0f0ff;
|
|
|
overflow-x: hidden;
|
|
|
min-height: 100vh;
|
|
|
}
|
|
|
|
|
|
/* 新增顶部标题栏样式 */
|
|
|
.topDiv {
|
|
|
width: 100%;
|
|
|
height: 8vh;
|
|
|
background: url('/assets/img/topBg.png') top no-repeat;
|
|
|
background-size: 100% 115%;
|
|
|
display: flex;
|
|
|
justify-content: center;
|
|
|
z-index: 990;
|
|
|
position: relative;
|
|
|
/* background-color: rgba(12, 42, 73, 0.8); */
|
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
|
|
}
|
|
|
|
|
|
.topDiv .center {
|
|
|
font-family: PangMenZhengDao, 'Source Han Sans CN';
|
|
|
font-weight: 400;
|
|
|
font-size: 4.1vh;
|
|
|
color: #ffffff;
|
|
|
align-self: center;
|
|
|
margin-top: -40px;
|
|
|
}
|
|
|
|
|
|
.topDiv .left {
|
|
|
position: absolute;
|
|
|
top: 3vh;
|
|
|
left: 1vw;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
|
|
|
.topDiv .left span:nth-child(1) {
|
|
|
font-weight: bold;
|
|
|
font-size: 0.8vw;
|
|
|
margin-right: 0.5vh;
|
|
|
}
|
|
|
|
|
|
.topDiv .left span:nth-child(2) {
|
|
|
font-weight: bold;
|
|
|
font-size: 1.2vw;
|
|
|
}
|
|
|
|
|
|
.topDiv .right {
|
|
|
position: absolute;
|
|
|
top: 3vh;
|
|
|
right: 0;
|
|
|
color: #ffffff;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
}
|
|
|
|
|
|
.topDiv .right img:nth-child(1) {}
|
|
|
|
|
|
.topDiv .right span {
|
|
|
font-weight: bold;
|
|
|
font-size: 1.2vw;
|
|
|
}
|
|
|
|
|
|
.topDiv .icon-home {
|
|
|
position: absolute;
|
|
|
top: 1vh;
|
|
|
right: 8vw;
|
|
|
width: 4vh;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
|
|
|
/* 控制面板 */
|
|
|
.control-panel {
|
|
|
width: 100%;
|
|
|
padding: 15px 20px;
|
|
|
background: rgba(12, 42, 73, 0.7);
|
|
|
backdrop-filter: blur(10px);
|
|
|
border-bottom: 1px solid rgba(64, 156, 255, 0.3);
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
flex-wrap: wrap;
|
|
|
gap: 15px;
|
|
|
}
|
|
|
|
|
|
.control-group {
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
gap: 10px;
|
|
|
}
|
|
|
|
|
|
.control-label {
|
|
|
font-size: 0.9rem;
|
|
|
color: #a0d2ff;
|
|
|
}
|
|
|
|
|
|
.control-select {
|
|
|
background: rgba(25, 55, 95, 0.8);
|
|
|
border: 1px solid rgba(64, 156, 255, 0.5);
|
|
|
border-radius: 4px;
|
|
|
color: #e0f0ff;
|
|
|
padding: 6px 12px;
|
|
|
font-size: 0.9rem;
|
|
|
cursor: pointer;
|
|
|
}
|
|
|
|
|
|
.control-button {
|
|
|
background: linear-gradient(90deg, #1a6aa9 0%, #4fc3f7 100%);
|
|
|
border: none;
|
|
|
border-radius: 4px;
|
|
|
color: #ffffff;
|
|
|
padding: 6px 15px;
|
|
|
font-size: 0.9rem;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.control-button:hover {
|
|
|
background: linear-gradient(90deg, #4fc3f7 0%, #1a6aa9 100%);
|
|
|
box-shadow: 0 0 10px rgba(79, 195, 247, 0.5);
|
|
|
}
|
|
|
|
|
|
/* 仪表盘布局 */
|
|
|
.dashboard {
|
|
|
display: grid;
|
|
|
grid-template-columns: repeat(12, 1fr);
|
|
|
grid-template-rows: repeat(6, 1fr);
|
|
|
gap: 15px;
|
|
|
padding: 15px;
|
|
|
height: calc(100vh - 8vh - 80px);
|
|
|
}
|
|
|
|
|
|
.dashboard-item {
|
|
|
background: rgba(12, 42, 73, 0.7);
|
|
|
backdrop-filter: blur(10px);
|
|
|
border: 1px solid rgba(64, 156, 255, 0.3);
|
|
|
border-radius: 8px;
|
|
|
overflow: hidden;
|
|
|
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
|
|
|
transition: all 0.3s ease;
|
|
|
position: relative;
|
|
|
}
|
|
|
|
|
|
.dashboard-item:hover {
|
|
|
transform: translateY(-5px);
|
|
|
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.4);
|
|
|
border-color: rgba(79, 195, 247, 0.5);
|
|
|
}
|
|
|
|
|
|
.dashboard-item-header {
|
|
|
background: url(/assets/img/common/homeTitle2.png) no-repeat;
|
|
|
background-position: center;
|
|
|
background-size: 120% 120%;
|
|
|
height: 50px;
|
|
|
padding: 10px 15px;
|
|
|
border-bottom: 1px solid rgba(79, 195, 247, 0.3);
|
|
|
text-align: center;
|
|
|
/* height: 40px;
|
|
|
background: linear-gradient(to right, #1a3a5f, #2a5a8f);
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
align-items: center;
|
|
|
padding: 0 15px;
|
|
|
border-bottom: 1px solid rgba(79, 195, 247, 0.3);
|
|
|
text-align: center; */
|
|
|
}
|
|
|
|
|
|
.dashboard-item-title {
|
|
|
font-size: 1.2rem;
|
|
|
font-weight: 600;
|
|
|
color: #ffffff;
|
|
|
}
|
|
|
|
|
|
.dashboard-item-controls {
|
|
|
display: flex;
|
|
|
gap: 5px;
|
|
|
}
|
|
|
|
|
|
.dashboard-item-control {
|
|
|
background: rgba(255, 255, 255, 0.1);
|
|
|
border: none;
|
|
|
border-radius: 3px;
|
|
|
color: #e0f2ff;
|
|
|
width: 24px;
|
|
|
height: 24px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
cursor: pointer;
|
|
|
transition: all 0.2s ease;
|
|
|
}
|
|
|
|
|
|
.dashboard-item-control:hover {
|
|
|
background: rgba(255, 255, 255, 0.2);
|
|
|
}
|
|
|
|
|
|
.dashboard-item-content {
|
|
|
height: calc(100% - 50px);
|
|
|
padding: 10px;
|
|
|
}
|
|
|
|
|
|
/* 特定布局 */
|
|
|
.earth-globe {
|
|
|
grid-column: 1 / 9;
|
|
|
grid-row: 1 / 5;
|
|
|
}
|
|
|
|
|
|
.time-series {
|
|
|
grid-column: 9 / 13;
|
|
|
grid-row: 1 / 4;
|
|
|
}
|
|
|
|
|
|
.stats-cards {
|
|
|
grid-column: 1 / 5;
|
|
|
grid-row: 5 / 7;
|
|
|
}
|
|
|
|
|
|
.profile-chart {
|
|
|
grid-column: 5 / 9;
|
|
|
grid-row: 5 / 7;
|
|
|
}
|
|
|
|
|
|
.data-table {
|
|
|
grid-column: 9 / 13;
|
|
|
grid-row: 4 / 7;
|
|
|
}
|
|
|
|
|
|
/* 统计卡片组 */
|
|
|
.stats-container {
|
|
|
display: grid;
|
|
|
grid-template-columns: 1fr 1fr;
|
|
|
grid-template-rows: 1fr 1fr;
|
|
|
gap: 10px;
|
|
|
height: 100%;
|
|
|
}
|
|
|
|
|
|
.stat-card {
|
|
|
background: rgba(25, 55, 95, 0.6);
|
|
|
border-radius: 8px;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
justify-content: center;
|
|
|
align-items: center;
|
|
|
/* padding: 15px; */
|
|
|
border: 1px solid rgba(64, 156, 255, 0.2);
|
|
|
transition: all 0.3s ease;
|
|
|
}
|
|
|
|
|
|
.stat-card:hover {
|
|
|
transform: scale(1.03);
|
|
|
border-color: rgba(79, 195, 247, 0.5);
|
|
|
box-shadow: 0 0 15px rgba(79, 195, 247, 0.3);
|
|
|
}
|
|
|
|
|
|
.stat-value {
|
|
|
font-size: 2rem;
|
|
|
font-weight: bold;
|
|
|
color: #4fc3f7;
|
|
|
margin-bottom: 5px;
|
|
|
text-shadow: 0 0 10px rgba(79, 195, 247, 0.5);
|
|
|
}
|
|
|
|
|
|
.stat-label {
|
|
|
font-size: 0.9rem;
|
|
|
color: #a0d2ff;
|
|
|
text-align: center;
|
|
|
}
|
|
|
|
|
|
/* 数据表格 */
|
|
|
.data-table-content {
|
|
|
height: 100%;
|
|
|
overflow-y: auto;
|
|
|
}
|
|
|
|
|
|
.data-table-row {
|
|
|
display: flex;
|
|
|
justify-content: space-between;
|
|
|
padding: 10px 5px;
|
|
|
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
|
|
}
|
|
|
|
|
|
.data-table-row:last-child {
|
|
|
border-bottom: none;
|
|
|
}
|
|
|
|
|
|
.data-table-cell {
|
|
|
flex: 1;
|
|
|
text-align: center;
|
|
|
font-size: 0.9rem;
|
|
|
}
|
|
|
|
|
|
.data-table-header {
|
|
|
font-weight: bold;
|
|
|
color: #4fc3f7;
|
|
|
border-bottom: 1px solid rgba(79, 195, 247, 0.3);
|
|
|
padding-bottom: 8px;
|
|
|
}
|
|
|
|
|
|
/* 响应式调整 */
|
|
|
@media (max-width: 1200px) {
|
|
|
.dashboard {
|
|
|
grid-template-columns: repeat(6, 1fr);
|
|
|
grid-template-rows: repeat(8, 1fr);
|
|
|
}
|
|
|
|
|
|
.earth-globe {
|
|
|
grid-column: 1 / 7;
|
|
|
grid-row: 1 / 5;
|
|
|
}
|
|
|
|
|
|
.time-series {
|
|
|
grid-column: 1 / 7;
|
|
|
grid-row: 5 / 7;
|
|
|
}
|
|
|
|
|
|
.stats-cards {
|
|
|
grid-column: 1 / 4;
|
|
|
grid-row: 7 / 9;
|
|
|
}
|
|
|
|
|
|
.profile-chart {
|
|
|
grid-column: 4 / 7;
|
|
|
grid-row: 7 / 9;
|
|
|
}
|
|
|
|
|
|
.data-table {
|
|
|
grid-column: 1 / 7;
|
|
|
grid-row: 9 / 11;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
/* 滚动条样式 */
|
|
|
::-webkit-scrollbar {
|
|
|
width: 8px;
|
|
|
height: 8px;
|
|
|
}
|
|
|
|
|
|
::-webkit-scrollbar-track {
|
|
|
background: rgba(12, 42, 73, 0.3);
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
::-webkit-scrollbar-thumb {
|
|
|
background: rgba(106, 149, 201, 0.5);
|
|
|
border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
::-webkit-scrollbar-thumb:hover {
|
|
|
background: rgba(106, 149, 201, 0.7);
|
|
|
}
|
|
|
|
|
|
/* 动画效果 */
|
|
|
@keyframes pulse {
|
|
|
0% {
|
|
|
box-shadow: 0 0 0 0 rgba(79, 195, 247, 0.4);
|
|
|
}
|
|
|
|
|
|
70% {
|
|
|
box-shadow: 0 0 0 10px rgba(79, 195, 247, 0);
|
|
|
}
|
|
|
|
|
|
100% {
|
|
|
box-shadow: 0 0 0 0 rgba(79, 195, 247, 0);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
.pulse {
|
|
|
animation: pulse 2s infinite;
|
|
|
}
|
|
|
</style>
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
<!-- 顶部标题栏 -->
|
|
|
<div class="topDiv">
|
|
|
<div class="left">
|
|
|
<span id="current-date">2025年9月17日</span>
|
|
|
<span id="current-time">15:18:00</span>
|
|
|
</div>
|
|
|
<div class="center">全球海洋环境综合监测与分析平台</div>
|
|
|
<div class="right">
|
|
|
<img src="/common/images/logo_gt.png" alt="" />
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 控制面板 -->
|
|
|
<div class="control-panel">
|
|
|
<div class="control-group">
|
|
|
<span class="control-label">数据类型:</span>
|
|
|
<select class="control-select" id="dataTypeSelect">
|
|
|
<option value="temperature">海水温度</option>
|
|
|
<option value="chlorophyll">叶绿素浓度</option>
|
|
|
<option value="salinity">盐度</option>
|
|
|
<option value="wave">波浪数据</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
|
|
|
<div class="control-group">
|
|
|
<span class="control-label">时间范围:</span>
|
|
|
<select class="control-select" id="timeRangeSelect">
|
|
|
<option value="24h">过去24小时</option>
|
|
|
<option value="7d">过去7天</option>
|
|
|
<option value="30d">过去30天</option>
|
|
|
<option value="custom">自定义</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
|
|
|
<div class="control-group">
|
|
|
<span class="control-label">地理区域:</span>
|
|
|
<select class="control-select" id="regionSelect">
|
|
|
<option value="global">全球</option>
|
|
|
<option value="pacific">太平洋</option>
|
|
|
<option value="atlantic">大西洋</option>
|
|
|
<option value="indian">印度洋</option>
|
|
|
<option value="south-china-sea">南海</option>
|
|
|
</select>
|
|
|
</div>
|
|
|
|
|
|
<div class="control-group">
|
|
|
<button class="control-button" id="playAnimation">
|
|
|
<i class="fas fa-play"></i> 播放动画
|
|
|
</button>
|
|
|
<button class="control-button" id="pauseAnimation">
|
|
|
<i class="fas fa-pause"></i> 暂停
|
|
|
</button>
|
|
|
<button class="control-button" id="resetView">
|
|
|
<i class="fas fa-sync"></i> 重置视图
|
|
|
</button>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 仪表盘 -->
|
|
|
<div class="dashboard">
|
|
|
<!-- 3D地球模型 -->
|
|
|
<div class="dashboard-item earth-globe">
|
|
|
<div class="dashboard-item-header">
|
|
|
<div class="dashboard-item-title">全球海洋温度分布</div>
|
|
|
<!-- <div class="dashboard-item-controls">
|
|
|
<button class="dashboard-item-control"><i class="fas fa-expand"></i></button>
|
|
|
</div> -->
|
|
|
</div>
|
|
|
<div class="dashboard-item-content" id="earthGlobe"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 时序图 -->
|
|
|
<div class="dashboard-item time-series">
|
|
|
<div class="dashboard-item-header">
|
|
|
<div class="dashboard-item-title">特定区域时序变化</div>
|
|
|
<!-- <div class="dashboard-item-controls">
|
|
|
<button class="dashboard-item-control"><i class="fas fa-expand"></i></button>
|
|
|
</div> -->
|
|
|
</div>
|
|
|
<div class="dashboard-item-content" id="timeSeriesChart"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 统计卡片组 -->
|
|
|
<div class="dashboard-item stats-cards">
|
|
|
<div class="dashboard-item-header">
|
|
|
<div class="dashboard-item-title">关键指标</div>
|
|
|
<!-- <div class="dashboard-item-controls">
|
|
|
<button class="dashboard-item-control"><i class="fas fa-sync"></i></button>
|
|
|
</div> -->
|
|
|
</div>
|
|
|
<div class="dashboard-item-content">
|
|
|
<div class="stats-container">
|
|
|
<div class="stat-card pulse">
|
|
|
<div class="stat-value" id="avgTemp">24.7°C</div>
|
|
|
<div class="stat-label">平均温度</div>
|
|
|
</div>
|
|
|
<div class="stat-card">
|
|
|
<div class="stat-value" id="maxWave">12.3m</div>
|
|
|
<div class="stat-label">最大波高</div>
|
|
|
</div>
|
|
|
<div class="stat-card">
|
|
|
<div class="stat-value" id="anomalyCount">8</div>
|
|
|
<div class="stat-label">异常区域数量</div>
|
|
|
</div>
|
|
|
<div class="stat-card">
|
|
|
<div class="stat-value" id="avgSalinity">35.2‰</div>
|
|
|
<div class="stat-label">平均盐度</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 剖面图 -->
|
|
|
<div class="dashboard-item profile-chart">
|
|
|
<div class="dashboard-item-header">
|
|
|
<div class="dashboard-item-title">温度/盐度垂直剖面</div>
|
|
|
<!-- <div class="dashboard-item-controls">
|
|
|
<button class="dashboard-item-control"><i class="fas fa-expand"></i></button>
|
|
|
</div> -->
|
|
|
</div>
|
|
|
<div class="dashboard-item-content" id="profileChart"></div>
|
|
|
</div>
|
|
|
|
|
|
<!-- 数据表格 -->
|
|
|
<div class="dashboard-item data-table">
|
|
|
<div class="dashboard-item-header">
|
|
|
<div class="dashboard-item-title">区域数据详情</div>
|
|
|
<!-- <div class="dashboard-item-controls">
|
|
|
<button class="dashboard-item-control"><i class="fas fa-download"></i></button>
|
|
|
</div> -->
|
|
|
</div>
|
|
|
<div class="dashboard-item-content">
|
|
|
<div class="data-table-content" id="dataTableContent">
|
|
|
<!-- 表格内容将通过JavaScript动态生成 -->
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
<script>
|
|
|
// 全局变量
|
|
|
let earthChart, timeSeriesChart, profileChart;
|
|
|
let animationInterval;
|
|
|
let currentDataType = 'temperature';
|
|
|
let currentTimeRange = '24h';
|
|
|
let currentRegion = 'global';
|
|
|
|
|
|
// 数据存储
|
|
|
let timeSeriesData = {
|
|
|
time: [],
|
|
|
temperature: [],
|
|
|
chlorophyll: [],
|
|
|
salinity: []
|
|
|
};
|
|
|
|
|
|
let profileData = {
|
|
|
depth: [],
|
|
|
temperature: [],
|
|
|
salinity: []
|
|
|
};
|
|
|
|
|
|
let tableData = [];
|
|
|
|
|
|
// 初始化函数
|
|
|
function init() {
|
|
|
// 更新当前时间
|
|
|
updateCurrentTime();
|
|
|
setInterval(updateCurrentTime, 1000);
|
|
|
|
|
|
// 初始化数据
|
|
|
initData();
|
|
|
|
|
|
// 初始化图表
|
|
|
initEarthGlobe();
|
|
|
initTimeSeriesChart();
|
|
|
initProfileChart();
|
|
|
initDataTable();
|
|
|
|
|
|
// 添加事件监听器
|
|
|
document.getElementById('dataTypeSelect').addEventListener('change', function (e) {
|
|
|
currentDataType = e.target.value;
|
|
|
updateAllCharts();
|
|
|
});
|
|
|
|
|
|
document.getElementById('timeRangeSelect').addEventListener('change', function (e) {
|
|
|
currentTimeRange = e.target.value;
|
|
|
updateAllCharts();
|
|
|
});
|
|
|
|
|
|
document.getElementById('regionSelect').addEventListener('change', function (e) {
|
|
|
currentRegion = e.target.value;
|
|
|
updateAllCharts();
|
|
|
});
|
|
|
|
|
|
document.getElementById('playAnimation').addEventListener('click', startAnimation);
|
|
|
document.getElementById('pauseAnimation').addEventListener('click', pauseAnimation);
|
|
|
document.getElementById('resetView').addEventListener('click', resetView);
|
|
|
|
|
|
// 启动数据更新定时器
|
|
|
setInterval(updateAllData, 3000);
|
|
|
}
|
|
|
|
|
|
// 初始化数据
|
|
|
function initData() {
|
|
|
// 初始化时序数据
|
|
|
const now = new Date();
|
|
|
for (let i = 23; i >= 0; i--) {
|
|
|
const time = new Date(now);
|
|
|
time.setHours(now.getHours() - i);
|
|
|
timeSeriesData.time.push(time.getHours() + ':00');
|
|
|
|
|
|
// 生成模拟数据
|
|
|
timeSeriesData.temperature.push(20 + Math.sin(i / 3) * 5 + Math.random() * 2);
|
|
|
timeSeriesData.chlorophyll.push(0.2 + Math.sin(i / 4) * 0.15 + Math.random() * 0.1);
|
|
|
timeSeriesData.salinity.push(34 + Math.cos(i / 5) * 1 + Math.random() * 0.5);
|
|
|
}
|
|
|
|
|
|
// 初始化剖面数据
|
|
|
for (let i = 0; i <= 1000; i += 50) {
|
|
|
profileData.depth.push(i);
|
|
|
|
|
|
// 生成模拟数据并保留两位小数
|
|
|
const temp = parseFloat(25 - (i / 1000) * 20 + Math.random() * 2).toFixed(2);
|
|
|
const salinity = parseFloat(34 + (i / 1000) * 1 + Math.random() * 0.5).toFixed(2);
|
|
|
|
|
|
profileData.temperature.push(parseFloat(temp)); // 转换回数字用于计算
|
|
|
profileData.salinity.push(parseFloat(salinity)); // 转换回数字用于计算
|
|
|
}
|
|
|
|
|
|
// 初始化表格数据
|
|
|
tableData = [
|
|
|
{ region: '南海', temperature: 28.5, salinity: 34.1, chlorophyll: 0.32 },
|
|
|
{ region: '东海', temperature: 22.3, salinity: 33.8, chlorophyll: 0.45 },
|
|
|
{ region: '黄海', temperature: 18.7, salinity: 32.5, chlorophyll: 0.28 },
|
|
|
{ region: '渤海', temperature: 16.2, salinity: 31.2, chlorophyll: 0.51 },
|
|
|
{ region: '西太平洋', temperature: 26.8, salinity: 35.1, chlorophyll: 0.18 }
|
|
|
];
|
|
|
}
|
|
|
|
|
|
// 更新当前时间
|
|
|
function updateCurrentTime() {
|
|
|
const now = new Date();
|
|
|
const dateStr = `${now.getFullYear()}年${now.getMonth() + 1}月${now.getDate()}日`;
|
|
|
const timeStr = now.toLocaleTimeString('zh-CN', { hour12: false });
|
|
|
|
|
|
document.getElementById('current-date').textContent = dateStr;
|
|
|
document.getElementById('current-time').textContent = timeStr;
|
|
|
}
|
|
|
|
|
|
// 初始化3D地球
|
|
|
function initEarthGlobe() {
|
|
|
const chartContainer = document.getElementById('earthGlobe');
|
|
|
earthChart = echarts.init(chartContainer, null, { renderer: 'canvas' });
|
|
|
|
|
|
// 配置项
|
|
|
const option = {
|
|
|
backgroundColor: 'transparent',
|
|
|
globe: {
|
|
|
baseTexture: "/assets/img/earth-topology.png",
|
|
|
heightTexture: "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCAxMDI0IDUxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjEwMjQiIGhlaWdodD0iNTEyIiBmaWxsPSIjMGMxYTJkIi8+CjxwYXRoIGQ9Ik01MTIgMjU2QzUxMiAzOTcuODgzIDM5Ny44ODMgNTEyIDI1NiA1MTIgMTA0LjExNyA1MTIgMCAzOTcuODgzIDAgMjU2QzAgMTE0LjExNyAxMDQuMTE3IDAgMjU2IDBDMzk3Ljg4MyAwIDUxMiAxMTQuMTE3IDUxMiAyNTZaIiBmaWxsPSIjMWIzZDc5Ii8+Cjwvc3ZnPg==",
|
|
|
displacementScale: 0.04,
|
|
|
displacementQuality: 'medium',
|
|
|
environment: "/assets/img/night-sky.png",
|
|
|
globeRadius: 100,
|
|
|
globeOuterRadius: 120,
|
|
|
shading: 'realistic',
|
|
|
realisticMaterial: {
|
|
|
roughness: 0.8,
|
|
|
metalness: 0
|
|
|
},
|
|
|
postEffect: {
|
|
|
enable: true,
|
|
|
bloom: {
|
|
|
enable: true,
|
|
|
intensity: 0.1
|
|
|
}
|
|
|
},
|
|
|
light: {
|
|
|
main: {
|
|
|
intensity: 2,
|
|
|
shadow: true
|
|
|
},
|
|
|
ambientCubemap: {
|
|
|
texture: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAyNCIgaGVpZ2h0PSI1MTIiIHZpZXdCb3g9IjAgMCAxMDI0IDUxMiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjEwMjQiIGhlaWdodD0iNTEyIiBmaWxsPSIjMGMxYTJkIi8+Cjwvc3ZnPg=='
|
|
|
}
|
|
|
},
|
|
|
viewControl: {
|
|
|
autoRotate: true,
|
|
|
autoRotateSpeed: 1,
|
|
|
// autoRotateDirection: 'left', // 固定向左旋转
|
|
|
targetCoord: [105, 35], // 默认视角对准中国 (经度105, 纬度35)
|
|
|
zoom: 1.2 // 适当的缩放级别
|
|
|
},
|
|
|
layers: [
|
|
|
{
|
|
|
type: 'blend',
|
|
|
blendTo: 'emission',
|
|
|
texture: "/assets/img/earth-night.jpg",
|
|
|
intensity: 1.5
|
|
|
}
|
|
|
]
|
|
|
},
|
|
|
series: [] // 初始时系列为空
|
|
|
};
|
|
|
|
|
|
earthChart.setOption(option);
|
|
|
|
|
|
// 添加温度数据层
|
|
|
updateEarthData();
|
|
|
}
|
|
|
|
|
|
function updateEarthData() {
|
|
|
const temperatureData = [];
|
|
|
|
|
|
// 生成模拟温度数据
|
|
|
for (let i = 0; i < 1000; i++) {
|
|
|
const lat = Math.random() * 180 - 90;
|
|
|
const lng = Math.random() * 360 - 180;
|
|
|
const temp = 10 + Math.random() * 25; // 10-35°C
|
|
|
|
|
|
// 使用4个值:[经度, 纬度, 高度(设为0使数据贴合地球表面), 温度值]
|
|
|
temperatureData.push([lng, lat, 0, temp]);
|
|
|
}
|
|
|
|
|
|
// 只更新系列数据,使用 replaceMerge 确保不重置其他配置
|
|
|
const option = {
|
|
|
series: [{
|
|
|
type: 'scatter3D',
|
|
|
coordinateSystem: 'globe',
|
|
|
blendMode: 'lighter',
|
|
|
symbolSize: 2,
|
|
|
itemStyle: {
|
|
|
color: function (params) {
|
|
|
const temp = params.value[3]; // 温度值现在是第4个元素
|
|
|
if (temp < 15) return '#4A90E2'; // 冷色
|
|
|
if (temp < 20) return '#7ED321'; // 凉色
|
|
|
if (temp < 25) return '#F5A623'; // 暖色
|
|
|
return '#D0021B'; // 热色
|
|
|
},
|
|
|
opacity: 0.8
|
|
|
},
|
|
|
data: temperatureData
|
|
|
}]
|
|
|
};
|
|
|
|
|
|
// 使用 replaceMerge 方式更新,避免重置视图
|
|
|
earthChart.setOption(option, {
|
|
|
replaceMerge: ['series']
|
|
|
});
|
|
|
}
|
|
|
// 初始化时序图
|
|
|
function initTimeSeriesChart() {
|
|
|
const chartContainer = document.getElementById('timeSeriesChart');
|
|
|
timeSeriesChart = echarts.init(chartContainer, null, { renderer: 'canvas' });
|
|
|
|
|
|
updateTimeSeriesChart();
|
|
|
}
|
|
|
|
|
|
function updateTimeSeriesChart() {
|
|
|
const option = {
|
|
|
backgroundColor: 'transparent',
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
axisPointer: {
|
|
|
type: 'shadow'
|
|
|
},
|
|
|
formatter: function (params) {
|
|
|
let result = params[0].name + '<br/>';
|
|
|
params.forEach(param => {
|
|
|
// 保留两位小数
|
|
|
const value = parseFloat(param.value).toFixed(2);
|
|
|
result += `${param.seriesName}: ${value}${param.seriesName.includes('温度') ? '°C' : param.seriesName.includes('叶绿素') ? 'mg/m³' : '‰'}<br/>`;
|
|
|
});
|
|
|
return result;
|
|
|
}
|
|
|
},
|
|
|
legend: {
|
|
|
data: ['温度', '叶绿素', '盐度'],
|
|
|
textStyle: {
|
|
|
color: '#e0f0ff'
|
|
|
},
|
|
|
top: 10
|
|
|
},
|
|
|
grid: {
|
|
|
left: '3%',
|
|
|
right: '4%',
|
|
|
bottom: '3%',
|
|
|
top: '20%',
|
|
|
containLabel: true
|
|
|
},
|
|
|
xAxis: {
|
|
|
type: 'category',
|
|
|
data: timeSeriesData.time,
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#4fc3f7'
|
|
|
}
|
|
|
},
|
|
|
axisLabel: {
|
|
|
color: '#a0d2ff'
|
|
|
}
|
|
|
},
|
|
|
yAxis: [
|
|
|
{
|
|
|
type: 'value',
|
|
|
name: '温度(°C)',
|
|
|
position: 'left',
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#ff7043'
|
|
|
}
|
|
|
},
|
|
|
axisLabel: {
|
|
|
formatter: '{value} °C',
|
|
|
color: '#a0d2ff'
|
|
|
},
|
|
|
splitLine: {
|
|
|
lineStyle: {
|
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
type: 'value',
|
|
|
name: '叶绿素(mg/m³)',
|
|
|
position: 'right',
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#66bb6a'
|
|
|
}
|
|
|
},
|
|
|
axisLabel: {
|
|
|
formatter: '{value} mg/m³',
|
|
|
color: '#a0d2ff'
|
|
|
},
|
|
|
splitLine: {
|
|
|
show: false
|
|
|
}
|
|
|
}
|
|
|
],
|
|
|
series: [
|
|
|
{
|
|
|
name: '温度',
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
lineStyle: {
|
|
|
width: 3,
|
|
|
color: '#ff7043'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
color: {
|
|
|
type: 'linear',
|
|
|
x: 0,
|
|
|
y: 0,
|
|
|
x2: 0,
|
|
|
y2: 1,
|
|
|
colorStops: [{
|
|
|
offset: 0, color: 'rgba(255, 112, 67, 0.5)'
|
|
|
}, {
|
|
|
offset: 1, color: 'rgba(255, 112, 67, 0.1)'
|
|
|
}]
|
|
|
}
|
|
|
},
|
|
|
data: timeSeriesData.temperature.map(value => parseFloat(value).toFixed(2))
|
|
|
},
|
|
|
{
|
|
|
name: '叶绿素',
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
yAxisIndex: 1,
|
|
|
lineStyle: {
|
|
|
width: 3,
|
|
|
color: '#66bb6a'
|
|
|
},
|
|
|
areaStyle: {
|
|
|
color: {
|
|
|
type: 'linear',
|
|
|
x: 0,
|
|
|
y: 0,
|
|
|
x2: 0,
|
|
|
y2: 1,
|
|
|
colorStops: [{
|
|
|
offset: 0, color: 'rgba(102, 187, 106, 0.5)'
|
|
|
}, {
|
|
|
offset: 1, color: 'rgba(102, 187, 106, 0.1)'
|
|
|
}]
|
|
|
}
|
|
|
},
|
|
|
data: timeSeriesData.chlorophyll.map(value => parseFloat(value).toFixed(2))
|
|
|
},
|
|
|
{
|
|
|
name: '盐度',
|
|
|
type: 'line',
|
|
|
smooth: true,
|
|
|
lineStyle: {
|
|
|
width: 3,
|
|
|
color: '#4fc3f7'
|
|
|
},
|
|
|
data: timeSeriesData.salinity.map(value => parseFloat(value).toFixed(2))
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
|
|
|
timeSeriesChart.setOption(option);
|
|
|
}
|
|
|
|
|
|
// 初始化剖面图
|
|
|
function initProfileChart() {
|
|
|
const chartContainer = document.getElementById('profileChart');
|
|
|
profileChart = echarts.init(chartContainer, null, { renderer: 'canvas' });
|
|
|
|
|
|
updateProfileChart();
|
|
|
}
|
|
|
|
|
|
// 修改 updateProfileChart 函数中的 tooltip formatter 部分
|
|
|
function updateProfileChart() {
|
|
|
const option = {
|
|
|
backgroundColor: 'transparent',
|
|
|
tooltip: {
|
|
|
trigger: 'axis',
|
|
|
axisPointer: {
|
|
|
type: 'cross'
|
|
|
},
|
|
|
formatter: function (params) {
|
|
|
// 自定义提示框内容,只显示需要的信息,并保留两位小数
|
|
|
let result = '';
|
|
|
params.forEach((param, index) => {
|
|
|
if (index === 0) {
|
|
|
result += `深度: ${param.data[1]}m<br/>`;
|
|
|
}
|
|
|
// 保留两位小数
|
|
|
const value = parseFloat(param.data[0]).toFixed(2);
|
|
|
result += `${param.seriesName}: ${value}${param.seriesName.includes('温度') ? '°C' : '‰'}<br/>`;
|
|
|
});
|
|
|
return result;
|
|
|
}
|
|
|
},
|
|
|
legend: {
|
|
|
data: ['温度', '盐度'],
|
|
|
textStyle: {
|
|
|
color: '#e0f0ff'
|
|
|
},
|
|
|
top: 10
|
|
|
},
|
|
|
grid: {
|
|
|
left: '3%',
|
|
|
right: '4%',
|
|
|
bottom: '3%',
|
|
|
top: '15%',
|
|
|
containLabel: true
|
|
|
},
|
|
|
xAxis: [
|
|
|
{
|
|
|
type: 'value',
|
|
|
name: '温度(°C)',
|
|
|
position: 'bottom',
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#ff7043'
|
|
|
}
|
|
|
},
|
|
|
axisLabel: {
|
|
|
formatter: '{value} °C',
|
|
|
color: '#a0d2ff'
|
|
|
},
|
|
|
splitLine: {
|
|
|
lineStyle: {
|
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
type: 'value',
|
|
|
name: '盐度(‰)',
|
|
|
position: 'top',
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#4fc3f7'
|
|
|
}
|
|
|
},
|
|
|
axisLabel: {
|
|
|
formatter: '{value} ‰',
|
|
|
color: '#a0d2ff'
|
|
|
},
|
|
|
splitLine: {
|
|
|
show: false
|
|
|
}
|
|
|
}
|
|
|
],
|
|
|
yAxis: {
|
|
|
type: 'value',
|
|
|
name: '深度(m)',
|
|
|
inverse: true,
|
|
|
axisLine: {
|
|
|
lineStyle: {
|
|
|
color: '#a0d2ff'
|
|
|
}
|
|
|
},
|
|
|
axisLabel: {
|
|
|
formatter: '{value} m',
|
|
|
color: '#a0d2ff'
|
|
|
},
|
|
|
splitLine: {
|
|
|
lineStyle: {
|
|
|
color: 'rgba(255, 255, 255, 0.1)'
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
name: '温度',
|
|
|
type: 'line',
|
|
|
xAxisIndex: 0,
|
|
|
smooth: true,
|
|
|
lineStyle: {
|
|
|
width: 3,
|
|
|
color: '#ff7043'
|
|
|
},
|
|
|
data: profileData.temperature.map((temp, index) => [parseFloat(temp).toFixed(2), profileData.depth[index]])
|
|
|
},
|
|
|
{
|
|
|
name: '盐度',
|
|
|
type: 'line',
|
|
|
xAxisIndex: 1,
|
|
|
smooth: true,
|
|
|
lineStyle: {
|
|
|
width: 3,
|
|
|
color: '#4fc3f7'
|
|
|
},
|
|
|
data: profileData.salinity.map((salinity, index) => [parseFloat(salinity).toFixed(2), profileData.depth[index]])
|
|
|
}
|
|
|
]
|
|
|
};
|
|
|
|
|
|
profileChart.setOption(option);
|
|
|
}
|
|
|
// 初始化数据表格
|
|
|
function initDataTable() {
|
|
|
updateDataTable();
|
|
|
}
|
|
|
|
|
|
// 更新数据表格
|
|
|
function updateDataTable() {
|
|
|
const container = document.getElementById('dataTableContent');
|
|
|
let html = `
|
|
|
<div class="data-table-row data-table-header">
|
|
|
<div class="data-table-cell">区域</div>
|
|
|
<div class="data-table-cell">温度(°C)</div>
|
|
|
<div class="data-table-cell">盐度(‰)</div>
|
|
|
<div class="data-table-cell">叶绿素(mg/m³)</div>
|
|
|
</div>
|
|
|
`;
|
|
|
|
|
|
tableData.forEach(item => {
|
|
|
html += `
|
|
|
<div class="data-table-row">
|
|
|
<div class="data-table-cell">${item.region}</div>
|
|
|
<div class="data-table-cell">${item.temperature.toFixed(1)}</div>
|
|
|
<div class="data-table-cell">${item.salinity.toFixed(1)}</div>
|
|
|
<div class="data-table-cell">${item.chlorophyll.toFixed(2)}</div>
|
|
|
</div>
|
|
|
`;
|
|
|
});
|
|
|
|
|
|
container.innerHTML = html;
|
|
|
}
|
|
|
|
|
|
// 修改 updateAllData 函数中的剖面数据更新部分
|
|
|
function updateAllData() {
|
|
|
// 更新时序数据
|
|
|
const now = new Date();
|
|
|
timeSeriesData.time.shift();
|
|
|
timeSeriesData.time.push(now.getHours() + ':' + now.getMinutes().toString().padStart(2, '0'));
|
|
|
|
|
|
timeSeriesData.temperature.shift();
|
|
|
timeSeriesData.temperature.push(20 + Math.sin(now.getHours() / 3) * 5 + Math.random() * 2);
|
|
|
|
|
|
timeSeriesData.chlorophyll.shift();
|
|
|
timeSeriesData.chlorophyll.push(0.2 + Math.sin(now.getHours() / 4) * 0.15 + Math.random() * 0.1);
|
|
|
|
|
|
timeSeriesData.salinity.shift();
|
|
|
timeSeriesData.salinity.push(34 + Math.cos(now.getHours() / 5) * 1 + Math.random() * 0.5);
|
|
|
|
|
|
// 更新剖面数据并保留两位小数
|
|
|
profileData.temperature = profileData.temperature.map(temp =>
|
|
|
parseFloat(Math.max(5, Math.min(30, temp + (Math.random() - 0.5) * 0.5)).toFixed(2))
|
|
|
);
|
|
|
profileData.salinity = profileData.salinity.map(salinity =>
|
|
|
parseFloat(Math.max(30, Math.min(38, salinity + (Math.random() - 0.5) * 0.2)).toFixed(2))
|
|
|
);
|
|
|
|
|
|
// 更新表格数据
|
|
|
tableData = tableData.map(item => ({
|
|
|
region: item.region,
|
|
|
temperature: Math.max(10, Math.min(35, item.temperature + (Math.random() - 0.5) * 0.3)),
|
|
|
salinity: Math.max(30, Math.min(38, item.salinity + (Math.random() - 0.5) * 0.1)),
|
|
|
chlorophyll: Math.max(0.1, Math.min(0.8, item.chlorophyll + (Math.random() - 0.5) * 0.05))
|
|
|
}));
|
|
|
|
|
|
// 更新统计卡片
|
|
|
updateStatsCards();
|
|
|
|
|
|
// 更新所有图表
|
|
|
updateAllCharts();
|
|
|
}
|
|
|
|
|
|
// 更新所有图表
|
|
|
function updateAllCharts() {
|
|
|
//updateEarthData();
|
|
|
updateTimeSeriesChart();
|
|
|
updateProfileChart();
|
|
|
updateDataTable();
|
|
|
}
|
|
|
|
|
|
// 更新统计卡片
|
|
|
function updateStatsCards() {
|
|
|
// 计算平均值
|
|
|
const avgTemp = timeSeriesData.temperature.reduce((a, b) => a + b, 0) / timeSeriesData.temperature.length;
|
|
|
const avgSalinity = timeSeriesData.salinity.reduce((a, b) => a + b, 0) / timeSeriesData.salinity.length;
|
|
|
|
|
|
// 更新卡片数据
|
|
|
document.getElementById('avgTemp').textContent = avgTemp.toFixed(1) + '°C';
|
|
|
document.getElementById('maxWave').textContent = (8 + Math.random() * 5).toFixed(1) + 'm';
|
|
|
document.getElementById('anomalyCount').textContent = Math.floor(3 + Math.random() * 8);
|
|
|
document.getElementById('avgSalinity').textContent = avgSalinity.toFixed(1) + '‰';
|
|
|
}
|
|
|
|
|
|
// 开始动画
|
|
|
function startAnimation() {
|
|
|
if (animationInterval) {
|
|
|
clearInterval(animationInterval);
|
|
|
}
|
|
|
|
|
|
animationInterval = setInterval(() => {
|
|
|
updateAllData();
|
|
|
}, 2000);
|
|
|
|
|
|
document.getElementById('playAnimation').disabled = true;
|
|
|
document.getElementById('pauseAnimation').disabled = false;
|
|
|
}
|
|
|
|
|
|
// 暂停动画
|
|
|
function pauseAnimation() {
|
|
|
if (animationInterval) {
|
|
|
clearInterval(animationInterval);
|
|
|
animationInterval = null;
|
|
|
}
|
|
|
|
|
|
document.getElementById('playAnimation').disabled = false;
|
|
|
document.getElementById('pauseAnimation').disabled = true;
|
|
|
}
|
|
|
|
|
|
// 重置视图
|
|
|
function resetView() {
|
|
|
// 重置所有选择器
|
|
|
document.getElementById('dataTypeSelect').value = 'temperature';
|
|
|
document.getElementById('timeRangeSelect').value = '24h';
|
|
|
document.getElementById('regionSelect').value = 'global';
|
|
|
|
|
|
// 更新当前选择
|
|
|
currentDataType = 'temperature';
|
|
|
currentTimeRange = '24h';
|
|
|
currentRegion = 'global';
|
|
|
|
|
|
// 重新初始化数据
|
|
|
initData();
|
|
|
|
|
|
// 更新图表
|
|
|
updateAllCharts();
|
|
|
|
|
|
// 暂停动画
|
|
|
pauseAnimation();
|
|
|
}
|
|
|
|
|
|
// 窗口大小改变时调整图表大小
|
|
|
window.addEventListener('resize', function () {
|
|
|
if (earthChart) earthChart.resize();
|
|
|
if (timeSeriesChart) timeSeriesChart.resize();
|
|
|
if (profileChart) profileChart.resize();
|
|
|
});
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
|
document.addEventListener('DOMContentLoaded', init);
|
|
|
</script>
|
|
|
</body>
|
|
|
|
|
|
</html> |