const accountHealthText = document.querySelector("#accountHealthText"); const refreshBtn = document.querySelector("#refreshBtn"); const accountForm = document.querySelector("#accountForm"); const accountQuery = document.querySelector("#accountQuery"); const accountStatus = document.querySelector("#accountStatus"); const accountMeta = document.querySelector("#accountMeta"); const accountRows = document.querySelector("#accountRows"); function fmtHours(value) { return Number(value || 0).toLocaleString("zh-CN", { maximumFractionDigits: 2 }); } function fmtTime(seconds) { if (!seconds) return "未知"; return new Date(seconds * 1000).toLocaleString("zh-CN", { hour12: false }); } function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") .replaceAll("<", "<") .replaceAll(">", ">") .replaceAll('"', """) .replaceAll("'", "'"); } function metric(label, value) { return `
${escapeHtml(label)}${escapeHtml(value)}
`; } function statusClass(status) { if (status === "欠费") return "debt"; if (status === "预警") return "warning"; if (status === "正常") return "normal"; return "closed"; } function renderPayments(payments) { if (!payments.length) return "暂无"; return payments.map((item) => `${escapeHtml(item.date)}:${fmtHours(item.hours)} 小时`).join("
"); } async function fetchJson(url) { const response = await fetch(url, { cache: "no-store" }); if (!response.ok) { let detail = `${response.status} ${response.statusText}`; try { const payload = await response.json(); detail = payload.detail || detail; } catch (_error) { detail = response.statusText || detail; } throw new Error(detail); } return response.json(); } function renderSummary(summary, count) { accountMeta.innerHTML = [ metric("当前结果", `${count} 人`), metric("欠费", summary.debt), metric("预警", summary.warning), metric("正常", summary.normal), ].join(""); } async function loadAccountHealth() { try { const data = await fetchJson("/api/account-health"); accountHealthText.textContent = `账户 ${data.accounts_count} 人;数据更新时间 ${fmtTime(data.accounts.mtime)}`; } catch (error) { accountHealthText.textContent = `读取失败:${error.message}`; } } async function loadAccounts() { accountRows.innerHTML = `正在读取`; const params = new URLSearchParams(); if (accountQuery.value.trim()) params.set("q", accountQuery.value.trim()); if (accountStatus.value) params.set("status", accountStatus.value); try { const data = await fetchJson(`/api/accounts?${params.toString()}`); renderSummary(data.summary, data.count); accountRows.innerHTML = data.accounts .map( (row) => ` ${escapeHtml(row.student)}
${escapeHtml(row.student_id)} ${escapeHtml(row.account_status)} ${fmtHours(row.remaining)} ${renderPayments(row.payments)} ${escapeHtml(row.note || "")} `, ) .join(""); if (!data.accounts.length) { accountRows.innerHTML = `没有符合条件的账户`; } } catch (error) { accountRows.innerHTML = `读取失败:${escapeHtml(error.message)}`; } } accountForm.addEventListener("submit", (event) => { event.preventDefault(); loadAccounts(); }); accountStatus.addEventListener("change", loadAccounts); refreshBtn.addEventListener("click", () => { loadAccountHealth(); loadAccounts(); }); loadAccountHealth(); loadAccounts();