73 lines
8.0 KiB
HTML
73 lines
8.0 KiB
HTML
<!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:#f4f8ff;--line:#d6e2f5;--txt:#111111;--sub:#5b6472;--accent:#2f6bff;--accent2:#ff9f5b;}
|
||
*{box-sizing:border-box}
|
||
body{margin:0;background:var(--bg);color:var(--txt);font-family:"PingFang SC","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}
|
||
.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}
|
||
.pkg{font-family:ui-monospace,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}
|
||
</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="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="panel" id="unmatchedPanel" style="display:none"><h2>未匹配到投手的 Adpingid</h2><div id="unmatched"></div></div>
|
||
<div class="foot" id="foot"></div>
|
||
</div>
|
||
<script>
|
||
const fmt=n=>n.toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2});
|
||
// 优先从 Excel 文件读取数据,如果失败则使用 data.json
|
||
async function loadData(){try{const resp=await fetch('2608每日数据-武汉萌奇熊.xlsx?t='+Date.now());const arrayBuffer=await resp.arrayBuffer();const workbook=XLSX.read(arrayBuffer,{type:'array'});const investorMap=buildInvestorMap(workbook);const consumptionData=readConsumptionData(workbook);const processedData=processExcelData(consumptionData,investorMap);render(processedData);}catch(e){console.error('Excel加载失败:',e);loadDataJson();}}
|
||
function loadDataJson(){fetch('data.json?t='+Date.now()).then(r=>r.json()).then(d=>render(d)).catch(e=>{document.getElementById('metaLine').textContent='数据加载失败:'+e.message;});}
|
||
function buildInvestorMap(workbook){const investorSheet=workbook.Sheets['投手'];if(!investorSheet)return {};const data=XLSX.utils.sheet_to_json(investorSheet,{header:1});const map={};for(let i=1;i<data.length;i++){const row=data[i];if(!row[1]||!row[11])continue;const adping=String(row[1]);let investor=String(row[11]||'').trim();if(investor==='XX'||!investor)continue;investor=investor.replace(/^@/,'');map[adping]=investor;}return map;}
|
||
function readConsumptionData(workbook){const sheet=workbook.Sheets['平台消耗统计'];if(!sheet)return[];const data=XLSX.utils.sheet_to_json(sheet,{header:1});const result=[];for(let i=1;i<data.length;i++){const row=data[i];if(!row[1]||!row[2]||!row[8])continue;const date=row[1];const adping=String(row[2]);const cost=parseFloat(row[8])||0;if(cost>0)result.push({date,adping,cost});}return result;}
|
||
function processExcelData(consumptionData,investorMap){const investors=['黄政','谭超','万科','詹涵粤','周梓君'];const buyerData={};investors.forEach(name=>buyerData[name]={total:0,pkgs:{}});const unmatched=[];let totalCost=0;let adpingCount=new Set();consumptionData.forEach(({date,adping,cost})=>{const investor=investorMap[adping];if(investor&&investors.includes(investor)){buyerData[investor].total+=cost;buyerData[investor].pkgs[date]=(buyerData[investor].pkgs[date]||0)+cost;adpingCount.add(adping);totalCost+=cost;}else{unmatched.push({adping,cost});}});const buyers=investors.map(name=>({name,total:buyerData[name].total,pkgs:Object.entries(buyerData[name].pkgs).map(([pkg,spend])=>({pkg,spend})).sort((a,b)=>b.spend-a.spend).slice(0,5)})).filter(b=>b.total>0);const dateRange='2026/08/22-2026/08/24';return{meta:{week:'Last 3 Days: 08/22-08/24',source:'数据已处理(已排除指定包名和未匹配的投手)',generated_at:'2026-08-25',date_range:dateRange,note:''},grand_total:totalCost,week34_id_count:adpingCount.size,buyers,unmatched};}
|
||
loadData();
|
||
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],['投放花费合计',fmt(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('');
|
||
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])}]},options:{indexAxis:'y',plugins:{legend:{display:false}},scales:{x:{ticks:{color:'#5b6472'},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:{afterLabel:(ctx)=>buyers[ctx.dataIndex].pkgs[0].pkg}}},scales:{x:{ticks:{color:'#111111'},grid:{display:false}},y:{ticks:{color:'#5b6472'},grid:{color:'#d6e2f5'}}}}});
|
||
document.getElementById('tables').innerHTML=buyers.map(b=>{
|
||
const rows=b.pkgs.map(p=>'<tr><td class="pkg">'+p.pkg+'</td><td class="num">'+fmt(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">合计 '+fmt(b.total)+' · 主要订单 '+b.pkgs[0].pkg+'</span></div><details><summary>展开 '+b.pkgs.length+' 个 Package 明细</summary><table><thead><tr><th>Package</th><th class="num">花费</th></tr></thead><tbody>'+rows+'</tbody></table></details></div>';
|
||
}).join('');
|
||
if(d.unmatched&&d.unmatched.length){document.getElementById('unmatchedPanel').style.display='';document.getElementById('unmatched').innerHTML='<table><thead><tr><th>Adpingid</th><th>Package</th><th class="num">花费</th></tr></thead><tbody>'+d.unmatched.map(u=>'<tr><td>'+u.id+'</td><td class="pkg">'+u.pkg+'</td><td class="num">'+fmt(u.spend)+'</td></tr>').join('')+'</tbody></table>';}
|
||
document.getElementById('foot').textContent=d.meta.note;
|
||
}
|
||
</script>
|
||
</body>
|
||
</html> |