Files
DSP-routing-daily/index.html
T

207 lines
9.4 KiB
HTML
Raw 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.
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>DSP Routing Daily 报告</title>
<style>
:root{--bg:#f5f6f8;--card:#fff;--line:#e3e6ea;--head:#1f2937;--sub:#6b7280;--accent:#2563eb;--accent-bg:#eff4ff;}
*{box-sizing:border-box;}
body{margin:0;background:var(--bg);color:#1f2937;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI","PingFang SC","Microsoft YaHei",sans-serif;font-size:13px;}
.wrap{max-width:1500px;margin:0 auto;padding:24px 20px 60px;}
h1{font-size:20px;margin:0 0 4px;}
.meta{color:var(--sub);font-size:12px;margin-bottom:20px;}
.card{background:var(--card);border:1px solid var(--line);border-radius:10px;padding:16px 18px;margin-bottom:22px;box-shadow:0 1px 2px rgba(0,0,0,.03);}
.card h2{font-size:15px;margin:0 0 12px;display:flex;align-items:center;gap:8px;}
.card h2 .tag{font-size:11px;font-weight:normal;color:var(--sub);background:var(--bg);border:1px solid var(--line);padding:1px 8px;border-radius:20px;}
table{border-collapse:collapse;width:100%;font-variant-numeric:tabular-nums;}
th,td{border:1px solid var(--line);padding:7px 10px;text-align:right;white-space:nowrap;}
th{background:var(--head);color:#fff;font-weight:600;text-align:center;position:sticky;top:0;cursor:default;}
td:first-child,th:first-child{text-align:center;}
.detail-scroll{max-height:620px;overflow:auto;border:1px solid var(--line);border-radius:8px;}
.detail-scroll table th{top:0;}
tbody tr:nth-child(even){background:#fafbfc;}
tbody tr:hover{background:var(--accent-bg);}
.txt{text-align:left;}
.filters{display:flex;flex-wrap:wrap;gap:14px;align-items:flex-end;margin-bottom:14px;}
.fld{display:flex;flex-direction:column;gap:4px;}
.fld label{font-size:11px;color:var(--sub);}
.fld input,.fld select{border:1px solid var(--line);border-radius:6px;padding:6px 8px;font-size:13px;min-width:150px;background:#fff;}
.fld input[type=date]{min-width:140px;}
.btn{border:1px solid var(--accent);background:var(--accent);color:#fff;border-radius:6px;padding:7px 16px;font-size:13px;cursor:pointer;}
.btn.ghost{background:#fff;color:var(--accent);}
.count-note{color:var(--sub);font-size:12px;margin:6px 2px 0;}
.pos{color:#059669;} .neg{color:#dc2626;}
.empty{padding:30px;text-align:center;color:var(--sub);}
.sum-metric{color:var(--accent);font-weight:600;}
</style>
</head>
<body>
<div class="wrap">
<h1>DSP Routing Daily 报告</h1>
<div class="meta" id="meta">加载中…</div>
<div class="card">
<h2>每日总表 <span class="tag">每天累加一行</span></h2>
<div style="overflow:auto;">
<table id="summaryTable">
<thead>
<tr>
<th>日期</th><th>原始订单</th><th>pkg</th><th>中转订单</th>
<th>有点击订单</th><th>无转化订单</th><th>无点击订单</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
<div class="card">
<h2>Kibana 监测明细表 <span class="tag">按 adping_id 聚合 · 近14天</span></h2>
<div class="filters">
<div class="fld"><label>dt 起</label><input type="date" id="fDtFrom"></div>
<div class="fld"><label>dt 止</label><input type="date" id="fDtTo"></div>
<div class="fld"><label>融合后campaign id (routing_campaign_id)</label><input type="text" id="fRc" placeholder="模糊匹配"></div>
<div class="fld"><label>融合前campaign id (campaign_id)</label><input type="text" id="fCid" placeholder="模糊匹配"></div>
<div class="fld"><label>second_channel</label><select id="fSc"><option value="">全部</option></select></div>
<div class="fld"><label>account</label><select id="fAcc"><option value="">全部</option></select></div>
<button class="btn" id="applyBtn">筛选</button>
<button class="btn ghost" id="resetBtn">重置</button>
</div>
<div class="count-note" id="detailNote"></div>
<div class="detail-scroll">
<table id="detailTable">
<thead>
<tr>
<th>dt</th>
<th>融合后campaign id</th>
<th>融合前campaign id</th>
<th>adping_id</th>
<th>second_channel</th>
<th>account</th>
<th>pkg</th>
<th>clicks</th>
<th>conversion</th>
<th>cvr</th>
<th>revenue</th>
<th>cost</th>
<th>GP</th>
<th>ecpc</th>
</tr>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
<script>
var ALL = []; // 全部明细
var SUMMARY = [];
function num(n){ return (n==null||n==='')?'':Number(n).toLocaleString('en-US'); }
function pct(n){ if(n==null||n===''||isNaN(n)) return ''; return (Number(n)*100).toFixed(2)+'%'; }
function money(n){ if(n==null||n===''||isNaN(n)) return ''; return Number(n).toLocaleString('en-US',{minimumFractionDigits:2,maximumFractionDigits:2}); }
function dec(n,d){ if(n==null||n===''||isNaN(n)) return ''; return Number(n).toFixed(d==null?4:d); }
function dtKey(s){ // "2026/9/9" -> 20260909 数值,便于比较
var p=String(s).split('/'); return parseInt(p[0])*10000+parseInt(p[1])*100+parseInt(p[2]);
}
function dtToInput(s){ var p=String(s).split('/'); return p[0]+'-'+String(p[1]).padStart(2,'0')+'-'+String(p[2]).padStart(2,'0'); }
function inputToKey(v){ if(!v) return null; var p=v.split('-'); return parseInt(p[0])*10000+parseInt(p[1])*100+parseInt(p[2]); }
function renderSummary(){
var tb=document.querySelector('#summaryTable tbody');
if(!SUMMARY.length){ tb.innerHTML='<tr><td colspan="7" class="empty">暂无数据</td></tr>'; return; }
tb.innerHTML = SUMMARY.slice().sort(function(a,b){return dtKey(b.date)-dtKey(a.date);}).map(function(r){
return '<tr><td>'+r.date+'</td>'
+'<td class="sum-metric">'+num(r.raw_orders)+'</td>'
+'<td>'+num(r.pkg)+'</td>'
+'<td class="sum-metric">'+num(r.relay_orders)+'</td>'
+'<td>'+num(r.has_click_orders)+'</td>'
+'<td>'+num(r.no_conv_orders)+'</td>'
+'<td>'+num(r.no_click_orders)+'</td></tr>';
}).join('');
}
function fillSelects(){
var scSet={}, accSet={};
ALL.forEach(function(d){ if(d.second_channel) scSet[d.second_channel]=1; if(d.account) accSet[d.account]=1; });
var scSel=document.getElementById('fSc'), accSel=document.getElementById('fAcc');
Object.keys(scSet).sort().forEach(function(v){ scSel.insertAdjacentHTML('beforeend','<option>'+v+'</option>'); });
Object.keys(accSet).sort().forEach(function(v){ accSel.insertAdjacentHTML('beforeend','<option>'+v+'</option>'); });
}
function currentFilter(d){
var from=inputToKey(document.getElementById('fDtFrom').value);
var to=inputToKey(document.getElementById('fDtTo').value);
var rc=document.getElementById('fRc').value.trim().toLowerCase();
var cid=document.getElementById('fCid').value.trim().toLowerCase();
var sc=document.getElementById('fSc').value;
var acc=document.getElementById('fAcc').value;
var k=dtKey(d.dt);
if(from!=null && k<from) return false;
if(to!=null && k>to) return false;
if(rc && String(d.routing_campaign_id).toLowerCase().indexOf(rc)<0) return false;
if(cid && String(d.campaign_id).toLowerCase().indexOf(cid)<0) return false;
if(sc && d.second_channel!==sc) return false;
if(acc && d.account!==acc) return false;
return true;
}
function renderDetail(){
var rows = ALL.filter(currentFilter).sort(function(a,b){
return dtKey(b.dt)-dtKey(a.dt) || (b.clicks-a.clicks);
});
var tb=document.querySelector('#detailTable tbody');
if(!rows.length){ tb.innerHTML='<tr><td colspan="14" class="empty">无匹配数据</td></tr>'; }
else {
tb.innerHTML = rows.map(function(d){
var gpCls = (d.gp>=0)?'pos':'neg';
return '<tr>'
+'<td>'+d.dt+'</td>'
+'<td>'+d.routing_campaign_id+'</td>'
+'<td>'+d.campaign_id+'</td>'
+'<td>'+d.adping_id+'</td>'
+'<td class="txt">'+d.second_channel+'</td>'
+'<td class="txt">'+d.account+'</td>'
+'<td class="txt">'+d.pkg+'</td>'
+'<td>'+num(d.clicks)+'</td>'
+'<td>'+num(d.conversion)+'</td>'
+'<td>'+pct(d.cvr)+'</td>'
+'<td>'+money(d.revenue)+'</td>'
+'<td>'+money(d.cost)+'</td>'
+'<td class="'+gpCls+'">'+pct(d.gp)+'</td>'
+'<td>'+dec(d.ecpc,4)+'</td>'
+'</tr>';
}).join('');
}
document.getElementById('detailNote').textContent='显示 '+rows.length+' / '+ALL.length+' 行';
}
document.getElementById('applyBtn').onclick=renderDetail;
document.getElementById('resetBtn').onclick=function(){
['fDtFrom','fDtTo','fRc','fCid'].forEach(function(id){document.getElementById(id).value='';});
document.getElementById('fSc').value=''; document.getElementById('fAcc').value='';
renderDetail();
};
fetch('data.json?_='+Date.now()).then(function(r){return r.json();}).then(function(d){
SUMMARY = d.summary||[];
ALL = d.detail||[];
document.getElementById('meta').textContent='数据更新时间:'+(d.updated_at||'—')+' | 总表 '+SUMMARY.length+' 天 | 明细 '+ALL.length+' 行';
renderSummary();
fillSelects();
// 默认 dt 范围 = 数据里的最早~最新
if(ALL.length){
var ks=ALL.map(function(x){return x.dt;}).sort(function(a,b){return dtKey(a)-dtKey(b);});
document.getElementById('fDtFrom').value=dtToInput(ks[0]);
document.getElementById('fDtTo').value=dtToInput(ks[ks.length-1]);
}
renderDetail();
}).catch(function(e){
document.getElementById('meta').textContent='数据加载失败:'+e;
});
</script>
</body>
</html>