Compare commits

..

2 Commits

Author SHA1 Message Date
linkeyu 3f5356ad87 feat(admin): 领券完成率和点位成功率展示计算明细 (#53)
## 变更内容
- 将汇总卡“领券成功率”明确为“领券完成率”
- 提示中动态展示完成数、发起数、中途退出数及本期代入计算结果
- 点位成功率提示逐券展示成功数、尝试数、单券成功率和最终等权平均结果
- 展示累计成功/尝试数作为辅助核对,并明确不作为当前加权公式

## 验证
- npx tsc --noEmit
- npm run build

---------

Co-authored-by: linkeyu <798648091@qq.com>
Reviewed-on: #53
Co-authored-by: linkeyu <linkeyu@wonderable.ai>
Co-committed-by: linkeyu <linkeyu@wonderable.ai>
2026-07-21 11:59:50 +08:00
linkeyu c90775d09c fix(admin): 领券广告收益未填充时显示明确文案 (#54)
原 MR #52 已合并到功能分支,无法修改目标分支。本 MR 将同一项“领券广告收益未填充时显示明确文案”修复独立提交到 main,不包含后台看板功能分支的其他提交。

验证:npx tsc --noEmit;npm run build。

Reviewed-on: #54
Co-authored-by: linkeyu <linkeyu@wonderable.ai>
Co-committed-by: linkeyu <linkeyu@wonderable.ai>
2026-07-21 11:59:35 +08:00
+40 -9
View File
@@ -137,11 +137,6 @@ 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': '美团',
@@ -597,6 +592,10 @@ 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 (
@@ -714,8 +713,19 @@ export default function CouponDataPage() {
<Statistic
title={
<span>
{' '}
<Tooltip title="领券完成数 ÷(领券发起数-中途退出数)">
{' '}
<Tooltip
overlayStyle={{ maxWidth: 520 }}
title={
<div>
<div> ÷退</div>
<div style={{ marginTop: 6 }}>
{summary.completed_count} ÷{summary.started_count}{abandonedCount}
{fmtPct(couponSuccessRate)}
</div>
</div>
}
>
<InfoCircleOutlined style={{ color: '#999' }} />
</Tooltip>
</span>
@@ -728,12 +738,33 @@ export default function CouponDataPage() {
title={
<span>
{' '}
<Tooltip title={POINT_RATE_HINT}>
<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>
}
>
<InfoCircleOutlined style={{ color: '#999' }} />
</Tooltip>
</span>
}
value={fmtPct(slotRateMean(couponSlots))}
value={fmtPct(pointSuccessRate)}
/>
</Col>
<Col flex="1 1 0">