From f3cfd622a79c644a3f4a0021e24ec55f82ab8544 Mon Sep 17 00:00:00 2001 From: linkeyu Date: Wed, 22 Jul 2026 11:46:47 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E9=A2=86=E5=88=B8=E7=82=B9?= =?UTF-8?q?=E4=BD=8D=E5=88=86=E6=95=B0=E6=94=AF=E6=8C=81=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E6=98=8E=E7=BB=86=20(#56)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 变更内容 - 领券记录列表的点位成功率展示为成功数/尝试数,例如 8/8 - 点击分数弹出点位明细 - 逐条展示点位名称、ID、成功、已领、失败或跳过状态 - 失败点位额外展示失败原因 - 无逐券埋点的旧记录显示 - ## 依赖 - 依赖 app-server #153 提供 point_success_count、point_total_count 和 point_details ## 验证 - npx tsc --noEmit - npm run build - 本地实际验证 8/8 全部成功和 7/8 含失败原因两种状态 --------- Co-authored-by: guke Co-authored-by: linkeyu <798648091@qq.com> Reviewed-on: https://gitea.shaguabijia.com/WonderableAI/shaguabijia-admin-web/pulls/56 Co-authored-by: linkeyu Co-committed-by: linkeyu --- src/app/(main)/coupon-data/page.tsx | 116 +++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 11 deletions(-) diff --git a/src/app/(main)/coupon-data/page.tsx b/src/app/(main)/coupon-data/page.tsx index 3f0a696..f7c339c 100644 --- a/src/app/(main)/coupon-data/page.tsx +++ b/src/app/(main)/coupon-data/page.tsx @@ -10,9 +10,11 @@ import { DatePicker, Divider, Input, + Popover, Row, Select, Space, + Spin, Statistic, Table, Tag, @@ -72,9 +74,23 @@ interface CouponDataRow { app_env: string | null; started_at: string; claimed_count: number | null; + point_success_count: number | null; + point_total_count: number | null; + // 兼容旧后端的内嵌明细;新后端不再返回,改为点击后按 trace 加载。 + point_details?: CouponPointDetail[]; trace_url: string | null; ad_revenue_yuan: number | null; // 本次领券看的信息流广告预估收益(元);未填充为 null } +interface CouponPointDetail { + coupon_id: string; + coupon_name: string | null; + status: string; + reason: string | null; +} +interface CouponPointDetailsOut { + trace_id: string; + items: CouponPointDetail[]; +} interface CouponDataReport { date_from: string; date_to: string; @@ -116,6 +132,94 @@ const STATUS_TAG: Record = { abandoned: { color: 'orange', label: '中途退出' }, }; +const POINT_STATUS_TAG: Record = { + success: { color: 'success', label: '成功' }, + already_claimed: { color: 'processing', label: '已领' }, + failed: { color: 'error', label: '失败' }, + skipped: { color: 'default', label: '跳过' }, +}; + +function PointScorePopover({ row }: { row: CouponDataRow }) { + const { message } = App.useApp(); + const [details, setDetails] = useState(row.point_details ?? null); + const [loading, setLoading] = useState(false); + const [loadError, setLoadError] = useState(null); + + if (row.point_success_count == null || row.point_total_count == null || row.point_total_count <= 0) { + return -; + } + const score = `${row.point_success_count}/${row.point_total_count}`; + const loadDetails = async () => { + if (details !== null || loading) return; + setLoading(true); + setLoadError(null); + try { + const response = await api.get('/admin/api/coupon-data/point-details', { + params: { trace_id: row.trace_id }, + }); + setDetails(response.data.items); + } catch (error) { + const errorMessage = errMsg(error); + setLoadError(errorMessage); + message.error(errorMessage); + } finally { + setLoading(false); + } + }; + + return ( + { + if (open) void loadDetails(); + }} + content={( +
+ {loading || (details === null && loadError === null) ? ( + + ) : loadError ? ( + 明细加载失败,请关闭后重试 + ) : details && details.length > 0 ? details.map((point, index) => { + const status = POINT_STATUS_TAG[point.status] ?? { + color: 'default', label: point.status, + }; + return ( +
+
+
+ {point.coupon_name || point.coupon_id} + {point.coupon_name ? ( +
{point.coupon_id}
+ ) : null} +
+ {status.label} +
+ {point.reason ? ( +
+ 原因:{point.reason} +
+ ) : null} +
+ ); + }) : ( + 暂无点位明细 + )} +
+ )} + > + +
+ ); +} + // ms → "1.5s"(空值显示 -) const fmtSec = (ms: number | null | undefined): string => ms == null ? '-' : `${(ms / 1000).toFixed(1)}s`; @@ -495,17 +599,7 @@ export default function CouponDataPage() { title: '点位成功率', key: 'point_success_rate', width: 110, - render: (_: unknown, r: CouponDataRow) => ( - - {r.trace_url ? ( - - 查看明细 - - ) : ( - 待埋点 - )} - - ), + render: (_: unknown, r: CouponDataRow) => , }, { title: '广告收益',