338 lines
11 KiB
HTML
338 lines
11 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;
|
|
background-color: #f8f9fa;
|
|
}
|
|
|
|
.container {
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
padding: 15px;
|
|
}
|
|
|
|
.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;
|
|
}
|
|
|
|
.chart-container {
|
|
margin-bottom: 20px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
padding: 15px;
|
|
background-color: #fff;
|
|
}
|
|
|
|
.table-container {
|
|
overflow-x: auto;
|
|
margin-top: 20px;
|
|
border: 1px solid #ddd;
|
|
border-radius: 4px;
|
|
background-color: #fff;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.controls {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.control-group {
|
|
width: 100%;
|
|
}
|
|
|
|
select, input {
|
|
width: 100%;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<h1>气象站数据监控</h1>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<div class="controls">
|
|
<div class="control-group">
|
|
<label for="interval">数据粒度:</label>
|
|
<select id="interval">
|
|
<option value="5min">5分钟</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="queryData()">查询</button>
|
|
<button onclick="exportData()">导出数据</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="chart-container">
|
|
<canvas id="mainChart"></canvas>
|
|
</div>
|
|
|
|
<div class="table-container">
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>时间</th>
|
|
<th>降雨量(mm)</th>
|
|
<th>平均温度(℃)</th>
|
|
<th>平均湿度(%)</th>
|
|
<th>平均风速(m/s)</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="tableBody"></tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
let mainChart = null;
|
|
|
|
// 初始化日期选择器
|
|
function initDatePickers() {
|
|
const now = new Date();
|
|
const yesterday = new Date(now);
|
|
yesterday.setDate(yesterday.getDate() - 1);
|
|
|
|
document.getElementById('startDate').value = formatDateTime(yesterday);
|
|
document.getElementById('endDate').value = formatDateTime(now);
|
|
}
|
|
|
|
// 格式化日期时间
|
|
function formatDateTime(date) {
|
|
return date.toISOString().slice(0, 16);
|
|
}
|
|
|
|
// 查询数据
|
|
function queryData() {
|
|
const interval = document.getElementById('interval').value;
|
|
const startDate = document.getElementById('startDate').value;
|
|
const endDate = document.getElementById('endDate').value;
|
|
|
|
fetch(`/api/data?interval=${interval}&start=${startDate}&end=${endDate}`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
updateChart(data);
|
|
updateTable(data);
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
alert('获取数据失败,请检查网络连接');
|
|
});
|
|
}
|
|
|
|
// 更新图表
|
|
function updateChart(data) {
|
|
const ctx = document.getElementById('mainChart').getContext('2d');
|
|
|
|
if (mainChart) {
|
|
mainChart.destroy();
|
|
}
|
|
|
|
const labels = data.map(item => {
|
|
// 确保使用正确的时间格式解析
|
|
const date = new Date(item.timestamp.replace(' ', 'T') + 'Z');
|
|
// 调整为本地时间
|
|
const localDate = new Date(date.getTime() + (8 * 60 * 60 * 1000));
|
|
|
|
// 格式化为中文日期时间格式
|
|
return localDate.getFullYear() + '/' +
|
|
(localDate.getMonth() + 1).toString().padStart(2, '0') + '/' +
|
|
localDate.getDate().toString().padStart(2, '0') + ' ' +
|
|
localDate.getHours().toString().padStart(2, '0') + ':' +
|
|
localDate.getMinutes().toString().padStart(2, '0');
|
|
});
|
|
|
|
mainChart = new Chart(ctx, {
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
type: 'bar',
|
|
label: '降雨量(mm)',
|
|
data: data.map(item => item.rainfall),
|
|
backgroundColor: 'rgba(54, 162, 235, 0.5)',
|
|
borderColor: 'rgba(54, 162, 235, 1)',
|
|
borderWidth: 1,
|
|
yAxisID: 'y-rainfall',
|
|
},
|
|
{
|
|
type: 'line',
|
|
label: '温度(℃)',
|
|
data: data.map(item => item.avg_temperature),
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.5)',
|
|
tension: 0.1,
|
|
yAxisID: 'y-temp',
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
interaction: {
|
|
mode: 'index',
|
|
intersect: false,
|
|
},
|
|
scales: {
|
|
'y-rainfall': {
|
|
type: 'linear',
|
|
position: 'left',
|
|
title: {
|
|
display: true,
|
|
text: '降雨量(mm)'
|
|
},
|
|
grid: {
|
|
drawOnChartArea: false
|
|
}
|
|
},
|
|
'y-temp': {
|
|
type: 'linear',
|
|
position: 'right',
|
|
title: {
|
|
display: true,
|
|
text: '温度(℃)'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
// 更新表格
|
|
function updateTable(data) {
|
|
const tbody = document.getElementById('tableBody');
|
|
tbody.innerHTML = '';
|
|
|
|
data.forEach(item => {
|
|
const row = document.createElement('tr');
|
|
// 确保使用正确的时间格式解析
|
|
const date = new Date(item.timestamp.replace(' ', 'T') + 'Z');
|
|
// 调整为本地时间
|
|
const localDate = new Date(date.getTime() + (8 * 60 * 60 * 1000));
|
|
|
|
const formattedDate =
|
|
localDate.getFullYear() + '/' +
|
|
(localDate.getMonth() + 1).toString().padStart(2, '0') + '/' +
|
|
localDate.getDate().toString().padStart(2, '0') + ' ' +
|
|
localDate.getHours().toString().padStart(2, '0') + ':' +
|
|
localDate.getMinutes().toString().padStart(2, '0') + ':' +
|
|
localDate.getSeconds().toString().padStart(2, '0');
|
|
|
|
row.innerHTML = `
|
|
<td>${formattedDate}</td>
|
|
<td>${item.rainfall.toFixed(1)}</td>
|
|
<td>${item.avg_temperature.toFixed(1)}</td>
|
|
<td>${item.avg_humidity.toFixed(1)}</td>
|
|
<td>${item.avg_wind_speed.toFixed(1)}</td>
|
|
`;
|
|
tbody.appendChild(row);
|
|
});
|
|
}
|
|
|
|
// 导出数据
|
|
function exportData() {
|
|
const data = mainChart.data;
|
|
let csv = '时间,降雨量(mm),温度(℃),湿度(%),风速(m/s)\n';
|
|
|
|
for (let i = 0; i < data.labels.length; i++) {
|
|
csv += `${data.labels[i]},${data.datasets[0].data[i]},${data.datasets[1].data[i]}\n`;
|
|
}
|
|
|
|
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8;' });
|
|
const link = document.createElement('a');
|
|
link.href = URL.createObjectURL(blob);
|
|
link.download = '气象站数据.csv';
|
|
link.click();
|
|
}
|
|
|
|
// 页面加载完成后初始化
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
initDatePickers();
|
|
queryData();
|
|
});
|
|
</script>
|
|
</body>
|
|
</html> |