const API_ENDPOINTS = [
"https://60s.api.shumengya.top/v2/maoyan/realtime/web"
];
const FALLBACK_ENDPOINT = "./返回接口.json";
const REFRESH_INTERVAL = 4500;
const MAX_ITEMS = 40;
const refreshButton = document.getElementById("refreshButton");
const updateTimeEl = document.getElementById("updateTime");
const seriesListEl = document.getElementById("seriesList");
const seriesCountEl = document.getElementById("seriesCount");
const topHeatEl = document.getElementById("topHeat");
const avgHeatEl = document.getElementById("avgHeat");
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 formatGap(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 remainder = Math.round(numeric % 60);
if (remainder === 0) {
return `约每 ${minutes} 分钟`;
}
return `约每 ${minutes} 分 ${remainder} 秒`;
}
function formatUpdateTime(data) {
if (data && typeof data.updated === "string" && data.updated.trim()) {
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 renderStats(list, gapSeconds) {
const total = Array.isArray(list) ? list.length : 0;
seriesCountEl.textContent = total ? total.toString() : "--";
if (total) {
let maxHeat = 0;
let sumHeat = 0;
list.forEach(item => {
const heat = Number(item?.curr_heat);
if (Number.isFinite(heat)) {
if (heat > maxHeat) {
maxHeat = heat;
}
sumHeat += heat;
}
});
topHeatEl.textContent = maxHeat ? maxHeat.toFixed(2) : "--";
const average = sumHeat && total ? (sumHeat / total) : 0;
avgHeatEl.textContent = average ? average.toFixed(2) : "--";
} else {
topHeatEl.textContent = "--";
avgHeatEl.textContent = "--";
}
refreshGapEl.textContent = formatGap(gapSeconds);
}
function createMetric(label, value) {
return `
${name}
${releaseInfo} · ${platform}
${createMetric("实时热度", heatDesc)}
${createMetric("上线信息", releaseInfo)}
${createMetric("播出平台", platform)}
${createMetric("剧集ID", safeText(series?.series_id))}
`;
return article;
}
function renderSeriesList(list) {
seriesListEl.innerHTML = "";
if (!Array.isArray(list) || list.length === 0) {
const empty = document.createElement("div");
empty.className = "empty-message";
empty.textContent = "暂时没有可展示的剧集数据";
seriesListEl.appendChild(empty);
return;
}
const maxBar = normalizeBarValue(list);
list.slice(0, MAX_ITEMS).forEach((series, index) => {
seriesListEl.appendChild(createSeriesItem(series, index, maxBar));
});
}
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 (fallbackError) {
console.warn("本地示例数据读取失败", fallbackError);
}
return null;
}
async function loadData(isManual = false) {
if (isLoading) {
return;
}
isLoading = true;
if (isManual) {
refreshButton.disabled = true;
refreshButton.textContent = "刷新中...";
}
if (!seriesListEl.children.length) {
seriesListEl.innerHTML = '正在载入网剧热度...
';
}
try {
const data = await retrieveData();
if (!data) {
throw new Error("无法获取数据");
}
const list = Array.isArray(data.list) ? data.list : [];
renderSeriesList(list);
renderStats(list, data.update_gap_second);
updateTimeEl.textContent = `最近更新 ${formatUpdateTime(data)}`;
} catch (error) {
console.error("加载数据失败", error);
seriesListEl.innerHTML = "";
const errBox = document.createElement("div");
errBox.className = "error-message";
errBox.textContent = "数据获取暂时不可用,系统稍后会自动重试";
seriesListEl.appendChild(errBox);
updateTimeEl.textContent = "最近更新 --";
renderStats([], 0);
} finally {
if (isManual) {
refreshButton.disabled = false;
refreshButton.textContent = "手动刷新";
}
isLoading = false;
}
}
function startAutoRefresh() {
if (autoTimer) {
clearInterval(autoTimer);
}
autoTimer = setInterval(() => {
loadData(false);
}, REFRESH_INTERVAL);
}
refreshButton.addEventListener("click", () => {
loadData(true);
});
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
if (autoTimer) {
clearInterval(autoTimer);
autoTimer = null;
}
} else {
startAutoRefresh();
loadData(false);
}
});
function init() {
loadData(false);
startAutoRefresh();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", init);
} else {
init();
}