refactor: separate TaurusX performance page

This commit is contained in:
王源
2026-10-15 17:37:42 +08:00
parent 6e8238e5cf
commit 01b4e3ddae
5 changed files with 2235 additions and 4123 deletions
+1 -161
View File
@@ -157,7 +157,7 @@
<div class="eyebrow">AgentStage · Order Coverage</div>
<h1>TaurusX订单</h1>
<p>以 <strong>TaurusxDSP</strong> 为目标平台,按「Pkg + 投放国家」聚合各平台订单,快速定位其他平台已出现、但尚未搬入 TaurusxDSP 的订单组合。</p>
<div class="hero-meta"><span id="sourceLabel">数据源加载中</span><span id="updatedLabel">更新时间加载中</span></div>
<div class="hero-meta"><span id="sourceLabel">数据源加载中</span><span id="updatedLabel">更新时间加载中</span><a href="taurusx.html" style="color:#e8fff6;font-weight:700;text-decoration:none;border-bottom:1px solid rgba(232,255,246,.55);">TaurusX 投放数据 →</a></div>
</header>
<main class="page" id="app">
@@ -697,165 +697,6 @@
document.body.append(link); link.click(); link.remove(); URL.revokeObjectURL(url);
}
const txState = { filtered: [], page: 1 };
function renderTaurusxPerf() {
const data = state.data.taurusx_perf;
if (!data || !Array.isArray(data.records)) return;
const panel = el('section', 'panel workspace');
panel.id = 'taurusxPerfPanel';
const header = el('div', 'workspace-head');
const head = el('div', 'panel-head');
const title = el('div');
title.append(
el('h2', '', 'TaurusX 近 7 日投放表现'),
el('div', 'panel-desc', `${data.meta.start_date} 至 ${data.meta.end_date} · UTC${data.meta.timezone} · 拉取于 ${formatTime(data.meta.fetched_at)}`)
);
head.append(title);
const summary = el('section', 'kpis');
summary.append(
kpi('Impressions', nf.format(data.meta.impressions), `${nf.format(data.meta.campaigns)} 个 Campaign`),
kpi('Clicks', nf.format(data.meta.clicks), `${nf.format(data.meta.pkg_country_count)} 个 Pkg_国家`),
kpi('Conversions', nf.format(data.meta.conversions), `${nf.format(data.meta.matched_order_rows)} 条已匹配订单`),
kpi('Cost', money(data.meta.cost), `${nf.format(data.meta.unmatched_order_rows)} 条订单未登记`)
);
summary.style.marginBottom = '16px';
const filters = el('div', 'filters');
filters.style.gridTemplateColumns = 'minmax(210px, 1.2fr) repeat(4, minmax(125px, .55fr)) auto';
const search = el('input', 'control');
search.id = 'txSearch'; search.type = 'search'; search.placeholder = '搜索 Campaign、Pkg、Adping ID'; search.autocomplete = 'off';
const reportDate = selectControl('txDate', '全部日期');
[...new Set(data.records.map(row => row.date))].sort().reverse().forEach(value => reportDate.append(new Option(value, value)));
const country = selectControl('txCountry', '全部国家');
[...new Set(data.records.map(row => row.country))].sort().forEach(value => country.append(new Option(value, value)));
const orderMatch = selectControl('txOrderMatch', '');
[['all','全部订单匹配'], ['matched','订单已登记'], ['unmatched','API有数据·订单未登记']]
.forEach(([value,label]) => orderMatch.append(new Option(label, value)));
const sort = selectControl('txSort', '');
[
['date-desc','日期:新到旧'], ['cost-desc','Cost:高到低'],
['impressions-desc','Impressions:高到低'], ['clicks-desc','Clicks:高到低'],
['conversions-desc','Conversions:高到低'], ['pkg-asc','Pkg:A-Z']
].forEach(([value,label]) => sort.append(new Option(`排序 · ${label}`, value)));
const exportButton = el('button', 'button', '导出 TaurusX 数据');
exportButton.type = 'button';
filters.append(search, reportDate, country, orderMatch, sort, exportButton);
header.append(head, summary, filters);
panel.append(header, el('div', 'result-meta'), el('div', 'table-wrap'), el('div', 'pagination'));
document.getElementById('app').insertBefore(panel, document.querySelector('footer'));
search.addEventListener('input', () => { txState.page = 1; applyTxFilters(); });
[reportDate, country, orderMatch, sort].forEach(control => control.addEventListener('change', () => { txState.page = 1; applyTxFilters(); }));
exportButton.addEventListener('click', exportTxCsv);
applyTxFilters();
}
function applyTxFilters() {
const query = document.getElementById('txSearch').value.trim().toLocaleLowerCase();
const reportDate = document.getElementById('txDate').value;
const country = document.getElementById('txCountry').value;
const orderMatch = document.getElementById('txOrderMatch').value;
const sort = document.getElementById('txSort').value;
txState.filtered = state.data.taurusx_perf.records.filter(row => {
const searchable = `${row.campaign_id} ${row.campaign_name} ${row.pkg} ${row.adpingids.join(' ')}`.toLocaleLowerCase();
return (!query || searchable.includes(query))
&& (reportDate === 'all' || row.date === reportDate)
&& (country === 'all' || row.country === country)
&& (orderMatch === 'all' || (orderMatch === 'matched' ? row.order_match : !row.order_match));
});
const [field, direction] = sort.split('-');
const multiplier = direction === 'asc' ? 1 : -1;
txState.filtered.sort((left, right) => {
const a = left[field]; const b = right[field];
if (typeof a === 'string' || typeof b === 'string') {
return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' }) * multiplier
|| left.campaign_id.localeCompare(right.campaign_id, undefined, { numeric: true });
}
return (Number(a) - Number(b)) * multiplier
|| left.campaign_id.localeCompare(right.campaign_id, undefined, { numeric: true });
});
renderTxResults();
}
function renderTxResults() {
const panel = document.getElementById('taurusxPerfPanel');
const totalPages = Math.max(1, Math.ceil(txState.filtered.length / PAGE_SIZE));
txState.page = Math.min(txState.page, totalPages);
const start = (txState.page - 1) * PAGE_SIZE;
const rows = txState.filtered.slice(start, start + PAGE_SIZE);
const impressions = txState.filtered.reduce((sum, row) => sum + row.impressions, 0);
const clicks = txState.filtered.reduce((sum, row) => sum + row.clicks, 0);
const conversions = txState.filtered.reduce((sum, row) => sum + row.conversions, 0);
const cost = txState.filtered.reduce((sum, row) => sum + row.cost, 0);
const cards = panel.querySelectorAll('.kpi');
const setCard = (index, value, sub) => {
cards[index].querySelector('.value').textContent = value;
cards[index].querySelector('.sub').textContent = sub;
};
setCard(0, nf.format(impressions), `${nf.format(txState.filtered.length)} 条筛选明细`);
setCard(1, nf.format(clicks), `CTR ${impressions ? percent(clicks / impressions) : '-'}`);
setCard(2, nf.format(conversions), `CVR ${clicks ? percent(conversions / clicks) : '-'}`);
setCard(3, money(cost), `${nf.format(txState.filtered.filter(row => !row.order_match).length)} 条订单未登记`);
const meta = panel.querySelector('.result-meta');
meta.textContent = '';
meta.append(el('span', '', `筛选结果 ${nf.format(txState.filtered.length)} 条`), el('span', '', `第 ${txState.page} / ${totalPages} 页`));
const wrap = panel.querySelector('.table-wrap');
wrap.textContent = '';
const table = el('table');
const thead = el('thead'); const header = el('tr');
['日期', 'Campaign ID', 'Campaign', 'Pkg', '国家', 'Adpingid', '订单状态', 'Impressions', 'Clicks', 'Conversions', 'Cost', '订单关联']
.forEach(label => header.append(el('th', '', label)));
thead.append(header); table.append(thead);
const tbody = el('tbody');
if (!rows.length) {
const cell = el('td', 'empty', '没有符合当前条件的 TaurusX 记录'); cell.colSpan = 12;
const row = el('tr'); row.append(cell); tbody.append(row);
}
rows.forEach(record => {
const row = el('tr');
const country = el('td'); country.append(el('span', 'country-pill', record.country || '-'));
const status = el('td');
const statusPills = el('div', 'platforms');
(record.order_statuses.length ? record.order_statuses : ['未登记']).forEach(value => statusPills.append(el('span', 'platform-pill', value)));
status.append(statusPills);
const match = el('td');
match.append(el('span', `badge ${record.order_match ? 'covered' : 'target-only'}`, record.order_match ? '订单已登记' : 'API有数据·订单未登记'));
row.append(
el('td', '', record.date), el('td', 'pkg', record.campaign_id), el('td', 'product', record.campaign_name),
el('td', 'pkg', record.pkg || '-'), country, el('td', 'pkg', record.adpingids.join(' / ') || '-'), status,
el('td', '', nf.format(record.impressions)), el('td', '', nf.format(record.clicks)),
el('td', '', nf.format(record.conversions)), el('td', '', money(record.cost)), match
);
tbody.append(row);
});
table.append(tbody); wrap.append(table);
const pager = panel.querySelector('.pagination');
pager.textContent = '';
const prev = el('button', 'page-button', '‹'); prev.type = 'button'; prev.disabled = txState.page <= 1;
const next = el('button', 'page-button', '›'); next.type = 'button'; next.disabled = txState.page >= totalPages;
prev.addEventListener('click', () => { txState.page -= 1; renderTxResults(); panel.scrollIntoView({ behavior: 'smooth' }); });
next.addEventListener('click', () => { txState.page += 1; renderTxResults(); panel.scrollIntoView({ behavior: 'smooth' }); });
pager.append(prev, el('span', 'panel-desc', `${txState.page} / ${totalPages}`), next);
}
function exportTxCsv() {
const headers = ['日期', 'Campaign ID', 'Campaign', 'Pkg', '国家', 'Adpingid', '订单状态', 'Impressions', 'Clicks', 'Conversions', 'Cost', '订单关联'];
const rows = [headers, ...txState.filtered.map(record => [
record.date, record.campaign_id, record.campaign_name, record.pkg, record.country,
record.adpingids.join(' / '), record.order_statuses.join(' / '), record.impressions,
record.clicks, record.conversions, record.cost, record.order_match ? '订单已登记' : 'API有数据·订单未登记'
])];
const csv = '\ufeff' + rows.map(row => row.map(value => `"${String(value).replaceAll('"', '""')}"`).join(',')).join('\n');
const url = URL.createObjectURL(new Blob([csv], { type: 'text/csv;charset=utf-8' }));
const link = document.createElement('a');
link.href = url; link.download = `TaurusX近7日_${new Date().toISOString().slice(0,10)}.csv`;
document.body.append(link); link.click(); link.remove(); URL.revokeObjectURL(url);
}
fetch(`data.json?v=${Date.now()}`, { cache: 'no-store' })
.then(response => {
if (!response.ok) throw new Error(`HTTP ${response.status}`);
@@ -866,7 +707,6 @@
state.data = data;
renderShell();
renderMtg1013();
renderTaurusxPerf();
})
.catch(error => {
const app = document.getElementById('app');