weather-station/web/index.html

530 lines
17 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>
// ===== 前端直接配置KML图层 =====
window.TIANDITU_KEY = '0c260b8a094a4e0bc507808812cefdac'; // 天地图Key
window.KML_LAYERS = [
{
name: "昭君镇示范社区",
url: "../static/kml/selected_polygons.kml"
}
// 如果有更多KML文件继续添加
// { name: "另一个区域", url: "../static/kml/another.kml" }
];
let selectedStation = null;
let combinedChart = null;
// 初始化页面
document.addEventListener('DOMContentLoaded', function() {
loadStations();
initializeDateInputs();
});
// 初始化日期输入
function initializeDateInputs() {
const now = new Date();
// 设置为当前整点
const endDate = new Date(now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), 0, 0);
// 24小时前的整点
const startDate = new Date(endDate.getTime() - 24 * 60 * 60 * 1000);
document.getElementById('startDate').value = formatDatetimeLocal(startDate);
document.getElementById('endDate').value = formatDatetimeLocal(endDate);
}
function formatDatetimeLocal(date) {
// 直接格式化为本地时间字符串YYYY-MM-DDTHH:mm
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
return `${year}-${month}-${day}T${hours}:${minutes}`;
}
// 加载站点列表
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({
decimal_id: selectedStation.decimal_id,
start_time: startTime.replace('T', ' ') + ':00',
end_time: endTime.replace('T', ' ') + ':00',
interval: interval
});
const response = await fetch(`/api/data?${params}`);
if (!response.ok) {
throw new Error('查询失败');
}
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('查询历史数据失败: ' + error.message);
}
}
// 显示图表
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>