import { api } from "../api.js"; import { emptyState, mediaCard } from "../components.js"; import { escapeAttr, escapeHtml, formatCount, formatDelta, formatPercent, formatScore, } from "../format.js"; import { canAccessProtected } from "../session.js"; function statChip(label, value, { href = null, tone = "" } = {}) { const inner = `${escapeHtml(label)}${value}`; if (href) { return `${inner}`; } return `
${inner}
`; } function heatGrid(items) { if (!items.length) return emptyState("暂无热度数据", "完成一次快照后这里会出现榜单。"); return `
${items.map((item, index) => mediaCard(item, { rank: index + 1, highlight: `热度 ${formatScore(item.heat)} · 前 ${Math.max(0, 100 - (item.heat_percentile || 0)).toFixed(0)}%`, })).join("")}
`; } function publishedCard(item) { return mediaCard(item, { highlight: `${formatCount(item.metrics?.view)} 播放 · 互动 ${formatPercent(item.interaction_rate)}`, }); } function growthCard(item, index) { const delta = item.growth?.delta; let highlight = `${formatCount(item.metrics?.view)} 播放`; if (delta !== null && delta !== undefined) { const tone = Number(delta) >= 0 ? "up" : "down"; highlight += ` ${formatDelta(delta)}`; } return mediaCard(item, { rank: index + 1, highlight }); } export async function renderHome(root) { const [overview, published, heat, growth] = await Promise.all([ api("/api/overview"), api("/api/videos", { sort: "published", within_days: 14, limit: 6 }), api("/api/videos", { sort: "heat", limit: 6 }), // 不带天数:后端按 home_growth_days(默认 auto:有 7 日则 7,否则 1)决定,响应带 days api("/api/videos", { sort: "growth", limit: 6 }), ]); const fans = overview.fans; const firstDay = overview.is_first_day; const addedLabel = firstDay ? formatCount(overview.initial_import_count) : formatCount(overview.added_today); const activeCount = overview.counts?.active || 0; const pending = overview.counts?.candidates_pending || 0; // 候选提示仅对已登录(或未启用鉴权)用户可见,访客不展示运行期信息。 const showCandidates = pending > 0 && canAccessProtected(); // 以后端返回的 days(与 growth_days 同步)为准,避免前端再探查 const growthDays = Number(growth.days ?? growth.growth_days) || 1; const growthLabel = `近 ${growthDays} 日`; const growthEmptyTitle = growth.reason || `近 ${growthDays} 日增长暂不可用`; const growthEmptyHint = growthDays <= 1 ? "当前快照不足,完成至少 2 天快照后即可显示播放增量。" : `需要至少 ${growthDays + 1} 天快照才能计算近 ${growthDays} 日增长。`; root.innerHTML = `

最新动态

收录视频 ${formatCount(activeCount)}

${statChip(firstDay ? "首次导入" : "今日入库", escapeHtml(addedLabel), { href: "/videos?sort=added&within_days=7", tone: "accent", })} ${statChip("播放增量", overview.view_delta_available ? formatDelta(overview.view_delta) : "—", { href: "/videos?sort=growth&growth_days=1", })} ${statChip("7 日粉丝变化", fans?.delta_7d === null || fans?.delta_7d === undefined ? "—" : formatDelta(fans.delta_7d), { href: "/fans", })} ${statChip("数据日期", escapeHtml(overview.data_date || "—"))}
${showCandidates ? ` ${formatCount(pending)} 条候选待确认 请在 Telegram 中处理 → ` : ""}

最近发布

查看全部

按视频发布时间排序,与入库时间无关。

${published.items.length ? published.items.map((item) => publishedCard(item)).join("") : emptyState("最近 14 天没有新发布视频", "搜索发现的新视频会逐步补充。")}

增长动态 · ${escapeHtml(growthLabel)}

增长榜
${growth.available && growth.items.length ? `
${growth.items.map((item, index) => growthCard(item, index)).join("")}
` : emptyState(growthEmptyTitle, growthEmptyHint)}

热度速览

完整榜单
${heatGrid(heat.items)}
`; root.querySelector("#refresh-home")?.addEventListener("click", () => window.location.reload()); }