feat: channel efficiency table grouped by product_country
This commit is contained in:
+28
-8
@@ -179,14 +179,15 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模块4:渠道效率指标表 -->
|
||||
<!-- 模块4:渠道效率对比(按 产品_国家 分组,展开各渠道)-->
|
||||
<div class="section">
|
||||
<h2>渠道效率对比</h2>
|
||||
<h2>渠道效率对比(产品_国家 × 渠道)</h2>
|
||||
<p class="hint">每个产品_国家下列出其投放过的各渠道及效率指标,同组内可横向对比不同渠道表现。</p>
|
||||
<div class="table-wrapper">
|
||||
<table id="channelTable">
|
||||
<thead>
|
||||
<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>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -446,17 +447,36 @@
|
||||
options:{ responsive:true, maintainAspectRatio:false, plugins:{legend:{position:'right',labels:{boxWidth:12,font:{size:11}}}} }
|
||||
});
|
||||
|
||||
// ---- 模块4:渠道效率表 ----
|
||||
document.querySelector('#channelTable tbody').innerHTML = chans.map(c => {
|
||||
const cm = metrics(data.filter(x=>x.channel===c));
|
||||
return `<tr>
|
||||
// ---- 模块4:渠道效率对比(按 产品_国家 分组,展开各渠道)----
|
||||
// 分组键 = 产品 + 国家
|
||||
const groupMap = {};
|
||||
data.forEach(x => {
|
||||
const g = x.product + '_' + x.location;
|
||||
(groupMap[g] = groupMap[g] || []).push(x);
|
||||
});
|
||||
// 分组按总展示降序;组内渠道也按展示降序
|
||||
const groupKeys = Object.keys(groupMap).sort((a,b)=> sum(groupMap[b],'impression') - sum(groupMap[a],'impression'));
|
||||
let rowsHtml = '';
|
||||
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>`;
|
||||
}).join('') || `<tr><td colspan="10" class="empty">暂无数据</td></tr>`;
|
||||
});
|
||||
});
|
||||
document.querySelector('#channelTable tbody').innerHTML = rowsHtml || `<tr><td colspan="11" class="empty">暂无数据</td></tr>`;
|
||||
|
||||
// ---- 模块5:产品表现排行(按花费降序)----
|
||||
const prods = [...new Set(data.map(d=>d.product))];
|
||||
|
||||
Reference in New Issue
Block a user