feat(web): report page views via sendBeacon and show stats on system page
This commit is contained in:
1 parent
3c16caac1a
commit
b215f8eb09
4 files changed
+154
-3
No files matched your search
@@ -1,4 +1,5 @@
|
||||
import { api } from "../api.js";
|
||||
import { api, stats } from "../api.js";
|
||||
import { emptyChart, mixedChart } from "../charts.js";
|
||||
import { emptyState, sectionHeader } from "../components.js";
|
||||
import {
|
||||
escapeHtml, formatCount, formatDateTime, formatNumber,
|
||||
@@ -30,11 +31,42 @@ function taskRows(tasks) {
|
||||
}
|
||||
|
||||
export async function renderSystem(root) {
|
||||
const system = await api("/api/system");
|
||||
const [system, visitStats] = await Promise.all([
|
||||
api("/api/system"),
|
||||
stats({ days: 30 }).catch(() => null),
|
||||
]);
|
||||
const health = system.snapshot_health || { total: 0, ok: 0, failed: 0 };
|
||||
const counts = system.counts || {};
|
||||
const next = system.next_runs || {};
|
||||
|
||||
const visitExtra = visitStats
|
||||
? `<span class="section-note">近 ${visitStats.days} 天 · ${visitStats.pending ? "含未落库缓冲" : "已落库"}</span>`
|
||||
: "";
|
||||
const visitPanel = visitStats ? `
|
||||
<section class="panel chart-panel">
|
||||
${sectionHeader("访问统计", visitExtra)}
|
||||
<div class="mini-stats">
|
||||
<div><span>今日 PV</span><strong>${formatNumber(visitStats.today_pv)}</strong></div>
|
||||
<div><span>今日 UV</span><strong>${formatNumber(visitStats.today_uv)}</strong></div>
|
||||
<div><span>${visitStats.days} 天 PV</span><strong>${formatNumber(visitStats.total_pv)}</strong></div>
|
||||
<div><span>${visitStats.days} 天 UV</span><strong>${formatNumber(visitStats.total_uv)}</strong></div>
|
||||
</div>
|
||||
<div class="chart" id="visit-chart"></div>
|
||||
<p class="section-note">柱状为每日 PV,折线为每日 UV(按客户端 IP 去重)。数据来自前端埋点,先落内存、每分钟批量入库;仅统计页面访问,不含 API 与静态资源请求。</p>
|
||||
${(visitStats.top_pages || []).length ? `<div class="table-wrap">
|
||||
<table class="data-table">
|
||||
<thead><tr><th>页面</th><th>PV</th><th>UV</th></tr></thead>
|
||||
<tbody>
|
||||
${visitStats.top_pages.map((page) => `<tr>
|
||||
<td>${escapeHtml(page.path)}</td>
|
||||
<td>${formatNumber(page.pv)}</td>
|
||||
<td>${formatNumber(page.uv)}</td>
|
||||
</tr>`).join("")}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>` : `<p class="section-note">暂无访问记录。</p>`}
|
||||
</section>` : "";
|
||||
|
||||
root.innerHTML = `<section class="page system-page">
|
||||
<header class="page-head">
|
||||
<div>
|
||||
@@ -51,6 +83,8 @@ export async function renderSystem(root) {
|
||||
<div><span>已移出库</span><strong>${formatCount(counts.dropped)}</strong></div>
|
||||
</div>
|
||||
|
||||
${visitPanel}
|
||||
|
||||
<section class="panel">
|
||||
${sectionHeader("最近任务")}
|
||||
${taskRows(system.tasks)}
|
||||
@@ -119,4 +153,19 @@ export async function renderSystem(root) {
|
||||
</div>` : emptyState("黑名单为空")}
|
||||
</section>
|
||||
</section>`;
|
||||
|
||||
const visitChart = root.querySelector("#visit-chart");
|
||||
if (visitChart) {
|
||||
if (visitStats && (visitStats.total_pv || visitStats.total_uv)) {
|
||||
mixedChart(visitChart, {
|
||||
dates: visitStats.series.dates,
|
||||
bars: visitStats.series.pv,
|
||||
line: visitStats.series.uv,
|
||||
barName: "PV",
|
||||
lineName: "UV",
|
||||
});
|
||||
} else {
|
||||
emptyChart(visitChart, "暂无访问数据");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user