骗量排行榜表头支持点击升序/降序排序

This commit is contained in:
2026-09-10 19:18:47 +08:00
parent ce7317258a
commit f74c4eae6c
+41 -16
View File
@@ -38,8 +38,8 @@
.note-list b{color:var(--head);} .note-list b{color:var(--head);}
.total-row td{position:sticky;top:33px;background:#eef2f7;font-weight:700;color:var(--head);border-top:2px solid #cbd5e1;z-index:1;} .total-row td{position:sticky;top:33px;background:#eef2f7;font-weight:700;color:var(--head);border-top:2px solid #cbd5e1;z-index:1;}
.total-row td.txt{text-align:left;} .total-row td.txt{text-align:left;}
th.sortable{cursor:pointer;user-select:none;} th.sortable,th.fsortable{cursor:pointer;user-select:none;}
th.sortable:hover{background:#374151;} th.sortable:hover,th.fsortable:hover{background:#374151;}
th .arrow{display:inline-block;width:10px;margin-left:3px;font-size:10px;color:#93c5fd;} th .arrow{display:inline-block;width:10px;margin-left:3px;font-size:10px;color:#93c5fd;}
th.sort-asc .arrow::after{content:"\25B2";} th.sort-asc .arrow::after{content:"\25B2";}
th.sort-desc .arrow::after{content:"\25BC";} th.sort-desc .arrow::after{content:"\25BC";}
@@ -132,10 +132,10 @@
<thead> <thead>
<tr> <tr>
<th>排名</th> <th>排名</th>
<th class="txt">account</th> <th class="txt fsortable" data-key="account" data-type="str">account<span class="arrow"></span></th>
<th>campaign 总数</th> <th class="fsortable" data-key="total" data-type="num">campaign 总数<span class="arrow"></span></th>
<th>可疑 campaign 数<br><span style="font-weight:normal;font-size:11px;">(&gt;500点击 &amp; 0转化)</span></th> <th class="fsortable" data-key="suspicious" data-type="num">可疑 campaign 数<span class="arrow"></span><br><span style="font-weight:normal;font-size:11px;">(&gt;500点击 &amp; 0转化)</span></th>
<th>骗量占比</th> <th class="fsortable" data-key="ratio" data-type="num">骗量占比<span class="arrow"></span></th>
</tr> </tr>
</thead> </thead>
<tbody></tbody> <tbody></tbody>
@@ -253,13 +253,15 @@ function renderDetail(){
updateSortArrows(); updateSortArrows();
} }
function renderFraud(){ var FRAUD = [];
// 取"前一天" = 明细里最新的 dt var FSORT_KEY='ratio', FSORT_TYPE='num', FSORT_DIR='desc';
if(!ALL.length){ document.querySelector('#fraudTable tbody').innerHTML='<tr><td colspan="5" class="empty">暂无数据</td></tr>'; return; }
function computeFraud(){
FRAUD=[];
if(!ALL.length) return;
var maxDt = ALL.reduce(function(m,d){ return dtKey(d.dt)>dtKey(m)?d.dt:m; }, ALL[0].dt); var maxDt = ALL.reduce(function(m,d){ return dtKey(d.dt)>dtKey(m)?d.dt:m; }, ALL[0].dt);
var rows = ALL.filter(function(d){ return d.dt===maxDt; }); var rows = ALL.filter(function(d){ return d.dt===maxDt; });
// 按 account -> campaign_id 聚合 clicks/conversion var byAcc = {};
var byAcc = {}; // acc -> { campId -> {clicks,conv} }
rows.forEach(function(d){ rows.forEach(function(d){
var acc=d.account||'未知', cid=String(d.campaign_id||''); var acc=d.account||'未知', cid=String(d.campaign_id||'');
if(!byAcc[acc]) byAcc[acc]={}; if(!byAcc[acc]) byAcc[acc]={};
@@ -267,16 +269,24 @@ function renderFraud(){
byAcc[acc][cid].clicks += (+d.clicks||0); byAcc[acc][cid].clicks += (+d.clicks||0);
byAcc[acc][cid].conv += (+d.conversion||0); byAcc[acc][cid].conv += (+d.conversion||0);
}); });
var list = Object.keys(byAcc).map(function(acc){ FRAUD = Object.keys(byAcc).map(function(acc){
var camps = byAcc[acc]; var camps = byAcc[acc], ids = Object.keys(camps);
var ids = Object.keys(camps);
var total = ids.length; var total = ids.length;
var suspicious = ids.filter(function(cid){ return camps[cid].clicks>500 && camps[cid].conv===0; }).length; var suspicious = ids.filter(function(cid){ return camps[cid].clicks>500 && camps[cid].conv===0; }).length;
return { account:acc, total:total, suspicious:suspicious, ratio: total? suspicious/total : 0 }; return { account:acc, total:total, suspicious:suspicious, ratio: total? suspicious/total : 0 };
}); });
list.sort(function(a,b){ return b.ratio-a.ratio || b.suspicious-a.suspicious || b.total-a.total; }); }
function renderFraud(){
var dir=(FSORT_DIR==='asc'?1:-1), k=FSORT_KEY, type=FSORT_TYPE;
var list = FRAUD.slice().sort(function(a,b){
if(type==='str'){ return dir*String(a[k]).localeCompare(String(b[k])); }
var va=Number(a[k])||0, vb=Number(b[k])||0;
return (va<vb?-1:va>vb?1:0)*dir || b.ratio-a.ratio || b.suspicious-a.suspicious;
});
var tb=document.querySelector('#fraudTable tbody'); var tb=document.querySelector('#fraudTable tbody');
if(!list.length){ tb.innerHTML='<tr><td colspan="5" class="empty">暂无数据</td></tr>'; return; } if(!list.length){ tb.innerHTML='<tr><td colspan="5" class="empty">暂无数据</td></tr>'; }
else {
tb.innerHTML = list.map(function(r,i){ tb.innerHTML = list.map(function(r,i){
var hot = r.ratio>=0.5 ? 'neg' : ''; var hot = r.ratio>=0.5 ? 'neg' : '';
return '<tr>' return '<tr>'
@@ -288,6 +298,11 @@ function renderFraud(){
+'</tr>'; +'</tr>';
}).join(''); }).join('');
} }
document.querySelectorAll('#fraudTable th.fsortable').forEach(function(th){
th.classList.remove('sort-asc','sort-desc');
if(th.getAttribute('data-key')===FSORT_KEY) th.classList.add(FSORT_DIR==='asc'?'sort-asc':'sort-desc');
});
}
function renderTotal(rows){ function renderTotal(rows){
var c=0,cv=0,rev=0,cost=0; var c=0,cv=0,rev=0,cost=0;
@@ -314,6 +329,15 @@ document.querySelectorAll('#detailTable th.sortable').forEach(function(th){
}); });
}); });
document.querySelectorAll('#fraudTable th.fsortable').forEach(function(th){
th.addEventListener('click',function(){
var key=th.getAttribute('data-key'), type=th.getAttribute('data-type');
if(FSORT_KEY===key){ FSORT_DIR=(FSORT_DIR==='asc'?'desc':'asc'); }
else { FSORT_KEY=key; FSORT_TYPE=type; FSORT_DIR='desc'; }
renderFraud();
});
});
document.getElementById('applyBtn').onclick=renderDetail; document.getElementById('applyBtn').onclick=renderDetail;
document.getElementById('resetBtn').onclick=function(){ document.getElementById('resetBtn').onclick=function(){
['fDtFrom','fDtTo','fRc','fCid','fPkg'].forEach(function(id){document.getElementById(id).value='';}); ['fDtFrom','fDtTo','fRc','fCid','fPkg'].forEach(function(id){document.getElementById(id).value='';});
@@ -326,6 +350,7 @@ fetch('data.json?_='+Date.now()).then(function(r){return r.json();}).then(functi
ALL = d.detail||[]; ALL = d.detail||[];
document.getElementById('meta').textContent='数据更新时间:'+(d.updated_at||'—')+' | 总表 '+SUMMARY.length+' 天 | 明细 '+ALL.length+' 行'; document.getElementById('meta').textContent='数据更新时间:'+(d.updated_at||'—')+' | 总表 '+SUMMARY.length+' 天 | 明细 '+ALL.length+' 行';
renderSummary(); renderSummary();
computeFraud();
renderFraud(); renderFraud();
fillSelects(); fillSelects();
// 默认 dt 范围 = 数据里的最早~最新 // 默认 dt 范围 = 数据里的最早~最新