feat(web): add mobile-first read-only dashboard frontend
This commit is contained in:
1 parent
257fbe009b
commit
cbba405271
18 files changed
+2628
-2
No files matched your search
@@ -0,0 +1,108 @@
|
||||
import { api } from "../api.js";
|
||||
import {
|
||||
badge, emptyState, sectionHeader, videoCard,
|
||||
} from "../components.js";
|
||||
import { barChart, lineChart } 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=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>
|
||||
|
||||
<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: "#fb7299",
|
||||
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: "#00aeec",
|
||||
});
|
||||
}
|
||||
root.querySelector("#up-sort")?.addEventListener("change", (event) => {
|
||||
replaceQuery({ sort: event.target.value });
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user