PKG板块:按pkg/account可选呈现+pkg/account筛选+排序; 客户分类板块加排序; 数据改为pkg×account明细
This commit is contained in:
+247
-56
@@ -33,6 +33,16 @@
|
||||
.dod-hi-neg{background:#fee2e2;color:#dc2626;font-weight:600;}
|
||||
.sub-note{color:var(--sub);font-size:12px;margin:4px 2px 10px;}
|
||||
.total-row td{font-weight:700;background:#eef2f7;}
|
||||
.ctrl{display:flex;gap:14px;align-items:center;flex-wrap:wrap;margin-bottom:10px;}
|
||||
.ctrl label{font-size:13px;cursor:pointer;user-select:none;}
|
||||
.ctrl input[type=text]{border:1px solid var(--line);border-radius:6px;padding:5px 8px;font-size:12px;min-width:150px;}
|
||||
th.sortable{cursor:pointer;user-select:none;}
|
||||
th.sortable:hover{background:#374151;}
|
||||
th .arw{display:inline-block;width:9px;margin-left:3px;font-size:10px;color:#93c5fd;}
|
||||
th.asc .arw::after{content:"\25B2";} th.desc .arw::after{content:"\25BC";}
|
||||
tr.acc-sub td{background:#f3f6fb;color:#475569;font-size:12px;}
|
||||
tr.acc-sub td.txt{padding-left:26px;}
|
||||
tr.acc-sub:hover td{background:#e8eef7;}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -67,12 +77,24 @@
|
||||
<div id="tab-pkg" class="hide">
|
||||
<div class="card">
|
||||
<h2>2.1 PKG 收入波动 - Midas <span class="tag">保留14天</span></h2>
|
||||
<div class="sub-note">DoD=最新日与前一日收入差;|DoD|>100 标注,+绿 −红。波动原因可编辑(暂存本浏览器)。</div>
|
||||
<div class="ctrl">
|
||||
<label><input type="checkbox" class="m-pkg" data-t="midas" checked> 按 pkg</label>
|
||||
<label><input type="checkbox" class="m-acc" data-t="midas"> 按 account</label>
|
||||
<input type="text" class="f-pkg" data-t="midas" placeholder="筛选 pkg">
|
||||
<input type="text" class="f-acc" data-t="midas" placeholder="筛选 account">
|
||||
<span class="sub-note" style="margin:0;">|DoD|>100 标注(+绿−红);表头点击排序;波动原因暂存本浏览器</span>
|
||||
</div>
|
||||
<div class="scroll"><table id="tPkgMidas"><thead></thead><tbody></tbody></table></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>2.2 PKG 收入波动 - MS <span class="tag">保留14天</span></h2>
|
||||
<div class="sub-note">DoD=最新日与前一日收入差;|DoD|>100 标注,+绿 −红。波动原因可编辑(暂存本浏览器)。</div>
|
||||
<div class="ctrl">
|
||||
<label><input type="checkbox" class="m-pkg" data-t="ms" checked> 按 pkg</label>
|
||||
<label><input type="checkbox" class="m-acc" data-t="ms"> 按 account</label>
|
||||
<input type="text" class="f-pkg" data-t="ms" placeholder="筛选 pkg">
|
||||
<input type="text" class="f-acc" data-t="ms" placeholder="筛选 account">
|
||||
<span class="sub-note" style="margin:0;">|DoD|>100 标注(+绿−红);表头点击排序;波动原因暂存本浏览器</span>
|
||||
</div>
|
||||
<div class="scroll"><table id="tPkgMS"><thead></thead><tbody></tbody></table></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,14 +105,26 @@
|
||||
<h2>3.1 客户分类日报</h2>
|
||||
<div class="sub-note">DoD=T-1−T-2,DoD%=T-1/T-2−1。波动原因可编辑(暂存本浏览器)。</div>
|
||||
<div class="scroll"><table id="tCustDaily"><thead><tr>
|
||||
<th class="txt">客户分类</th><th>iDSP个数</th><th>base daily revenue</th>
|
||||
<th>T-4收入</th><th>T-3收入</th><th>T-2收入</th><th>T-1收入</th><th>DoD</th><th>DoD%</th><th class="txt">波动原因</th>
|
||||
<th class="txt cd-sort" data-k="cat" data-ty="str">客户分类<span class="arw"></span></th>
|
||||
<th class="cd-sort" data-k="idsp" data-ty="num">iDSP个数<span class="arw"></span></th>
|
||||
<th class="cd-sort" data-k="base" data-ty="num">base daily revenue<span class="arw"></span></th>
|
||||
<th class="cd-sort" data-k="t4" data-ty="num">T-4收入<span class="arw"></span></th>
|
||||
<th class="cd-sort" data-k="t3" data-ty="num">T-3收入<span class="arw"></span></th>
|
||||
<th class="cd-sort" data-k="t2" data-ty="num">T-2收入<span class="arw"></span></th>
|
||||
<th class="cd-sort" data-k="t1" data-ty="num">T-1收入<span class="arw"></span></th>
|
||||
<th class="cd-sort" data-k="dod" data-ty="num">DoD<span class="arw"></span></th>
|
||||
<th class="cd-sort" data-k="dodp" data-ty="num">DoD%<span class="arw"></span></th>
|
||||
<th class="txt">波动原因</th>
|
||||
</tr></thead><tbody></tbody></table></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>3.2 客户明细 <span class="tag">保留14天</span></h2>
|
||||
<div class="scroll"><table id="tCustDetail"><thead><tr>
|
||||
<th class="txt">客户分类</th><th class="txt">客户</th><th>base收入</th><th>T-1收入</th><th>DoD(与T-2差)</th>
|
||||
<th class="txt de-sort" data-k="cat" data-ty="str">客户分类<span class="arw"></span></th>
|
||||
<th class="txt de-sort" data-k="acc" data-ty="str">客户<span class="arw"></span></th>
|
||||
<th class="de-sort" data-k="base" data-ty="num">base收入<span class="arw"></span></th>
|
||||
<th class="de-sort" data-k="t1" data-ty="num">T-1收入<span class="arw"></span></th>
|
||||
<th class="de-sort" data-k="dod" data-ty="num">DoD(与T-2差)<span class="arw"></span></th>
|
||||
</tr></thead><tbody></tbody></table></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -157,82 +191,237 @@ function renderAccTable(tblId, rowsData, totalKey){
|
||||
document.querySelector('#'+tblId+' tbody').innerHTML=body;
|
||||
}
|
||||
|
||||
// ===== 2.1/2.2 PKG波动 (pkg为行, dt为列, 末列DoD+波动原因) =====
|
||||
function renderPkg(tblId, byDt, noteTbl){
|
||||
var dts=sortedDates(byDt); // 升序
|
||||
var showDts=dts.slice(-14);
|
||||
// ===== 2.1/2.2 PKG波动 =====
|
||||
// byDt: {dt: {pkg: {account: rev}}}
|
||||
// 模式: byPkg / byAcc 两个复选框;排序: 按 latest(最新日收入) 或 dod
|
||||
var PKG_STATE={
|
||||
midas:{tbl:'tPkgMidas', note:'midas', byPkg:true, byAcc:false, fPkg:'', fAcc:'', sort:'latest', dir:'desc'},
|
||||
ms:{tbl:'tPkgMS', note:'ms', byPkg:true, byAcc:false, fPkg:'', fAcc:'', sort:'latest', dir:'desc'}
|
||||
};
|
||||
|
||||
function pkgDatesFor(byDt){ return sortedDates(byDt).slice(-14); }
|
||||
|
||||
// 聚合出:pkg -> {dt: total}, pkg -> {acc: {dt: rev}}
|
||||
function buildPkgIndex(byDt, showDts){
|
||||
var pkgTotal={}; // pkg -> {dt: sum}
|
||||
var pkgAcc={}; // pkg -> {acc: {dt: rev}}
|
||||
showDts.forEach(function(dt){
|
||||
var m=byDt[dt]||{};
|
||||
Object.keys(m).forEach(function(pkg){
|
||||
var accs=m[pkg]||{};
|
||||
Object.keys(accs).forEach(function(acc){
|
||||
var v=accs[acc]||0;
|
||||
(pkgTotal[pkg]=pkgTotal[pkg]||{})[dt]=((pkgTotal[pkg][dt]||0)+v);
|
||||
((pkgAcc[pkg]=pkgAcc[pkg]||{})[acc]=pkgAcc[pkg][acc]||{})[dt]=v;
|
||||
});
|
||||
});
|
||||
});
|
||||
return {pkgTotal:pkgTotal, pkgAcc:pkgAcc};
|
||||
}
|
||||
// account视角: acc -> {dt: sum(over pkg)}
|
||||
function buildAccIndex(byDt, showDts){
|
||||
var accTotal={};
|
||||
showDts.forEach(function(dt){
|
||||
var m=byDt[dt]||{};
|
||||
Object.keys(m).forEach(function(pkg){
|
||||
var accs=m[pkg]||{};
|
||||
Object.keys(accs).forEach(function(acc){
|
||||
(accTotal[acc]=accTotal[acc]||{})[dt]=((accTotal[acc][dt]||0)+(accs[acc]||0));
|
||||
});
|
||||
});
|
||||
});
|
||||
return accTotal;
|
||||
}
|
||||
function dodOf(map,latest,prev){ return (map[latest]||0)-(prev?(map[prev]||0):0); }
|
||||
function dodCls(dod){ if(Math.abs(dod)>100) return dod>0?'dod-hi-pos':'dod-hi-neg'; return dod>=0?'pos':'neg'; }
|
||||
|
||||
function renderPkg(which){
|
||||
var st=PKG_STATE[which]; var byDt=(which==='midas'?D.pkg_midas:D.pkg_ms);
|
||||
var showDts=pkgDatesFor(byDt);
|
||||
var latest=showDts[showDts.length-1], prev=showDts[showDts.length-2];
|
||||
// 收集所有pkg
|
||||
var pkgset={};
|
||||
showDts.forEach(function(dt){ Object.keys(byDt[dt]).forEach(function(p){pkgset[p]=1;}); });
|
||||
var pkgs=Object.keys(pkgset);
|
||||
// 按最新日收入降序
|
||||
pkgs.sort(function(a,b){ return (byDt[latest][b]||0)-(byDt[latest][a]||0); });
|
||||
var thead=document.querySelector('#'+tblId+' thead');
|
||||
var th='<tr><th class="txt sticky-col">PKG</th>';
|
||||
showDts.forEach(function(dt){ th+='<th>'+dt+'</th>'; });
|
||||
th+='<th>DoD</th><th class="txt">波动原因</th></tr>';
|
||||
thead.innerHTML=th;
|
||||
var body='';
|
||||
pkgs.forEach(function(p){
|
||||
var row='<tr><td class="txt sticky-col">'+p+'</td>';
|
||||
showDts.forEach(function(dt){ var v=byDt[dt][p]; row+='<td>'+(v==null?'':n2(v))+'</td>'; });
|
||||
var lv=byDt[latest]&&byDt[latest][p]||0, pv=(prev&&byDt[prev]&&byDt[prev][p])||0;
|
||||
var dod=lv-pv;
|
||||
var cls='';
|
||||
if(Math.abs(dod)>100){ cls = dod>0?'dod-hi-pos':'dod-hi-neg'; }
|
||||
else { cls = dod>=0?'pos':'neg'; }
|
||||
row+='<td class="'+cls+'">'+n2(dod)+'</td>';
|
||||
row+=reasonCell(noteTbl, p);
|
||||
row+='</tr>';
|
||||
body+=row;
|
||||
var thead=document.querySelector('#'+st.tbl+' thead');
|
||||
var tbody=document.querySelector('#'+st.tbl+' tbody');
|
||||
var fp=st.fPkg.trim().toLowerCase(), fa=st.fAcc.trim().toLowerCase();
|
||||
|
||||
function sortArr(arr, valLatest, valDod){
|
||||
var dir=st.dir==='asc'?1:-1;
|
||||
arr.sort(function(a,b){
|
||||
var va=st.sort==='dod'?valDod(a):valLatest(a);
|
||||
var vb=st.sort==='dod'?valDod(b):valLatest(b);
|
||||
return (va<vb?-1:va>vb?1:0)*dir;
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
function headRow(firstCol){
|
||||
var th='<tr><th class="txt sticky-col">'+firstCol+'</th>';
|
||||
showDts.forEach(function(dt){ th+='<th>'+dt+'</th>'; });
|
||||
var la=(st.sort==='latest'?(' '+st.dir):''), da=(st.sort==='dod'?(' '+st.dir):'');
|
||||
th+='<th class="sortable'+la+'" data-sort="latest" data-t="'+which+'">最新收入<span class="arw"></span></th>';
|
||||
th+='<th class="sortable'+da+'" data-sort="dod" data-t="'+which+'">DoD<span class="arw"></span></th>';
|
||||
th+='<th class="txt">波动原因</th></tr>';
|
||||
return th;
|
||||
}
|
||||
|
||||
var html='';
|
||||
if(st.byPkg){
|
||||
var idx=buildPkgIndex(byDt,showDts);
|
||||
var pkgs=Object.keys(idx.pkgTotal);
|
||||
if(fp) pkgs=pkgs.filter(function(p){return p.toLowerCase().indexOf(fp)>=0;});
|
||||
if(fa) pkgs=pkgs.filter(function(p){ return Object.keys(idx.pkgAcc[p]||{}).some(function(a){return a.toLowerCase().indexOf(fa)>=0;}); });
|
||||
thead.innerHTML=headRow('PKG');
|
||||
sortArr(pkgs, function(p){return idx.pkgTotal[p][latest]||0;}, function(p){return dodOf(idx.pkgTotal[p],latest,prev);});
|
||||
pkgs.forEach(function(p){
|
||||
var tot=idx.pkgTotal[p];
|
||||
var row='<tr><td class="txt sticky-col">'+p+'</td>';
|
||||
showDts.forEach(function(dt){ var v=tot[dt]; row+='<td>'+(v==null?'':n2(v))+'</td>'; });
|
||||
var dod=dodOf(tot,latest,prev);
|
||||
row+='<td>'+n2(tot[latest]||0)+'</td><td class="'+dodCls(dod)+'">'+n2(dod)+'</td>'+reasonCell(st.note,p)+'</tr>';
|
||||
html+=row;
|
||||
// 两者都选: 该pkg下各account子行
|
||||
if(st.byAcc){
|
||||
var accs=idx.pkgAcc[p]||{};
|
||||
var alist=Object.keys(accs);
|
||||
if(fa) alist=alist.filter(function(a){return a.toLowerCase().indexOf(fa)>=0;});
|
||||
sortArr(alist, function(a){return accs[a][latest]||0;}, function(a){return dodOf(accs[a],latest,prev);});
|
||||
alist.forEach(function(a){
|
||||
var m=accs[a]; var sub='<tr class="acc-sub"><td class="txt">↳ '+a+'</td>';
|
||||
showDts.forEach(function(dt){ var v=m[dt]; sub+='<td>'+(v==null?'':n2(v))+'</td>'; });
|
||||
var d2=dodOf(m,latest,prev);
|
||||
sub+='<td>'+n2(m[latest]||0)+'</td><td class="'+dodCls(d2)+'">'+n2(d2)+'</td><td></td></tr>';
|
||||
html+=sub;
|
||||
});
|
||||
}
|
||||
});
|
||||
} else if(st.byAcc){
|
||||
// 仅按account
|
||||
var accTotal=buildAccIndex(byDt,showDts);
|
||||
var accs=Object.keys(accTotal);
|
||||
if(fa) accs=accs.filter(function(a){return a.toLowerCase().indexOf(fa)>=0;});
|
||||
// fp在纯account模式下无意义,忽略
|
||||
thead.innerHTML=headRow('account');
|
||||
sortArr(accs, function(a){return accTotal[a][latest]||0;}, function(a){return dodOf(accTotal[a],latest,prev);});
|
||||
accs.forEach(function(a){
|
||||
var tot=accTotal[a];
|
||||
var row='<tr><td class="txt sticky-col">'+a+'</td>';
|
||||
showDts.forEach(function(dt){ var v=tot[dt]; row+='<td>'+(v==null?'':n2(v))+'</td>'; });
|
||||
var dod=dodOf(tot,latest,prev);
|
||||
row+='<td>'+n2(tot[latest]||0)+'</td><td class="'+dodCls(dod)+'">'+n2(dod)+'</td>'+reasonCell(st.note+'_acc',a)+'</tr>';
|
||||
html+=row;
|
||||
});
|
||||
} else {
|
||||
// 都没勾,默认回到按pkg
|
||||
st.byPkg=true; return renderPkg(which);
|
||||
}
|
||||
tbody.innerHTML=html;
|
||||
}
|
||||
|
||||
// PKG 控制交互
|
||||
function bindPkgControls(){
|
||||
document.querySelectorAll('.m-pkg').forEach(function(el){ el.onchange=function(){ PKG_STATE[el.getAttribute('data-t')].byPkg=el.checked; renderPkg(el.getAttribute('data-t')); }; });
|
||||
document.querySelectorAll('.m-acc').forEach(function(el){ el.onchange=function(){ PKG_STATE[el.getAttribute('data-t')].byAcc=el.checked; renderPkg(el.getAttribute('data-t')); }; });
|
||||
document.querySelectorAll('.f-pkg').forEach(function(el){ el.oninput=function(){ PKG_STATE[el.getAttribute('data-t')].fPkg=el.value; renderPkg(el.getAttribute('data-t')); }; });
|
||||
document.querySelectorAll('.f-acc').forEach(function(el){ el.oninput=function(){ PKG_STATE[el.getAttribute('data-t')].fAcc=el.value; renderPkg(el.getAttribute('data-t')); }; });
|
||||
// 表头排序(事件委托)
|
||||
document.addEventListener('click',function(e){
|
||||
var th=e.target.closest && e.target.closest('th.sortable');
|
||||
if(!th || !th.getAttribute('data-t')) return;
|
||||
var which=th.getAttribute('data-t'), s=th.getAttribute('data-sort'), st=PKG_STATE[which];
|
||||
if(st.sort===s){ st.dir=(st.dir==='asc'?'desc':'asc'); } else { st.sort=s; st.dir='desc'; }
|
||||
renderPkg(which);
|
||||
});
|
||||
document.querySelector('#'+tblId+' tbody').innerHTML=body;
|
||||
}
|
||||
|
||||
// ===== 3.1 客户分类日报 =====
|
||||
var CD_SORT={k:null,ty:'num',dir:'desc'};
|
||||
function renderCustDaily(){
|
||||
var dts=sortedDates(D.cust_daily); // 升序
|
||||
var t1=dts[dts.length-1], t2=dts[dts.length-2], t3=dts[dts.length-3], t4=dts[dts.length-4];
|
||||
var cats=Object.keys(D.cust_idsp);
|
||||
// base = 该分类各account base之和
|
||||
var baseByCat={};
|
||||
(D.cust_base||[]).forEach(function(x){ baseByCat[x.cat]=(baseByCat[x.cat]||0)+(+x.base||0); });
|
||||
var body='';
|
||||
cats.forEach(function(cat){
|
||||
var rows=cats.map(function(cat){
|
||||
function val(dt){ return dt&&D.cust_daily[dt]? (D.cust_daily[dt][cat]||0):null; }
|
||||
var v1=val(t1),v2=val(t2),v3=val(t3),v4=val(t4);
|
||||
var dod = (v1!=null&&v2!=null)? v1-v2 : null;
|
||||
var dodp= (v1!=null&&v2)? (v1/v2-1) : null;
|
||||
var dc = dod==null?'':(dod>=0?'pos':'neg');
|
||||
body+='<tr><td class="txt">'+cat+'</td><td>'+(D.cust_idsp[cat]||'')+'</td><td>'+n2(baseByCat[cat])+'</td>'
|
||||
+'<td>'+(v4==null?'':n2(v4))+'</td><td>'+(v3==null?'':n2(v3))+'</td><td>'+(v2==null?'':n2(v2))+'</td>'
|
||||
+'<td class="pos">'+(v1==null?'':n2(v1))+'</td>'
|
||||
+'<td class="'+dc+'">'+(dod==null?'':n2(dod))+'</td>'
|
||||
+'<td class="'+dc+'">'+(dodp==null?'':pct(dodp))+'</td>'
|
||||
+reasonCell('cust_daily', cat)+'</tr>';
|
||||
var dod=(v1!=null&&v2!=null)?v1-v2:null;
|
||||
var dodp=(v1!=null&&v2)?(v1/v2-1):null;
|
||||
return {cat:cat,idsp:D.cust_idsp[cat]||0,base:baseByCat[cat]||0,t4:v4,t3:v3,t2:v2,t1:v1,dod:dod,dodp:dodp};
|
||||
});
|
||||
if(CD_SORT.k){
|
||||
var dir=CD_SORT.dir==='asc'?1:-1;
|
||||
rows.sort(function(a,b){
|
||||
if(CD_SORT.ty==='str') return dir*String(a[CD_SORT.k]).localeCompare(String(b[CD_SORT.k]));
|
||||
var va=a[CD_SORT.k]==null?-Infinity:a[CD_SORT.k], vb=b[CD_SORT.k]==null?-Infinity:b[CD_SORT.k];
|
||||
return (va<vb?-1:va>vb?1:0)*dir;
|
||||
});
|
||||
}
|
||||
var body=rows.map(function(r){
|
||||
var dc=r.dod==null?'':(r.dod>=0?'pos':'neg');
|
||||
return '<tr><td class="txt">'+r.cat+'</td><td>'+(r.idsp||'')+'</td><td>'+n2(r.base)+'</td>'
|
||||
+'<td>'+(r.t4==null?'':n2(r.t4))+'</td><td>'+(r.t3==null?'':n2(r.t3))+'</td><td>'+(r.t2==null?'':n2(r.t2))+'</td>'
|
||||
+'<td class="pos">'+(r.t1==null?'':n2(r.t1))+'</td>'
|
||||
+'<td class="'+dc+'">'+(r.dod==null?'':n2(r.dod))+'</td>'
|
||||
+'<td class="'+dc+'">'+(r.dodp==null?'':pct(r.dodp))+'</td>'
|
||||
+reasonCell('cust_daily', r.cat)+'</tr>';
|
||||
}).join('');
|
||||
document.querySelector('#tCustDaily tbody').innerHTML=body;
|
||||
document.querySelectorAll('#tCustDaily th.cd-sort').forEach(function(th){
|
||||
th.classList.remove('asc','desc');
|
||||
if(th.getAttribute('data-k')===CD_SORT.k) th.classList.add(CD_SORT.dir);
|
||||
});
|
||||
}
|
||||
|
||||
// ===== 3.2 客户明细 =====
|
||||
var DE_SORT={k:null,ty:'num',dir:'desc'};
|
||||
function renderCustDetail(){
|
||||
var dts=sortedDates(D.cust_detail);
|
||||
var t1=dts[dts.length-1], t2=dts[dts.length-2];
|
||||
var t1list=D.cust_detail[t1]||[];
|
||||
var t2map={};
|
||||
(D.cust_detail[t2]||[]).forEach(function(x){ t2map[x.cat+'||'+x.acc]=x.t1; });
|
||||
// 按分类、再按T-1收入降序
|
||||
var rows=t1list.slice().sort(function(a,b){ return a.cat<b.cat?-1:a.cat>b.cat?1:(b.t1-a.t1); });
|
||||
var body='';
|
||||
rows.forEach(function(r){
|
||||
var rows=t1list.map(function(r){
|
||||
var prev=t2map[r.cat+'||'+r.acc];
|
||||
var dod = prev!=null? r.t1-prev : null;
|
||||
var dc=dod==null?'':(dod>=0?'pos':'neg');
|
||||
body+='<tr><td class="txt">'+r.cat+'</td><td class="txt">'+r.acc+'</td><td>'+n2(r.base)+'</td>'
|
||||
+'<td>'+n2(r.t1)+'</td><td class="'+dc+'">'+(dod==null?'':n2(dod))+'</td></tr>';
|
||||
return {cat:r.cat,acc:r.acc,base:r.base,t1:r.t1,dod:(prev!=null?r.t1-prev:null)};
|
||||
});
|
||||
if(DE_SORT.k){
|
||||
var dir=DE_SORT.dir==='asc'?1:-1;
|
||||
rows.sort(function(a,b){
|
||||
if(DE_SORT.ty==='str') return dir*String(a[DE_SORT.k]).localeCompare(String(b[DE_SORT.k]));
|
||||
var va=a[DE_SORT.k]==null?-Infinity:a[DE_SORT.k], vb=b[DE_SORT.k]==null?-Infinity:b[DE_SORT.k];
|
||||
return (va<vb?-1:va>vb?1:0)*dir;
|
||||
});
|
||||
} else {
|
||||
rows.sort(function(a,b){ return a.cat<b.cat?-1:a.cat>b.cat?1:(b.t1-a.t1); });
|
||||
}
|
||||
var body=rows.map(function(r){
|
||||
var dc=r.dod==null?'':(r.dod>=0?'pos':'neg');
|
||||
return '<tr><td class="txt">'+r.cat+'</td><td class="txt">'+r.acc+'</td><td>'+n2(r.base)+'</td>'
|
||||
+'<td>'+n2(r.t1)+'</td><td class="'+dc+'">'+(r.dod==null?'':n2(r.dod))+'</td></tr>';
|
||||
}).join('');
|
||||
document.querySelector('#tCustDetail tbody').innerHTML=body;
|
||||
document.querySelectorAll('#tCustDetail th.de-sort').forEach(function(th){
|
||||
th.classList.remove('asc','desc');
|
||||
if(th.getAttribute('data-k')===DE_SORT.k) th.classList.add(DE_SORT.dir);
|
||||
});
|
||||
}
|
||||
|
||||
// 3.1/3.2 表头排序绑定
|
||||
function bindCustSort(){
|
||||
document.querySelectorAll('#tCustDaily th.cd-sort').forEach(function(th){
|
||||
th.style.cursor='pointer';
|
||||
th.onclick=function(){
|
||||
var k=th.getAttribute('data-k'), ty=th.getAttribute('data-ty');
|
||||
if(CD_SORT.k===k){ CD_SORT.dir=(CD_SORT.dir==='asc'?'desc':'asc'); } else { CD_SORT.k=k; CD_SORT.ty=ty; CD_SORT.dir='desc'; }
|
||||
renderCustDaily();
|
||||
};
|
||||
});
|
||||
document.querySelectorAll('#tCustDetail th.de-sort').forEach(function(th){
|
||||
th.style.cursor='pointer';
|
||||
th.onclick=function(){
|
||||
var k=th.getAttribute('data-k'), ty=th.getAttribute('data-ty');
|
||||
if(DE_SORT.k===k){ DE_SORT.dir=(DE_SORT.dir==='asc'?'desc':'asc'); } else { DE_SORT.k=k; DE_SORT.ty=ty; DE_SORT.dir='desc'; }
|
||||
renderCustDetail();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// 波动原因输入监听(事件委托)
|
||||
@@ -249,8 +438,10 @@ fetch('data.json?_='+Date.now()).then(function(r){return r.json();}).then(functi
|
||||
renderOverall();
|
||||
renderAccTable('tCN', d.cn, 'DSP CN revenue');
|
||||
renderAccTable('tIntl', d.international, 'DSP international');
|
||||
renderPkg('tPkgMidas', d.pkg_midas, 'midas');
|
||||
renderPkg('tPkgMS', d.pkg_ms, 'ms');
|
||||
bindPkgControls();
|
||||
bindCustSort();
|
||||
renderPkg('midas');
|
||||
renderPkg('ms');
|
||||
renderCustDaily();
|
||||
renderCustDetail();
|
||||
}).catch(function(e){
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
# 每小时被平台执行;UTC+8 过11点且当天未更新则补跑,查询 T-1(前一天)。
|
||||
# 产出 data.json:
|
||||
# overall/cn/international : 每天一行,持续叠加
|
||||
# pkg_midas / pkg_ms : 每个pkg按天的revenue,保留14天(前端算DoD)
|
||||
# pkg_midas / pkg_ms : {dt:{pkg:{account:rev}}} 保留14天(前端按模式聚合/算DoD)
|
||||
# cust_daily / cust_detail : 客户分类,保留14天
|
||||
# ============================================================
|
||||
set -euo pipefail
|
||||
@@ -180,17 +180,18 @@ print(f' superset T-1行={len(sup_prev)} (全部{len(sup)}) ; report行={len(re
|
||||
|
||||
# ---- 汇总:无_API account revenue (来自Superset attr_platform) ----
|
||||
sup_acc_rev=defaultdict(float) # account(小写) -> rev
|
||||
sup_pkg_rev=defaultdict(float) # pkg -> rev (Midas)
|
||||
sup_pkg_acc=defaultdict(lambda:defaultdict(float)) # pkg -> {account -> rev} (Midas, account=attr_platform)
|
||||
for r in sup_prev:
|
||||
acc=norm(r.get('attr_platform'))
|
||||
rev=float(r.get('revenue(还原前)') or 0)
|
||||
sup_acc_rev[acc]+=rev
|
||||
pkg=r.get('pkg name(EN)') or ''
|
||||
sup_pkg_rev[pkg]+=rev
|
||||
if rev:
|
||||
sup_pkg_acc[pkg][r.get('attr_platform') or acc]+=rev
|
||||
|
||||
# ---- 汇总:带_API account revenue (来自report, 按account_id映射, 排除18) ----
|
||||
rep_acc_rev=defaultdict(float) # _API名(小写) -> rev
|
||||
rep_pkg_rev=defaultdict(float) # pkg -> rev (MS, 排除account18)
|
||||
rep_pkg_acc=defaultdict(lambda:defaultdict(float)) # pkg -> {account -> rev} (MS, 排除account18)
|
||||
for r in rep:
|
||||
aid=r.get('advertiser_account_id')
|
||||
rev=float(r.get('revenues') or 0)
|
||||
@@ -200,7 +201,9 @@ for r in rep:
|
||||
if name:
|
||||
rep_acc_rev[norm(name)]+=rev
|
||||
pkg=r.get('package_name') or ''
|
||||
rep_pkg_rev[pkg]+=rev
|
||||
disp = name if name else ('acct_%s'%aid)
|
||||
if rev:
|
||||
rep_pkg_acc[pkg][disp]+=rev
|
||||
|
||||
# 统一取某 account 收入:无_API走superset,带_API走report
|
||||
def acc_rev(acc):
|
||||
@@ -225,9 +228,9 @@ intl_row['DSP international']=round(intl_total,2)
|
||||
overall_row={'dt':PREV_DATE,'DSP total revenue':round(cn_total+intl_total,2),
|
||||
'DSP CN revenue':round(cn_total,2),'DSP international':round(intl_total,2)}
|
||||
|
||||
# ============ 2.1 PKG-Midas / 2.2 PKG-MS (当天pkg->rev) ============
|
||||
pkg_midas={pkg:round(v,4) for pkg,v in sup_pkg_rev.items() if v} # Midas: superset
|
||||
pkg_ms={pkg:round(v,4) for pkg,v in rep_pkg_rev.items() if v} # MS: report(已排除18)
|
||||
# ============ 2.1 PKG-Midas / 2.2 PKG-MS (当天 pkg -> {account: rev}) ============
|
||||
pkg_midas={pkg:{a:round(v,4) for a,v in accs.items() if v} for pkg,accs in sup_pkg_acc.items() if accs}
|
||||
pkg_ms={pkg:{a:round(v,4) for a,v in accs.items() if v} for pkg,accs in rep_pkg_acc.items() if accs}
|
||||
|
||||
# ============ 3.x 客户分类 (当天) ============
|
||||
# 每个 account 的 T-1 收入
|
||||
|
||||
Reference in New Issue
Block a user