From 1b9fdd2fc048716ed7c2e62c2ff6b238efa67817 Mon Sep 17 00:00:00 2001 From: guke Date: Fri, 26 Jun 2026 14:35:58 +0800 Subject: [PATCH] =?UTF-8?q?feat(cps):=20=E6=AF=8F=E6=97=A5=E6=98=8E?= =?UTF-8?q?=E7=BB=86=E6=8C=89=E5=A4=A9=E4=B8=8B=E9=92=BB=E5=BC=B9=E7=AA=97?= =?UTF-8?q?(=E6=8C=89=E7=94=A8=E6=88=B7=E9=A2=86=E5=88=B8/=E7=82=B9?= =?UTF-8?q?=E5=87=BB=20+=20=E5=88=B8=E6=98=8E=E7=BB=86=20tooltip)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 每日明细表加「明细」列 → 弹窗按用户展示当天领券(copy)/点击(visit) - 点击次数悬停 tooltip 展示该用户当天 visit 过的券(券×次数) - 日期列加宽显示全 YYYY-MM-DD(配合后端 /daily date 改全日期) Co-Authored-By: Claude Opus 4.8 (1M context) --- src/app/(main)/cps/groups/[id]/page.tsx | 130 +++++++++++++++++++++++- 1 file changed, 127 insertions(+), 3 deletions(-) diff --git a/src/app/(main)/cps/groups/[id]/page.tsx b/src/app/(main)/cps/groups/[id]/page.tsx index 7c616e9..4a1b752 100644 --- a/src/app/(main)/cps/groups/[id]/page.tsx +++ b/src/app/(main)/cps/groups/[id]/page.tsx @@ -6,7 +6,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import dynamic from 'next/dynamic'; import type { ColumnsType } from 'antd/es/table'; -import { Button, Card, Col, Empty, Row, Segmented, Space, Spin, Statistic, Table, message } from 'antd'; +import { Button, Card, Col, Empty, Modal, Row, Segmented, Space, Spin, Statistic, Table, Tooltip, message } from 'antd'; import { api, errMsg } from '@/lib/api'; import { formatUtcTime, yuan } from '@/lib/format'; @@ -57,6 +57,20 @@ interface WxUser { copy_count: number; } +// 某天该群按用户领券下钻(/day-users) +interface DayCoupon { + name: string; + count: number; +} +interface DayUser { + openid: string; + nickname: string | null; + headimgurl: string | null; + copy_count: number; + visit_count: number; + coupons: DayCoupon[]; +} + const RANGES = [ { label: '近 3 天', value: 'd3' }, { label: '近 7 天', value: 'd7' }, @@ -139,6 +153,30 @@ export default function GroupDetailPage() { loadWxUsers(); }, [loadWxUsers]); + // 某天领券下钻弹窗 + const [dayDetail, setDayDetail] = useState<{ + open: boolean; + date: string; + loading: boolean; + users: DayUser[]; + }>({ open: false, date: '', loading: false, users: [] }); + + const openDayDetail = useCallback( + async (date: string) => { + setDayDetail({ open: true, date, loading: true, users: [] }); + try { + const r = await api.get<{ users: DayUser[] }>( + `/admin/api/cps/groups/${id}/day-users?date=${date}`, + ); + setDayDetail((s) => ({ ...s, loading: false, users: r.data.users || [] })); + } catch (e) { + message.error(errMsg(e, '明细加载失败')); + setDayDetail((s) => ({ ...s, loading: false })); + } + }, + [id], + ); + const points = useMemo(() => data?.points ?? [], [data]); // 展平成「长表」给折线图:每个 (时间桶 × 指标) 一行,colorField 按指标分 4 条线。 const chartData = useMemo( @@ -171,7 +209,7 @@ export default function GroupDetailPage() { }; const dailyColumns: ColumnsType = [ - { title: '日期', dataIndex: 'date', width: 64, fixed: 'left' }, + { title: '日期', dataIndex: 'date', width: 96, fixed: 'left' }, { title: '点击', dataIndex: 'click_pv', width: 56 }, { title: '独立', dataIndex: 'click_uv', width: 56 }, { title: '复制', dataIndex: 'copy_pv', width: 56, render: (v: number) => v || '-' }, @@ -224,6 +262,17 @@ export default function GroupDetailPage() { width: 84, render: (v: number | null) => (v == null ? '-' : {yuan(v)}), }, + { + title: '明细', + key: 'drill', + width: 64, + fixed: 'right', + render: (_, r) => ( + + ), + }, ]; const wxColumns: ColumnsType = [ @@ -258,6 +307,59 @@ export default function GroupDetailPage() { { title: '首次进入', dataIndex: 'first_seen', width: 160, render: (v: string) => formatUtcTime(v) }, ]; + const dayUserColumns: ColumnsType = [ + { + title: '用户', + key: 'user', + render: (_, u) => ( + + {u.headimgurl ? ( + + ) : ( + + )} + {u.nickname || '(未授权昵称)'} + + ), + }, + { + title: '领券次数', + dataIndex: 'copy_count', + width: 100, + sorter: (a, b) => a.copy_count - b.copy_count, + render: (v: number) => (v ? {v} : 0), + }, + { + title: '点击次数', + dataIndex: 'visit_count', + width: 100, + render: (v: number, u) => + u.coupons.length ? ( + + {u.coupons.map((c) => ( +
+ {c.name} ×{c.count} +
+ ))} + + } + > + {v} +
+ ) : ( + v + ), + }, + ]; + return (
@@ -307,7 +409,7 @@ export default function GroupDetailPage() { columns={dailyColumns} dataSource={daily?.rows ?? []} pagination={false} - scroll={{ x: 900 }} + scroll={{ x: 1000 }} /> )} @@ -326,6 +428,28 @@ export default function GroupDetailPage() { locale={{ emptyText: '暂无授权用户' }} /> + + setDayDetail((s) => ({ ...s, open: false }))} + > +
+ 仅统计微信授权(openid)用户;美团/京东多为匿名跳转,可能为空。悬停「点击次数」看该用户当天点过的券。 +
+ + rowKey="openid" + size="small" + loading={dayDetail.loading} + columns={dayUserColumns} + dataSource={dayDetail.users} + pagination={false} + locale={{ emptyText: '当天无授权用户领券/点击' }} + /> +
); }