From b7957e58c04a1fd54395ace1a475c0e4e935badf Mon Sep 17 00:00:00 2001 From: marco Date: Fri, 19 Jun 2026 11:44:14 +0800 Subject: [PATCH] =?UTF-8?q?feat(cps):=20=E7=BE=A4=E8=AF=A6=E6=83=85?= =?UTF-8?q?=E9=A1=B5=E5=8A=A0=E3=80=8C=E7=BE=A4=E5=86=85=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E7=94=A8=E6=88=B7=E3=80=8D=E5=8C=BA=E5=9D=97(=E5=A4=B4?= =?UTF-8?q?=E5=83=8F/=E6=98=B5=E7=A7=B0/=E9=A2=86=E5=88=B8=E6=AC=A1?= =?UTF-8?q?=E6=95=B0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 微信落地页授权来的用户列表:头像+昵称+领券次数(点复制)+点击次数+首次进入。 Co-Authored-By: Claude Opus 4.8 --- src/app/(main)/cps/groups/[id]/page.tsx | 73 ++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) 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: '暂无授权用户' }} + /> +
); }