import { useQuery } from '@tanstack/react-query' import api from '@/lib/api' import { DataTable } from '@/components/DataTable' import { Pagination } from '@/components/Pagination' import { LoadingSpinner } from '@/components/LoadingSpinner' import { CollapsibleSection } from '@/components/CollapsibleSection' import { MetricCard } from '@/components/MetricCard' import { formatCurrency, formatNumber, formatPercent } from '@/lib/utils' import { useState, useMemo } from 'react' import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts' const PAGE_SIZE = 15 export function PlatformPage() { const [platformPage, setPlatformPage] = useState(1) const [marketingPage, setMarketingPage] = useState(1) const { data: platformData, isLoading: platformLoading } = useQuery({ queryKey: ['platform/economics'], queryFn: () => api.get('/platform/economics'), }) const { data: marketingData, isLoading: marketingLoading } = useQuery({ queryKey: ['marketing/plans'], queryFn: () => api.get('/marketing/plans'), }) const platformRows = (platformData as any)?.data || [] const marketingRows = (marketingData as any)?.data || [] const pageLoading = platformLoading || marketingLoading const storesWithPlatform = platformRows.filter((r: any) => Number(r.meituan_received) > 0 || Number(r.taobao_received) > 0 || Number(r.jd_received) > 0) const totalMeituan = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.meituan_received || 0), 0) const totalTaobao = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.taobao_received || 0), 0) const totalJd = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.jd_received || 0), 0) const totalMeituanCost = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.meituan_discount || 0) + Number(r.meituan_commission || 0), 0) const totalTaobaoCost = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.taobao_discount || 0) + Number(r.taobao_commission || 0), 0) const totalJdCost = storesWithPlatform.reduce((s: number, r: any) => s + Number(r.jd_discount || 0) + Number(r.jd_commission || 0), 0) const totalPlatformReceived = totalMeituan + totalTaobao + totalJd const totalPlatformCost = totalMeituanCost + totalTaobaoCost + totalJdCost const blendedCostRate = totalPlatformReceived > 0 ? (totalPlatformCost / totalPlatformReceived) * 100 : 0 const highCostStores = storesWithPlatform.filter((r: any) => { const rates = [Number(r.meituan_cost_rate_pct || 0), Number(r.taobao_cost_rate_pct || 0), Number(r.jd_cost_rate_pct || 0)].filter((v: number) => v > 0) return rates.some((v: number) => v >= 38) }) const platformChartData = [ { name: '美团', 实收: totalMeituan, 折扣: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.meituan_discount || 0), 0), 佣金: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.meituan_commission || 0), 0) }, { name: '淘宝', 实收: totalTaobao, 折扣: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.taobao_discount || 0), 0), 佣金: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.taobao_commission || 0), 0) }, { name: '京东', 实收: totalJd, 折扣: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.jd_discount || 0), 0), 佣金: storesWithPlatform.reduce((s: number, r: any) => s + Number(r.jd_commission || 0), 0) }, ] const pagedPlatform = useMemo(() => storesWithPlatform.slice((platformPage - 1) * PAGE_SIZE, platformPage * PAGE_SIZE), [storesWithPlatform, platformPage]) const pagedMarketing = useMemo(() => marketingRows.slice((marketingPage - 1) * PAGE_SIZE, marketingPage * PAGE_SIZE), [marketingRows, marketingPage]) if (pageLoading) { return } return (

平台与优惠管理

三平台经济性 · 营销方案ROI · 优惠结构 · 2026年4月

{/* 概览指标 */}
{/* 三平台对比图 */} v >= 10000 ? `${(v / 10000).toFixed(0)}万` : v} tick={{ fontSize: 10 }} /> formatCurrency(v)} /> {/* 门店平台经济性 */}
formatCurrency(r.meituan_received) }, { key: 'meituan_cost_rate_pct', label: '美团成本率', align: 'right', render: (r) => { const v = Number(r.meituan_cost_rate_pct || 0) return v > 0 ? = 38 ? 'font-medium text-red-600' : v >= 35 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(1)}% : - }}, { key: 'taobao_received', label: '淘宝实收', align: 'right', render: (r) => formatCurrency(r.taobao_received) }, { key: 'taobao_cost_rate_pct', label: '淘宝成本率', align: 'right', render: (r) => { const v = Number(r.taobao_cost_rate_pct || 0) return v > 0 ? = 38 ? 'font-medium text-red-600' : v >= 35 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(1)}% : - }}, { key: 'jd_received', label: '京东实收', align: 'right', render: (r) => formatCurrency(r.jd_received) }, { key: 'jd_cost_rate_pct', label: '京东成本率', align: 'right', render: (r) => { const v = Number(r.jd_cost_rate_pct || 0) return v > 0 ? = 38 ? 'font-medium text-red-600' : v >= 35 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(1)}% : - }}, ]} data={pagedPlatform} />
{/* 营销方案ROI */}
formatNumber(r.bill_count) }, { key: 'received', label: '实收', align: 'right', render: (r) => formatCurrency(r.received) }, { key: 'avg_bill_value', label: '客单价', align: 'right', render: (r) => formatCurrency(r.avg_bill_value) }, { key: 'discount_rate_pct', label: '优惠率', align: 'right', render: (r) => { const v = Number(r.discount_rate_pct || 0) return = 25 ? 'font-medium text-red-600' : v >= 15 ? 'text-yellow-600' : 'text-green-600'}>{v.toFixed(1)}% }}, { key: 'theoretical_margin_pct', label: '理论毛利率', align: 'right', render: (r) => formatPercent(r.theoretical_margin_pct) }, ]} data={pagedMarketing} />
) }