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 范围 = 数据里的最早~最新