刷新数据 2026-09-15 11:30

This commit is contained in:
FANGKK
2026-09-15 11:30:20 +08:00
parent e374b5aefa
commit 89205da321
+49 -10
View File
@@ -111,6 +111,13 @@ footer { color: var(--muted); font-size: 12px; margin-top: 8px; }
<div class="chart" id="spend-chart"><div class="tip" hidden></div></div>
</section>
<section class="card">
<h2>每日安装</h2>
<p class="desc">按包堆叠,悬停查看当天每个包的数据;与上方消耗图共用时间范围</p>
<div class="legend" id="installs-legend"></div>
<div class="chart" id="installs-chart"><div class="tip" hidden></div></div>
</section>
<section class="card">
<h2>余额走势</h2>
<p class="desc">每天日终余额 = 截至当天的累计到账 − 累计消耗,圆点是到账日</p>
@@ -321,20 +328,20 @@ function renderTiles(S, d) {
let RANGE = '7';
function drawSpend(S, d) {
const wrap = $('#spend-chart');
function drawStacked(S, d, opts) {
const wrap = $(opts.wrapSel);
wrap.querySelector('svg')?.remove();
hideTip(wrap);
const start = RANGE === 'all' ? d.range.start : addDays(S.end, -(Number(RANGE) - 1));
const dates = dayRange(start < d.range.start ? d.range.start : start, S.end);
const stacks = dates.map(dt => {
const acc = S.series.map(() => 0);
S.rowsOn(dt).forEach(r => { acc[S.seriesOf[r.pkg]] += r.spend; });
S.rowsOn(dt).forEach(r => { acc[S.seriesOf[r.pkg]] += opts.valueOf(r); });
return acc.map(r2);
});
const totals = stacks.map(s => r2(s.reduce((a, b) => a + b, 0)));
$('#spend-legend').replaceChildren(...S.series
$(opts.legendSel).replaceChildren(...S.series
.filter((s, i) => stacks.some(st => st[i] > 0))
.map(s => {
const e = h('span');
@@ -349,12 +356,12 @@ function drawSpend(S, d) {
const y = v => m.t + ph - (v - sc.lo) / (sc.hi - sc.lo) * ph;
const n = dates.length, band = pw / n, bw = Math.max(2, Math.min(24, band * 0.6));
const clampX = x => Math.min(Math.max(x, m.l + 18), W - m.r - 18);
const svg = el('svg', { viewBox: `0 0 ${W} ${H}`, width: W, height: H, role: 'img', 'aria-label': '每日消耗,按包堆叠' });
const svg = el('svg', { viewBox: `0 0 ${W} ${H}`, width: W, height: H, role: 'img', 'aria-label': opts.ariaLabel });
wrap.prepend(svg);
const hover = el('rect', { class: 'hover-band', y: m.t, height: ph, width: band, visibility: 'hidden' }, svg);
sc.ticks.forEach(t => {
el('line', { x1: m.l, x2: W - m.r, y1: y(t), y2: y(t), class: t === 0 ? 'axis' : 'grid' }, svg);
el('text', { x: m.l - 8, y: y(t) + 4, 'text-anchor': 'end', class: 'tick' }, svg).textContent = usd(t, sc.dp);
el('text', { x: m.l - 8, y: y(t) + 4, 'text-anchor': 'end', class: 'tick' }, svg).textContent = opts.fmt(t, sc.dp);
});
// 表格数据和 MIDAS 数据的分界线
@@ -384,7 +391,7 @@ function drawSpend(S, d) {
el('text', { x: clampX(cx), y: H - m.b + 18, 'text-anchor': 'middle', class: 'tick' }, svg).textContent = shortDate(dt);
}
if (totals[i] > 0 && (i === lastI || (i === maxI && lastI - maxI >= 3 * step))) {
el('text', { x: clampX(cx), y: y(totals[i]) - 7, 'text-anchor': 'middle', class: 'val' }, svg).textContent = usd(totals[i]);
el('text', { x: clampX(cx), y: y(totals[i]) - 7, 'text-anchor': 'middle', class: 'val' }, svg).textContent = opts.fmt(totals[i]);
}
});
dates.forEach((dt, i) => {
@@ -393,12 +400,26 @@ function drawSpend(S, d) {
hit.addEventListener('pointerenter', () => {
hover.setAttribute('x', cx - band / 2);
hover.setAttribute('visibility', 'visible');
showTip(wrap, cx, y(totals[i]), spendTip(S, dt, totals[i]));
showTip(wrap, cx, y(totals[i]), opts.tip(S, dt, totals[i]));
});
hit.addEventListener('pointerleave', () => { hover.setAttribute('visibility', 'hidden'); hideTip(wrap); });
});
}
function drawSpend(S, d) {
drawStacked(S, d, {
wrapSel: '#spend-chart', legendSel: '#spend-legend', valueOf: r => r.spend,
fmt: usd, ariaLabel: '每日消耗,按包堆叠', tip: spendTip,
});
}
function drawInstalls(S, d) {
drawStacked(S, d, {
wrapSel: '#installs-chart', legendSel: '#installs-legend', valueOf: r => r.installs,
fmt: v => int(v), ariaLabel: '每日安装,按包堆叠', tip: installsTip,
});
}
function spendTip(S, dt, total) {
const f = document.createDocumentFragment();
f.append(h('div', 'tip-title', dt));
@@ -418,6 +439,24 @@ function spendTip(S, dt, total) {
return f;
}
function installsTip(S, dt, total) {
const f = document.createDocumentFragment();
f.append(h('div', 'tip-title', dt));
const rows = S.rowsOn(dt).filter(r => r.installs > 0).sort((a, b) => b.installs - a.installs);
if (!rows.length) f.append(h('div', 'tip-sub', '当天没有安装'));
rows.forEach(r => {
const row = h('div', 'tip-row');
row.append(swatch(S.colorOf(r.pkg)), h('span', 'tip-name', r.pkg), h('span', 'tip-val', int(r.installs)));
f.append(row);
});
if (rows.length > 1) {
const tot = h('div', 'tip-row tip-total');
tot.append(h('span', 'tip-name', '合计'), h('span', 'tip-val', int(total)));
f.append(tot);
}
return f;
}
function drawBalance(S) {
const wrap = $('#balance-chart');
wrap.querySelector('svg')?.remove();
@@ -519,14 +558,14 @@ function renderDaily(S, d) {
}
let S, DATA;
const drawCharts = () => { if (S) { drawSpend(S, DATA); drawBalance(S); } };
const drawCharts = () => { if (S) { drawSpend(S, DATA); drawInstalls(S, DATA); drawBalance(S); } };
$('#range').addEventListener('click', e => {
const b = e.target.closest('button');
if (!b) return;
RANGE = b.dataset.range;
[...$('#range').children].forEach(x => x.setAttribute('aria-pressed', String(x === b)));
if (S) drawSpend(S, DATA);
if (S) { drawSpend(S, DATA); drawInstalls(S, DATA); }
});
fetch('data.json', { cache: 'no-store' })