weather-station/templates/bigscreen.html

419 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="/static/js/chart.js"></script>
<link rel="stylesheet" href="/static/css/ol.css">
<script src="/static/js/ol.js"></script>
<style>
:root {
--color-bg: #0b1e39;
--color-fg: #e6eefc;
--color-fg-muted: #dbe7ff;
--panel-bg: rgba(255,255,255,0.04);
--panel-border: rgba(255,255,255,0.12);
--table-head-bg: rgba(20,45,80,0.98);
--table-head-fg: #e6f0ff;
--table-row-alt: rgba(255,255,255,0.03);
--orange-bg-1: rgba(255, 171, 64, 0.18);
--orange-bg-2: rgba(255, 171, 64, 0.10);
--orange-border: rgba(230, 120, 20, 0.6);
--orange-shadow-1: rgba(255, 159, 64, 0.35);
--orange-shadow-2: rgba(255, 159, 64, 0.10);
--orange-shadow-3: rgba(255, 159, 64, 0.25);
--orange-fg: #2b1900;
--orange-fg-sub: #4a2b00;
/* 间距与圆角(相对单位) */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--radius-1: 0.25rem; /* 4px */
--radius-2: 0.375rem; /* 6px */
}
html, body { height: 100%; }
html { font-size: clamp(14px, 0.95vw, 18px); }
body {
margin: 0;
padding: 0;
background: var(--color-bg);
color: var(--color-fg);
font-family: Arial, sans-serif;
font-size: 1rem;
}
body.alert-mode { position: relative; }
body.alert-mode::before {
content: '';
position: fixed;
inset: 0;
pointer-events: none;
background: radial-gradient(circle at 50% 50%,
rgba(255, 159, 64, 0.00) 45%,
rgba(255, 159, 64, 0.12) 70%,
rgba(255, 159, 64, 0.20) 100%);
opacity: 0.16;
animation: alert-vignette 3s ease-in-out infinite;
z-index: 1;
}
@keyframes alert-vignette {
0% { opacity: 0.12; }
50% { opacity: 0.25; }
100% { opacity: 0.12; }
}
.screen {
height: 100vh;
width: 100vw;
display: grid;
grid-template-columns: 1fr 1fr;
gap: var(--space-2);
box-sizing: border-box;
padding: var(--space-2);
}
.left, .right { min-width: 0; min-height: 0; }
.left {
display: flex;
flex-direction: column;
gap: var(--space-2);
}
.chart-container {
flex: 1;
min-height: 0;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: var(--radius-1);
padding: var(--space-2);
padding-bottom: 2em; /* 额外底部留白,避免溢出 */
}
.chart-wrapper { height: 100%; }
#combinedChart { width: 100% !important; height: 100% !important; }
.station-info-title { font-size: 1rem; margin-bottom: var(--space-2); color: #d5e3ff; text-align: center; font-weight: 700; }
.accuracy-panel { font-size: 0.8125rem; color: #a9bfe6; text-align: right; margin-top: calc(-1 * var(--space-1)); }
.accuracy-panel .label { color: #90a7d4; margin-right: 4px; }
.accuracy-panel .value { color: #ffffff; font-weight: 700; }
.map-container {
flex: 1;
min-height: 0;
position: relative;
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: var(--radius-1);
overflow: hidden;
}
#map { width: 100%; height: 100%; }
.right { display: flex; flex-direction: column; gap: var(--space-2); }
.controls {
display: flex; align-items: center; flex-wrap: wrap;
gap: var(--space-2);
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: var(--radius-1);
padding: calc(var(--space-1) + 0.125rem) var(--space-2);
color: var(--color-fg-muted);
}
.controls label { font-size: 0.8125rem; color: #bfd0ef; }
.controls input, .controls select, .controls button {
padding: var(--space-1) var(--space-2);
font-size: 0.8125rem;
border-radius: 0.1875rem;
border: 1px solid rgba(255,255,255,0.18);
background: rgba(0,0,0,0.25);
color: var(--color-fg);
}
.controls button { background: #1e5eff; border-color: #1e5eff; cursor: pointer; }
.controls button:hover { background: #184bd1; }
.controls select option { background: var(--color-bg); color: var(--color-fg); }
.table-container {
flex: 1;
min-height: 0;
overflow: auto;
background: rgba(0,0,0,0.25);
border: 1px solid var(--panel-border);
border-radius: var(--radius-1);
}
table { width: 100%; border-collapse: collapse; }
thead th {
position: sticky;
top: 0;
background: var(--table-head-bg);
color: var(--table-head-fg);
font-weight: 600;
padding: var(--space-3) var(--space-2);
border-bottom: 1px solid var(--panel-border);
font-size: 0.875rem;
}
tbody td {
padding: var(--space-3) var(--space-2);
border-bottom: 1px solid rgba(255,255,255,0.08);
color: #dfe9ff;
font-size: 0.8125rem;
text-align: center;
}
tbody tr:nth-child(even) { background: var(--table-row-alt); }
/* Summary 面板默认样式(深蓝主题内轻边框) */
.summary-panel {
background: var(--panel-bg);
border: 1px solid var(--panel-border);
border-radius: var(--radius-2);
margin: var(--space-2) 0;
padding: var(--space-3) var(--space-3);
color: #dfe9ff;
line-height: 1.6;
}
.summary-title { font-size: 0.875rem; color: #e6f0ff; font-weight: 600; }
.summary-sub { font-size: 0.8125rem; color: #bcd0ff; }
.summary-panel.alert-on {
background: linear-gradient(180deg, var(--orange-bg-1), var(--orange-bg-2));
border: 1px solid var(--orange-border);
box-shadow: 0 0.375rem 1.125rem var(--orange-shadow-1), 0 0 0 1px var(--orange-shadow-2) inset, 0 0 1.5rem var(--orange-shadow-3) inset;
color: var(--orange-fg);
}
.summary-panel.alert-on .summary-title {
color: #bd6718;
font-size: 1.7rem;
font-weight: 600;
padding: 0.1rem 0.35rem;
border-radius: var(--radius-1);
}
.summary-panel.alert-on .summary-sub {
color: #bcd0ff;
font-size: 0.9rem;
}
.summary-panel.alert-red {
background: linear-gradient(180deg, rgba(255, 107, 107, 0.22), rgba(255, 107, 107, 0.12));
border: 1px solid rgba(200, 40, 40, 0.6);
box-shadow: 0 0.375rem 1.125rem rgba(255, 107, 107, 0.35), 0 0 0 1px rgba(255,107,107,0.10) inset, 0 0 1.5rem rgba(255, 107, 107, 0.25) inset;
color: #3a0b0b;
}
.summary-panel.alert-red .summary-title {
color: #b81f1f;
font-size: 1.7rem;
font-weight: 600;
padding: 0.1rem 0.35rem;
border-radius: var(--radius-1);
}
.summary-panel.alert-red .summary-sub {
color: #bcd0ff;
font-size: 0.9rem;
}
.screen.alert-on {
box-shadow:
inset 0 0 20rem var(--orange-shadow-3),
inset 0 0 20rem var(--orange-shadow-1);
animation: screenGlowOrange 1.8s ease-in-out infinite;
}
@keyframes screenGlowOrange {
0%, 100% { box-shadow:
inset 0 0 20rem var(--orange-shadow-3),
inset 0 0 20rem var(--orange-shadow-1);
}
50% { box-shadow: none; }
}
.screen.alert-red {
box-shadow:
inset 0 0 20rem rgba(255, 107, 107, 0.25),
inset 0 0 20rem rgba(255, 107, 107, 0.35);
animation: screenGlowRed 1.8s ease-in-out infinite;
}
@keyframes screenGlowRed {
0%, 100% { box-shadow:
inset 0 0 20rem rgba(255, 107, 107, 0.25),
inset 0 0 20rem rgba(255, 107, 107, 0.35);
}
50% { box-shadow: none; }
}
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; }
}
.summary-panel.alert-on { animation: panelGlowOrange 1.8s ease-in-out infinite; }
.summary-panel.alert-on .summary-title,
.summary-panel.alert-on .summary-sub { animation: textPulseOrange 1.8s ease-in-out infinite; }
@keyframes panelGlowOrange {
0%, 100% {
background: linear-gradient(180deg, var(--orange-bg-1), var(--orange-bg-2));
border-color: var(--orange-border);
box-shadow: 0 0.375rem 1.125rem var(--orange-shadow-1), 0 0 0 1px var(--orange-shadow-2) inset, 0 0 1.5rem var(--orange-shadow-3) inset;
}
50% {
background: var(--color-bg);
border-color: var(--panel-border);
box-shadow: none;
}
}
@keyframes textPulseOrange {
0%, 100% { /* 使用各自定义色 */ }
50% { color: var(--color-fg); }
}
.summary-panel.alert-red { animation: panelGlowRed 1.8s ease-in-out infinite; }
.summary-panel.alert-red .summary-title,
.summary-panel.alert-red .summary-sub { animation: textPulseRed 1.8s ease-in-out infinite; }
@keyframes panelGlowRed {
0%, 100% {
background: linear-gradient(180deg, rgba(255, 107, 107, 0.22), rgba(255, 107, 107, 0.12));
border-color: rgba(200, 40, 40, 0.6);
box-shadow: 0 0.375rem 1.125rem rgba(255, 107, 107, 0.35), 0 0 0 1px rgba(255,107,107,0.10) inset, 0 0 1.5rem rgba(255, 107, 107, 0.25) inset;
}
50% {
background: var(--color-bg);
border-color: var(--panel-border);
box-shadow: none;
}
}
@keyframes textPulseRed {
0%, 100% { /* 使用各自定义色 */ }
50% { color: var(--color-fg); }
}
</style>
</head>
<body>
<div class="screen">
<div class="left">
<div class="chart-container" id="chartContainer">
<div id="stationInfoTitle" class="station-info-title"></div>
<div id="accuracyPanel" class="accuracy-panel">
<span class="item"><span class="label">+1h</span><span id="accH1" class="value">--</span></span>
<span class="item"><span class="label">+2h</span><span id="accH2" class="value">--</span></span>
<span class="item"><span class="label">+3h</span><span id="accH3" class="value">--</span></span>
</div>
<div class="chart-wrapper">
<canvas id="combinedChart"></canvas>
</div>
</div>
<div class="map-container" id="mapContainer">
<div id="map"></div>
<div id="tileValueTooltip" style="position:absolute;pointer-events:none;z-index:1003;display:none;background:rgba(0,0,0,0.65);color:#fff;font-size:12px;padding:4px 6px;border-radius:4px;"></div>
</div>
</div>
<div class="right">
<div class="controls">
<label for="stationInput">站点:</label>
<input type="text" id="stationInput" placeholder="十进制或HEX" style="width:7.5rem;">
<label for="interval">粒度:</label>
<select id="interval">
<option value="raw">原始(16s)</option>
<option value="10min">10分钟</option>
<option value="30min">30分钟</option>
<option value="1hour" selected>1小时</option>
</select>
<label for="forecastProvider">预报源:</label>
<select id="forecastProvider">
<option value="">不显示预报</option>
<option value="imdroid_mix" selected>V4</option>
<option value="open-meteo">V3</option>
<option value="caiyun">V2</option>
<option value="imdroid">V1</option>
</select>
<label for="startDate">开始:</label>
<input type="datetime-local" id="startDate">
<label for="endDate">结束:</label>
<input type="datetime-local" id="endDate">
<button onclick="queryHistoryData()">查询</button>
<label for="tileProduct">叠加:</label>
<select id="tileProduct">
<option value="none">不显示</option>
<option value="rain">1h 实际降雨</option>
<option value="radar" selected>水汽含量</option>
</select>
<button id="btnTilePrev">上一时次</button>
<span id="tileCountInfo">共0条第0条</span>
<button id="btnTileNext">下一时次</button>
<label for="tileTimeSelect">时间:</label>
<select id="tileTimeSelect" style="min-width:12rem">
<option value="">请选择时间</option>
</select>
</div>
<div id="summaryPanel" class="summary-panel">
<div id="futureRainSummary" class="summary-title">未来1~3小时降雨 -- mm</div>
<div id="pastAccuracySummary" class="summary-sub">过去预报准确率 +1h: -- +2h: -- +3h: --</div>
</div>
<div class="table-container" id="tableContainer">
<table>
<thead>
<tr id="tableHeader">
<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>
</div>
<script>
window.TIANDITU_KEY = '{{.TiandituKey}}';
</script>
<script>
if (window.Chart && Chart.defaults) {
Chart.defaults.color = '#d5e3ff';
Chart.defaults.borderColor = 'rgba(213,227,255,0.25)';
if (Chart.defaults.scale && Chart.defaults.scale.grid) {
Chart.defaults.scale.grid.color = 'rgba(213,227,255,0.12)';
Chart.defaults.scale.ticks = Chart.defaults.scale.ticks || {};
Chart.defaults.scale.ticks.color = '#cbd9ff';
}
}
</script>
<script defer src="/static/js/alpinejs.min.js"></script>
<script src="/static/js/utils.js"></script>
<script src="/static/js/weather-app.js"></script>
<script src="/static/js/weather-chart.js"></script>
<script src="/static/js/weather-table.js"></script>
<script src="/static/js/app.js"></script>
<script>
window.IS_BIGSCREEN = true;
// 大屏默认展示指定站点 RS485-0029C3十进制 10691并自动发起查询
document.addEventListener('DOMContentLoaded', () => {
const input = document.getElementById('stationInput');
const titleEl = document.getElementById('stationInfoTitle');
if (input) {
input.value = '10691';
// 先设置默认标题
if (titleEl) {
titleEl.textContent = '第七台气象站(宾阳县细塘村东南约112米)';
}
const maxTries = 50; // ~10s 内重试
let tries = 0;
const timer = setInterval(() => {
tries += 1;
const stations = (window.WeatherMap && Array.isArray(window.WeatherMap.stations)) ? window.WeatherMap.stations : [];
const s = stations.find(x => x && String(x.decimal_id) === '10691');
if (s && titleEl) {
if (s.location) titleEl.textContent = s.location;
clearInterval(timer);
}
if (tries >= maxTries) clearInterval(timer);
}, 200);
setTimeout(() => {
window.dispatchEvent(new CustomEvent('query-history-data'));
}, 600);
}
});
</script>
</body>
</html>