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(() => {
const today = new Date()
const dealDate = `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}`
fetch(`/api/information/list?info_type=deal&deal_date=${dealDate}&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 => (
{p} |
))}
{categoryOrder.map(cat => {
const rowData = ['标百', '标十', '单张'].map(pkg => calcDealAvg(pkg, cat))
const hasData = rowData.some(d => d !== null)
if (!hasData) return null
return (
| {cat} |
{rowData.map((d, i) => (
{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()}
)
})}
)}
)
}