feat(web): add fan date range modes and delta column
This commit is contained in:
1 parent
998d0fd0c0
commit
274eccba93
6 files changed
+253
-22
No files matched your search
@@ -2,7 +2,7 @@ import { api } from "../api.js";
|
||||
import { centerActiveChips, emptyState, sectionHeader } from "../components.js";
|
||||
import { lineChart, themeColor } from "../charts.js";
|
||||
import {
|
||||
escapeHtml, formatCount, formatDelta, formatNumber,
|
||||
escapeAttr, escapeHtml, formatCount, formatDelta, formatNumber,
|
||||
} from "../format.js";
|
||||
import { replaceQuery } from "../router.js";
|
||||
|
||||
@@ -11,7 +11,15 @@ const RANGES = [["7", "7 天"], ["30", "30 天"], ["90", "90 天"], ["3650", "
|
||||
export async function renderFans(root, { query }) {
|
||||
const days = query.get("days") || "90";
|
||||
const mid = query.get("mid");
|
||||
const fans = await api("/api/fans", { days, mid });
|
||||
const start = query.get("start") || "";
|
||||
const end = query.get("end") || "";
|
||||
const customRange = Boolean(start || end);
|
||||
const fans = await api("/api/fans", {
|
||||
days: customRange ? null : days,
|
||||
start,
|
||||
end,
|
||||
mid,
|
||||
});
|
||||
const options = fans.options || [];
|
||||
|
||||
root.innerHTML = `<section class="page fans-page">
|
||||
@@ -43,10 +51,22 @@ export async function renderFans(root, { query }) {
|
||||
${sectionHeader("粉丝曲线")}
|
||||
<div class="chip-row scroll-x">
|
||||
${RANGES.map(([value, label]) => `<button type="button"
|
||||
class="chip${days === value ? " active" : ""}" data-days="${value}">${label}</button>`).join("")}
|
||||
class="chip${!customRange && days === value ? " active" : ""}" data-days="${value}">${label}</button>`).join("")}
|
||||
</div>
|
||||
<form class="date-range" id="fan-range">
|
||||
<input type="date" name="start" value="${escapeAttr(start)}" aria-label="开始日期">
|
||||
<span>至</span>
|
||||
<input type="date" name="end" value="${escapeAttr(end)}" aria-label="结束日期">
|
||||
<button class="button" type="submit">应用</button>
|
||||
${customRange ? `<button class="button ghost" type="button" id="fan-range-clear">清除</button>` : ""}
|
||||
</form>
|
||||
<div class="chart" id="fans-chart"></div>
|
||||
<p class="section-note">只绘制粉丝数,不叠加 reaction 视频数据;缺失日断开,不插值。</p>
|
||||
<p class="section-note">
|
||||
只绘制粉丝数,不叠加 reaction 视频数据;缺失日断开,不插值。
|
||||
${fans.mode === "monthly"
|
||||
? "当前范围超过一年,按每月粉丝峰值所在日期绘制。"
|
||||
: `当前按日绘制(${escapeHtml(fans.range_start || "—")} ~ ${escapeHtml(fans.range_end || "—")})。`}
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<section class="panel">
|
||||
@@ -54,13 +74,13 @@ export async function renderFans(root, { query }) {
|
||||
<p class="section-note">默认展示最近 ${fans.recent_days || 14} 天;更早的数据只在曲线中显示。</p>
|
||||
<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>日期</th><th>粉丝</th><th>状态</th></tr></thead>
|
||||
<thead><tr><th>日期</th><th>粉丝</th><th>较上一成功日</th></tr></thead>
|
||||
<tbody>
|
||||
${(fans.recent_points || []).slice().reverse().map((point) => `
|
||||
<tr class="${point.ok ? "" : "failed-row"}">
|
||||
<td>${escapeHtml(point.stat_date)}</td>
|
||||
<td>${point.ok ? formatNumber(point.follower) : "—"}</td>
|
||||
<td>${point.ok ? "成功" : "失败"}</td>
|
||||
<td>${point.delta === null || point.delta === undefined ? "—" : formatDelta(point.delta)}</td>
|
||||
</tr>`).join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -69,11 +89,25 @@ export async function renderFans(root, { query }) {
|
||||
</section>`;
|
||||
|
||||
root.querySelectorAll("[data-days]").forEach((button) => button.addEventListener("click", () => {
|
||||
replaceQuery({ days: button.dataset.days });
|
||||
replaceQuery({ days: button.dataset.days, start: null, end: null });
|
||||
}));
|
||||
root.querySelector("#fan-mid")?.addEventListener("change", (event) => {
|
||||
replaceQuery({ mid: event.target.value });
|
||||
});
|
||||
root.querySelector("#fan-range")?.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const form = new FormData(event.target);
|
||||
const nextStart = String(form.get("start") || "").trim();
|
||||
const nextEnd = String(form.get("end") || "").trim();
|
||||
if (nextStart && nextEnd && nextStart > nextEnd) {
|
||||
window.alert("开始日期不能晚于结束日期");
|
||||
return;
|
||||
}
|
||||
replaceQuery({ start: nextStart || null, end: nextEnd || null, days: null });
|
||||
});
|
||||
root.querySelector("#fan-range-clear")?.addEventListener("click", () => {
|
||||
replaceQuery({ start: null, end: null, days: null });
|
||||
});
|
||||
centerActiveChips(root);
|
||||
const chartElement = root.querySelector("#fans-chart");
|
||||
if (chartElement) {
|
||||
|
||||
Reference in new issue
Block a user