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 }) => (
+
+ )
+
+ 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 ? (
+
+ ) : (
+ 下一页
+ )}
+
+
+
+ )}
+
+ )
+}