更新html

This commit is contained in:
2569718930@qq.com
2026-05-13 21:26:26 +08:00
parent d97bb76480
commit 7555da8e6a
+143 -140
View File
@@ -1,164 +1,167 @@
{% if full_page %} {% if full_page %}
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh"> <html lang="zh">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="viewport" content="width=device-width,initial-scale=1">
<title>Market Monitor — PolyWeather</title> <title>Market Monitor — PolyWeather</title>
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
<meta http-equiv="refresh" content="120"> <meta http-equiv="refresh" content="120">
</head> </head>
<body> <body>
<div class="page"> <div class="page">
<header class="header"> <header class="header">
<h1>🔥 Market Monitor</h1> <h1>🔥 Market Monitor</h1>
<div class="header-right"> <div class="header-right">
<button id="notify-toggle" class="notify-btn" onclick="toggleNotify()" title="新高提醒"> <button id="notify-toggle" class="notify-btn" onclick="toggleNotify()" title="新高提醒">
<span id="notify-icon">🔔</span> <span id="notify-icon">🔔</span>
</button> </button>
<span class="updated" id="update-time">更新中…</span> </div>
</div> </header>
</header> {% endif %}
{% endif %}
<div id="card-grid" class="card-grid" <div id="card-grid" class="card-grid" hx-get="/api/data" hx-trigger="every 30s" hx-swap="outerHTML"
hx-get="/api/data" hx-trigger="every 30s" hx-swap="outerHTML" hx-indicator="#spinner"> hx-indicator="#spinner">
<span class="updated" style="display:block;grid-column:1/-1;text-align:right;margin-bottom:2px"> <span class="updated" style="display:block;grid-column:1/-1;text-align:right;margin-bottom:2px">
{{ generated_at }}</span> {{ generated_at }}</span>
{% for c in cities %} {% for c in cities %}
<div class="card{% if c.new_high %} new-high-card{% endif %}" data-new-high="{{ c.new_high }}"> <div class="card{% if c.new_high %} new-high-card{% endif %}" data-new-high="{{ c.new_high }}">
<div class="card-top"> <div class="card-top">
<span class="city-name">{{ c.en_name }}</span> <span class="city-name">{{ c.en_name }}</span>
<span class="airport">/ {{ c.airport }}</span> <span class="airport">/ {{ c.airport }}</span>
<span class="local-time">{{ c.local_time }}</span> <span class="local-time">{{ c.local_time }}</span>
</div> </div>
<div class="card-temp"> <div class="card-temp">
{% match c.current_temp %} {% match c.current_temp %}
{% when Some with (t) %} {% when Some with (t) %}
<span class="temp-value{% if c.temp_warm %} warm{% endif %}{% if c.new_high %} new-high-val{% endif %}">{{ "{:.1}"|format(t) }}</span><span class="temp-unit">°C</span> <span class="temp-value{% if c.temp_warm %} warm{% endif %}{% if c.new_high %} new-high-val{% endif %}">{{
{% when None %} "{:.1}"|format(t) }}</span><span class="temp-unit">°C</span>
<span class="temp-value na">--</span> {% when None %}
{% endmatch %} <span class="temp-value na">--</span>
</div> {% endmatch %}
</div>
<div class="card-meta"> <div class="card-meta">
<div class="meta-row"> <div class="meta-row">
<span class="label">High</span> <span class="label">High</span>
{% match c.today_max %} {% match c.today_max %}
{% when Some with (m) %} {% when Some with (m) %}
<span class="value">{{ "{:.1}"|format(m) }}°C</span> <span class="value">{{ "{:.1}"|format(m) }}°C</span>
{% match c.max_time %} {% match c.max_time %}
{% when Some with (mt) %} {% when Some with (mt) %}
<span class="value max-time">{{ mt }}</span> <span class="value max-time">{{ mt }}</span>
{% when None %} {% when None %}
{% endmatch %} {% endmatch %}
{% when None %} {% when None %}
<span class="value na">--</span> <span class="value na">--</span>
{% endmatch %} {% endmatch %}
<span class="trend {{ c.trend.css_class() }}">{{ c.trend.symbol() }}</span> <span class="trend {{ c.trend.css_class() }}">{{ c.trend.symbol() }}</span>
</div>
<div class="meta-row">
<span class="label">Obs</span>
{% match c.obs_age_min %}
{% when Some with (m) %}
<span class="value obs-age">{{ m }} min ago</span>
{% when None %}
<span class="value na">--</span>
{% endmatch %}
<span class="trend {{ c.trend.css_class() }}">{{ c.trend.symbol() }}</span>
</div>
</div>
</div>
{% endfor %}
</div> </div>
<div class="meta-row">
<span class="label">Obs</span>
{% match c.obs_age_min %}
{% when Some with (m) %}
<span class="value obs-age">{{ m }} min ago</span>
{% when None %}
<span class="value na">--</span>
{% endmatch %}
<span class="trend {{ c.trend.css_class() }}">{{ c.trend.symbol() }}</span>
</div>
</div>
</div>
{% endfor %}
</div>
<div id="spinner" class="htmx-indicator">Refreshing…</div> <div id="spinner" class="htmx-indicator">Refreshing…</div>
{% if full_page %} {% if full_page %}
<script src="https://unpkg.com/htmx.org@2.0.4"></script> <script src="https://unpkg.com/htmx.org@2.0.4"></script>
<script> <script>
// ── Notification ── // ── Notification ──
var NOTIFY_ENABLED = localStorage.getItem('monitor_notify') !== 'off'; var NOTIFY_ENABLED = localStorage.getItem('monitor_notify') !== 'off';
var NOTIFIED_HIGHS = JSON.parse(localStorage.getItem('monitor_notified_highs') || '{}'); var NOTIFIED_HIGHS = JSON.parse(localStorage.getItem('monitor_notified_highs') || '{}');
function toggleNotify() { function toggleNotify() {
NOTIFY_ENABLED = !NOTIFY_ENABLED; NOTIFY_ENABLED = !NOTIFY_ENABLED;
localStorage.setItem('monitor_notify', NOTIFY_ENABLED ? 'on' : 'off'); localStorage.setItem('monitor_notify', NOTIFY_ENABLED ? 'on' : 'off');
updateNotifyIcon(); updateNotifyIcon();
if (NOTIFY_ENABLED && Notification.permission === 'default') { if (NOTIFY_ENABLED && Notification.permission === 'default') {
Notification.requestPermission(); Notification.requestPermission();
} }
} }
function updateNotifyIcon() { function updateNotifyIcon() {
var icon = document.getElementById('notify-icon'); var icon = document.getElementById('notify-icon');
var btn = document.getElementById('notify-toggle'); var btn = document.getElementById('notify-toggle');
if (NOTIFY_ENABLED) { if (NOTIFY_ENABLED) {
icon.textContent = '🔔'; icon.textContent = '🔔';
btn.classList.remove('muted'); btn.classList.remove('muted');
} else { } else {
icon.textContent = '🔕'; icon.textContent = '🔕';
btn.classList.add('muted'); btn.classList.add('muted');
} }
} }
function fireNotification(enName, temp) { function fireNotification(enName, temp) {
if (!NOTIFY_ENABLED) return; if (!NOTIFY_ENABLED) return;
if (Notification.permission !== 'granted') return; if (Notification.permission !== 'granted') return;
var key = enName + '|' + temp; var key = enName + '|' + temp;
var today = new Date().toDateString(); var today = new Date().toDateString();
// Reset notified list on new day // Reset notified list on new day
if (NOTIFIED_HIGHS._day !== today) { if (NOTIFIED_HIGHS._day !== today) {
NOTIFIED_HIGHS = { _day: today }; NOTIFIED_HIGHS = { _day: today };
} }
if (NOTIFIED_HIGHS[key]) return; // already notified if (NOTIFIED_HIGHS[key]) return; // already notified
NOTIFIED_HIGHS[key] = true; NOTIFIED_HIGHS[key] = true;
localStorage.setItem('monitor_notified_highs', JSON.stringify(NOTIFIED_HIGHS)); localStorage.setItem('monitor_notified_highs', JSON.stringify(NOTIFIED_HIGHS));
new Notification('🔴 New High — ' + enName, { new Notification('🔴 New High — ' + enName, {
body: temp + '°C\nNew daily high.', body: temp + '°C\nNew daily high.',
icon: '/static/favicon.png', icon: '/static/favicon.png',
tag: key, tag: key,
requireInteraction: true, requireInteraction: true,
});
}
updateNotifyIcon();
// ── Flash on temp change + notifications ──
(function(){
let prev = {};
document.body.addEventListener('htmx:afterSwap', function(evt){
if(evt.detail.target.id !== 'card-grid') return;
document.querySelectorAll('.card').forEach(function(card){
var name = card.querySelector('.city-name').textContent;
var tv = card.querySelector('.temp-value').textContent;
var old = prev[name];
prev[name] = tv;
// Flash if changed
if(old && old !== tv && tv !== '--'){
card.style.transition = 'none';
card.style.boxShadow = '0 0 14px rgba(52,211,153,0.40)';
requestAnimationFrame(function(){
card.style.transition = 'box-shadow 2s ease-out';
card.style.boxShadow = '';
}); });
} }
// Notification for new high updateNotifyIcon();
if(card.dataset.newHigh === 'true'){
fireNotification(name, tv); // ── Flash on temp change + notifications ──
} (function () {
}); let prev = {};
}); document.body.addEventListener('htmx:afterSwap', function (evt) {
})(); if (evt.detail.target.id !== 'card-grid') return;
</script> document.querySelectorAll('.card').forEach(function (card) {
</div> var name = card.querySelector('.city-name').textContent;
var tv = card.querySelector('.temp-value').textContent;
var old = prev[name];
prev[name] = tv;
// Flash if changed
if (old && old !== tv && tv !== '--') {
card.style.transition = 'none';
card.style.boxShadow = '0 0 14px rgba(52,211,153,0.40)';
requestAnimationFrame(function () {
card.style.transition = 'box-shadow 2s ease-out';
card.style.boxShadow = '';
});
}
// Notification for new high
if (card.dataset.newHigh === 'true') {
fireNotification(name, tv);
}
});
});
})();
</script>
</div>
</body> </body>
</html> </html>
{% endif %} {% endif %}