diff --git a/reaction_board/web/static/app.js b/reaction_board/web/static/app.js
index 2280cda..7441acf 100644
--- a/reaction_board/web/static/app.js
+++ b/reaction_board/web/static/app.js
@@ -4,25 +4,31 @@ import { onRoute, parseLocation, 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 { renderUps, renderVideos } from "./views/rankings.js";
import { renderSystem } from "./views/system.js";
import { renderUp } from "./views/up.js";
import { renderVideo } from "./views/video.js";
+import { renderAbout } from "./views/about.js";
const NAV_ITEMS = [
{ path: "/home", label: "首页", icon: "home" },
- { path: "/rank", label: "榜单", icon: "rank" },
+ { path: "/videos", label: "视频", icon: "video" },
+ { path: "/ups", label: "UP主", icon: "up" },
{ path: "/fans", label: "粉丝", icon: "fans" },
{ path: "/candidates", label: "候选", icon: "candidate", protected: true },
{ path: "/system", label: "系统", icon: "system", protected: true },
+ { path: "/about", label: "说明", icon: "info" },
];
const ROUTES = [
{ pattern: /^\/home$/, view: renderHome, title: "最新动态" },
- { pattern: /^\/rank$/, view: renderRankings, title: "榜单" },
+ { pattern: /^\/videos$/, view: renderVideos, title: "视频" },
+ { pattern: /^\/ups$/, view: renderUps, title: "UP主" },
+ { pattern: /^\/rank$/, view: renderLegacyRank, title: "榜单" },
{ pattern: /^\/video\/(\d+)$/, view: renderVideo, title: "视频详情" },
{ pattern: /^\/up\/(\d+)$/, view: renderUp, title: "UP 详情" },
{ pattern: /^\/fans$/, view: renderFans, title: "粉丝" },
+ { pattern: /^\/about$/, view: renderAbout, title: "数据说明" },
{
pattern: /^\/candidates$/, view: renderCandidates, title: "候选池",
beforeEnter: requireAuth,
@@ -33,6 +39,13 @@ const ROUTES = [
},
];
+function renderLegacyRank(root, context) {
+ // 旧链接 /rank?scope=up 仍可用,但不再在页面内混排视频与 UP。
+ return context.query.get("scope") === "up"
+ ? renderUps(root, context)
+ : renderVideos(root, context);
+}
+
let authState = { authEnabled: false, authenticated: true };
function canAccessProtected() {
@@ -53,9 +66,12 @@ function icon(name) {
const paths = {
home: '
${escapeHtml(metric)} ${(Number(value) * 100).toFixed(0)}%relation/stat,匿名可用。${data.schema_version};${data.preview ? "当前为预览数据,不影响真实数据库。" : "当前为真实数据库。"}pubdate):视频在 B 站上线的时刻。first_seen):本站第一次看到并写入该视频的时刻。人工加收藏或搜索确认都可能导致入库晚于发布。stat_date):每天采集一次播放与互动,写入 snapshot。缺失日不插值,曲线直接断开。因此,同一个视频的“发布后 7 天数据”和“入库后 7 天数据”可能不是同一段区间;详情页会同时展示发布时间与入库时间。
+log1p,再按中位数与 IQR 做稳健标准化,最后加权求和。分数只用于同一批数据内排序,不能跨日期比较。pages 是分 P 数,duration 是该 bvid 各 P 时长之和。B 站不公开单 P 播放,因此没有单 P 榜;分 P 数只用于详情展示,不参与排序。duration 为空或 ≤ 0 的视频(通常只在失效视频上出现)不进入任何时长相关榜,也不做插补。权重是主观配置,可在配置文件中调整;页面展示的是当前生效值。
+video.up_follower;同一 UP 的旧视频不会被新快照覆盖。up_profile.follower_first 保留首次采集值,follower_latest 随刷新更新。backfilled;它与真实的“入库时”存在时间差,暂不用于榜单排序。app.py backfill-followers 补齐。按视频发布时间排序,与入库时间无关。
该 UP 在黑名单中;黑名单只影响未来发现,不影响当前视频追踪。
` : ""} + ${detail.up_follower_backfilled ? `UP 粉丝数为回填值,不是真实的入库时点。
` : ""}