import { useQuery, useMutation, useQueryClient } 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 { Tabs } from '@/components/Tabs' import { formatNumber, formatPercent } from '@/lib/utils' import { useState } from 'react' import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, PieChart, Pie, Cell } from 'recharts' const STAGE_COLORS: Record = { '爬坡': '#3b82f6', '成熟': '#22c55e', '衰退': '#eab308', '淘汰': '#ef4444' } export function ProductLifecyclePage() { const [tab, setTab] = useState('lifecycle') const [stage, setStage] = useState('') const queryClient = useQueryClient() const { data: productData, isLoading: productLoading } = useQuery({ queryKey: ['product/products', stage], queryFn: () => api.get('/product/products', { params: stage ? { stage } : {} }), }) const { data: reviewOverview, isLoading: reviewLoading } = useQuery({ queryKey: ['product/reviews/overview'], queryFn: () => api.get('/product/reviews/overview'), }) const { data: reviewData, isLoading: reviewListLoading } = useQuery({ queryKey: ['product/reviews'], queryFn: () => api.get('/product/reviews', { params: { page: 1, page_size: 100 } }), }) const trackMutation = useMutation({ mutationFn: (id: number) => api.post(`/product/products/${id}/track`), onSuccess: () => queryClient.invalidateQueries({ queryKey: ['product/products'] }), }) if (tab === 'lifecycle' && productLoading) return if (tab === 'reviews' && reviewLoading) return const products = (productData as any)?.data || [] const reviews = (reviewData as any)?.data || [] const overview = (reviewOverview as any)?.data || {} const stageDistribution = products.reduce((acc: Record, r: any) => { acc[r.lifecycle_stage] = (acc[r.lifecycle_stage] || 0) + 1 return acc }, {}) const stagePieData = Object.entries(stageDistribution).map(([name, value]) => ({ name, value })) const categoryData = [ { name: '口味', value: Number(overview.taste_count || 0) }, { name: '服务', value: Number(overview.service_count || 0) }, { name: '环境', value: Number(overview.env_count || 0) }, { name: '异物', value: Number(overview.foreign_matter_count || 0) }, { name: '分量', value: Number(overview.portion_count || 0) }, ].filter(d => d.value > 0) const totalReviews = Number(overview.total_reviews || 0) const positiveRate = totalReviews > 0 ? (Number(overview.positive || 0) / totalReviews * 100) : 0 const avgRating = Number(overview.avg_rating || 0) return (

产品生命周期与评价归因

SKU生命周期追踪 · 评价NLP归因 · 存活判定

{tab === 'lifecycle' && ( <>
{`${name}: ${value}`}}> {stagePieData.map((entry, i) => )} [formatNumber(v), 'SKU数']} /> (r.launch_date || '').toString().substring(0, 10) }, { key: 'lifecycle_stage', label: '阶段', align: 'center', render: (r) => { const colors: Record = { '爬坡': 'text-blue-600', '成熟': 'text-green-600', '衰退': 'text-yellow-600', '淘汰': 'text-red-600' } return {r.lifecycle_stage} }}, { key: 'survival_status', label: '存活状态', align: 'center' }, { key: 'first_week_report_date', label: '首周战报', render: (r) => r.first_week_report_date ? (r.first_week_report_date as string).substring(0, 10) : '-' }, { key: 'day90_report_date', label: '90天判定', render: (r) => r.day90_report_date ? (r.day90_report_date as string).substring(0, 10) : '-' }, { key: 'action', label: '操作', align: 'center', render: (r) => ( )}, ]} />
)} {tab === 'reviews' && ( <>
= 4 ? 'good' : avgRating >= 3 ? 'warn' : 'bad'} /> = 80 ? 'good' : positiveRate >= 60 ? 'warn' : 'bad'} />
{`${name}: ${value}`}}> {categoryData.map((_, i) => )} [formatNumber(v), '条']} /> ({ date: (r.review_date || '').substring(0, 10), rating: Number(r.rating || 0), }))}>
(r.review_date || '').toString().substring(0, 10) }, { key: 'review_source', label: '来源' }, { key: 'store_name', label: '门店' }, { key: 'sku_name', label: 'SKU' }, { key: 'rating', label: '评分', align: 'center', render: (r) => { const colors: Record = { 1: 'text-red-600', 2: 'text-red-500', 3: 'text-yellow-600', 4: 'text-blue-600', 5: 'text-green-600' } return {r.rating}★ }}, { key: 'nlp_category', label: '归因', align: 'center' }, { key: 'nlp_sentiment', label: '情感', align: 'center', render: (r) => { const colors: Record = { '正面': 'text-green-600', '负面': 'text-red-600', '中性': 'text-yellow-600' } return {r.nlp_sentiment} }}, { key: 'content', label: '评价内容' }, ]} /> )}
) }