feat: channel efficiency table grouped by product_country
This commit is contained in:
+34
-14
@@ -179,14 +179,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 模块4:渠道效率指标表 -->
|
<!-- 模块4:渠道效率对比(按 产品_国家 分组,展开各渠道)-->
|
||||||
<div class="section">
|
<div class="section">
|
||||||
<h2>渠道效率对比</h2>
|
<h2>渠道效率对比(产品_国家 × 渠道)</h2>
|
||||||
|
<p class="hint">每个产品_国家下列出其投放过的各渠道及效率指标,同组内可横向对比不同渠道表现。</p>
|
||||||
<div class="table-wrapper">
|
<div class="table-wrapper">
|
||||||
<table id="channelTable">
|
<table id="channelTable">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>渠道</th><th>展示</th><th>点击</th><th>转化</th>
|
<th>产品_国家</th><th>渠道</th><th>展示</th><th>点击</th><th>转化</th>
|
||||||
<th>花费</th><th>CTR</th><th>CVR</th><th>eCPM</th><th>CPC</th><th>CPA</th>
|
<th>花费</th><th>CTR</th><th>CVR</th><th>eCPM</th><th>CPC</th><th>CPA</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -446,17 +447,36 @@
|
|||||||
options:{ responsive:true, maintainAspectRatio:false, plugins:{legend:{position:'right',labels:{boxWidth:12,font:{size:11}}}} }
|
options:{ responsive:true, maintainAspectRatio:false, plugins:{legend:{position:'right',labels:{boxWidth:12,font:{size:11}}}} }
|
||||||
});
|
});
|
||||||
|
|
||||||
// ---- 模块4:渠道效率表 ----
|
// ---- 模块4:渠道效率对比(按 产品_国家 分组,展开各渠道)----
|
||||||
document.querySelector('#channelTable tbody').innerHTML = chans.map(c => {
|
// 分组键 = 产品 + 国家
|
||||||
const cm = metrics(data.filter(x=>x.channel===c));
|
const groupMap = {};
|
||||||
return `<tr>
|
data.forEach(x => {
|
||||||
<td><span class="tag" style="background:${(CH_COLORS[c]||'#999')}20;color:${CH_COLORS[c]||'#666'}">${c}</span></td>
|
const g = x.product + '_' + x.location;
|
||||||
<td class="num">${fmtNum(cm.imp)}</td><td class="num">${fmtNum(cm.clk)}</td><td class="num">${cm.cv}</td>
|
(groupMap[g] = groupMap[g] || []).push(x);
|
||||||
<td class="num">${fmtMoney(cm.sp)}</td><td class="num">${(cm.ctr*100).toFixed(2)}%</td>
|
});
|
||||||
<td class="num">${(cm.cvr*100).toFixed(2)}%</td><td class="num">$${cm.ecpm.toFixed(2)}</td>
|
// 分组按总展示降序;组内渠道也按展示降序
|
||||||
<td class="num">$${cm.cpc.toFixed(3)}</td><td class="num">${cm.cpa?fmtMoney(cm.cpa):'-'}</td>
|
const groupKeys = Object.keys(groupMap).sort((a,b)=> sum(groupMap[b],'impression') - sum(groupMap[a],'impression'));
|
||||||
</tr>`;
|
let rowsHtml = '';
|
||||||
}).join('') || `<tr><td colspan="10" class="empty">暂无数据</td></tr>`;
|
groupKeys.forEach(g => {
|
||||||
|
const rows = groupMap[g];
|
||||||
|
const chansInG = [...new Set(rows.map(x=>x.channel))]
|
||||||
|
.sort((a,b)=> sum(rows.filter(x=>x.channel===b),'impression') - sum(rows.filter(x=>x.channel===a),'impression'));
|
||||||
|
chansInG.forEach((c, idx) => {
|
||||||
|
const cm = metrics(rows.filter(x=>x.channel===c));
|
||||||
|
const groupCell = idx===0
|
||||||
|
? `<td rowspan="${chansInG.length}" style="font-weight:600;vertical-align:top;background:#fafbfc;">${g}</td>`
|
||||||
|
: '';
|
||||||
|
rowsHtml += `<tr>
|
||||||
|
${groupCell}
|
||||||
|
<td><span class="tag" style="background:${(CH_COLORS[c]||'#999')}20;color:${CH_COLORS[c]||'#666'}">${c}</span></td>
|
||||||
|
<td class="num">${fmtNum(cm.imp)}</td><td class="num">${fmtNum(cm.clk)}</td><td class="num">${cm.cv}</td>
|
||||||
|
<td class="num">${fmtMoney(cm.sp)}</td><td class="num">${(cm.ctr*100).toFixed(2)}%</td>
|
||||||
|
<td class="num">${(cm.cvr*100).toFixed(2)}%</td><td class="num">$${cm.ecpm.toFixed(2)}</td>
|
||||||
|
<td class="num">$${cm.cpc.toFixed(3)}</td><td class="num">${cm.cpa?fmtMoney(cm.cpa):'-'}</td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document.querySelector('#channelTable tbody').innerHTML = rowsHtml || `<tr><td colspan="11" class="empty">暂无数据</td></tr>`;
|
||||||
|
|
||||||
// ---- 模块5:产品表现排行(按花费降序)----
|
// ---- 模块5:产品表现排行(按花费降序)----
|
||||||
const prods = [...new Set(data.map(d=>d.product))];
|
const prods = [...new Set(data.map(d=>d.product))];
|
||||||
|
|||||||
Reference in New Issue
Block a user