import { useQuery } from '@tanstack/react-query' import api from '@/lib/api' import { FilterableTable } from '@/components/FilterableTable' import { LoadingSpinner } from '@/components/LoadingSpinner' import { CollapsibleSection } from '@/components/CollapsibleSection' import { MetricCard } from '@/components/MetricCard' import { formatCurrency, formatNumber, formatPercent } from '@/lib/utils' import { useState } from 'react' import { PieChart, Pie, Cell, Tooltip, Legend, ResponsiveContainer, BarChart, Bar, XAxis, YAxis, CartesianGrid } from 'recharts' import { MonthPicker } from '@/components/MonthPicker' const ABC_COLORS = { 'A-核心': '#22c55e', 'B-成长': '#3b82f6', 'C-长尾': '#ef4444' } export function SKUPage() { const [month, setMonth] = useState('2026-04') const { data: skuData, isLoading: skuLoading } = useQuery({ queryKey: ['sku/abc', month], queryFn: () => api.get('/sku/abc', { params: { month } }), }) const { data: catData, isLoading: catLoading } = useQuery({ queryKey: ['sku/category'], queryFn: () => api.get('/sku/category'), }) const skuRows = ((skuData as any)?.data || []) const catRows = (catData as any)?.data || [] const abcSummary = (skuData as any)?.data?.reduce((acc: any, r: any) => { const k = r.abc_class if (!acc[k]) acc[k] = { sku_count: 0, received: 0, share: 0 } acc[k].sku_count++ acc[k].received += Number(r.received_amount || 0) acc[k].share += Number(r.revenue_share_pct || 0) return acc }, {}) || {} const abcPieData = Object.entries(abcSummary).map(([name, v]: any) => ({ name, value: v.sku_count, received: v.received, share: v.share, })) const totalSku = (skuData as any)?.data?.length || 0 const totalReceived = Object.values(abcSummary).reduce((s: number, v: any) => s + v.received, 0) const cClassCount = abcSummary['C-长尾']?.sku_count || 0 const lowValueSku = ((skuData as any)?.data || []).filter((r: any) => Number(r.received_amount) < 1000).length const singleStoreSku = ((skuData as any)?.data || []).filter((r: any) => Number(r.store_count) <= 1).length const pageLoading = skuLoading || catLoading if (pageLoading) { return } return (

商品SKU管理

SKU ABC分析 · 长尾治理 · 品类结构 · {month}

{/* 概览指标 */}
{/* ABC分布图 */}
`${name}: ${value}个 (${Number(share).toFixed(1)}%)`} > {abcPieData.map((entry) => ( ))} [`${v}个SKU, 实收¥${formatNumber(p.payload.received)}`, p.payload.name]} /> v >= 10000 ? `${(v / 10000).toFixed(0)}万` : v} /> formatCurrency(v)} />
{/* SKU明细表 */} ( {r.abc_class} )}, { key: 'sales_quadrant', label: '象限' }, { key: 'store_count', label: '覆盖门店', align: 'center' }, { key: 'bill_count', label: '账单数', align: 'right', render: (r) => formatNumber(r.bill_count) }, { key: 'received_amount', label: '实收', align: 'right', render: (r) => formatCurrency(r.received_amount) }, { key: 'revenue_share_pct', label: '占比', align: 'right', render: (r) => `${Number(r.revenue_share_pct).toFixed(2)}%` }, { key: 'discount_rate_pct', label: '优惠率', align: 'right', render: (r) => `${Number(r.discount_rate_pct).toFixed(1)}%` }, ]} /> {/* 长尾治理建议 */}

首轮停用候选:约 {lowValueSku} 个SKU

月实收不足¥1,000的SKU,停用前需确认是否有区域特色或套餐组件

单店专属SKU:{singleStoreSku} 个

仅1家门店销售,评估是否为区域特色或可推广

合并/归档候选:约300个套餐/赠品/餐具类

转为技术SKU或合并为统一编码,目标有效SKU池800-900个

) }