510 lines
16 KiB
HTML
510 lines
16 KiB
HTML
<!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://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
body {
|
|
font-family: Arial, sans-serif;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
.header {
|
|
padding: 10px;
|
|
text-align: center;
|
|
border-bottom: 1px solid #ddd;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 15px;
|
|
}
|
|
|
|
.station-selection {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
padding: 15px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.station-card {
|
|
padding: 10px 15px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background-color: white;
|
|
cursor: pointer;
|
|
transition: all 0.3s;
|
|
min-width: 150px;
|
|
}
|
|
|
|
.station-card:hover {
|
|
background-color: #e9ecef;
|
|
}
|
|
|
|
.station-card.selected {
|
|
background-color: #007bff;
|
|
color: white;
|
|
}
|
|
|
|
.station-name {
|
|
font-weight: bold;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.station-id {
|
|
font-size: 12px;
|
|
color: #666;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.station-card.selected .station-id {
|
|
color: #cce7ff;
|
|
}
|
|
|
|
.controls {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
margin-bottom: 20px;
|
|
padding: 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background-color: #fff;
|
|
}
|
|
|
|
.control-group {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
}
|
|
|
|
select, input, button {
|
|
padding: 5px 10px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
button {
|
|
background-color: #007bff;
|
|
color: white;
|
|
border: none;
|
|
cursor: pointer;
|
|
transition: background-color 0.3s;
|
|
}
|
|
|
|
button:hover {
|
|
background-color: #0056b3;
|
|
}
|
|
|
|
button:disabled {
|
|
background-color: #ccc;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.chart-container {
|
|
margin-bottom: 20px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
background-color: #fff;
|
|
display: none;
|
|
}
|
|
|
|
.chart-container.show {
|
|
display: block;
|
|
}
|
|
|
|
.chart-wrapper {
|
|
height: 500px;
|
|
margin-bottom: 30px;
|
|
}
|
|
|
|
.table-container {
|
|
overflow-x: auto;
|
|
margin-top: 20px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background-color: #fff;
|
|
display: none;
|
|
}
|
|
|
|
.table-container.show {
|
|
display: block;
|
|
}
|
|
|
|
table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
}
|
|
|
|
th, td {
|
|
border: 1px solid #ddd;
|
|
padding: 12px 8px;
|
|
text-align: left;
|
|
}
|
|
|
|
th {
|
|
background-color: #f8f9fa;
|
|
font-weight: bold;
|
|
}
|
|
|
|
tr:nth-child(even) {
|
|
background-color: #f9f9f9;
|
|
}
|
|
|
|
tr:hover {
|
|
background-color: #f5f5f5;
|
|
}
|
|
|
|
.loading {
|
|
text-align: center;
|
|
padding: 20px;
|
|
color: #666;
|
|
}
|
|
|
|
.error {
|
|
color: #dc3545;
|
|
background-color: #f8d7da;
|
|
border: 1px solid #f5c6cb;
|
|
border-radius: 4px;
|
|
padding: 10px;
|
|
margin: 10px 0;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.controls {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.control-group {
|
|
width: 100%;
|
|
}
|
|
|
|
select, input {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>英卓气象站 - WH65LP设备监控</h1>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<!-- 站点选择 -->
|
|
<div class="station-selection">
|
|
<h3 style="width: 100%; margin: 0 0 10px 0;">请选择监测站点:</h3>
|
|
<div id="stationList" class="loading">正在加载站点信息...</div>
|
|
</div>
|
|
|
|
<!-- 控制面板 -->
|
|
<div class="controls">
|
|
<div class="control-group">
|
|
<label for="interval">数据粒度:</label>
|
|
<select id="interval">
|
|
<option value="10min">10分钟</option>
|
|
<option value="30min">30分钟</option>
|
|
<option value="1hour" selected>1小时</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label for="startDate">开始时间:</label>
|
|
<input type="datetime-local" id="startDate">
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<label for="endDate">结束时间:</label>
|
|
<input type="datetime-local" id="endDate">
|
|
</div>
|
|
|
|
<div class="control-group">
|
|
<button onclick="queryHistoryData()" id="queryBtn" disabled>查询历史数据</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 图表容器 -->
|
|
<div class="chart-container" id="chartContainer">
|
|
<div class="chart-wrapper">
|
|
<canvas id="combinedChart"></canvas>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 数据表格 -->
|
|
<div class="table-container" id="tableContainer">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>时间</th>
|
|
<th>温度 (°C)</th>
|
|
<th>湿度 (%)</th>
|
|
<th>气压 (hPa)</th>
|
|
<th>风速 (m/s)</th>
|
|
<th>风向 (°)</th>
|
|
<th>雨量 (mm)</th>
|
|
<th>光照 (lux)</th>
|
|
<th>紫外线</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tableBody">
|
|
<!-- 数据行将动态填充 -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let selectedStation = null;
|
|
let combinedChart = null;
|
|
|
|
// 初始化页面
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadStations();
|
|
initializeDateInputs();
|
|
});
|
|
|
|
// 初始化日期输入
|
|
function initializeDateInputs() {
|
|
const now = new Date();
|
|
const endDate = new Date(now);
|
|
const startDate = new Date(now.getTime() - 24 * 60 * 60 * 1000); // 24小时前
|
|
|
|
document.getElementById('startDate').value = formatDatetimeLocal(startDate);
|
|
document.getElementById('endDate').value = formatDatetimeLocal(endDate);
|
|
}
|
|
|
|
function formatDatetimeLocal(date) {
|
|
const offset = date.getTimezoneOffset();
|
|
const localDate = new Date(date.getTime() - offset * 60 * 1000);
|
|
return localDate.toISOString().slice(0, 16);
|
|
}
|
|
|
|
// 加载站点列表
|
|
async function loadStations() {
|
|
try {
|
|
const response = await fetch('/api/stations');
|
|
const stations = await response.json();
|
|
|
|
const stationList = document.getElementById('stationList');
|
|
stationList.innerHTML = '';
|
|
|
|
if (stations.length === 0) {
|
|
stationList.innerHTML = '<div class="error">暂无WH65LP设备站点</div>';
|
|
return;
|
|
}
|
|
|
|
stations.forEach(station => {
|
|
const stationCard = document.createElement('div');
|
|
stationCard.className = 'station-card';
|
|
stationCard.onclick = () => selectStation(station, stationCard);
|
|
|
|
stationCard.innerHTML = `
|
|
<div class="station-name">${station.station_name || station.station_id}</div>
|
|
<div class="station-id">${station.station_id}</div>
|
|
<div class="station-id">最后更新: ${station.last_update}</div>
|
|
`;
|
|
|
|
stationList.appendChild(stationCard);
|
|
});
|
|
} catch (error) {
|
|
console.error('加载站点失败:', error);
|
|
document.getElementById('stationList').innerHTML = '<div class="error">加载站点失败</div>';
|
|
}
|
|
}
|
|
|
|
// 选择站点
|
|
function selectStation(station, cardElement) {
|
|
// 清除之前的选择
|
|
document.querySelectorAll('.station-card').forEach(card => {
|
|
card.classList.remove('selected');
|
|
});
|
|
|
|
// 选择当前站点
|
|
cardElement.classList.add('selected');
|
|
selectedStation = station;
|
|
|
|
// 启用按钮
|
|
document.getElementById('queryBtn').disabled = false;
|
|
|
|
// 隐藏之前的数据
|
|
hideDataContainers();
|
|
}
|
|
|
|
// 隐藏数据容器
|
|
function hideDataContainers() {
|
|
document.getElementById('chartContainer').classList.remove('show');
|
|
document.getElementById('tableContainer').classList.remove('show');
|
|
}
|
|
|
|
// 查询历史数据
|
|
async function queryHistoryData() {
|
|
if (!selectedStation) {
|
|
alert('请先选择一个站点');
|
|
return;
|
|
}
|
|
|
|
const startTime = document.getElementById('startDate').value;
|
|
const endTime = document.getElementById('endDate').value;
|
|
const interval = document.getElementById('interval').value;
|
|
|
|
if (!startTime || !endTime) {
|
|
alert('请选择开始和结束时间');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const params = new URLSearchParams({
|
|
station_id: selectedStation.station_id,
|
|
start_time: startTime.replace('T', ' ') + ':00',
|
|
end_time: endTime.replace('T', ' ') + ':00',
|
|
interval: interval
|
|
});
|
|
|
|
const response = await fetch(`/api/data?${params}`);
|
|
const data = await response.json();
|
|
|
|
if (data.length === 0) {
|
|
alert('该时间段内无数据');
|
|
return;
|
|
}
|
|
|
|
displayChart(data);
|
|
displayTable(data);
|
|
|
|
// 显示图表和表格
|
|
document.getElementById('chartContainer').classList.add('show');
|
|
document.getElementById('tableContainer').classList.add('show');
|
|
|
|
} catch (error) {
|
|
console.error('查询历史数据失败:', error);
|
|
alert('查询历史数据失败');
|
|
}
|
|
}
|
|
|
|
// 显示图表
|
|
function displayChart(data) {
|
|
const labels = data.map(item => item.date_time);
|
|
const temperatures = data.map(item => item.temperature);
|
|
const humidities = data.map(item => item.humidity);
|
|
const rainfalls = data.map(item => item.rainfall);
|
|
|
|
// 销毁旧图表
|
|
if (combinedChart) combinedChart.destroy();
|
|
|
|
// 创建组合图表
|
|
const ctx = document.getElementById('combinedChart').getContext('2d');
|
|
combinedChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: '温度 (°C)',
|
|
data: temperatures,
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
|
yAxisID: 'y-temperature',
|
|
tension: 0.4
|
|
},
|
|
{
|
|
label: '湿度 (%)',
|
|
data: humidities,
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
|
yAxisID: 'y-humidity',
|
|
tension: 0.4,
|
|
hidden: true // 默认隐藏湿度数据
|
|
},
|
|
{
|
|
label: '雨量 (mm)',
|
|
data: rainfalls,
|
|
type: 'bar',
|
|
backgroundColor: 'rgba(54, 162, 235, 0.6)',
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
yAxisID: 'y-rainfall'
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
},
|
|
scales: {
|
|
'y-temperature': {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'left',
|
|
title: {
|
|
display: true,
|
|
text: '温度 (°C)'
|
|
}
|
|
},
|
|
'y-humidity': {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: '湿度 (%)'
|
|
},
|
|
grid: {
|
|
drawOnChartArea: false
|
|
},
|
|
min: 0,
|
|
max: 100
|
|
},
|
|
'y-rainfall': {
|
|
type: 'linear',
|
|
display: true,
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: '雨量 (mm)'
|
|
},
|
|
grid: {
|
|
drawOnChartArea: false
|
|
},
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 显示数据表格
|
|
function displayTable(data) {
|
|
const tbody = document.getElementById('tableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.forEach(item => {
|
|
const row = document.createElement('tr');
|
|
row.innerHTML = `
|
|
<td>${item.date_time}</td>
|
|
<td>${item.temperature.toFixed(2)}</td>
|
|
<td>${item.humidity.toFixed(2)}</td>
|
|
<td>${item.pressure.toFixed(2)}</td>
|
|
<td>${item.wind_speed.toFixed(2)}</td>
|
|
<td>${item.wind_direction.toFixed(2)}</td>
|
|
<td>${item.rainfall.toFixed(3)}</td>
|
|
<td>${item.light.toFixed(2)}</td>
|
|
<td>${item.uv.toFixed(2)}</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
</html> |