feat(cps): 群详情页加「群内微信用户」区块(头像/昵称/领券次数)
微信落地页授权来的用户列表:头像+昵称+领券次数(点复制)+点击次数+首次进入。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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<WxUser[]>([]);
|
||||
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<WxUser> = [
|
||||
{
|
||||
title: '用户',
|
||||
key: 'user',
|
||||
render: (_, u) => (
|
||||
<Space>
|
||||
{u.headimgurl ? (
|
||||
<img
|
||||
src={u.headimgurl}
|
||||
alt=""
|
||||
style={{ width: 32, height: 32, borderRadius: '50%', objectFit: 'cover' }}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
style={{ width: 32, height: 32, borderRadius: '50%', background: '#f0f0f0', display: 'inline-block' }}
|
||||
/>
|
||||
)}
|
||||
<span>{u.nickname || '(未授权昵称)'}</span>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '领券次数',
|
||||
dataIndex: 'copy_count',
|
||||
width: 90,
|
||||
sorter: (a, b) => a.copy_count - b.copy_count,
|
||||
render: (v: number) => (v ? <b style={{ color: '#ff4d4f' }}>{v}</b> : 0),
|
||||
},
|
||||
{ title: '点击次数', dataIndex: 'visit_count', width: 90 },
|
||||
{ title: '首次进入', dataIndex: 'first_seen', width: 160, render: (v: string) => formatUtcTime(v) },
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space style={{ marginBottom: 16 }} align="center" wrap>
|
||||
@@ -255,6 +311,21 @@ export default function GroupDetailPage() {
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
<Card title={`群内微信用户(${wxUsers.length})`} style={{ marginTop: 16 }}>
|
||||
<div style={{ fontSize: 12, color: '#999', marginBottom: 8 }}>
|
||||
在微信里打开本群落地页并授权的用户。「领券次数」= 点了几次「复制口令」;昵称/头像需用户点领券时授权补充。
|
||||
</div>
|
||||
<Table<WxUser>
|
||||
rowKey="openid"
|
||||
size="small"
|
||||
columns={wxColumns}
|
||||
dataSource={wxUsers}
|
||||
pagination={false}
|
||||
scroll={{ x: 560 }}
|
||||
locale={{ emptyText: '暂无授权用户' }}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user