const API_ENDPOINTS = [ "https://60s.api.shumengya.top/v2/maoyan/realtime/tv" ]; const FALLBACK_ENDPOINT = "./返回接口.json"; const REFRESH_INTERVAL = 4000; const MAX_ITEMS = 40; const refreshButton = document.getElementById("refreshButton"); const updateTimeEl = document.getElementById("updateTime"); const programmeListEl = document.getElementById("programmeList"); const programmeCountEl = document.getElementById("programmeCount"); const topMarketRateEl = document.getElementById("topMarketRate"); const topAttentionRateEl = document.getElementById("topAttentionRate"); const refreshGapEl = document.getElementById("refreshGap"); let isLoading = false; let autoTimer = null; function escapeHtml(value) { if (value === undefined || value === null) { return ""; } return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function safeText(value, fallback = "--") { if (value === undefined || value === null || value === "") { return fallback; } return escapeHtml(value); } function formatNumber(value, fractionDigits = 2) { const numeric = Number(value); if (!Number.isFinite(numeric)) { return "--"; } return numeric.toFixed(fractionDigits); } function formatGapText(seconds) { const numeric = Number(seconds); if (!Number.isFinite(numeric) || numeric <= 0) { return "--"; } if (numeric < 60) { return `约每 ${Math.round(numeric)} 秒`; } const minutes = Math.floor(numeric / 60); const remaining = Math.round(numeric % 60); if (remaining === 0) { return `约每 ${minutes} 分钟`; } return `约每 ${minutes} 分 ${remaining} 秒`; } function parseRate(value) { const numeric = Number(value); if (Number.isFinite(numeric) && numeric >= 0) { return { text: numeric.toFixed(4).replace(/0+$/, "").replace(/\.$/, ""), ratio: Math.max(0, Math.min(numeric, 100)) }; } return { text: "--", ratio: 0 }; } function formatUpdateTime(data) { if (data && typeof data.updated === "string" && data.updated.trim().length > 0) { return data.updated.trim(); } if (data && typeof data.updated_at === "number" && Number.isFinite(data.updated_at)) { return new Date(data.updated_at).toLocaleString("zh-CN", { hour12: false }); } return new Date().toLocaleString("zh-CN", { hour12: false }); } function renderInsights(list, gapSecond) { const total = Array.isArray(list) ? list.length : 0; programmeCountEl.textContent = total ? total.toString() : "--"; if (total) { const topMarket = list.reduce((max, item) => { const value = Number(item?.market_rate); return value > max ? value : max; }, 0); const topAttention = list.reduce((max, item) => { const value = Number(item?.attention_rate); return value > max ? value : max; }, 0); topMarketRateEl.textContent = topMarket ? topMarket.toFixed(2) : "--"; topAttentionRateEl.textContent = topAttention ? topAttention.toFixed(2) : "--"; } else { topMarketRateEl.textContent = "--"; topAttentionRateEl.textContent = "--"; } refreshGapEl.textContent = formatGapText(gapSecond); } function createMetric(label, value) { return `