增加排名
This commit is contained in:
+263
-157
@@ -1,55 +1,64 @@
|
||||
const REFRESH_MS = 60_000;
|
||||
|
||||
const tableState = {
|
||||
yesterday: {
|
||||
items: [],
|
||||
meta: {},
|
||||
sortKey: "rank",
|
||||
sortDir: "asc",
|
||||
},
|
||||
today: {
|
||||
items: [],
|
||||
meta: {},
|
||||
sortKey: "rank",
|
||||
sortDir: "asc",
|
||||
},
|
||||
const PERIOD_API = {
|
||||
today: "/api/today/top30",
|
||||
yesterday: "/api/yesterday/top30",
|
||||
daybefore: "/api/daybefore/top30",
|
||||
};
|
||||
|
||||
const tableState = {
|
||||
today: { items: [], meta: {}, sortKey: "rank", sortDir: "asc" },
|
||||
yesterday: { items: [], meta: {}, sortKey: "rank", sortDir: "asc" },
|
||||
daybefore: { items: [], meta: {}, sortKey: "rank", sortDir: "asc" },
|
||||
};
|
||||
|
||||
let statsData = null;
|
||||
let currentView = "today";
|
||||
|
||||
const SORT_KEYS = {
|
||||
rank: (r) => Number(r.rank) || 0,
|
||||
symbol: (r) => String(r.symbol || ""),
|
||||
quote_volume: (r) => Number(r.quote_volume) || 0,
|
||||
price_change_pct: (r) => Number(r.price_change_pct) || 0,
|
||||
tags: (r) => {
|
||||
let score = 0;
|
||||
if (r.is_high_volume) score += 2;
|
||||
if (r.is_high_change) score += 1;
|
||||
return score;
|
||||
},
|
||||
funding_rate: (r) => Number(r.funding_rate_pct) || 0,
|
||||
tags: (r) => {
|
||||
let s = 0;
|
||||
if (r.is_high_volume) s += 2;
|
||||
if (r.is_high_change) s += 1;
|
||||
return s;
|
||||
},
|
||||
total_quote_volume: (r) => Number(r.total_quote_volume) || 0,
|
||||
avg_change_pct: (r) => Number(r.avg_change_pct) || 0,
|
||||
};
|
||||
|
||||
const TABLE_HEADER = `
|
||||
<thead><tr>
|
||||
<th class="sortable" data-sort="rank">排名</th>
|
||||
<th class="sortable" data-sort="symbol">合约</th>
|
||||
<th class="chart-col">日线图</th>
|
||||
<th class="sortable" data-sort="quote_volume">成交额 (USDT)</th>
|
||||
<th class="sortable" data-sort="price_change_pct">涨跌幅</th>
|
||||
<th class="funding-col">资金费率</th>
|
||||
<th class="sortable" data-sort="tags">标记</th>
|
||||
</tr></thead>`;
|
||||
|
||||
function formatPeriod(start, end) {
|
||||
const fmt = (s) => s.replace("T", " ").slice(0, 16);
|
||||
const fmt = (s) => (s || "").replace("T", " ").slice(0, 16);
|
||||
return `${fmt(start)} ~ ${fmt(end)}`;
|
||||
}
|
||||
|
||||
function tagText(row) {
|
||||
const tags = [];
|
||||
if (row.is_high_volume) tags.push("千万+");
|
||||
if (row.is_high_change) tags.push("涨跌5%+");
|
||||
return tags.join(" ") || "";
|
||||
const t = [];
|
||||
if (row.is_high_volume) t.push("千万+");
|
||||
if (row.is_high_change) t.push("涨跌5%+");
|
||||
return t.join(" ") || "";
|
||||
}
|
||||
|
||||
function renderTags(row) {
|
||||
const parts = [];
|
||||
if (row.is_high_volume) {
|
||||
parts.push('<span class="tag tag-vol">千万+</span>');
|
||||
}
|
||||
if (row.is_high_change) {
|
||||
parts.push('<span class="tag tag-chg">涨跌5%+</span>');
|
||||
}
|
||||
return parts.length ? parts.join("") : "—";
|
||||
const p = [];
|
||||
if (row.is_high_volume) p.push('<span class="tag tag-vol">千万+</span>');
|
||||
if (row.is_high_change) p.push('<span class="tag tag-chg">涨跌5%+</span>');
|
||||
return p.length ? p.join("") : "—";
|
||||
}
|
||||
|
||||
function pctClass(pct) {
|
||||
@@ -58,9 +67,9 @@ function pctClass(pct) {
|
||||
return "";
|
||||
}
|
||||
|
||||
function sortItems(items, key, dir) {
|
||||
const getter = SORT_KEYS[key] || SORT_KEYS.rank;
|
||||
const sorted = [...items].sort((a, b) => {
|
||||
function sortItems(items, key, dir, customKeys) {
|
||||
const getter = (customKeys || SORT_KEYS)[key] || SORT_KEYS.rank;
|
||||
return [...items].sort((a, b) => {
|
||||
const va = getter(a);
|
||||
const vb = getter(b);
|
||||
if (typeof va === "string") {
|
||||
@@ -68,58 +77,72 @@ function sortItems(items, key, dir) {
|
||||
}
|
||||
return dir === "asc" ? va - vb : vb - va;
|
||||
});
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function ensurePeriodTable(periodId) {
|
||||
const wrap = document.getElementById(`${periodId}-table-wrap`);
|
||||
if (!wrap) return null;
|
||||
let table = wrap.querySelector(`table[data-table="${periodId}"]`);
|
||||
if (!table) {
|
||||
wrap.innerHTML = `<table data-table="${periodId}">${TABLE_HEADER}<tbody id="${periodId}-body"></tbody></table>`;
|
||||
table = wrap.querySelector("table");
|
||||
bindSortHandlers(table);
|
||||
}
|
||||
return document.getElementById(`${periodId}-body`);
|
||||
}
|
||||
|
||||
function bindSortHandlers(table) {
|
||||
table.querySelectorAll("th.sortable").forEach((th) => {
|
||||
th.onclick = () => {
|
||||
const tableId = table.dataset.table;
|
||||
const key = th.dataset.sort;
|
||||
if (tableId && key) toggleSort(tableId, key);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function updateSortHeaders(tableId) {
|
||||
const table = document.querySelector(`table[data-table="${tableId}"]`);
|
||||
if (!table) return;
|
||||
const { sortKey, sortDir } = tableState[tableId];
|
||||
const { sortKey, sortDir } = tableState[tableId] || { sortKey: "rank", sortDir: "asc" };
|
||||
table.querySelectorAll("th.sortable").forEach((th) => {
|
||||
th.classList.remove("sorted-asc", "sorted-desc");
|
||||
const key = th.dataset.sort;
|
||||
if (key === sortKey) {
|
||||
if (th.dataset.sort === sortKey) {
|
||||
th.classList.add(sortDir === "asc" ? "sorted-asc" : "sorted-desc");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function renderTable(tableId, tbody) {
|
||||
const state = tableState[tableId];
|
||||
function renderPeriodTable(periodId) {
|
||||
const tbody = ensurePeriodTable(periodId);
|
||||
if (!tbody) return;
|
||||
const state = tableState[periodId];
|
||||
const items = sortItems(state.items, state.sortKey, state.sortDir);
|
||||
|
||||
if (!items.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="loading">暂无数据</td></tr>';
|
||||
updateSortHeaders(tableId);
|
||||
updateSortHeaders(periodId);
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = items
|
||||
.map((row, idx) => {
|
||||
const highlight =
|
||||
row.is_high_volume || row.is_high_change ? " row-highlight" : "";
|
||||
const hl = row.is_high_volume || row.is_high_change ? " row-highlight" : "";
|
||||
const pct = row.price_change_pct ?? 0;
|
||||
const displayRank =
|
||||
state.sortKey === "rank" && state.sortDir === "asc"
|
||||
? row.rank
|
||||
: idx + 1;
|
||||
return `<tr class="${highlight}">
|
||||
<td class="rank">${displayRank}</td>
|
||||
const rank =
|
||||
state.sortKey === "rank" && state.sortDir === "asc" ? row.rank : idx + 1;
|
||||
return `<tr class="${hl}">
|
||||
<td class="rank">${rank}</td>
|
||||
<td class="symbol-cell"><strong>${row.symbol}</strong></td>
|
||||
<td class="chart-cell">
|
||||
<div class="mini-chart" data-symbol="${row.symbol}">
|
||||
<canvas></canvas>
|
||||
<span class="chart-status"></span>
|
||||
</div>
|
||||
<div class="mini-chart" data-symbol="${row.symbol}"><canvas></canvas><span class="chart-status"></span></div>
|
||||
</td>
|
||||
<td data-value="${row.quote_volume ?? 0}">${row.quote_volume_fmt || row.quote_volume}</td>
|
||||
<td class="${pctClass(pct)}" data-value="${pct}">${row.price_change_pct_fmt || pct.toFixed(2) + "%"}</td>
|
||||
<td class="funding-cell" data-value="${row.funding_rate_pct ?? 0}">
|
||||
<div class="funding-cell-inner">
|
||||
<span class="funding-rate-label ${pctClass(row.funding_rate_pct ?? 0)}">${row.funding_rate_fmt || "—"}</span>
|
||||
<div class="mini-funding-chart" data-symbol="${row.symbol}">
|
||||
<canvas></canvas>
|
||||
</div>
|
||||
<div class="mini-funding-chart" data-symbol="${row.symbol}"><canvas></canvas></div>
|
||||
</div>
|
||||
</td>
|
||||
<td data-value="${tagText(row)}">${renderTags(row)}</td>
|
||||
@@ -127,145 +150,228 @@ function renderTable(tableId, tbody) {
|
||||
})
|
||||
.join("");
|
||||
|
||||
updateSortHeaders(tableId);
|
||||
enqueueCharts(tbody);
|
||||
if (typeof enqueueFundingCharts === "function") enqueueFundingCharts(tbody);
|
||||
updateSortHeaders(periodId);
|
||||
if (currentView === periodId) {
|
||||
enqueueCharts(tbody);
|
||||
if (typeof enqueueFundingCharts === "function") enqueueFundingCharts(tbody);
|
||||
}
|
||||
}
|
||||
|
||||
function setTableData(tableId, data) {
|
||||
tableState[tableId].items = data.items || [];
|
||||
tableState[tableId].meta = {
|
||||
function setPeriodData(periodId, data) {
|
||||
tableState[periodId].items = data.items || [];
|
||||
tableState[periodId].meta = {
|
||||
period_start: data.period_start,
|
||||
period_end: data.period_end,
|
||||
updated_at: data.updated_at,
|
||||
};
|
||||
const tbody = document.getElementById(`${tableId}-body`);
|
||||
renderTable(tableId, tbody);
|
||||
const pe = document.getElementById(`${periodId}-period`);
|
||||
const ue = document.getElementById(`${periodId}-updated`);
|
||||
if (pe) pe.textContent = formatPeriod(data.period_start, data.period_end);
|
||||
if (ue) ue.textContent = "更新: " + (data.updated_at || "").replace("T", " ").slice(0, 19);
|
||||
renderPeriodTable(periodId);
|
||||
}
|
||||
|
||||
async function loadPeriod(periodId) {
|
||||
const tbody = ensurePeriodTable(periodId);
|
||||
if (tbody) tbody.innerHTML = '<tr><td colspan="7" class="loading">加载中…</td></tr>';
|
||||
try {
|
||||
const res = await fetch(PERIOD_API[periodId]);
|
||||
const data = await res.json();
|
||||
setPeriodData(periodId, data);
|
||||
if (periodId === "today") {
|
||||
document.getElementById("status").textContent = "今日数据已刷新";
|
||||
}
|
||||
} catch (e) {
|
||||
if (tbody) tbody.innerHTML = `<tr><td colspan="7" class="error">${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSort(tableId, key) {
|
||||
const state = tableState[tableId];
|
||||
if (state.sortKey === key) {
|
||||
state.sortDir = state.sortDir === "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
state.sortKey = key;
|
||||
state.sortDir = key === "symbol" ? "asc" : "desc";
|
||||
const s = tableState[tableId];
|
||||
if (s.sortKey === key) s.sortDir = s.sortDir === "asc" ? "desc" : "asc";
|
||||
else {
|
||||
s.sortKey = key;
|
||||
s.sortDir = key === "symbol" ? "asc" : "desc";
|
||||
}
|
||||
const tbody = document.getElementById(`${tableId}-body`);
|
||||
renderTable(tableId, tbody);
|
||||
renderPeriodTable(tableId);
|
||||
}
|
||||
|
||||
function resetSort(tableId) {
|
||||
tableState[tableId].sortKey = "rank";
|
||||
tableState[tableId].sortDir = "asc";
|
||||
const tbody = document.getElementById(`${tableId}-body`);
|
||||
renderTable(tableId, tbody);
|
||||
renderPeriodTable(tableId);
|
||||
}
|
||||
|
||||
function exportCsv(tableId) {
|
||||
const state = tableState[tableId];
|
||||
if (!state.items.length) {
|
||||
alert("暂无数据可导出");
|
||||
return;
|
||||
}
|
||||
function exportPeriodCsv(periodId) {
|
||||
const state = tableState[periodId];
|
||||
if (!state.items.length) return alert("暂无数据");
|
||||
const items = sortItems(state.items, state.sortKey, state.sortDir);
|
||||
const header = [
|
||||
"排名",
|
||||
"合约",
|
||||
"成交额显示",
|
||||
"成交额USDT",
|
||||
"涨跌幅%",
|
||||
"资金费率%",
|
||||
"千万+",
|
||||
"涨跌5%+",
|
||||
"标记",
|
||||
];
|
||||
const header = ["排名", "合约", "成交额", "涨跌幅%", "资金费率%", "标记"];
|
||||
const rows = items.map((r, i) => [
|
||||
state.sortKey === "rank" && state.sortDir === "asc" ? r.rank : i + 1,
|
||||
r.symbol,
|
||||
r.quote_volume_fmt || "",
|
||||
r.quote_volume ?? "",
|
||||
r.price_change_pct ?? "",
|
||||
r.funding_rate_pct ?? "",
|
||||
r.is_high_volume ? "是" : "否",
|
||||
r.is_high_change ? "是" : "否",
|
||||
tagText(r),
|
||||
]);
|
||||
|
||||
const escape = (v) => `"${String(v).replace(/"/g, '""')}"`;
|
||||
const csv = [header, ...rows].map((row) => row.map(escape).join(",")).join("\n");
|
||||
const blob = new Blob(["\ufeff" + csv], { type: "text/csv;charset=utf-8" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const a = document.createElement("a");
|
||||
const period = state.meta.period_start
|
||||
? state.meta.period_start.slice(0, 10)
|
||||
: tableId;
|
||||
a.href = url;
|
||||
a.download = `binance-top30-${tableId}-${period}.csv`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(url);
|
||||
downloadCsv(`binance-${periodId}`, header, rows, state.meta.period_start);
|
||||
}
|
||||
|
||||
document.querySelectorAll("th.sortable").forEach((th) => {
|
||||
th.addEventListener("click", () => {
|
||||
const table = th.closest("table");
|
||||
const tableId = table?.dataset.table;
|
||||
const key = th.dataset.sort;
|
||||
if (tableId && key) toggleSort(tableId, key);
|
||||
function downloadCsv(name, header, rows, periodStart) {
|
||||
const esc = (v) => `"${String(v).replace(/"/g, '""')}"`;
|
||||
const csv = [header, ...rows].map((r) => r.map(esc).join(",")).join("\n");
|
||||
const blob = new Blob(["\ufeff" + csv], { type: "text/csv;charset=utf-8" });
|
||||
const a = document.createElement("a");
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `${name}-${(periodStart || "").slice(0, 10)}.csv`;
|
||||
a.click();
|
||||
}
|
||||
|
||||
function renderStatsTable() {
|
||||
const wrap = document.getElementById("stats-table-wrap");
|
||||
if (!wrap || !statsData) return;
|
||||
|
||||
const items = statsData.items || [];
|
||||
document.getElementById("stats-criteria").textContent = statsData.criteria || "";
|
||||
document.getElementById("stats-desc").textContent = statsData.message || "";
|
||||
const sum = statsData.summary;
|
||||
document.getElementById("stats-summary").textContent = statsData.ok
|
||||
? `符合条件 ${statsData.count} 个 · 三日交集 ${sum?.intersection ?? 0} 个`
|
||||
: "数据未就绪";
|
||||
|
||||
if (!statsData.ok) {
|
||||
wrap.innerHTML = `<p class="loading">${statsData.message || "请等待三个周期数据就绪"}</p>`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!items.length) {
|
||||
wrap.innerHTML = '<p class="loading">暂无符合条件的合约</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
wrap.innerHTML = `
|
||||
<table data-table="stats">
|
||||
<thead><tr>
|
||||
<th>合约</th>
|
||||
<th>今日排名</th><th>今日涨跌</th><th>今日成交额</th>
|
||||
<th>昨日排名</th><th>昨日涨跌</th><th>昨日成交额</th>
|
||||
<th>前日排名</th><th>前日涨跌</th><th>前日成交额</th>
|
||||
<th>三日总成交额</th>
|
||||
</tr></thead>
|
||||
<tbody id="stats-body"></tbody>
|
||||
</table>`;
|
||||
|
||||
const body = document.getElementById("stats-body");
|
||||
body.innerHTML = items
|
||||
.map((row) => {
|
||||
const d = (p) => row[p] || {};
|
||||
const cell = (p, f) => {
|
||||
const x = d(p);
|
||||
const pct = x.price_change_pct ?? 0;
|
||||
return `<td class="${f === "pct" ? pctClass(pct) : ""}">${f === "pct" ? x.price_change_pct_fmt || "—" : f === "rank" ? x.rank ?? "—" : x.quote_volume_fmt || "—"}</td>`;
|
||||
};
|
||||
return `<tr class="row-highlight">
|
||||
<td><strong>${row.symbol}</strong></td>
|
||||
${cell("today", "rank")}${cell("today", "pct")}${cell("today", "vol")}
|
||||
${cell("yesterday", "rank")}${cell("yesterday", "pct")}${cell("yesterday", "vol")}
|
||||
${cell("daybefore", "rank")}${cell("daybefore", "pct")}${cell("daybefore", "vol")}
|
||||
<td>${formatVol(row.total_quote_volume)}</td>
|
||||
</tr>`;
|
||||
})
|
||||
.join("");
|
||||
}
|
||||
|
||||
function formatVol(v) {
|
||||
if (v >= 1e8) return (v / 1e8).toFixed(2) + "亿";
|
||||
if (v >= 1e4) return (v / 1e4).toFixed(2) + "万";
|
||||
return String(Math.round(v));
|
||||
}
|
||||
|
||||
async function loadStats() {
|
||||
document.getElementById("stats-table-wrap").innerHTML =
|
||||
'<p class="loading">统计中…</p>';
|
||||
try {
|
||||
const res = await fetch("/api/stats/three-day");
|
||||
statsData = await res.json();
|
||||
renderStatsTable();
|
||||
} catch (e) {
|
||||
document.getElementById("stats-table-wrap").innerHTML = `<p class="error">${e.message}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
function exportStatsCsv() {
|
||||
if (!statsData?.items?.length) return alert("暂无数据");
|
||||
const header = [
|
||||
"合约",
|
||||
"今日排名", "今日涨跌%", "今日成交额",
|
||||
"昨日排名", "昨日涨跌%", "昨日成交额",
|
||||
"前日排名", "前日涨跌%", "前日成交额",
|
||||
"三日总成交额",
|
||||
];
|
||||
const rows = statsData.items.map((r) => [
|
||||
r.symbol,
|
||||
r.today?.rank, r.today?.price_change_pct, r.today?.quote_volume,
|
||||
r.yesterday?.rank, r.yesterday?.price_change_pct, r.yesterday?.quote_volume,
|
||||
r.daybefore?.rank, r.daybefore?.price_change_pct, r.daybefore?.quote_volume,
|
||||
r.total_quote_volume,
|
||||
]);
|
||||
downloadCsv("binance-three-day-stats", header, rows, "stats");
|
||||
}
|
||||
|
||||
function switchView(view) {
|
||||
currentView = view;
|
||||
document.querySelectorAll(".nav-item").forEach((b) => {
|
||||
b.classList.toggle("active", b.dataset.view === view);
|
||||
});
|
||||
document.querySelectorAll(".view-panel").forEach((p) => {
|
||||
p.classList.toggle("active", p.id === `view-${view}`);
|
||||
});
|
||||
|
||||
if (view === "stats") {
|
||||
if (!statsData) loadStats();
|
||||
return;
|
||||
}
|
||||
|
||||
const tbody = document.getElementById(`${view}-body`);
|
||||
if (tbody && tableState[view].items.length) {
|
||||
renderPeriodTable(view);
|
||||
enqueueCharts(tbody);
|
||||
if (typeof enqueueFundingCharts === "function") enqueueFundingCharts(tbody);
|
||||
} else if (!tableState[view].items.length) {
|
||||
loadPeriod(view);
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("main-nav").addEventListener("click", (e) => {
|
||||
const btn = e.target.closest(".nav-item");
|
||||
if (btn?.dataset.view) switchView(btn.dataset.view);
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-export]").forEach((btn) => {
|
||||
btn.addEventListener("click", () => exportCsv(btn.dataset.export));
|
||||
btn.addEventListener("click", () => exportPeriodCsv(btn.dataset.export));
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-reset]").forEach((btn) => {
|
||||
btn.addEventListener("click", () => resetSort(btn.dataset.reset));
|
||||
});
|
||||
|
||||
async function loadYesterday() {
|
||||
const body = document.getElementById("yesterday-body");
|
||||
body.innerHTML = '<tr><td colspan="7" class="loading">加载中…</td></tr>';
|
||||
try {
|
||||
const res = await fetch("/api/yesterday/top30");
|
||||
const data = await res.json();
|
||||
document.getElementById("yesterday-period").textContent = formatPeriod(
|
||||
data.period_start,
|
||||
data.period_end
|
||||
);
|
||||
document.getElementById("yesterday-updated").textContent =
|
||||
"更新: " + (data.updated_at || "").replace("T", " ").slice(0, 19);
|
||||
setTableData("yesterday", data);
|
||||
} catch (e) {
|
||||
body.innerHTML = `<tr><td colspan="7" class="error">加载失败: ${e.message}</td></tr>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadToday() {
|
||||
const body = document.getElementById("today-body");
|
||||
try {
|
||||
const res = await fetch("/api/today/top30");
|
||||
const data = await res.json();
|
||||
document.getElementById("today-period").textContent = formatPeriod(
|
||||
data.period_start,
|
||||
data.period_end
|
||||
);
|
||||
document.getElementById("today-updated").textContent =
|
||||
"更新: " + (data.updated_at || "").replace("T", " ").slice(0, 19);
|
||||
setTableData("today", data);
|
||||
document.getElementById("status").textContent = "今日数据已刷新";
|
||||
} catch (e) {
|
||||
body.innerHTML = `<tr><td colspan="7" class="error">加载失败: ${e.message}</td></tr>`;
|
||||
document.getElementById("status").textContent = e.message;
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById("btn-refresh").addEventListener("click", async () => {
|
||||
document.getElementById("status").textContent = "刷新中…";
|
||||
await fetch("/api/refresh/today", { method: "POST" });
|
||||
await loadToday();
|
||||
await loadPeriod("today");
|
||||
if (currentView === "stats") await loadStats();
|
||||
});
|
||||
|
||||
loadYesterday();
|
||||
loadToday();
|
||||
setInterval(loadToday, REFRESH_MS);
|
||||
document.getElementById("btn-reload-stats")?.addEventListener("click", () => {
|
||||
statsData = null;
|
||||
loadStats();
|
||||
});
|
||||
document.getElementById("btn-export-stats")?.addEventListener("click", exportStatsCsv);
|
||||
|
||||
loadPeriod("today");
|
||||
loadPeriod("yesterday");
|
||||
loadPeriod("daybefore");
|
||||
|
||||
setInterval(() => {
|
||||
if (currentView === "today") loadPeriod("today");
|
||||
}, REFRESH_MS);
|
||||
|
||||
Reference in New Issue
Block a user