diff --git a/src/app/(main)/cps/groups/[id]/page.tsx b/src/app/(main)/cps/groups/[id]/page.tsx index 2f60ddf..7c616e9 100644 --- a/src/app/(main)/cps/groups/[id]/page.tsx +++ b/src/app/(main)/cps/groups/[id]/page.tsx @@ -8,7 +8,7 @@ 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 { api, errMsg } from '@/lib/api'; -import { yuan } from '@/lib/format'; +import { formatUtcTime, yuan } from '@/lib/format'; // @ant-design/plots 依赖 DOM,关掉 SSR 仅在客户端渲染(否则 Next 预渲染报 document undefined)。 const Line = dynamic(() => import('@ant-design/plots').then((m) => m.Line), { ssr: false }); @@ -47,6 +47,16 @@ interface DailyResp { rows: DailyRow[]; } +// 群内微信用户(落地页授权拿到;nickname/headimgurl 仅 userinfo 授权后有) +interface WxUser { + openid: string; + nickname: string | null; + headimgurl: string | null; + first_seen: string; + visit_count: number; + copy_count: number; +} + const RANGES = [ { label: '近 3 天', value: 'd3' }, { label: '近 7 天', value: 'd7' }, @@ -115,6 +125,20 @@ export default function GroupDetailPage() { loadDaily(); }, [loadDaily]); + // 群内微信用户(独立于时间范围,累计画像) + const [wxUsers, setWxUsers] = useState([]); + const loadWxUsers = useCallback(async () => { + try { + const r = await api.get<{ users: WxUser[] }>(`/admin/api/cps/groups/${id}/wx-users`); + setWxUsers(r.data.users || []); + } catch (e) { + message.error(errMsg(e, '用户列表加载失败')); + } + }, [id]); + useEffect(() => { + loadWxUsers(); + }, [loadWxUsers]); + const points = useMemo(() => data?.points ?? [], [data]); // 展平成「长表」给折线图:每个 (时间桶 × 指标) 一行,colorField 按指标分 4 条线。 const chartData = useMemo( @@ -202,6 +226,38 @@ export default function GroupDetailPage() { }, ]; + const wxColumns: ColumnsType = [ + { + title: '用户', + key: 'user', + render: (_, u) => ( + + {u.headimgurl ? ( + + ) : ( + + )} + {u.nickname || '(未授权昵称)'} + + ), + }, + { + title: '领券次数', + dataIndex: 'copy_count', + width: 90, + sorter: (a, b) => a.copy_count - b.copy_count, + render: (v: number) => (v ? {v} : 0), + }, + { title: '点击次数', dataIndex: 'visit_count', width: 90 }, + { title: '首次进入', dataIndex: 'first_seen', width: 160, render: (v: string) => formatUtcTime(v) }, + ]; + return (
@@ -255,6 +311,21 @@ export default function GroupDetailPage() { /> )} + + +
+ 在微信里打开本群落地页并授权的用户。「领券次数」= 点了几次「复制口令」;昵称/头像需用户点领券时授权补充。 +
+ + rowKey="openid" + size="small" + columns={wxColumns} + dataSource={wxUsers} + pagination={false} + scroll={{ x: 560 }} + locale={{ emptyText: '暂无授权用户' }} + /> +
); }