骗量排行榜表头支持点击升序/降序排序
This commit is contained in:
+51
-26
@@ -38,8 +38,8 @@
|
||||
.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.txt{text-align:left;}
|
||||
th.sortable{cursor:pointer;user-select:none;}
|
||||
th.sortable:hover{background:#374151;}
|
||||
th.sortable,th.fsortable{cursor:pointer;user-select:none;}
|
||||
th.sortable:hover,th.fsortable:hover{background:#374151;}
|
||||
th .arrow{display:inline-block;width:10px;margin-left:3px;font-size:10px;color:#93c5fd;}
|
||||
th.sort-asc .arrow::after{content:"\25B2";}
|
||||
th.sort-desc .arrow::after{content:"\25BC";}
|
||||
@@ -132,10 +132,10 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>排名</th>
|
||||
<th class="txt">account</th>
|
||||
<th>campaign 总数</th>
|
||||
<th>可疑 campaign 数<br><span style="font-weight:normal;font-size:11px;">(>500点击 & 0转化)</span></th>
|
||||
<th>骗量占比</th>
|
||||
<th class="txt fsortable" data-key="account" data-type="str">account<span class="arrow"></span></th>
|
||||
<th class="fsortable" data-key="total" data-type="num">campaign 总数<span class="arrow"></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;">(>500点击 & 0转化)</span></th>
|
||||
<th class="fsortable" data-key="ratio" data-type="num">骗量占比<span class="arrow"></span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody></tbody>
|
||||
@@ -253,13 +253,15 @@ function renderDetail(){
|
||||
updateSortArrows();
|
||||
}
|
||||
|
||||
function renderFraud(){
|
||||
// 取"前一天" = 明细里最新的 dt
|
||||
if(!ALL.length){ document.querySelector('#fraudTable tbody').innerHTML='<tr><td colspan="5" class="empty">暂无数据</td></tr>'; return; }
|
||||
var FRAUD = [];
|
||||
var FSORT_KEY='ratio', FSORT_TYPE='num', FSORT_DIR='desc';
|
||||
|
||||
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 rows = ALL.filter(function(d){ return d.dt===maxDt; });
|
||||
// 按 account -> campaign_id 聚合 clicks/conversion
|
||||
var byAcc = {}; // acc -> { campId -> {clicks,conv} }
|
||||
var byAcc = {};
|
||||
rows.forEach(function(d){
|
||||
var acc=d.account||'未知', cid=String(d.campaign_id||'');
|
||||
if(!byAcc[acc]) byAcc[acc]={};
|
||||
@@ -267,26 +269,39 @@ function renderFraud(){
|
||||
byAcc[acc][cid].clicks += (+d.clicks||0);
|
||||
byAcc[acc][cid].conv += (+d.conversion||0);
|
||||
});
|
||||
var list = Object.keys(byAcc).map(function(acc){
|
||||
var camps = byAcc[acc];
|
||||
var ids = Object.keys(camps);
|
||||
FRAUD = Object.keys(byAcc).map(function(acc){
|
||||
var camps = byAcc[acc], ids = Object.keys(camps);
|
||||
var total = ids.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 };
|
||||
});
|
||||
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');
|
||||
if(!list.length){ tb.innerHTML='<tr><td colspan="5" class="empty">暂无数据</td></tr>'; return; }
|
||||
tb.innerHTML = list.map(function(r,i){
|
||||
var hot = r.ratio>=0.5 ? 'neg' : '';
|
||||
return '<tr>'
|
||||
+'<td>'+(i+1)+'</td>'
|
||||
+'<td class="txt">'+r.account+'</td>'
|
||||
+'<td>'+num(r.total)+'</td>'
|
||||
+'<td>'+num(r.suspicious)+'</td>'
|
||||
+'<td class="'+hot+'">'+pct(r.ratio)+'</td>'
|
||||
+'</tr>';
|
||||
}).join('');
|
||||
if(!list.length){ tb.innerHTML='<tr><td colspan="5" class="empty">暂无数据</td></tr>'; }
|
||||
else {
|
||||
tb.innerHTML = list.map(function(r,i){
|
||||
var hot = r.ratio>=0.5 ? 'neg' : '';
|
||||
return '<tr>'
|
||||
+'<td>'+(i+1)+'</td>'
|
||||
+'<td class="txt">'+r.account+'</td>'
|
||||
+'<td>'+num(r.total)+'</td>'
|
||||
+'<td>'+num(r.suspicious)+'</td>'
|
||||
+'<td class="'+hot+'">'+pct(r.ratio)+'</td>'
|
||||
+'</tr>';
|
||||
}).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){
|
||||
@@ -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('resetBtn').onclick=function(){
|
||||
['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||[];
|
||||
document.getElementById('meta').textContent='数据更新时间:'+(d.updated_at||'—')+' | 总表 '+SUMMARY.length+' 天 | 明细 '+ALL.length+' 行';
|
||||
renderSummary();
|
||||
computeFraud();
|
||||
renderFraud();
|
||||
fillSelects();
|
||||
// 默认 dt 范围 = 数据里的最早~最新
|
||||
|
||||
Reference in New Issue
Block a user