From 676245c987b8c04e8972a887b3bc5b0a793974e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=BA=90?= Date: Thu, 15 Oct 2026 17:58:55 +0800 Subject: [PATCH] style: simplify TaurusX performance layout --- taurusx.html | 365 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 228 insertions(+), 137 deletions(-) diff --git a/taurusx.html b/taurusx.html index 435d52e..c4e6d91 100644 --- a/taurusx.html +++ b/taurusx.html @@ -7,17 +7,18 @@ TaurusX 投放数据
- -
AgentStage · Performance
-

TaurusX 投放数据

-

查看 TaurusX 近 7 日 Campaign 投放表现,包含 Impressions、Clicks、Conversions 和 Cost。

-
日期加载中更新时间加载中
+
+
AgentStage · Performance
+ ← 返回首页 +
+
+

TaurusX 投放数据

+

近 7 日 Campaign 投放表现与单位成本效率。

+
日期加载中更新时间加载中
+
@@ -157,41 +211,67 @@ if (firstLabel) select.append(new Option(firstLabel, firstValue)); return select; } - function kpi(label, value, sub) { - const card = el('article', 'card kpi'); - card.append(el('div', 'label', label), el('div', 'value', value), el('div', 'sub', sub)); + function statCard(key, label, value, sub, tone = '') { + const card = el('article', `stat-card ${tone}`.trim()); + card.dataset.metric = key; + card.append(el('div', 'stat-label', label), el('div', 'stat-value', value), el('div', 'stat-sub', sub)); return card; } + function efficiencyItem(key, label, value, formula) { + const item = el('div', 'eff-item'); + item.dataset.metric = key; + const copy = el('div'); + copy.append(el('div', 'eff-label', label), el('div', 'eff-formula', formula)); + item.append(copy, el('div', 'eff-value', value)); + return item; + } + function updateMetric(key, value, sub) { + const node = document.querySelector(`[data-metric="${key}"]`); + if (!node) return; + const valueNode = node.querySelector('.stat-value, .eff-value'); + if (valueNode) valueNode.textContent = value; + if (sub !== undefined) { + const subNode = node.querySelector('.stat-sub'); + if (subNode) subNode.textContent = sub; + } + } function renderShell() { const { meta, records } = state.data; - document.getElementById('rangeLabel').textContent = `日期:${meta.start_date} 至 ${meta.end_date} · UTC${meta.timezone}`; - document.getElementById('updatedLabel').textContent = `拉取于:${formatTime(meta.fetched_at)}`; + document.getElementById('rangeLabel').textContent = `${meta.start_date} — ${meta.end_date} · UTC${meta.timezone}`; + document.getElementById('updatedLabel').textContent = `更新 ${formatTime(meta.fetched_at)}`; const app = document.getElementById('app'); app.textContent = ''; - const summary = el('section', 'kpis'); - summary.append( - kpi('Impressions', nf.format(meta.impressions), `${nf.format(meta.campaigns)} 个 Campaign`), - kpi('Clicks', nf.format(meta.clicks), `${nf.format(meta.pkg_country_count)} 个 Pkg_国家`), - kpi('Conversions', nf.format(meta.conversions), `${nf.format(meta.countries)} 个国家`), - kpi('Cost', money(meta.cost), `${nf.format(meta.rows)} 条投放明细`), - kpi('CPM', unitCost(meta.cpm), 'Cost / Impressions × 1000'), - kpi('CPC', unitCost(meta.cpc), 'Cost / Clicks'), - kpi('CPI', unitCost(meta.cpi), 'Cost / Conversions') + const primary = el('section', 'primary-kpis'); + primary.append( + statCard('impressions', 'Impressions', nf.format(meta.impressions), `${nf.format(meta.campaigns)} 个 Campaign`), + statCard('clicks', 'Clicks', nf.format(meta.clicks), `${nf.format(meta.pkg_country_count)} 个 Pkg_国家`, 'blue'), + statCard('conversions', 'Conversions', nf.format(meta.conversions), `${nf.format(meta.countries)} 个国家`, 'amber'), + statCard('cost', 'Cost', money(meta.cost), `${nf.format(meta.rows)} 条明细`) ); - const panel = el('section', 'panel workspace'); - const header = el('div', 'workspace-head'); + const efficiency = el('section', 'efficiency'); + const intro = el('div', 'efficiency-intro'); + intro.append(el('div', 'efficiency-title', '效率指标'), el('div', 'efficiency-note', 'CPI 以 Conversions 作为安装量口径;分母为 0 时显示 “-”')); + efficiency.append( + intro, + efficiencyItem('cpm', 'CPM', unitCost(meta.cpm), 'Cost / Impressions × 1000'), + efficiencyItem('cpc', 'CPC', unitCost(meta.cpc), 'Cost / Clicks'), + efficiencyItem('cpi', 'CPI', unitCost(meta.cpi), 'Cost / Conversions') + ); + + const panel = el('section', 'panel'); const heading = el('div', 'panel-head'); const title = el('div'); - title.append(el('h2', '', '近 7 日投放明细'), el('div', 'panel-desc', `${nf.format(meta.rows)} 条数据 · 支持筛选、排序与 CSV 导出`)); + title.append(el('h2', '', '投放明细'), el('div', 'panel-desc', '按日期与 Campaign 查看表现,筛选后上方指标同步更新')); heading.append(title); - const filters = el('div', 'filters'); - filters.style.gridTemplateColumns = 'minmax(220px, 1.2fr) repeat(3, minmax(125px, .55fr)) auto'; + const toolbar = el('div', 'toolbar'); + const searchWrap = el('div', 'search'); const search = el('input', 'control'); search.id = 'search'; search.type = 'search'; search.placeholder = '搜索 Campaign ID、Campaign 或 Pkg'; search.autocomplete = 'off'; + searchWrap.append(search); const reportDate = selectControl('dateFilter', '全部日期'); [...new Set(records.map(row => row.date))].sort().reverse().forEach(value => reportDate.append(new Option(value, value))); const country = selectControl('countryFilter', '全部国家'); @@ -203,12 +283,11 @@ ['conversions-desc','Conversions:高到低'], ['cpm-desc','CPM:高到低'], ['cpc-desc','CPC:高到低'], ['cpi-desc','CPI:高到低'], ['pkg-asc','Pkg:A-Z'] ].forEach(([value, label]) => sort.append(new Option(`排序 · ${label}`, value))); - const exportButton = el('button', 'button', '导出 TaurusX 数据'); + const exportButton = el('button', 'button', '导出 CSV'); exportButton.type = 'button'; exportButton.id = 'exportButton'; - filters.append(search, reportDate, country, sort, exportButton); - header.append(heading, filters); - panel.append(header, el('div', 'result-meta'), el('div', 'table-wrap'), el('div', 'pagination')); - app.append(summary, panel, el('footer', '', 'TaurusX 投放数据 · Impressions、Clicks、Conversions、Cost、CPM、CPC、CPI')); + toolbar.append(searchWrap, reportDate, country, sort, exportButton); + panel.append(heading, toolbar, el('div', 'result-meta'), el('div', 'table-wrap'), el('div', 'pagination')); + app.append(primary, efficiency, panel, el('footer', '', 'TaurusX Performance · Impressions · Clicks · Conversions · Cost')); search.addEventListener('input', resetAndFilter); [reportDate, country, sort].forEach(control => control.addEventListener('change', resetAndFilter)); @@ -236,6 +315,8 @@ const multiplier = direction === 'asc' ? 1 : -1; state.filtered.sort((left, right) => { const a = left[field]; const b = right[field]; + if (a === null && b !== null) return 1; + if (a !== null && b === null) return -1; if (typeof a === 'string' || typeof b === 'string') { return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' }) * multiplier || String(left.campaign_id).localeCompare(String(right.campaign_id), undefined, { numeric: true }); @@ -255,22 +336,17 @@ const clicks = state.filtered.reduce((sum, row) => sum + Number(row.clicks || 0), 0); const conversions = state.filtered.reduce((sum, row) => sum + Number(row.conversions || 0), 0); const cost = state.filtered.reduce((sum, row) => sum + Number(row.cost || 0), 0); - const cards = document.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(state.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(new Set(state.filtered.map(row => row.country)).size)} 个国家`); - setCard(4, unitCost(impressions ? cost / impressions * 1000 : null), 'Cost / Impressions × 1000'); - setCard(5, unitCost(clicks ? cost / clicks : null), 'Cost / Clicks'); - setCard(6, unitCost(conversions ? cost / conversions : null), 'Cost / Conversions'); + updateMetric('impressions', nf.format(impressions), `${nf.format(state.filtered.length)} 条筛选明细`); + updateMetric('clicks', nf.format(clicks), `CTR ${impressions ? percent(clicks / impressions) : '-'}`); + updateMetric('conversions', nf.format(conversions), `CVR ${clicks ? percent(conversions / clicks) : '-'}`); + updateMetric('cost', money(cost), `${nf.format(new Set(state.filtered.map(row => row.country)).size)} 个国家`); + updateMetric('cpm', unitCost(impressions ? cost / impressions * 1000 : null)); + updateMetric('cpc', unitCost(clicks ? cost / clicks : null)); + updateMetric('cpi', unitCost(conversions ? cost / conversions : null)); const meta = document.querySelector('.result-meta'); meta.textContent = ''; - meta.append(el('span', '', `筛选结果 ${nf.format(state.filtered.length)} 条`), el('span', '', `第 ${state.page} / ${totalPages} 页`)); + meta.append(el('span', '', `${nf.format(state.filtered.length)} 条结果`), el('span', '', `第 ${state.page} / ${totalPages} 页`)); renderTable(rows); renderPagination(totalPages); } @@ -280,30 +356,45 @@ wrap.textContent = ''; const table = el('table'); const thead = el('thead'); const header = el('tr'); - ['日期', 'Campaign ID', 'Campaign', 'Pkg', '国家', 'Impressions', 'Clicks', 'Conversions', 'Cost', 'CPM', 'CPC', 'CPI'] - .forEach(label => header.append(el('th', '', label))); + const columns = [ + ['日期', ''], ['Campaign / Pkg', ''], ['国家', ''], + ['Impressions', 'num'], ['Clicks', 'num'], ['Conversions', 'num'], ['Cost', 'num'], + ['CPM', 'num'], ['CPC', 'num'], ['CPI', 'num'] + ]; + columns.forEach(([label, className]) => header.append(el('th', className, label))); thead.append(header); table.append(thead); const tbody = el('tbody'); if (!rows.length) { - const cell = el('td', 'empty', '没有符合当前条件的 TaurusX 记录'); cell.colSpan = 12; + const cell = el('td', 'empty', '没有符合当前条件的记录'); cell.colSpan = 10; const row = el('tr'); row.append(cell); tbody.append(row); } rows.forEach(record => { const row = el('tr'); + const campaign = el('td', 'campaign-cell'); + campaign.append( + el('div', 'campaign-name', record.campaign_name || '-'), + (() => { + const meta = el('div', 'campaign-meta'); + meta.append(el('span', 'campaign-id', `#${record.campaign_id || '-'}`), el('span', 'pkg', record.pkg || '-')); + return meta; + })() + ); const country = el('td'); country.append(el('span', 'country-pill', record.country || '-')); + const numeric = (value, formatter = nf.format, extraClass = '') => { + const zero = !value; + return el('td', `num ${extraClass} ${zero ? 'zero' : ''}`.trim(), formatter(value)); + }; row.append( - el('td', '', record.date || '-'), - el('td', 'pkg', record.campaign_id || '-'), - el('td', 'product', record.campaign_name || '-'), - el('td', 'pkg', record.pkg || '-'), + el('td', 'date-cell', record.date || '-'), + campaign, country, - el('td', '', nf.format(record.impressions || 0)), - el('td', '', nf.format(record.clicks || 0)), - el('td', '', nf.format(record.conversions || 0)), - el('td', '', money(record.cost)), - el('td', '', unitCost(record.cpm)), - el('td', '', unitCost(record.cpc)), - el('td', '', unitCost(record.cpi)) + numeric(record.impressions || 0), + numeric(record.clicks || 0), + numeric(record.conversions || 0), + numeric(record.cost || 0, money), + numeric(record.cpm, unitCost, 'unit-col'), + numeric(record.cpc, unitCost, 'unit-col'), + numeric(record.cpi, unitCost, 'unit-col') ); tbody.append(row); }); @@ -313,11 +404,11 @@ function renderPagination(totalPages) { const box = document.querySelector('.pagination'); box.textContent = ''; - const prev = el('button', 'page-button', '‹'); prev.type = 'button'; prev.disabled = state.page <= 1; - const next = el('button', 'page-button', '›'); next.type = 'button'; next.disabled = state.page >= totalPages; - prev.addEventListener('click', () => { state.page -= 1; renderResults(); document.querySelector('.workspace').scrollIntoView({ behavior: 'smooth' }); }); - next.addEventListener('click', () => { state.page += 1; renderResults(); document.querySelector('.workspace').scrollIntoView({ behavior: 'smooth' }); }); - box.append(prev, el('span', 'panel-desc', `${state.page} / ${totalPages}`), next); + const prev = el('button', 'page-button', '‹'); prev.type = 'button'; prev.disabled = state.page <= 1; prev.title = '上一页'; + const next = el('button', 'page-button', '›'); next.type = 'button'; next.disabled = state.page >= totalPages; next.title = '下一页'; + prev.addEventListener('click', () => { state.page -= 1; renderResults(); document.querySelector('.panel').scrollIntoView({ behavior: 'smooth' }); }); + next.addEventListener('click', () => { state.page += 1; renderResults(); document.querySelector('.panel').scrollIntoView({ behavior: 'smooth' }); }); + box.append(prev, el('span', 'page-label', `${state.page} / ${totalPages}`), next); } function exportCsv() {