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: '当天无授权用户领券/点击' }} + /> +
); }