const API_ENDPOINTS = [ "https://60s.api.shumengya.top/v2/maoyan/realtime/movie" ]; const FALLBACK_ENDPOINT = "./返回接口.json"; const REFRESH_INTERVAL = 5000; const MAX_MOVIES_TO_RENDER = 40; const updateTimeEl = document.getElementById("updateTime"); const refreshButton = document.getElementById("refreshButton"); const summaryTitleEl = document.getElementById("summaryTitle"); const totalBoxOfficeEl = document.getElementById("totalBoxOffice"); const totalBoxOfficeUnitEl = document.getElementById("totalBoxOfficeUnit"); const combinedBoxOfficeEl = document.getElementById("combinedBoxOffice"); const combinedBoxOfficeUnitEl = document.getElementById("combinedBoxOfficeUnit"); const showCountEl = document.getElementById("showCount"); const viewCountEl = document.getElementById("viewCount"); const movieListEl = document.getElementById("movieList"); let autoRefreshTimer = null; let isLoading = false; function escapeHtml(value) { if (value === undefined || value === null) { return ""; } return String(value) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } function safeText(value) { if (value === undefined || value === null || value === "") { return "--"; } return escapeHtml(value); } function parseRate(rateText) { if (!rateText || typeof rateText !== "string") { return { text: "--", ratio: 0 }; } const trimmed = rateText.trim(); const numeric = parseFloat(trimmed.replace(/[^0-9.]/g, "")); let ratio = Number.isFinite(numeric) ? Math.max(0, Math.min(numeric, 100)) : 0; if (trimmed.startsWith("<")) { ratio = Math.max(3, ratio); } return { text: escapeHtml(trimmed), ratio }; } 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.isNaN(data.updated_at)) { return new Date(data.updated_at).toLocaleString("zh-CN", { hour12: false }); } return new Date().toLocaleString("zh-CN", { hour12: false }); } function renderSummary(data) { summaryTitleEl.textContent = data?.title ? data.title : "实时大盘"; totalBoxOfficeEl.textContent = data?.split_box_office ? data.split_box_office : "--"; totalBoxOfficeUnitEl.textContent = data?.split_box_office_unit ? data.split_box_office_unit : ""; combinedBoxOfficeEl.textContent = data?.box_office ? data.box_office : "--"; combinedBoxOfficeUnitEl.textContent = data?.box_office_unit ? data.box_office_unit : ""; showCountEl.textContent = data?.show_count_desc ? data.show_count_desc : "--"; viewCountEl.textContent = data?.view_count_desc ? data.view_count_desc : "--"; } function createStat(label, value) { return `
${label} ${safeText(value)}
`; } function createMovieItem(movie, index) { const item = document.createElement("article"); item.className = "movie-item"; const topClass = index < 3 ? ` top-${index + 1}` : ""; const name = safeText(movie?.movie_name || "未命名影片"); const releaseInfo = movie?.release_info ? `
${safeText(movie.release_info)}
` : ""; const boxOfficeDesc = movie?.box_office_desc || (movie?.box_office ? `${movie.box_office}${movie.box_office_unit || ""}` : "--"); const splitBoxOfficeDesc = movie?.split_box_office_desc || (movie?.split_box_office ? `${movie.split_box_office}${movie.split_box_office_unit || ""}` : "--"); const totalBoxOfficeDesc = movie?.sum_box_desc ?? "--"; const totalSplitBoxOfficeDesc = movie?.sum_split_box_desc ?? "--"; let showCountText = "--"; if (movie?.show_count !== undefined && movie.show_count !== null && movie.show_count !== "") { const numericShowCount = Number(movie.show_count); showCountText = Number.isFinite(numericShowCount) ? `${numericShowCount.toLocaleString("zh-CN")} 场` : movie.show_count; } const avgShowView = movie?.avg_show_view ?? "--"; const avgSeatView = movie?.avg_seat_view ?? "--"; const boxRate = parseRate(movie?.box_office_rate); const showRate = parseRate(movie?.show_count_rate); item.innerHTML = `
${index + 1}
${name}
${releaseInfo}
${createStat("单日综合票房", boxOfficeDesc)} ${createStat("单日分账票房", splitBoxOfficeDesc)} ${createStat("累计综合票房", totalBoxOfficeDesc)} ${createStat("累计分账票房", totalSplitBoxOfficeDesc)} ${createStat("排片场次", showCountText)} ${createStat("场均人次", avgShowView)} ${createStat("上座率", avgSeatView)}
综合票房占比 ${boxRate.text}
排片占比 ${showRate.text}
`; const progressBars = item.querySelectorAll(".progress-bar span"); if (progressBars[0]) { progressBars[0].style.width = `${boxRate.ratio}%`; } if (progressBars[1]) { progressBars[1].style.width = `${showRate.ratio}%`; } return item; } function renderMovieList(list) { movieListEl.innerHTML = ""; if (!Array.isArray(list) || list.length === 0) { const empty = document.createElement("div"); empty.className = "error-message"; empty.textContent = "暂时没有可展示的实时票房数据"; movieListEl.appendChild(empty); return; } const sliced = list.slice(0, MAX_MOVIES_TO_RENDER); sliced.forEach((movie, index) => { movieListEl.appendChild(createMovieItem(movie, index)); }); } async function requestJson(url) { const response = await fetch(url, { cache: "no-store" }); if (!response.ok) { throw new Error(`请求失败: ${response.status}`); } return response.json(); } async function retrieveData() { for (const endpoint of API_ENDPOINTS) { try { const result = await requestJson(endpoint); if (result?.code === 200 && result?.data) { return result.data; } } catch (error) { console.warn("主接口请求失败", error); } } try { const fallbackResult = await requestJson(FALLBACK_ENDPOINT); if (fallbackResult?.data) { return fallbackResult.data; } } catch (error) { console.warn("本地示例数据读取失败", error); } return null; } async function loadData(isManual = false) { if (isLoading) { return; } isLoading = true; if (isManual) { refreshButton.disabled = true; refreshButton.textContent = "刷新中..."; } if (!movieListEl.children.length) { movieListEl.innerHTML = '
正在加载实时票房...
'; } try { const data = await retrieveData(); if (!data) { throw new Error("无法获取数据"); } renderSummary(data); renderMovieList(Array.isArray(data.list) ? data.list : []); updateTimeEl.textContent = `最近更新 ${formatUpdateTime(data)}`; } catch (error) { console.error("加载数据失败", error); movieListEl.innerHTML = ""; const err = document.createElement("div"); err.className = "error-message"; err.textContent = "数据获取暂时遇到问题,系统会稍后自动重试"; movieListEl.appendChild(err); updateTimeEl.textContent = "最近更新 --"; renderSummary(null); } finally { if (isManual) { refreshButton.disabled = false; refreshButton.textContent = "手动刷新"; } isLoading = false; } } function startAutoRefresh() { if (autoRefreshTimer) { clearInterval(autoRefreshTimer); } autoRefreshTimer = setInterval(() => { loadData(false); }, REFRESH_INTERVAL); } refreshButton.addEventListener("click", () => { loadData(true); }); document.addEventListener("visibilitychange", () => { if (document.hidden) { if (autoRefreshTimer) { clearInterval(autoRefreshTimer); autoRefreshTimer = null; } } else { startAutoRefresh(); loadData(false); } }); function init() { loadData(false); startAutoRefresh(); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); }