骗量排行榜表头支持点击升序/降序排序
This commit is contained in:
+41
-16
@@ -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;">(>500点击 & 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;">(>500点击 & 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>'
|
||||||
@@ -287,6 +297,11 @@ function renderFraud(){
|
|||||||
+'<td class="'+hot+'">'+pct(r.ratio)+'</td>'
|
+'<td class="'+hot+'">'+pct(r.ratio)+'</td>'
|
||||||
+'</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){
|
||||||
@@ -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 范围 = 数据里的最早~最新
|
||||||
|
|||||||
Reference in New Issue
Block a user