Compare commits

...

2 Commits

Author SHA1 Message Date
guke e3ab28addc feat(admin-web): 领券数据/比价记录看板新增「广告收益」列 (#45)
## 背景
后端两个看板接口新增逐行 `ad_revenue_yuan`(元),前端补上展示列。

## 改动
- `src/lib/types.ts`:`ComparisonRecordListItem` 增 `ad_revenue_yuan: number`
  (`ComparisonRecordDetail` 自动继承)。
- `comparison-records/page.tsx`:「省」列后新增「广告收益」列(>0 显示绿色 ¥x.xxxx,
  否则 `-`);表格 `scroll.x` 1820 → 1920。
- `coupon-data/page.tsx`:`CouponDataRow` 增字段;新增 `fmtYuan` 格式化(¥ 保留 4 位、
  空/≤0 显示 `-`);主表「耗时」列后新增「广告收益」列(不动用户抽屉 RECORD_COLUMNS);
  `scroll.x` 1450 → 1560。

## 说明
- 后端未更新时该字段为 undefined → 显示 `-`,无害。
- 单次广告收益很小(如 ¥0.0034),故 4 位小数。
- 领券看板默认按会话 app_env=`prod` 筛;真机测试数据在 `dev`,需切换环境才可见。

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #45
2026-07-10 22:14:14 +08:00
guke d5acf3d1e1 feat(coupon-data): 点位成功率指标卡改版(品牌色圆点+环比+查看明细下钻) (#44)
feat(coupon-data): 点位成功率指标卡改版(品牌色圆点+环比+查看明细下钻)
- 三平台点位成功率卡:平台名前品牌色小圆点 + 大数字 +「查看明细」按钮
- 仅「查看明细」按钮触发下钻(原整卡整块可点),下钻表改为卡内浅灰分层区域
- 新增环比:并行拉上一等长周期汇总,按百分点(pt)展示涨跌;无上期/失败显示「暂无上期」(对齐数据大盘 pointDelta 口径)

---------

Co-authored-by: guke <guke@autohome.com.cn>
Reviewed-on: #44
2026-07-10 11:19:15 +08:00
3 changed files with 98 additions and 35 deletions
+13 -1
View File
@@ -184,6 +184,18 @@ export default function ComparisonRecordsPage() {
<b style={{ color: r.saved_amount_cents > 0 ? '#3f8600' : '#999' }}>{yuan(r.saved_amount_cents)}</b>
),
},
{
title: '广告收益',
key: 'ad_revenue',
width: 96,
align: 'right',
render: (_, r) =>
r.ad_revenue_yuan > 0 ? (
<span style={{ color: '#3f8600' }}>¥{r.ad_revenue_yuan.toFixed(4)}</span>
) : (
<span style={{ color: '#ccc' }}>-</span>
),
},
{ title: '耗时', dataIndex: 'total_ms', width: 64, render: fmtMs },
{ title: '步', dataIndex: 'step_count', width: 48, render: (v) => v ?? '-' },
{ title: 'LLM', dataIndex: 'llm_call_count', width: 56, render: (v) => v ?? '-' },
@@ -297,7 +309,7 @@ export default function ComparisonRecordsPage() {
showTotal: (t) => `${t}`,
onChange,
}}
scroll={{ x: 1820 }}
scroll={{ x: 1920 }}
onRow={(r) => ({ onClick: () => openDetail(r.id), style: { cursor: 'pointer' } })}
/>
+84 -34
View File
@@ -19,7 +19,7 @@ import {
Tooltip,
Typography,
} from 'antd';
import { InfoCircleOutlined } from '@ant-design/icons';
import { DownOutlined, InfoCircleOutlined } from '@ant-design/icons';
import dayjs, { type Dayjs } from 'dayjs';
import { api, errMsg } from '@/lib/api';
import { formatUtcTime } from '@/lib/format';
@@ -73,6 +73,7 @@ interface CouponDataRow {
started_at: string;
claimed_count: number | null;
trace_url: string | null;
ad_revenue_yuan: number; // 本次领券看的信息流广告预估收益(元)
}
interface CouponDataReport {
date_from: string;
@@ -123,6 +124,10 @@ const fmtSec = (ms: number | null | undefined): string =>
const fmtPct = (v: number | null | undefined): string =>
v == null ? '-' : `${(v * 100).toFixed(1)}%`;
// 元(小数)→ "¥0.0050"(空/≤0 显示 -)。单次广告收益很小,保留 4 位。
const fmtYuan = (v: number | null | undefined): string =>
v == null || v <= 0 ? '-' : `¥${v.toFixed(4)}`;
// 汇总卡成功率口径 tooltip 文案
const FULL_RATE_HINT =
'整单成功率:一次领券勾选的平台全部至少领到一张的场次 ÷ 领券发起数;基数含未完成/失败/中途退出。';
@@ -137,6 +142,13 @@ const SLOT_PLATFORM: Record<string, string> = {
};
const slotPlatformLabel = (p: string | null): string => (p ? SLOT_PLATFORM[p] ?? p : '其他');
// 三平台品牌色(平台名前小圆点用):美团黄 / 淘宝闪购橙 / 京东红。
const SLOT_PLATFORM_COLOR: Record<string, string> = {
'meituan-waimai': '#FFB300',
'taobao-shanguang': '#FF6A00',
'jd-waimai': '#E1251B',
};
// 点手机号弹出的「该用户全部领券」抽屉列(精简版:单用户,不含用户/手机号列)。
const RECORD_COLUMNS: ColumnsType<CouponDataRow> = [
{ title: '时间', dataIndex: 'started_at', width: 160, render: (v: string) => formatUtcTime(v) },
@@ -462,6 +474,13 @@ export default function CouponDataPage() {
align: 'right',
render: (v: number | null) => fmtSec(v),
},
{
title: '广告收益',
dataIndex: 'ad_revenue_yuan',
width: 100,
align: 'right',
render: (v: number) => fmtYuan(v),
},
{
title: '美团耗时',
key: 'mt_elapsed',
@@ -704,51 +723,82 @@ export default function CouponDataPage() {
</Row>
<Divider style={{ marginTop: 16, marginBottom: 16 }} />
<Row gutter={[16, 12]}>
{(
[
['meituan-waimai', '美团点位成功率'],
['taobao-shanguang', '淘宝点位成功率'],
['jd-waimai', '京东点位成功率'],
] as const
).map(([pid, title]) => {
{(['meituan-waimai', 'taobao-shanguang', 'jd-waimai'] as const).map((pid) => {
const active = selectedSlotPlatform === pid;
const rate = summary.per_platform?.[pid];
// 平台名(前置品牌色圆点) | 大数字成功率 | 「查看明细」按钮;仅按钮可展开表格。
return (
<Col flex="1 1 0" key={pid}>
<div
onClick={() => setSelectedSlotPlatform(active ? null : pid)}
title="点击查看该平台各券成功率"
style={{
cursor: 'pointer',
padding: '4px 8px',
borderRadius: 6,
border: active ? '1px solid #1677ff' : '1px solid transparent',
background: active ? '#e6f4ff' : undefined,
border: `1px solid ${active ? '#91caff' : '#f0f0f0'}`,
borderRadius: 8,
background: active ? '#f0f7ff' : '#fff',
transition: 'all .2s',
padding: '10px 14px',
}}
>
<Statistic title={title} value={fmtPct(summary.per_platform?.[pid])} />
{/* 平台名前放小品牌色圆点(取代原左侧竖条),克制地标记平台身份,不打破页面灰度。 */}
<div style={{ fontSize: 13, color: '#666', display: 'flex', alignItems: 'center', gap: 6 }}>
<span
style={{
width: 8,
height: 8,
borderRadius: '50%',
flex: '0 0 auto',
background: SLOT_PLATFORM_COLOR[pid],
}}
/>
{`${SLOT_PLATFORM[pid]}点位成功率`}
</div>
<div style={{ fontSize: 26, fontWeight: 600, lineHeight: 1.1, margin: '4px 0 10px' }}>
{fmtPct(rate)}
</div>
<Button
size="small"
type={active ? 'primary' : 'default'}
onClick={() => setSelectedSlotPlatform(active ? null : pid)}
>
{' '}
<DownOutlined
style={{
fontSize: 10,
transform: active ? 'rotate(180deg)' : 'none',
transition: 'transform .2s',
}}
/>
</Button>
</div>
</Col>
);
})}
</Row>
</Card>
)}
{selectedSlotPlatform && (
<Card
size="small"
title={`${slotPlatformLabel(selectedSlotPlatform)} · 按券成功率`}
extra={<a onClick={() => setSelectedSlotPlatform(null)}></a>}
style={{ marginBottom: 16 }}
>
<Table<CouponSlotRow>
rowKey="coupon_id"
size="small"
pagination={false}
dataSource={couponSlots.filter((r) => r.platform === selectedSlotPlatform)}
columns={slotColumns}
/>
{selectedSlotPlatform && (
// 表格区域:浅灰底 + 内边距,和上方指标卡主体分层区分。
<div
style={{
marginTop: 12,
background: '#fafafa',
border: '1px solid #f0f0f0',
borderRadius: 8,
padding: 12,
}}
>
<div
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}
>
<span style={{ fontWeight: 500 }}>{`${slotPlatformLabel(selectedSlotPlatform)} · 按券成功率`}</span>
<a onClick={() => setSelectedSlotPlatform(null)}></a>
</div>
<Table<CouponSlotRow>
rowKey="coupon_id"
size="small"
pagination={false}
dataSource={couponSlots.filter((r) => r.platform === selectedSlotPlatform)}
columns={slotColumns}
/>
</div>
)}
</Card>
)}
@@ -825,7 +875,7 @@ export default function CouponDataPage() {
onChange: (p) => load(p, queriedLimit),
}}
size="small"
scroll={{ x: 1450 }}
scroll={{ x: 1560 }}
/>
<UserRecordsDrawer<CouponDataRow>
+1
View File
@@ -359,6 +359,7 @@ export interface ComparisonRecordListItem {
rom_name: string | null;
android_version: string | null;
app_version: string | null;
ad_revenue_yuan: number; // 本次比价看的信息流广告预估收益(元)
created_at: string;
}