Compare commits

..

4 Commits

Author SHA1 Message Date
linkeyu fd9c685723 feat(admin): 点位分数支持查看明细 2026-07-21 15:52:23 +08:00
linkeyu 700c4e3128 feat(admin): 领券明细展示点位分数 2026-07-21 15:44:08 +08:00
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
+69 -15
View File
@@ -10,6 +10,7 @@ import {
DatePicker,
Divider,
Input,
Popover,
Row,
Select,
Space,
@@ -72,8 +73,16 @@ interface CouponDataRow {
app_env: string | null;
started_at: string;
claimed_count: number | null;
point_success_count: number | null;
point_total_count: number | null;
point_details: Array<{
coupon_id: string;
coupon_name: string | null;
status: string;
reason: string | null;
}>;
trace_url: string | null;
ad_revenue_yuan: number; // 本次领券看的信息流广告预估收益(元)
ad_revenue_yuan: number | null; // 本次领券看的信息流广告预估收益(元);未填充为 null
}
interface CouponDataReport {
date_from: string;
@@ -116,6 +125,13 @@ const STATUS_TAG: Record<string, { color: string; label: string }> = {
abandoned: { color: 'orange', label: '中途退出' },
};
const POINT_STATUS_TAG: Record<string, { color: string; label: string }> = {
success: { color: 'success', label: '成功' },
already_claimed: { color: 'processing', label: '已领' },
failed: { color: 'error', label: '失败' },
skipped: { color: 'default', label: '跳过' },
};
// ms → "1.5s"(空值显示 -)
const fmtSec = (ms: number | null | undefined): string =>
ms == null ? '-' : `${(ms / 1000).toFixed(1)}s`;
@@ -124,9 +140,9 @@ const fmtSec = (ms: number | null | undefined): string =>
const fmtPct = (v: number | null | undefined): string =>
v == null ? '-' : `${(v * 100).toFixed(1)}%`;
// 元(小数)→ "¥0.0050";空值-,真实 0 显示 ¥0.0000,以区分“零收益”和“无数据”
// 元(小数)→ "¥0.0050";空值eCPM 根本未填充,真实 0 显示 ¥0.0000。
const fmtYuan = (v: number | null | undefined): string =>
v == null ? '-' : `¥${v.toFixed(4)}`;
v == null ? '未填充' : `¥${v.toFixed(4)}`;
// 一组按券行的 success_rate 算术平均(每张券等权;空值券跳过);无有效券 → null(显示 -)。
// 汇总卡「分平台/合计点位成功率」据此由按券明细上卷,口径与下方按券表一致。
@@ -495,24 +511,62 @@ export default function CouponDataPage() {
title: '点位成功率',
key: 'point_success_rate',
width: 110,
render: (_: unknown, r: CouponDataRow) => (
<Tooltip title="当前接口未返回本场应领点位及逐点结果,前端不使用全局均值冒充本场成功率。">
{r.trace_url ? (
<a href={r.trace_url} target="_blank" rel="noreferrer">
</a>
) : (
<Typography.Text type="secondary"></Typography.Text>
)}
</Tooltip>
),
render: (_: unknown, r: CouponDataRow) => {
if (r.point_success_count == null || r.point_total_count == null || r.point_total_count <= 0) {
return <Typography.Text type="secondary">-</Typography.Text>;
}
const score = `${r.point_success_count}/${r.point_total_count}`;
const details = r.point_details ?? [];
return (
<Popover
trigger="click"
placement="bottomLeft"
title={`点位明细(${score}`}
content={(
<div style={{ width: 380, maxHeight: 360, overflowY: 'auto' }}>
{details.length > 0 ? details.map((point, index) => {
const status = POINT_STATUS_TAG[point.status] ?? {
color: 'default', label: point.status,
};
return (
<div
key={`${point.coupon_id}-${index}`}
style={{
padding: '8px 0',
borderBottom: index < details.length - 1 ? '1px solid #f0f0f0' : undefined,
}}
>
<div style={{ display: 'flex', alignItems: 'flex-start', gap: 12 }}>
<div style={{ flex: 1, minWidth: 0 }}>
<Typography.Text>{point.coupon_name || point.coupon_id}</Typography.Text>
{point.coupon_name ? (
<div><Typography.Text type="secondary">{point.coupon_id}</Typography.Text></div>
) : null}
</div>
<Tag color={status.color} style={{ marginInlineEnd: 0 }}>{status.label}</Tag>
</div>
{point.reason ? (
<div style={{ marginTop: 4 }}>
<Typography.Text type="danger">{point.reason}</Typography.Text>
</div>
) : null}
</div>
);
}) : <Typography.Text type="secondary"></Typography.Text>}
</div>
)}
>
<Button type="link" size="small" style={{ height: 'auto', padding: 0 }}>{score}</Button>
</Popover>
);
},
},
{
title: '广告收益',
dataIndex: 'ad_revenue_yuan',
width: 100,
align: 'right',
render: (v: number) => fmtYuan(v),
render: (v: number | null) => fmtYuan(v),
},
{
title: '美团耗时',