feat: 大屏幕的页面

This commit is contained in:
yarnom 2025-11-03 16:42:45 +08:00
parent edf3e93eef
commit 573ef9ef3e

375
templates/bigscreen.html Normal file
View File

@ -0,0 +1,375 @@
<!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;
}
/* 预警时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;
}
.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);
}
.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);
}
/* 减少动画偏好时关闭动画(目前无动画,但作为扩展) */
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; }
}
</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>