UI 新增資料更新時間 badge

在地圖右下角顯示後端 /scan response 新增的 generated_at 欄位,
以中文相對時間呈現(剛剛 / N 分鐘前 / N 小時前 / 絕對日期),
每 10 秒自動重算顯示,資料空或缺欄位時保留上次值。

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
timmy
2026-04-20 11:17:36 +08:00
parent 0ad7f0fba5
commit 08b456a96e

View File

@@ -200,10 +200,30 @@
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* 右下角資料更新時間 badge半透明深色、小字、不搶視覺 */
#updated-badge {
position: absolute;
right: 12px;
bottom: 12px;
z-index: 500;
padding: 6px 10px;
background: rgba(15, 23, 42, 0.72);
color: #f1f5f9;
font-size: 12px;
line-height: 1.3;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.18);
pointer-events: none;
font-variant-numeric: tabular-nums;
letter-spacing: 0.02em;
}
#updated-badge[hidden] { display: none; }
</style>
</head>
<body>
<div id="map"></div>
<div id="updated-badge" hidden></div>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
@@ -439,6 +459,55 @@
// 頁面關閉/離開時清 timer避免 leak
window.addEventListener('beforeunload', clearRetry);
// ---- 資料更新時間 badge ----
const updatedBadgeEl = document.getElementById('updated-badge');
let lastGeneratedAtMs = null; // 暫存 ISO parsed 後的 ms供每 10s tick 重算相對時間
// 中文相對時間:<30s 剛剛;<60m N 分鐘前;<24h N 小時前;否則絕對日期
function formatRelativeTime(isoString) {
const ms = Date.parse(isoString);
if (!Number.isFinite(ms)) return null;
const diffSec = Math.max(0, Math.floor((Date.now() - ms) / 1000));
if (diffSec < 30) return '剛剛';
if (diffSec < 3600) return `${Math.floor(diffSec / 60)} 分鐘前`;
if (diffSec < 86400) return `${Math.floor(diffSec / 3600)} 小時前`;
const d = new Date(ms);
const pad = n => String(n).padStart(2, '0');
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
}
function renderUpdatedBadge() {
if (lastGeneratedAtMs === null) {
updatedBadgeEl.hidden = true;
return;
}
const iso = new Date(lastGeneratedAtMs).toISOString();
const rel = formatRelativeTime(iso);
if (rel === null) {
updatedBadgeEl.hidden = true;
return;
}
const d = new Date(lastGeneratedAtMs);
const pad = n => String(n).padStart(2, '0');
const hhmmss = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
updatedBadgeEl.textContent = `資料更新於 ${hhmmss}${rel}`;
updatedBadgeEl.hidden = false;
}
function updateGeneratedAt(iso) {
if (!iso) {
// 保留上次值即可,不主動清;若從未設過則維持隱藏
return;
}
const ms = Date.parse(iso);
if (!Number.isFinite(ms)) return;
lastGeneratedAtMs = ms;
renderUpdatedBadge();
}
// 每 10 秒重算一次相對時間(不重打 API
setInterval(renderUpdatedBadge, 10000);
function renderItems(list) {
const markers = [];
for (const item of list) {
@@ -531,6 +600,7 @@
clearRetry();
clearStatus();
retryCount = 0;
updateGeneratedAt(json.generated_at);
renderItems(list);
})
.catch(err => {