weather-station/templates/bigscreen.html
2025-11-03 17:14:36 +08:00

437 lines
18 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%; }
/* 流式字号:在 14px 与 18px 之间随视口变化 */
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 面板改为柔和橙色并带晕影 */
.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);
}
/* 预警时pastAccuracySummary 字体稍微变大,颜色保持 #bcd0ff */
.summary-panel.alert-on .summary-sub {
color: #bcd0ff;
font-size: 0.9rem;
}
/* 红色预警样式(>0.4mm */
.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; }
}
/* Panel 与文字整体闪烁:橙色 */
.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); }
}
/* Panel 与文字整体闪烁:红色 */
.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>
<!-- Chart.js 深色主题默认颜色(更适配深蓝底) -->
<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米)';
}
// 等待站点列表加载后,用 10691 的 location 设置标题
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>