diff --git a/index.html b/index.html
index 35c6db7..42d5a33 100644
--- a/index.html
+++ b/index.html
@@ -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 @@
| 排名 |
- account |
- campaign 总数 |
- 可疑 campaign 数 (>500点击 & 0转化) |
- 骗量占比 |
+ account |
+ campaign 总数 |
+ 可疑 campaign 数 (>500点击 & 0转化) |
+ 骗量占比 |
@@ -253,13 +253,15 @@ function renderDetail(){
updateSortArrows();
}
-function renderFraud(){
- // 取"前一天" = 明细里最新的 dt
- if(!ALL.length){ document.querySelector('#fraudTable tbody').innerHTML='| 暂无数据 |
'; 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 (vavb?1:0)*dir || b.ratio-a.ratio || b.suspicious-a.suspicious;
+ });
var tb=document.querySelector('#fraudTable tbody');
- if(!list.length){ tb.innerHTML='| 暂无数据 |
'; return; }
- tb.innerHTML = list.map(function(r,i){
- var hot = r.ratio>=0.5 ? 'neg' : '';
- return ''
- +'| '+(i+1)+' | '
- +''+r.account+' | '
- +''+num(r.total)+' | '
- +''+num(r.suspicious)+' | '
- +''+pct(r.ratio)+' | '
- +'
';
- }).join('');
+ if(!list.length){ tb.innerHTML='| 暂无数据 |
'; }
+ else {
+ tb.innerHTML = list.map(function(r,i){
+ var hot = r.ratio>=0.5 ? 'neg' : '';
+ return ''
+ +'| '+(i+1)+' | '
+ +''+r.account+' | '
+ +''+num(r.total)+' | '
+ +''+num(r.suspicious)+' | '
+ +''+pct(r.ratio)+' | '
+ +'
';
+ }).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 范围 = 数据里的最早~最新