Files
FANGKKandClaude Opus 5 88084ce5ff Add period-over-period trend table at top of report
build_data.py now also computes each buyer's total for the preceding
3-day window under identical rules (exclusions, attribution), exposed as
prev_total plus meta.prev_range. index.html renders it as the first
panel, with green/red deltas and a total row; the panel stays hidden if
no prior period exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 23:42:03 +08:00

106 lines
7.9 KiB
HTML
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>武汉自投 · 投手投放花费报告</title>
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
<style>
:root{--bg:#ffffff;--card:#FAF7F7;--line:#EBE7E8;--txt:#2D1B20;--sub:#827578;--accent:#C4506A;--accent2:#C4506A;}*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--txt);font-family:"Inter","PingFang TC","Apple LiGothic","STHeiti","Hiragino Sans GB",-apple-system,BlinkMacSystemFont,"Microsoft YaHei",sans-serif;line-height:1.5}
.wrap{max-width:1080px;margin:0 auto;padding:28px 20px 60px}
h1{font-size:24px;margin:0 0 4px;font-family:"DM Sans","Inter",sans-serif}.sub{color:var(--sub);font-size:13px;margin-bottom:22px}
.cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:14px;margin-bottom:26px}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px 18px}
.card .k{color:var(--sub);font-size:12px}
.card .v{font-size:22px;font-weight:600;margin-top:6px}
.panel{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:18px;margin-bottom:22px}
.panel h2{font-size:16px;margin:0 0 14px}
table{width:100%;border-collapse:collapse;font-size:13px}
th,td{padding:9px 10px;text-align:left;border-bottom:1px solid var(--line)}
th{color:var(--sub);font-weight:500;font-size:12px}
td.num,th.num{text-align:right;font-variant-numeric:tabular-nums;font-family:"JetBrains Mono","Menlo",monospace}.pkg{font-family:"JetBrains Mono","Menlo","Consolas",monospace;font-size:12px;color:#333a45}.buyer-name{font-weight:600}
details{margin-top:6px}
summary{cursor:pointer;color:var(--accent);font-size:12px}
.foot{color:var(--sub);font-size:12px;margin-top:30px;border-top:1px solid var(--line);padding-top:14px}
.badge{display:inline-block;background:#e6eefc;color:var(--sub);border-radius:6px;padding:2px 8px;font-size:11px;margin-left:8px}
.chartbox{position:relative;height:300px}
.up{color:#0F8A5F;font-weight:600}.down{color:#C4506A;font-weight:600}.flat{color:var(--sub)}
#trend th.sub2{font-weight:400;color:var(--sub);font-size:11px}
#trend tr.total td{border-top:2px solid var(--line);font-weight:600}
#trend td:first-child{font-weight:600}
#trend th,#trend td{white-space:nowrap}
</style>
<script src="https://cdn.jsdelivr.net/npm/xlsx@0.18.5/dist/xlsx.full.min.js"></script>
</head>
<body>
<div class="wrap">
<h1>武汉自投 · 投手投放花费报告 <span class="badge" id="weekBadge"></span></h1>
<div class="sub" id="metaLine">加载中…</div>
<div class="panel" id="trendPanel" hidden><h2>投手花费环比<span class="badge" id="trendRange"></span></h2><div style="overflow-x:auto"><table id="trend"></table></div></div>
<div class="cards" id="cards"></div>
<div class="panel"><h2>各投手本周期内投放花费</h2><div class="chartbox"><canvas id="spendChart"></canvas></div></div>
<div class="panel"><h2>各投手主要订单 Package(花费最高)</h2><div class="chartbox"><canvas id="pkgChart"></canvas></div></div>
<div class="panel"><h2>投手明细</h2><div id="tables"></div></div>
<div class="foot" id="foot"></div>
</div>
<script>
const fmt=n=>n.toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2});
const usd=n=>'$'+fmt(n);
const axisUsd=v=>'$'+Number(v).toLocaleString('en-US');
// 柱状条末端花费标签(内联插件,避免额外引入 chartjs-plugin-datalabels)
const barLabels={id:'barLabels',afterDatasetsDraw(chart){
const ctx=chart.ctx;
chart.data.datasets.forEach((ds,di)=>{
const meta=chart.getDatasetMeta(di);
if(meta.hidden)return;
meta.data.forEach((el,i)=>{
const v=ds.data[i];
if(v==null)return;
ctx.save();
ctx.font='600 11px "JetBrains Mono","Menlo",monospace';
ctx.fillStyle='#2D1B20';
ctx.textBaseline='middle';
ctx.textAlign='left';
ctx.fillText(usd(v),el.x+6,el.y);
ctx.restore();
});
});
}};
// 直接使用 data.json,Excel 支持由后续更新处理
fetch('data.json?t='+Date.now()).then(r=>r.json()).then(d=>render(d)).catch(e=>{document.getElementById('metaLine').textContent='数据加载失败:'+e.message;});
function render(d){
document.getElementById('weekBadge').textContent=d.meta.week||'';
document.getElementById('metaLine').textContent='数据周期:'+d.meta.date_range+' · 数据来源:'+d.meta.source+' · 生成时间:'+new Date(d.meta.generated_at).toLocaleString('zh-CN');
const buyers=d.buyers.slice().sort((a,b)=>b.total-a.total);
const cards=[['投手数',buyers.length],['投放花费合计',usd(d.grand_total)],['覆盖 Adpingid 数',d.week34_id_count],['花费最高投手',buyers[0].name]];
document.getElementById('cards').innerHTML=cards.map(c=>'<div class="card"><div class="k">'+c[0]+'</div><div class="v">'+c[1]+'</div></div>').join('');
// —— 环比趋势表 ——
if(d.meta.prev_range && buyers.some(b=>b.prev_total!==undefined)){
document.getElementById('trendPanel').hidden=false;
document.getElementById('trendRange').textContent=d.meta.prev_range+' → '+d.meta.date_range;
const cell=(prev,cur)=>{
const dv=cur-prev;
const cls=Math.abs(dv)<0.005?'flat':(dv>0?'up':'down');
const sign=dv>0?'+':(dv<0?'−':'');
const pct=prev>0?(sign+Math.abs(dv/prev*100).toFixed(1)+'%'):'—';
return '<td class="num '+cls+'">'+sign+'$'+fmt(Math.abs(dv))+'</td><td class="num '+cls+'">'+pct+'</td>';
};
const rows=buyers.map(b=>'<tr><td>'+b.name+'</td><td class="num">'+usd(b.prev_total||0)+'</td><td class="num">'+usd(b.total)+'</td>'+cell(b.prev_total||0,b.total)+'</tr>').join('');
const pT=buyers.reduce((s,b)=>s+(b.prev_total||0),0), cT=buyers.reduce((s,b)=>s+b.total,0);
document.getElementById('trend').innerHTML=
'<thead><tr><th>投手</th><th class="num sub2">上期</th><th class="num sub2">本期</th><th class="num">变化</th><th class="num">幅度</th></tr></thead><tbody>'
+rows+'<tr class="total"><td>合计</td><td class="num">'+usd(pT)+'</td><td class="num">'+usd(cT)+'</td>'+cell(pT,cT)+'</tr></tbody>';
}
const palette=['#5b8cff','#ff9f5b','#4dd6a8','#c78bff','#ff6b81','#f5c451'];
new Chart(document.getElementById('spendChart'),{type:'bar',data:{labels:buyers.map(b=>b.name),datasets:[{label:'投放花费',data:buyers.map(b=>b.total),backgroundColor:buyers.map((b,i)=>palette[i%palette.length])}]},plugins:[barLabels],options:{indexAxis:'y',layout:{padding:{right:76}},plugins:{legend:{display:false},tooltip:{callbacks:{label:(c)=>usd(c.parsed.x)}}},scales:{x:{ticks:{color:'#5b6472',callback:axisUsd},grid:{color:'#d6e2f5'}},y:{ticks:{color:'#111111'},grid:{display:false}}}}});
new Chart(document.getElementById('pkgChart'),{type:'bar',data:{labels:buyers.map(b=>b.name),datasets:[{label:'主要Package花费',data:buyers.map(b=>b.pkgs[0].spend),backgroundColor:'#4dd6a8'}]},options:{plugins:{legend:{display:false},tooltip:{callbacks:{label:(c)=>usd(c.parsed.y),afterLabel:(ctx)=>buyers[ctx.dataIndex].pkgs[0].pkg}}},scales:{x:{ticks:{color:'#111111'},grid:{display:false}},y:{ticks:{color:'#5b6472',callback:axisUsd},grid:{color:'#d6e2f5'}}}}});
document.getElementById('tables').innerHTML=buyers.map(b=>{
const rows=b.pkgs.map(p=>'<tr><td class="pkg">'+(p.label||p.pkg)+'</td><td class="pkg">'+(p.adping||'-')+'</td><td class="num">'+usd(p.spend)+'</td></tr>').join('');
return '<div style="margin-bottom:18px"><div class="buyer-name">'+b.name+' <span style="color:var(--sub);font-weight:400">合计 '+usd(b.total)+' · 在投 Adping '+(b.active_ids??'-')+' 个(共负责 '+(b.owned_ids??'-')+' 个)'+' · 主要订单 '+(b.pkgs[0].label||b.pkgs[0].pkg)+'</span></div><details><summary>展开 '+b.pkgs.length+' 个 Package 明细</summary><table><thead><tr><th>Package</th><th>Adpingid</th><th class="num">花费</th></tr></thead><tbody>'+rows+'</tbody></table></details></div>';
}).join('');
document.getElementById('foot').textContent=d.meta.note;
}
</script>
</body>
</html>