// 统计分析页面 - 支持点击跳转 import React, { useState, useEffect } from 'react' import { APP_VERSION } from '../config/version' export default function Stats() { const [stats, setStats] = useState({ totalCount: 0, byCategory: [], byStatus: [], byGrading: [], byPackaging: [], byRarity: [], byNumberCategory: [], byVersion: [], byGradingCompany: [], byGradingScore: [], bySpecialMark: [], byProfitLoss: [], totalCost: 0, totalTarget: 0, expectedProfit: 0, totalRevenue: 0, totalProfit: 0 }) const [loading, setLoading] = useState(true) useEffect(() => { fetchStats() }, []) const fetchStats = async () => { setLoading(true) const token = localStorage.getItem('token') try { const statsRes = await fetch('/api/collections/stats', { headers: { 'Authorization': 'Bearer ' + token } }) if (!statsRes.ok) { throw new Error(`HTTP ${statsRes.status}`) } const data = await statsRes.json() console.log('统计数据:', data) setStats({ totalCount: data.totalCount || 0, byCategory: data.byCategory || [], byStatus: data.byStatus || [], byGrading: data.byGrading || [], byPackaging: data.byPackaging || [], byRarity: data.byRarity || [], byNumberCategory: data.byNumberCategory || [], byVersion: data.byVersion || [], byGradingCompany: data.byGradingCompany || [], byGradingScore: data.byGradingScore || [], bySpecialMark: data.bySpecialMark || [], byProfitLoss: data.byProfitLoss || [], totalCost: data.totalCost || 0, totalTarget: data.totalTarget || 0, expectedProfit: data.expectedProfit || 0, totalRevenue: data.totalRevenue || 0, totalProfit: data.totalProfit || 0 }) } catch (e) { console.error('统计加载失败:', e) alert('加载失败:' + e.message) } finally { setLoading(false) } } // 点击统计项跳转到列表页 const handleItemClick = (type, value) => { const filterKey = getFilterKey(type) // 跳转到列表页并带上筛选条件 window.location.hash = `#/list?filter=${filterKey}&value=${encodeURIComponent(value)}` } // 根据统计类型获取对应的筛选字段名 const getFilterKey = (type) => { const map = { status: 'status', category: 'category', packaging: 'packaging', rarity: 'rarity', version: 'version', gradingCompany: 'gradingCompany', gradingScore: 'gradingScore', specialMark: 'specialMark', numberCategory: 'numberCategory', gradingCompany: 'gradingCompany', gradingScore: 'gradingScore', profitLoss: 'profitLoss' } return map[type] || type } // 颜色配置 const colors = { packaging: { '标十': '#22c55e', '标百': '#3b82f6', '单张': '#f59e0b', '裸钞': '#64748b' }, rarity: { '通货': '#64748b', '特色': '#22c55e', '少见': '#3b82f6', '稀有': '#8b5cf6', '珍品': '#ef4444', '孤品': '#fbbf24' }, status: { 'in_collection': '#22c55e', 'selling': '#f59e0b', 'sold': '#ef4444', 'grading': '#8b5cf6', 'repairing': '#f97316', 'transit': '#06b6d4', 'seeking': '#ec4899' }, category: { '自持': '#22c55e', '寄存': '#3b82f6', '寄售': '#f59e0b', '共有': '#8b5cf6', '寻号': '#06b6d4', '生肖钞': '#22c55e', '其他': '#64748b' }, profitLoss: { 'profit': '#22c55e', 'loss': '#ef4444' }, numberCategory: { '无2347': '#8b5cf6', '无347': '#ef4444', '无247': '#fbbf24', '无47': '#3b82f6', '无4': '#06b6d4', '带4': '#22c55e', '其他': '#64748b' }, version: {}, gradingCompany: {}, gradingScore: {}, specialMark: {} } // 标签映射 const labels = { status: { 'in_collection': '收藏中', 'selling': '出售中', 'sold': '已售', 'grading': '送评中', 'repairing': '修复中', 'transit': '在途中', 'seeking': '寻号中' }, profitLoss: { 'profit': '盈利', 'loss': '亏损' } } const colorPalette = ['#22c55e', '#3b82f6', '#f59e0b', '#ef4444', '#8b5cf6', '#06b6d4', '#ec4899', '#f97316', '#14b8a6', '#a855f7'] const getColor = (type, value) => { if (colors[type]?.[value]) return colors[type][value] // 动态生成颜色 const key = String(value) let hash = 0 for (let i = 0; i < key.length; i++) hash = key.charCodeAt(i) + ((hash << 5) - hash) return colorPalette[Math.abs(hash) % colorPalette.length] } const getLabel = (type, value) => { return labels[type]?.[value] || value } const formatMoney = (val) => { if (val === null || val === undefined) return '0' return Number(val).toLocaleString('zh-CN') } const numberCategoryOrder = ['无2347', '无347', '无247', '无47', '无4', '带4', '其他'] const getSortedData = (data, type) => { if (type === 'numberCategory') { return [...data].sort((a, b) => { const order = numberCategoryOrder.indexOf(a.numberCategory) const order2 = numberCategoryOrder.indexOf(b.numberCategory) return order - order2 }) } return data } const DistributionCard = ({ title, data, type, valueKey, labelKey }) => (
{title}
{getSortedData(data, type).map((item, index) => { const value = item[valueKey] const label = getLabel(type, item[labelKey] || value) const color = getColor(type, value) return (
handleItemClick(type, value)} style={{ background: 'rgba(255,255,255,0.05)', borderRadius: '8px', padding: '10px', cursor: 'pointer', border: '1px solid rgba(255,255,255,0.05)', transition: 'all 0.2s' }} onMouseEnter={(e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.1)' e.currentTarget.style.borderColor = 'rgba(251, 191, 36, 0.3)' }} onMouseLeave={(e) => { e.currentTarget.style.background = 'rgba(255,255,255,0.05)' e.currentTarget.style.borderColor = 'rgba(255,255,255,0.05)' }} >
{label}
{item.count}
) })}
{data.length > 6 && (
共{data.length}项,显示前 6 项
)}
) if (loading) { return (
加载中...
) } const gradedCount = stats.byGrading.find(g => g.isGraded === true)?.count || 0 const ungradedCount = stats.byGrading.find(g => g.isGraded === false)?.count || 0 return (
{/* 顶部 */}
统计分析
点击统计项查看明细
v{APP_VERSION}
{/* 财务统计 */}
💰 财务统计
总成本
¥{formatMoney(stats.totalCost)}
总收入
¥{formatMoney(stats.totalRevenue)}
预期利润
+¥{formatMoney(stats.expectedProfit)}
已实现利润
+¥{formatMoney(stats.totalProfit)}
{/* 盈亏统计移到顶部 */} {/* 总统计 */}
📊 总统计
总数量
{stats.totalCount}
已评级
{gradedCount}
未评级
{ungradedCount}
) }