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:
70
index.html
70
index.html
@@ -200,10 +200,30 @@
|
|||||||
0%, 100% { opacity: 1; }
|
0%, 100% { opacity: 1; }
|
||||||
50% { opacity: 0.3; }
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
|
<div id="updated-badge" hidden></div>
|
||||||
|
|
||||||
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
|
||||||
|
|
||||||
@@ -439,6 +459,55 @@
|
|||||||
// 頁面關閉/離開時清 timer,避免 leak
|
// 頁面關閉/離開時清 timer,避免 leak
|
||||||
window.addEventListener('beforeunload', clearRetry);
|
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) {
|
function renderItems(list) {
|
||||||
const markers = [];
|
const markers = [];
|
||||||
for (const item of list) {
|
for (const item of list) {
|
||||||
@@ -531,6 +600,7 @@
|
|||||||
clearRetry();
|
clearRetry();
|
||||||
clearStatus();
|
clearStatus();
|
||||||
retryCount = 0;
|
retryCount = 0;
|
||||||
|
updateGeneratedAt(json.generated_at);
|
||||||
renderItems(list);
|
renderItems(list);
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
|||||||
Reference in New Issue
Block a user