import { health } from "./api.js"; import { disposeAll } from "./charts.js"; import { escapeHtml } from "./format.js"; import { onRoute, startRouter } from "./router.js"; import { renderCandidates } from "./views/candidates.js"; import { renderFans } from "./views/fans.js"; import { renderHome } from "./views/home.js"; import { renderRankings } from "./views/rankings.js"; import { renderSystem } from "./views/system.js"; import { renderUp } from "./views/up.js"; import { renderVideo } from "./views/video.js"; const NAV_ITEMS = [ { path: "/home", label: "首页", icon: "home" }, { path: "/rank", label: "榜单", icon: "rank" }, { path: "/fans", label: "粉丝", icon: "fans" }, { path: "/candidates", label: "候选", icon: "candidate" }, { path: "/system", label: "系统", icon: "system" }, ]; const ROUTES = [ { pattern: /^\/home$/, view: renderHome, title: "最新动态" }, { pattern: /^\/rank$/, view: renderRankings, title: "榜单" }, { pattern: /^\/video\/(\d+)$/, view: renderVideo, title: "视频详情" }, { pattern: /^\/up\/(\d+)$/, view: renderUp, title: "UP 详情" }, { pattern: /^\/fans$/, view: renderFans, title: "粉丝" }, { pattern: /^\/candidates$/, view: renderCandidates, title: "候选池" }, { pattern: /^\/system$/, view: renderSystem, title: "系统状态" }, ]; function icon(name) { const paths = { home: '', rank: '', fans: '', candidate: '', system: '', }; return ``; } function renderNav() { const nav = document.querySelector("#bottom-nav"); nav.innerHTML = NAV_ITEMS.map((item) => ` ${icon(item.icon)}${item.label} `).join(""); } function activeNav(path) { let active = "/home"; if (path.startsWith("/rank") || path.startsWith("/video/") || path.startsWith("/up/")) active = "/rank"; else if (path.startsWith("/fans")) active = "/fans"; else if (path.startsWith("/candidates")) active = "/candidates"; else if (path.startsWith("/system")) active = "/system"; document.querySelectorAll("[data-nav]").forEach((item) => { item.classList.toggle("active", item.dataset.nav === active); }); } function setTitle(route, path) { const title = route?.title || "看板"; document.title = `${title} · 咻咻满 Reaction`; const element = document.querySelector("#topbar-title"); if (element) element.textContent = title; activeNav(path); } function toast(message, kind = "info") { const element = document.querySelector("#toast"); if (!element) return; element.textContent = message; element.className = `toast show ${kind}`; window.clearTimeout(toast.timer); toast.timer = window.setTimeout(() => { element.className = "toast"; }, 2600); } window.addEventListener("error", (event) => { const target = event.target; if (target instanceof HTMLImageElement) { target.classList.add("image-error"); const wrapper = target.closest(".video-thumb, .hero-cover, .heat-thumb, .candidate-thumb"); wrapper?.classList.add("cover-placeholder"); } }, true); async function boot() { renderNav(); const banner = document.querySelector("#preview-banner"); const hint = document.querySelector("#topbar-hint"); try { const payload = await health(); const data = payload?.data || payload; if (data?.preview) { banner.hidden = false; if (hint) hint.hidden = false; } } catch { toast("无法连接看板服务", "error"); } onRoute(({ path, route }) => { disposeAll(); setTitle(route, path); }); startRouter({ root: document.querySelector("#app"), outlet: document.querySelector("#view"), routes: ROUTES, }); } window.addEventListener("unhandledrejection", (event) => { const reason = event.reason; if (reason?.name === "ApiError") { toast(reason.message, "error"); } }); boot();