import { api, apiUrl } from "../api.js"; import { centerActiveChips, chip, emptyState, pager, sectionHeader, upCard, videoCard, } from "../components.js"; import { escapeAttr, escapeHtml, formatCount } from "../format.js"; import { replaceQuery } from "../router.js"; const VIDEO_SORTS = [ ["heat", "综合热度"], ["view", "播放"], ["like", "点赞"], ["coin", "投币"], ["favorite", "收藏"], ["share", "转发"], ["reply", "评论"], ["danmaku", "弹幕"], ["interaction", "互动率"], ["growth", "增长"], ["growth_rate", "增速"], ]; const UP_SORTS = [ ["views", "总播放"], ["count", "视频数"], ["avg_views", "平均播放"], ["interaction", "总互动"], ["avg_interaction", "平均互动"], ["latest", "最近发布"], ]; const METRIC_PRIMARY = new Set(["view", "like", "coin", "favorite", "share", "reply", "danmaku"]); function controls({ scope, sort, within, growthDays, q, folder, minVideos, folders }) { const sorts = scope === "up" ? UP_SORTS : VIDEO_SORTS; return `
${sorts.map(([value, label]) => chip(label, sort === value, "sort", value)).join("")}
${[["all", "全部发布时间"], ["7", "近 7 天"], ["30", "近 30 天"], ["90", "近 90 天"]] .map(([value, label]) => chip(label, String(within || "all") === value, "within", value)).join("")}
${scope === "video" && folders.length ? `` : ""} ${scope === "up" ? `` : ""}
${scope === "video" && (sort === "growth" || sort === "growth_rate") ? `
增长区间 ${[[1, "1 天"], [7, "7 天"], [30, "30 天"]] .map(([value, label]) => chip(label, Number(growthDays) === value, "growth-days", value)).join("")}
` : ""} `; } function listHtml(data, scope, sort, startRank) { if (!data.items.length) { return emptyState( data.available === false ? "增长榜暂不可用" : "没有匹配的视频", data.available === false ? (data.reason || "需要至少两天的快照。") : "换个筛选条件或清空搜索关键词试试。", ); } if (scope === "up") { return `
${data.items.map((item, index) => upCard(item, { rank: startRank + index })).join("")}
`; } const primary = METRIC_PRIMARY.has(sort) ? sort : "view"; return `
${data.items.map((item, index) => videoCard(item, { rank: startRank + index, primary })).join("")}
`; } export async function renderRankings(root, { query }) { const scope = query.get("scope") === "up" ? "up" : "video"; const defaultSort = scope === "up" ? "views" : "heat"; const sort = query.get("sort") || defaultSort; const within = query.get("within_days") || "all"; const growthDays = query.get("growth_days") || "7"; const q = query.get("q") || ""; const folder = query.get("folder") || ""; const minVideos = query.get("min_videos") || "1"; const page = Math.max(1, Number(query.get("page") || 1)); const overview = await api("/api/overview"); const folders = overview.folders || []; const params = { sort, page, page_size: 20, within_days: within === "all" ? null : within, growth_days: growthDays, q, }; let data; if (scope === "up") { data = await api("/api/rankings", { ...params, scope: "up", min_videos: minVideos, }); } else { data = await api("/api/videos", { ...params, folder: folder || null }); } const startRank = (data.page - 1) * data.page_size + 1; const exportParams = { ...params, folder: folder || null, scope, min_videos: scope === "up" ? minVideos : null, }; delete exportParams.page; delete exportParams.page_size; root.innerHTML = `

榜单

共 ${formatCount(data.total)} ${scope === "up" ? "个 UP" : "条视频"}

${scope === "video" ? `导出 CSV` : ""}
${controls({ scope, sort, within, growthDays, q, folder, minVideos, folders })} ${data.available === false ? `
增长数据尚不可用${escapeHtml(data.reason || "")}
` : ""} ${listHtml(data, scope, sort, startRank)} ${pager(data.page, data.pages)}
`; root.querySelectorAll("[data-scope]").forEach((button) => button.addEventListener("click", () => { replaceQuery({ scope: button.dataset.scope, sort: button.dataset.scope === "up" ? "views" : "heat", page: null, }); })); root.querySelectorAll("[data-sort]").forEach((button) => button.addEventListener("click", () => { replaceQuery({ sort: button.dataset.sort, page: null }); })); root.querySelectorAll("[data-within]").forEach((button) => button.addEventListener("click", () => { replaceQuery({ within_days: button.dataset.within === "all" ? null : button.dataset.within, page: null }); })); root.querySelectorAll("[data-growth-days]").forEach((button) => button.addEventListener("click", () => { replaceQuery({ growth_days: button.dataset.growthDays, page: null }); })); root.querySelector("#folder-filter")?.addEventListener("change", (event) => { replaceQuery({ folder: event.target.value || null, page: null }); }); root.querySelector("#min-videos")?.addEventListener("change", (event) => { replaceQuery({ min_videos: event.target.value, page: null }); }); root.querySelector("#rank-filter")?.addEventListener("submit", (event) => { event.preventDefault(); const value = new FormData(event.target).get("q") || ""; replaceQuery({ q: String(value).trim() || null, page: null }); }); root.querySelectorAll("[data-page]").forEach((button) => button.addEventListener("click", () => { if (button.disabled) return; replaceQuery({ page: button.dataset.page }); })); centerActiveChips(root); }