75 lines
3.4 KiB
JavaScript
75 lines
3.4 KiB
JavaScript
import { api } from "../api.js";
|
||
import { cover, emptyState, sectionHeader } from "../components.js";
|
||
import {
|
||
escapeAttr, escapeHtml, formatCount, formatDate, formatDuration,
|
||
} from "../format.js";
|
||
|
||
function candidateCard(item) {
|
||
const score = Number(item.score || 0);
|
||
return `<article class="candidate-card">
|
||
${cover(item, "candidate-thumb")}
|
||
<div class="candidate-body">
|
||
<div class="candidate-top">
|
||
<span class="score-badge ${score >= 4 ? "high" : score >= 2 ? "mid" : "low"}">score ${score}</span>
|
||
${item.notified_at ? `<span class="badge subtle">已通知</span>` : `<span class="badge warning">未通知</span>`}
|
||
<span class="badge subtle">剩余 ${item.days_left} 天</span>
|
||
</div>
|
||
<h3 class="video-title">${escapeHtml(item.title || "(无标题)")}</h3>
|
||
<div class="video-meta">
|
||
<span>${escapeHtml(item.up_name || "未知 UP")}</span>
|
||
<span>发布 ${escapeHtml(formatDate(item.pubdate))}</span>
|
||
<span>${escapeHtml(formatDuration(item.duration))}</span>
|
||
<span>${escapeHtml(item.channel || "")}</span>
|
||
</div>
|
||
<div class="video-stats">
|
||
<span class="secondary-stat">播放 ${formatCount(item.play)}</span>
|
||
<span class="secondary-stat">点赞 ${formatCount(item.like)}</span>
|
||
<span class="secondary-stat">收藏 ${formatCount(item.favorite)}</span>
|
||
<span class="secondary-stat">弹幕 ${formatCount(item.danmaku)}</span>
|
||
<span class="secondary-stat">评论 ${formatCount(item.review)}</span>
|
||
</div>
|
||
<div class="candidate-actions">
|
||
${item.url ? `<a class="button ghost" href="${escapeAttr(item.url)}" target="_blank" rel="noreferrer">打开 B 站</a>` : ""}
|
||
<span class="readonly-note">请在 Telegram 中收藏 / 忽略 / 拉黑</span>
|
||
</div>
|
||
</div>
|
||
</article>`;
|
||
}
|
||
|
||
export async function renderCandidates(root) {
|
||
const data = await api("/api/candidates");
|
||
const pending = data.items.filter((item) => item.decision === null);
|
||
const decided = data.items.filter((item) => item.decision !== null);
|
||
|
||
root.innerHTML = `<section class="page candidates-page">
|
||
<header class="page-head">
|
||
<div>
|
||
<h1>候选池</h1>
|
||
<p class="page-sub">${formatCount(data.pending)} 条待确认 · ${formatCount(data.total)} 条总计 · 保留 ${data.ttl_days} 天</p>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="notice-row">
|
||
<strong>Web 看板只读</strong>
|
||
<span>${data.telegram_enabled ? "请在 Telegram 消息中处理候选。" : "Telegram 未启用,请使用 CLI 或数据库处理候选。"}</span>
|
||
</div>
|
||
|
||
<section class="panel">
|
||
${sectionHeader("待确认")}
|
||
${pending.length ? `<div class="candidate-list">${pending.map(candidateCard).join("")}</div>`
|
||
: emptyState("没有待确认候选", "搜索发现达到阈值的视频会出现在这里,同时发送 Telegram 通知。")}
|
||
</section>
|
||
|
||
${decided.length ? `<details class="panel details-panel">
|
||
<summary>已处理候选(${decided.length})</summary>
|
||
<div class="candidate-list">
|
||
${decided.map((item) => `<div class="decided-row">
|
||
<span class="badge subtle">${escapeHtml(item.decision)}</span>
|
||
<a href="${escapeAttr(item.url || "#")}" target="_blank" rel="noreferrer">${escapeHtml(item.title)}</a>
|
||
<span>${escapeHtml(item.up_name || "")}</span>
|
||
</div>`).join("")}
|
||
</div>
|
||
</details>` : ""}
|
||
</section>`;
|
||
}
|