diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ae35683..3bdcdb0 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -9,6 +9,7 @@ import Info from './pages/Info' import Login from './pages/Login' import Detail from './pages/Detail' import Edit from './pages/Edit' +import YichensBoard from './pages/YichensBoard' import Admin from './pages/Admin' export default function App() { @@ -35,6 +36,7 @@ export default function App() { if (basePath === '/stats') return if (basePath === '/list') return if (basePath === '/add') return + if (basePath === '/yichens') return if (basePath === '/login') return if (basePath === '/settings') return if (basePath === '/admin') return @@ -91,6 +93,7 @@ export default function App() { { path: '/news', icon: '📰', label: '资讯' }, { path: '/stats', icon: '📊', label: '统计' }, { path: '/list', icon: '📚', label: '藏品' }, + { path: '/yichens', icon: '🐉', label: '一尘' }, { path: '/add', icon: '🎯', label: '添加' }, ...(canAccessInfo ? [{ path: '/info', icon: '📝', label: '信息' }] : []), ...(isAdmin ? [{ path: '/admin', icon: '⚙️', label: '管理' }] : []) diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx new file mode 100644 index 0000000..7af8ba1 --- /dev/null +++ b/frontend/src/pages/YichensBoard.jsx @@ -0,0 +1,218 @@ +import React, { useState, useEffect } from 'react' + +export default function YichensBoard() { + const [todayStats, setTodayStats] = useState({ total: 0, deals: 0, wants: 0, others: 0 }) + const [todayCategory, setTodayCategory] = useState([]) + const [posts, setPosts] = useState([]) + const [loading, setLoading] = useState(false) + const [expandedPosts, setExpandedPosts] = useState({}) + const [postTypeFilter, setPostTypeFilter] = useState('all') + const [categoryFilter, setCategoryFilter] = useState('') + const [page, setPage] = useState(1) + const [totalPosts, setTotalPosts] = useState(0) + const [searchKeyword, setSearchKeyword] = useState('') + const API_BASE = localStorage.getItem('API_BASE') || '' + + const today = new Date() + const dateStr = today.getFullYear() + '/' + (today.getMonth() + 1) + '/' + today.getDate() + + useEffect(() => { fetchTodayStats(); fetchPosts(1, '') }, []) + + const fetchTodayStats = async () => { + try { + const res = await fetch(API_BASE + '/api/yichens/stats/today') + setTodayStats(await res.json()) + } catch(e) { console.error(e) } + } + + const fetchTodayCategory = async () => { + try { + const res = await fetch(API_BASE + '/api/yichens/stats/today-category') + setTodayCategory(await res.json()) + } catch(e) { console.error(e) } + } + + const fetchPosts = async (p, cat) => { + setLoading(true) + const currentPage = p !== undefined ? p : page + const currentCat = cat !== undefined ? cat : categoryFilter + + let url = API_BASE + '/api/yichens/posts?limit=390&offset=' + ((currentPage - 1) * 390) + if (postTypeFilter === 'deal') url += '&post_type=deal' + else if (postTypeFilter === 'want') url += '&post_type=want' + else if (postTypeFilter === 'other') url += '&post_type=normal' + try { + const res = await fetch(url) + let data = await res.json() || [] + if (currentCat) { + if (currentCat === '龙') { + data = data.filter(p => p.category && (p.category.includes('龙') || p.category.includes('纪念钞'))) + } else if (currentCat === '蛇') { + data = data.filter(p => p.category && p.category.includes('蛇')) + } else if (currentCat === '马') { + data = data.filter(p => p.category && p.category.includes('马')) + } else if (currentCat === '其他') { + data = data.filter(p => p.category && !p.category.includes('龙') && !p.category.includes('纪念钞') && !p.category.includes('蛇') && !p.category.includes('马')) + } + } + // 搜索关键词过滤 - 强匹配(完全包含) + if (searchKeyword) { + const kw = searchKeyword.trim() + if (kw) { + data = data.filter(p => + (p.title && p.title.includes(kw)) || + (p.content && p.content.includes(kw)) || + (p.category && p.category.includes(kw)) || + (p.contact && p.contact.includes(kw)) + ) + } + } + setPosts(data) + setTotalPosts(todayStats.total || 0) + } catch { setPosts([]) } + setLoading(false) + } + + useEffect(() => { if (todayStats.total > 0) fetchTodayCategory() }, [todayStats]) + + // 搜索关键词变化时重新获取数据 + useEffect(() => { + setPage(1) + fetchPosts(1, '') + }, [searchKeyword]) + + const togglePost = id => setExpandedPosts(p => ({ ...p, [id]: !p[id] })) + + const StatCard = ({ label, value, color, onClick }) => ( +
+
{label}
+
{value}
+
+ ) + + return ( +
+
+
+ 📈 连体钞/纪念钞 {dateStr} +
+
+ { setPostTypeFilter('all'); setCategoryFilter(''); setPage(1); fetchPosts(1, '') }} /> + { setPostTypeFilter('deal'); setCategoryFilter(''); setPage(1); fetchPosts(1, '') }} /> + { setPostTypeFilter('want'); setCategoryFilter(''); setPage(1); fetchPosts(1, '') }} /> + { setPostTypeFilter('other'); setCategoryFilter(''); setPage(1); fetchPosts(1, '') }} /> +
+
+ +
+
📊 今日分类统计
+
+ {todayCategory.map(cat => ( + + {cat.category} ({cat.count}) + + ))} +
+
+ + {/* 搜索框 */} +
+
+ { setSearchKeyword(e.target.value); setPage(1); fetchPosts(1, '') }} + style={{ flex: 1, padding: '10px 14px', borderRadius: 8, border: '1px solid #374151', background: '#1f2937', color: '#fff', fontSize: 13, outline: 'none' }} + /> + +
+ {searchKeyword &&
搜索: "{searchKeyword}",找到 {posts.length} 条结果
} +
+ +
+ {[{key:'all',label:'全部'},{key:'deal',label:'出售'},{key:'want',label:'求购'},{key:'other',label:'其他'}].map(k => ( + + ))} +
+ +
+ {[{key:'',label:'全部'},{key:'龙',label:'龙钞'},{key:'蛇',label:'蛇钞'},{key:'马',label:'马钞'},{key:'其他',label:'其他'}].map(k => ( + + ))} +
+ + {loading ?
加载中...
: ( +
+
+ {posts.map(post => ( +
+
togglePost(post.post_id)} style={{ cursor: 'pointer' }}> +
+ {post.title||'无标题'} +
+ + {post.post_type==='deal'?'出售':post.post_type==='want'?'求购':post.post_type==='normal'?'其他':'普通'} + + + {post.category || '-'} + +
+
+
+ {post.author_username||'未知'} + {post.post_time?.substring(0,16)||''} +
+
+ {expandedPosts[post.post_id] && post.content && ( +
+
+ {post.content} +
+ {post.url && 查看原帖} +
+ )} +
+ ))} +
+ + {/* 分页按钮移到页面底部 */} +
+
+ 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页 +
+
+ {page > 1 ? ( + + ) : ( + 上一页 + )} + {posts.length >= 390 ? ( + + ) : ( + 下一页 + )} +
+
+
+ )} +
+ ) +}