Files
re-xxm/reaction_board/web/static/views/up.js
T

109 lines
4.2 KiB
JavaScript

import { api } from "../api.js";
import {
badge, emptyState, sectionHeader, videoCard,
} from "../components.js";
import { barChart, lineChart, themeColor } from "../charts.js";
import {
escapeAttr, escapeHtml, formatCount, formatDate, formatPercent,
} from "../format.js";
import { replaceQuery } from "../router.js";
const SORTS = [
["view", "播放"], ["latest", "最新发布"], ["interaction", "互动率"],
];
function sortedVideos(videos, sort) {
const items = [...videos];
if (sort === "latest") {
items.sort((a, b) => b.pubdate - a.pubdate);
} else if (sort === "interaction") {
items.sort((a, b) => (b.interaction_rate || 0) - (a.interaction_rate || 0));
} else {
items.sort((a, b) => (b.metrics?.view || 0) - (a.metrics?.view || 0));
}
return items;
}
export async function renderUp(root, { params, query }) {
const mid = Number(params[0]);
const sort = query.get("sort") || "view";
const up = await api(`/api/ups/${mid}`);
const videos = sortedVideos(up.videos || [], sort);
const topForBar = [...(up.videos || [])]
.sort((a, b) => (b.metrics?.view || 0) - (a.metrics?.view || 0))
.slice(0, 12);
root.innerHTML = `<section class="page up-page">
<header class="page-head">
<a class="back-link" href="#/rank?scope=up&sort=avg_views">← 返回 UP 榜</a>
<a class="button ghost" href="${escapeAttr(up.space_url)}" target="_blank" rel="noreferrer">B 站空间</a>
</header>
<div class="detail-hero compact-hero">
<div class="detail-hero-body">
<h1>${escapeHtml(up.up_name || `UP ${mid}`)}</h1>
<div class="detail-meta">
<span>mid ${up.up_mid}</span>
${up.blacklisted ? badge("黑名单 · 仅影响发现", "warning") : ""}
${(up.folders || []).map((title) => badge(title)).join("")}
</div>
<p class="section-note">首次发布 ${escapeHtml(formatDate(up.first_pubdate))} · 最近发布 ${escapeHtml(formatDate(up.latest_pubdate))}</p>
</div>
</div>
<div class="mini-stats up-stats">
<div><span>视频数</span><strong>${formatCount(up.video_count)}</strong></div>
<div><span>总播放</span><strong>${formatCount(up.total_views)}</strong></div>
<div><span>平均播放</span><strong>${formatCount(up.avg_views)}</strong></div>
<div><span>总互动</span><strong>${formatCount(up.total_interactions)}</strong></div>
<div><span>平均互动率</span><strong>${formatPercent(up.avg_interaction)}</strong></div>
</div>
<section class="panel chart-panel">
${sectionHeader("该 UP 视频总播放趋势")}
${up.series?.dates?.length ? `<div class="chart" id="up-trend"></div>` : emptyState("暂无趋势", "该 UP 还没有成功快照。")}
<p class="section-note">按该 UP 全部视频的每日播放合计绘制;缺失日期断开。</p>
</section>
${videos.length > 1 ? `<section class="panel chart-panel">
${sectionHeader("单视频播放对比")}
${topForBar.length ? `<div class="chart chart-tall" id="up-bars"></div>` : emptyState("暂无视频数据")}
</section>` : ""}
<section class="panel">
<div class="section-head">
<h2>视频列表</h2>
<select class="select" id="up-sort">
${SORTS.map(([value, label]) => `<option value="${value}" ${sort === value ? "selected" : ""}>${label}</option>`).join("")}
</select>
</div>
<div class="video-list">
${videos.length ? videos.map((item) => videoCard(item)).join("") : emptyState("没有视频")}
</div>
</section>
</section>`;
const trendElement = root.querySelector("#up-trend");
if (trendElement) {
lineChart(trendElement, {
dates: up.series.dates,
values: up.series.views,
name: "总播放",
color: themeColor("accent"),
scale: true,
});
}
const barsElement = root.querySelector("#up-bars");
if (barsElement) {
barChart(barsElement, {
labels: topForBar.map((item) => (item.title || `#${item.aid}`).slice(0, 12)),
values: topForBar.map((item) => item.metrics?.view || 0),
name: "播放",
color: themeColor("blue"),
});
}
root.querySelector("#up-sort")?.addEventListener("change", (event) => {
replaceQuery({ sort: event.target.value });
});
}