import { api } from "../api.js";
import {
badge, centerActiveChips, cover, emptyState, metricChips, metricGrid,
sectionHeader, videoCard,
} from "../components.js";
import { lineChart, themeColor } from "../charts.js";
import {
escapeAttr, escapeHtml, formatCount, formatDate, formatDelta,
formatDuration, formatNumber, formatPercent, formatScore,
metricLabel, statusLabel,
} from "../format.js";
import { replaceQuery } from "../router.js";
const METRICS = ["view", "like", "coin", "favorite", "share", "reply", "danmaku"];
const RANGES = [["7", "7 天"], ["30", "30 天"], ["90", "90 天"], ["all", "全部"]];
function snapshotTable(points) {
if (!points.length) return emptyState("暂无快照", "该视频还没有成功采集的数据。");
return `
| 日期 | ${METRICS.map((metric) => `${metricLabel(metric)} | `).join("")}状态 |
${points.slice().reverse().map((point) => `
| ${escapeHtml(point.stat_date)} |
${METRICS.map((metric) => `${point.ok ? formatNumber(point[metric]) : "—"} | `).join("")}
${point.ok ? "成功" : `失败`} |
`).join("")}
`;
}
export async function renderVideo(root, { params, query }) {
const aid = Number(params[0]);
const metric = METRICS.includes(query.get("metric")) ? query.get("metric") : "view";
const range = query.get("range") || "30";
const trendDays = range === "all" ? "all" : range;
const [detail, trend] = await Promise.all([
api(`/api/videos/${aid}`),
api(`/api/videos/${aid}/trend`, { range: trendDays }),
]);
const up = detail.up_mid ? await api(`/api/ups/${detail.up_mid}`) : null;
const samples = detail.snapshot_count || 0;
const others = up?.videos?.filter((item) => item.aid !== aid).slice(0, 5) || [];
root.innerHTML = `
${cover(detail, "hero-cover")}
${escapeHtml(detail.title)}
${detail.blacklisted ? `
该 UP 在黑名单中;黑名单只影响未来发现,不影响当前视频追踪。
` : ""}
${detail.up_follower_backfilled ? `
UP 粉丝数为回填值,不是真实的入库时点。
` : ""}
${metricGrid(detail)}
互动率${formatPercent(detail.interaction_rate)}
综合热度${formatScore(detail.heat)}
全库百分位${detail.heat_percentile === null || detail.heat_percentile === undefined ? "—" : `前 ${Math.max(0, 100 - detail.heat_percentile).toFixed(0)}%`}
播放日增${detail.growth?.available ? formatDelta(detail.growth.delta) : "—"}
内容效率${detail.efficiency == null ? "—" : `${formatCount(detail.efficiency)}/小时`}
互动效率${detail.interaction_efficiency == null ? "—" : `${formatCount(detail.interaction_efficiency)}/小时`}
UP 粉丝(入库时)${formatCount(detail.up_follower)}
快照数${samples}
最近快照${escapeHtml(detail.snapshot_date || "—")}
${sectionHeader("七项趋势")}
${metricChips(METRICS, metric)}
${RANGES.map(([value, label]) => ``).join("")}
${trend.available ? `` : emptyState("暂无趋势数据", "该视频还没有成功快照。")}
${!trend.available ? "" : `缺失日期会断开,不插值;失败快照按缺失处理。纵轴按数据范围缩放,精确值见下方快照记录。
`}
${sectionHeader("快照记录")}
${snapshotTable(trend.points || [])}
${up ? `
${others.length ? others.map((item) => videoCard(item)).join("") : emptyState("没有其他视频")}
` : ""}
`;
root.querySelectorAll("[data-metric]").forEach((button) => button.addEventListener("click", () => {
replaceQuery({ metric: button.dataset.metric });
}));
root.querySelectorAll("[data-range]").forEach((button) => button.addEventListener("click", () => {
replaceQuery({ range: button.dataset.range });
}));
centerActiveChips(root);
const chartElement = root.querySelector("#video-trend");
if (chartElement) {
lineChart(chartElement, {
dates: trend.dates || [],
values: trend.series?.[metric] || [],
name: metricLabel(metric),
color: metric === "view" ? themeColor("accent") : themeColor("blue"),
scale: true,
});
}
}