import React, { useState, useEffect } from 'react' import { APP_VERSION } from '../config/version' export default function Home() { const [user, setUser] = useState(null) const [stats, setStats] = useState({ totalCount: 0, totalCost: 0, totalRevenue: 0, expectedProfit: 0, totalProfit: 0, gradedCount: 0 }) const [recentCollections, setRecentCollections] = useState([]) const [yichensStats, setYichensStats] = useState({ total: 0, deals: 0, wants: 0, dragons: 0, horses: 0, tianma: 0 }) const [seekStats, setSeekStats] = useState({ seekCount: 0, matchedCount: 0 }) const [recentPosts, setRecentPosts] = useState([]) const [dragonStats, setDragonStats] = useState({}) const [dealStats, setDealStats] = useState({ total: 0, items: [] }) const [dealVersion, setDealVersion] = useState('龙钞') const [dealDetailItems, setDealDetailItems] = useState(null) const currentPath = window.location.hash.slice(1) || '/' // 成交行情数据处理 const categoryOrder = ['带4号', '带7号', '永恒号', '天马号', '天马王', '金山号', '金山王', '钻石号', '朦胧号', '朦胧王', '金马号', '金马王', '倒置号', '圆圆号'] const normalizeCat = (c) => { if (c === '通货') return '带4号' if (c === '无4') return '带7号' return c } const calcDealAvg = (pkg, cat) => { const items = dealStats.items.filter(item => { const content = item.content || '' const p = content.includes('包装:') ? content.split('包装:')[1].split('\n')[0].trim() : (item.packaging || '') let c = content.includes('分类:') ? content.split('分类:')[1].split('\n')[0].trim() : (item.category || '') c = normalizeCat(c) return p === pkg && c === cat }) if (items.length === 0) return null const sum = items.reduce((a, b) => a + (b.deal_price || 0), 0) return { avg: Math.round(sum / items.length), count: items.length, items } } // 获取今日成交数据 useEffect(() => { // 获取最新成交数据 fetch(`/api/information/list?info_type=deal&page_size=500`) .then(res => res.json()) .then(data => { if (Array.isArray(data)) { setDealStats({ total: data.length, items: data }) } }) .catch(() => {}) }, []) useEffect(() => { const token = localStorage.getItem('token') const userData = localStorage.getItem('user') if (userData) { try { setUser(JSON.parse(userData)) } catch (e) { console.error('Parse user error:', e) } } if (token) { fetch('/api/collections/stats', { headers: { 'Authorization': 'Bearer ' + token } }).then(res => { if (!res.ok) throw new Error('Stats API failed') return res.json() }).then(data => { // stats API直接返回对象,不需要data.data包装 const info = data.totalCount !== undefined ? data : (data.data || data) if (info && info.totalCount !== undefined) { // 从byGrading计算评级数 const gradedCount = info.byGrading ? (info.byGrading.find(x => x.isGraded === true)?.count || 0) : 0 setStats({ totalCount: info.totalCount || 0, totalCost: info.totalCost || 0, totalRevenue: info.totalRevenue || 0, expectedProfit: info.expectedProfit || 0, totalProfit: info.totalProfit || 0, gradedCount: gradedCount }) } }) fetch('/api/collections?limit=5&sort=createdAt&order=desc', { headers: { 'Authorization': 'Bearer ' + token } }).then(res => res.json()).then(data => { // 新格式 {data:[], pagination:{}} 或老格式 {items:[]} const list = data.data || data.items || data if (Array.isArray(list)) { setRecentCollections(list) } }) } }, []) // 获取一尘看板数据 useEffect(() => { fetch('/api/yichens/stats/dragons-today').then(res => res.json()).then(data => { setDragonStats(data || {}) }).catch(() => {}) fetch('/api/yichens/stats/today').then(res => res.json()).then(data => { setYichensStats(data || {}) }).catch(() => {}) // 获取最新一尘帖子 fetch('/api/yichens/stats/dragons-today').then(res => res.json()).then(data => { setDragonStats(data || {}) }).catch(() => {}) // 获取最新一尘帖子 fetch('/api/yichens/posts?limit=20&offset=0').then(res => res.json()).then(data => { setRecentPosts(data.posts || data || []) }).catch(() => {}) // 获取寻配号统计数据 fetch('/api/information/seek/stats').then(res => res.json()).then(data => { setSeekStats(data || {}) }).catch(() => {}) }, []) // 检查是否为管理员 const isAdmin = user && user.role === 'admin' const handleLogout = () => { localStorage.removeItem('token') localStorage.removeItem('user') window.location.hash = '#/login' window.location.reload() } const formatMoney = (val) => { if (!val || val === 0) return '0' const v = val / 10000 return v.toFixed(1) } const statCards = [ { label: '藏品数', value: stats.totalCount, color: '#3b82f6' }, { label: '总成本', value: formatMoney(stats.totalCost) + '万', color: '#22c55e' }, { label: '总收入', value: formatMoney(stats.totalRevenue) + '万', color: '#06b6d4' }, { label: '评级数', value: stats.gradedCount, color: '#8b5cf6' }, { label: '预期利润', value: formatMoney(stats.expectedProfit) + '万', color: stats.expectedProfit >= 0 ? '#f59e0b' : '#ef4444' }, { label: '总利润', value: formatMoney(stats.totalProfit) + '万', color: stats.totalProfit >= 0 ? '#10b981' : '#f43f5e' } ] return (
{/* 顶部用户信息 */}
甲辰收藏
{user?.username || '用户'} ID:{user?.user_code || '-'}
欢迎回来 👋
v{APP_VERSION}
window.location.hash = '#/settings'} style={{ color: 'rgba(255,255,255,0.5)', cursor: 'pointer', fontSize: '12px', padding: '4px 8px', border: '1px solid rgba(255,255,255,0.2)', borderRadius: '4px' }}>设置
退出
{/* 统计卡片网格 */}
{statCards.map((card, idx) => (
{ e.currentTarget.style.transform = 'translateY(-2px)' }} onMouseOut={e => { e.currentTarget.style.transform = 'translateY(0)' }} onClick={() => window.location.hash = '#/stats'} >
{card.value}
{card.label}
))}
{/* 快捷操作 */}
快捷操作
window.location.hash = '#/add?mode=ocr'} style={{ background: 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)', borderRadius: '12px', padding: '12px 16px', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center' }}>
藏品录入
AI识别添加
window.location.hash = '#/add?mode=deal'} style={{ background: 'linear-gradient(135deg, #ec4899 0%, #db2777 100%)', borderRadius: '12px', padding: '12px 16px', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center' }}>
行情录入
录入成交行情
window.location.hash = '#/news?type=seek'} style={{ background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)', borderRadius: '12px', padding: '12px 16px', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center' }}>
发布寻号
发布寻配需求
window.location.hash = '#/add?mode=manual'} style={{ background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)', borderRadius: '12px', padding: '12px 16px', cursor: 'pointer', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', textAlign: 'center' }}>
发布藏品
手动发布
{/* 寻配号数据 */}
🔍 寻配号数据
window.location.hash = '#/news?type=seek'} style={{ background: 'linear-gradient(135deg, rgba(245,158,11,0.15) 0%, rgba(245,158,11,0.05) 100%)', borderRadius: '12px', padding: '16px', border: '1px solid rgba(245,158,11,0.2)', cursor: 'pointer' }}>
{seekStats.seekCount || 0}
寻号需求
{seekStats.userMatchedCount || 0}
我的匹配
{seekStats.totalMatchedCount || 0}
总共匹配
{/* 今日成交数据统计 */} {dealStats.total > 0 && (
📈 最新成交信息统计(均价)
{['龙钞', '马钞', '蛇钞', '其他'].map(v => ( ))}
{['标百', '标十', '单张'].map(p => ( ))} {categoryOrder.map(cat => { const rowData = ['标百', '标十', '单张'].map(pkg => calcDealAvg(pkg, cat)) const hasData = rowData.some(d => d !== null) if (!hasData) return null return ( {rowData.map((d, i) => ( ))} ) })}
{p}
{cat} {d ? (
setDealDetailItems(d.items)}> ¥{d.avg.toLocaleString()} ({d.count})
) : -}
)} {/* 一尘今日数据 */}
📊 一尘今日连体纪念钞数据
{yichensStats.total || 0}
总帖子
{yichensStats.deals || 0}
出售
{yichensStats.wants || 0}
求购
{yichensStats.dragons || 0}
龙钞
{yichensStats.horses || 0}
马钞
{yichensStats.tianma || 0}
天马
{/* 今日龙钞帖子数据统计 */}
🐉 今日龙钞帖子数据统计
{/* 表头 */}
出售
求购
合计
{/* 数据行 */}
带4
{dragonStats.dai4?.deals || 0}
{dragonStats.dai4?.wants || 0}
{(dragonStats.dai4?.deals || 0) + (dragonStats.dai4?.wants || 0)}
带7号
{dragonStats.wu4?.deals || 0}
{dragonStats.wu4?.wants || 0}
{(dragonStats.wu4?.deals || 0) + (dragonStats.wu4?.wants || 0)}
无47
{dragonStats.wu47?.deals || 0}
{dragonStats.wu47?.wants || 0}
{(dragonStats.wu47?.deals || 0) + (dragonStats.wu47?.wants || 0)}
无247
{dragonStats.wu247?.deals || 0}
{dragonStats.wu247?.wants || 0}
{(dragonStats.wu247?.deals || 0) + (dragonStats.wu247?.wants || 0)}
无347
{dragonStats.wu347?.deals || 0}
{dragonStats.wu347?.wants || 0}
{(dragonStats.wu347?.deals || 0) + (dragonStats.wu347?.wants || 0)}
{/* 最新一尘发帖 */}
📝 最新一尘发帖
window.location.hash = '#/news'} style={{ color: '#3b82f6', fontSize: '12px', cursor: 'pointer' }}>查看全部 →
{recentPosts.length === 0 ? (
暂无帖子
) : ( recentPosts.map((item, idx) => (
window.open(item.url, '_blank')} style={{ padding: '12px 16px', borderBottom: idx < recentPosts.length - 1 ? '1px solid rgba(255,255,255,0.05)' : 'none', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
{item.title || '无标题'}
{item.category || '-'} · {item.author_username || '未知'} · {item.post_time?.substring(0, 16) || ''}
{item.post_type === 'deal' ? '出售' : item.post_type === 'want' ? '求购' : '其他'}
)) )}
{/* 底部导航 */}
{(isAdmin ? [ { icon: '🏠', label: '首页', hash: '#/', idx: 0 }, { icon: '📚', label: '藏品', hash: '#/list', idx: 1 }, { icon: '➕', label: '录入', hash: '#/ocr', idx: 2 }, { icon: '📊', label: '统计', hash: '#/stats', idx: 3 }, { icon: '⚙️', label: '管理', hash: '#/admin', idx: 4 } ] : [ { icon: '🏠', label: '首页', hash: '#/', idx: 0 }, { icon: '📚', label: '藏品', hash: '#/list', idx: 1 }, { icon: '➕', label: '录入', hash: '#/ocr', idx: 2 }, { icon: '📊', label: '统计', hash: '#/stats', idx: 3 } ]).map((item) => (
window.location.hash = item.hash} style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', cursor: 'pointer', color: currentPath === item.hash ? '#fbbf24' : '#64748b' }}>
{item.icon}
{item.label}
))}
{/* 成交详情弹窗 */} {dealDetailItems && (
setDealDetailItems(null)}>
e.stopPropagation()}>
成交详情列表
{([...dealDetailItems].sort((a, b) => (a.deal_price || 0) - (b.deal_price || 0))).map((item, idx) => { const content = item.content || '' const grade = content.includes('评级:') ? content.split('评级:')[1].split('\n')[0].trim() : (item.grading_score || '') const sizeMatch = content.match(/大小号:\s*(.+?)(?:\n|$)/) const size = sizeMatch ? sizeMatch[1].trim() : '' const platformMatch = content.match(/平台:\s*(.+?)(?:\n|$)/) const platform = platformMatch ? platformMatch[1].trim() : '-' return (
{(item.title || '').split('-')[0]} {size && | {size}} {grade && | {grade}}
{item.deal_date} | {item.category} | {item.packaging} | {platform}
¥{item.deal_price?.toLocaleString()}
) })}
)}
) }