diff --git a/src/app/(main)/cps/groups/[id]/page.tsx b/src/app/(main)/cps/groups/[id]/page.tsx index c2760c5..2f60ddf 100644 --- a/src/app/(main)/cps/groups/[id]/page.tsx +++ b/src/app/(main)/cps/groups/[id]/page.tsx @@ -5,8 +5,10 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useParams, useRouter } from 'next/navigation'; import dynamic from 'next/dynamic'; -import { Button, Card, Col, Empty, Row, Segmented, Space, Spin, Statistic, message } from 'antd'; +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'; // @ant-design/plots 依赖 DOM,关掉 SSR 仅在客户端渲染(否则 Next 预渲染报 document undefined)。 const Line = dynamic(() => import('@ant-design/plots').then((m) => m.Line), { ssr: false }); @@ -25,6 +27,26 @@ interface TsResp { points: TsPoint[]; } +// 按天大表格一行(订单侧字段对淘宝/京东群为 null → 显示 "-") +interface DailyRow { + date: string; + click_pv: number; + click_uv: number; + copy_pv: number; + order_count: number | null; + canceled_count: number | null; + gmv_cents: number | null; + est_commission_cents: number | null; + refund_profit_cents: number | null; + settled_commission_cents: number | null; +} +interface DailyResp { + group_name: string; + is_meituan: boolean; + days: number; + rows: DailyRow[]; +} + const RANGES = [ { label: '近 3 天', value: 'd3' }, { label: '近 7 天', value: 'd7' }, @@ -46,6 +68,11 @@ function rangeToQuery(r: string): string { return `granularity=day&days=${days[r] ?? 7}`; } +// 大表格只按天:天级范围 → 天数;今日小时(h) → null(此时隐藏表格,小时维度看上面折线) +function rangeToDays(r: string): number | null { + return ({ d3: 3, d7: 7, d14: 14, d30: 30 } as Record)[r] ?? null; +} + export default function GroupDetailPage() { const params = useParams(); const router = useRouter(); @@ -72,6 +99,22 @@ export default function GroupDetailPage() { load(); }, [load]); + // 按天大表格(独立于折线;今日小时模式 tableDays=null 时不取、不显示) + const [daily, setDaily] = useState(null); + const tableDays = rangeToDays(range); + const loadDaily = useCallback(async () => { + if (tableDays == null) return; + try { + const r = await api.get(`/admin/api/cps/groups/${id}/daily?days=${tableDays}`); + setDaily(r.data); + } catch (e) { + message.error(errMsg(e, '明细加载失败')); + } + }, [id, tableDays]); + useEffect(() => { + loadDaily(); + }, [loadDaily]); + const points = useMemo(() => data?.points ?? [], [data]); // 展平成「长表」给折线图:每个 (时间桶 × 指标) 一行,colorField 按指标分 4 条线。 const chartData = useMemo( @@ -103,6 +146,62 @@ export default function GroupDetailPage() { interaction: { tooltip: { shared: true } }, }; + const dailyColumns: ColumnsType = [ + { title: '日期', dataIndex: 'date', width: 64, fixed: 'left' }, + { title: '点击', dataIndex: 'click_pv', width: 56 }, + { title: '独立', dataIndex: 'click_uv', width: 56 }, + { title: '复制', dataIndex: 'copy_pv', width: 56, render: (v: number) => v || '-' }, + { + title: '点击→下单', + key: 'rate', + width: 86, + render: (_, r) => + r.order_count == null + ? '-' + : r.click_uv + ? `${((r.order_count / r.click_uv) * 100).toFixed(0)}%` + : '-', + }, + { title: '有效单', dataIndex: 'order_count', width: 64, render: (v: number | null) => (v == null ? '-' : v) }, + { + title: '取消/风控', + dataIndex: 'canceled_count', + width: 78, + render: (v: number | null) => (v == null ? '-' : v ? {v} : 0), + }, + { title: '成交额', dataIndex: 'gmv_cents', width: 84, render: (v: number | null) => (v == null ? '-' : yuan(v)) }, + { + title: '预估佣金', + dataIndex: 'est_commission_cents', + width: 84, + render: (v: number | null) => (v == null ? '-' : yuan(v)), + }, + { + title: '退款佣金', + dataIndex: 'refund_profit_cents', + width: 84, + render: (v: number | null) => + v == null ? '-' : v ? {yuan(v)} : yuan(0), + }, + { + title: '净佣金', + key: 'net', + width: 84, + render: (_, r) => + r.est_commission_cents == null ? ( + '-' + ) : ( + {yuan(r.est_commission_cents - (r.refund_profit_cents || 0))} + ), + }, + { + title: '结算佣金', + dataIndex: 'settled_commission_cents', + width: 84, + render: (v: number | null) => (v == null ? '-' : {yuan(v)}), + }, + ]; + return (
@@ -138,6 +237,24 @@ export default function GroupDetailPage() { )} + + {tableDays != null && ( + + {daily && !daily.is_meituan && ( +
+ 该群非美团渠道,订单/佣金类无对账数据,显示「-」 +
+ )} + + rowKey="date" + size="small" + columns={dailyColumns} + dataSource={daily?.rows ?? []} + pagination={false} + scroll={{ x: 900 }} + /> +
+ )}
); }