Compare commits

..

1 Commits

Author SHA1 Message Date
linkeyu 234ae9213f fix(admin): 领券广告收益未填充时显示明确文案 (#52)
## 修改内容
- 广告收益字段支持 null
- null 显示未填充
- 真实零收益仍显示 ¥0.0000

## 验证
- TypeScript 类型检查通过
- Next.js 生产构建通过

---------

Co-authored-by: linkeyu <798648091@qq.com>
Reviewed-on: #52
Co-authored-by: linkeyu <linkeyu@wonderable.ai>
Co-committed-by: linkeyu <linkeyu@wonderable.ai>
2026-07-21 11:33:20 +08:00
2 changed files with 11 additions and 72 deletions
+2 -32
View File
@@ -300,8 +300,6 @@ export default function AdRevenueReportPage() {
const { message } = App.useApp();
const [range, setRange] = useState<[Dayjs, Dayjs]>([dayjs().subtract(1, 'day'), dayjs().subtract(1, 'day')]);
const [userId, setUserId] = useState<number | null>(null);
const [appEnv, setAppEnv] = useState<'prod' | 'test' | 'all'>('prod');
const [revenueScope, setRevenueScope] = useState<'business' | 'all'>('business');
const [adType, setAdType] = useState<string | undefined>();
// 「场景」作为后端全局筛选(feed_scene):同时影响明细 / 合计 / 趋势,与「用户 / 类型」一致,点「查询」生效。
const [scene, setScene] = useState<string | undefined>();
@@ -335,8 +333,6 @@ export default function AdRevenueReportPage() {
date_from: from,
date_to: to,
user_id: userId ?? undefined,
app_env: appEnv === 'all' ? undefined : appEnv,
revenue_scope: revenueScope,
ad_type: adType ?? undefined,
feed_scene: scene ?? undefined,
granularity: gran,
@@ -378,7 +374,7 @@ export default function AdRevenueReportPage() {
setLoading(false);
}
},
[range, userId, appEnv, revenueScope, adType, scene, granularity, limit, sortBy],
[range, userId, adType, scene, granularity, limit, sortBy],
);
useEffect(() => {
@@ -624,8 +620,7 @@ export default function AdRevenueReportPage() {
<br />
<b>穿(T+1)</b>穿 GroMore API(,):
<b>穿</b>= revenue<b>API</b>= ADN 穿//,
<b></b>(//);穿
+ ;广
<b></b>(//);,
</div>
}
>
@@ -660,31 +655,6 @@ export default function AdRevenueReportPage() {
style={{ width: 130 }}
/>
</Space>
<Space size={6}>
<Typography.Text type="secondary"></Typography.Text>
<Select
value={appEnv}
onChange={setAppEnv}
style={{ width: 130 }}
options={[
{ value: 'prod', label: '正式(prod)' },
{ value: 'test', label: '测试(dev)' },
{ value: 'all', label: '全部' },
]}
/>
</Space>
<Space size={6}>
<Typography.Text type="secondary"></Typography.Text>
<Select
value={revenueScope}
onChange={setRevenueScope}
style={{ width: 150 }}
options={[
{ value: 'business', label: '业务代码位' },
{ value: 'all', label: '全部代码位' },
]}
/>
</Space>
<Space size={6}>
<Typography.Text type="secondary"></Typography.Text>
<Select
+9 -40
View File
@@ -137,6 +137,11 @@ const slotRateMean = (rows: CouponSlotRow[]): number | null => {
return rates.length ? rates.reduce((a, b) => a + b, 0) / rates.length : null;
};
// 汇总卡成功率口径 tooltip 文案
const POINT_RATE_HINT =
'点位成功率(合计):当前区间内全部券成功率的算术平均(每张券等权);' +
'单券成功率=成功(含已领)÷尝试(不含跳过),源自领券每券记录。';
// 按券表:coupon_id 平台 → 中文
const SLOT_PLATFORM: Record<string, string> = {
'meituan-waimai': '美团',
@@ -592,10 +597,6 @@ export default function CouponDataPage() {
const successDenominator = summary ? Math.max(0, summary.started_count - abandonedCount) : 0;
const couponSuccessRate =
summary && successDenominator > 0 ? summary.completed_count / successDenominator : null;
const validCouponSlots = couponSlots.filter((r) => r.success_rate != null);
const pointSuccessRate = slotRateMean(validCouponSlots);
const pointSucceededTotal = validCouponSlots.reduce((total, r) => total + r.succeeded, 0);
const pointTriedTotal = validCouponSlots.reduce((total, r) => total + r.tried, 0);
const items = data?.items ?? [];
return (
@@ -713,19 +714,8 @@ export default function CouponDataPage() {
<Statistic
title={
<span>
{' '}
<Tooltip
overlayStyle={{ maxWidth: 520 }}
title={
<div>
<div> ÷退</div>
<div style={{ marginTop: 6 }}>
{summary.completed_count} ÷{summary.started_count}{abandonedCount}
{fmtPct(couponSuccessRate)}
</div>
</div>
}
>
{' '}
<Tooltip title="领券完成数 ÷(领券发起数-中途退出数)">
<InfoCircleOutlined style={{ color: '#999' }} />
</Tooltip>
</span>
@@ -738,33 +728,12 @@ export default function CouponDataPage() {
title={
<span>
{' '}
<Tooltip
overlayStyle={{ maxWidth: 560 }}
title={
<div>
<div></div>
<div>÷ </div>
<div style={{ marginTop: 6 }}>
{validCouponSlots.length}
</div>
{validCouponSlots.map((slot) => (
<div key={slot.coupon_id}>
{slot.coupon_name || slot.coupon_id}{slot.succeeded} ÷ {slot.tried}
{fmtPct(slot.success_rate)}
</div>
))}
<div style={{ marginTop: 6 }}>
{fmtPct(pointSuccessRate)}/
{pointSucceededTotal}/{pointTriedTotal}
</div>
</div>
}
>
<Tooltip title={POINT_RATE_HINT}>
<InfoCircleOutlined style={{ color: '#999' }} />
</Tooltip>
</span>
}
value={fmtPct(pointSuccessRate)}
value={fmtPct(slotRateMean(couponSlots))}
/>
</Col>
<Col flex="1 1 0">