diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py
index ea7771b..1b56533 100644
--- a/backend/app/routers/information.py
+++ b/backend/app/routers/information.py
@@ -494,3 +494,29 @@ def get_my_information_list(
))
return result
+
+# ============ 获取当前用户发布的列表 ============
+@router.get("/my")
+def get_my_information(
+ page: int = Query(1, ge=1),
+ limit: int = Query(20, ge=1, le=100),
+ current_user: User = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """获取当前用户发布的信息列表"""
+ total = db.query(Information).filter(Information.author == current_user.f01_01_name).count()
+ infos = db.query(Information).filter(
+ Information.author == current_user.f01_01_name
+ ).order_by(Information.created_at.desc()).offset((page-1)*limit).limit(limit).all()
+
+ return {
+ "total": total,
+ "list": [{
+ "id": i.id,
+ "title": i.title,
+ "content": i.content,
+ "info_type": i.info_type,
+ "author": i.author,
+ "created_at": i.created_at.isoformat() if i.created_at else None
+ } for i in infos]
+ }
diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index 4c37794..ae35683 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -5,6 +5,7 @@ import List from './pages/List'
import Add from './pages/Add'
import Stats from './pages/Stats'
import News from './pages/News'
+import Info from './pages/Info'
import Login from './pages/Login'
import Detail from './pages/Detail'
import Edit from './pages/Edit'
@@ -30,6 +31,7 @@ export default function App() {
const basePath = path.split('?')[0]
if (basePath === '/') return
if (basePath === '/news') return
+ if (basePath === '/info') return
if (basePath === '/stats') return
if (basePath === '/list') return
if (basePath === '/add') return
@@ -50,6 +52,8 @@ export default function App() {
console.error('Parse user error:', e)
}
const isAdmin = user && user.role === 'admin'
+ const isEditor = user && user.role === 'editor'
+ const canAccessInfo = isAdmin || isEditor
// 登录页面独立渲染,不显示底部导航
if (!token) {
@@ -88,6 +92,7 @@ export default function App() {
{ path: '/stats', icon: '📊', label: '统计' },
{ path: '/list', icon: '📚', label: '藏品' },
{ path: '/add', icon: '🎯', label: '添加' },
+ ...(canAccessInfo ? [{ path: '/info', icon: '📝', label: '信息' }] : []),
...(isAdmin ? [{ path: '/admin', icon: '⚙️', label: '管理' }] : [])
].map(tab => (
{
+ const now = new Date()
+ const date = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`
+ const gradeNote = formData.isGraded ? '(评级币)' : '(裸钞)'
+ const title = `「${date} ${formData.edition} ${formData.type} ${formData.category} 行情信息${gradeNote}」`
+ setFormData(prev => ({...prev, title}))
+ }, [formData.edition, formData.type, formData.isGraded, formData.category])
+
+ const API_BASE = localStorage.getItem('API_BASE') || ''
+
+ // 获取我的发布列表
+ useEffect(() => {
+ if (activeTab === 'manage') {
+ fetchMyList()
+ }
+ }, [activeTab])
+
+ const fetchMyList = async () => {
+ setLoading(true)
+ try {
+ const token = localStorage.getItem('token')
+ const res = await fetch(`${API_BASE}/api/information/my`, {
+ headers: { Authorization: `Bearer ${token}` }
+ })
+ const data = await res.json()
+ setMyList(data || [])
+ } catch (e) {
+ console.error(e)
+ }
+ setLoading(false)
+ }
+
+ // 发布信息
+ const handlePublish = async () => {
+ if (!formData.title) {
+ alert('请填写标题')
+ return
+ }
+
+ try {
+ const token = localStorage.getItem('token')
+ const gradeNote = formData.isGraded ? '(评级币)' : '(裸钞)'
+
+ let priceContent = ''
+ if (formData.prices && Object.keys(formData.prices).length > 0) {
+ priceContent = '\n价格:'
+ const priceLabels = {
+ price_4: '带4',
+ price_no4: '无4',
+ price_no47: '无47',
+ price_no347: '无347',
+ price_no1347: '无1347',
+ price_no247: '无247',
+ price_no1247: '无1247',
+ price_no12347: '无12347'
+ }
+ Object.entries(formData.prices).forEach(([key, value]) => {
+ if (value) {
+ priceContent += `${priceLabels[key] || key}:¥${value} `
+ }
+ })
+ }
+
+ const res = await fetch(`${API_BASE}/api/information/publish`, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${token}`,
+ 'Content-Type': 'application/json'
+ },
+ body: JSON.stringify({
+ title: formData.title,
+ content: formData.content + priceContent,
+ info_type: formData.category === '成交' ? 'deal' : 'seek'
+ })
+ })
+
+ const data = await res.json()
+ if (data.id || data.code === 0) {
+ alert('发布成功!')
+ setShowPublish(false)
+ setFormData({
+ edition: '龙钞',
+ type: '标百',
+ isGraded: true,
+ category: '成交',
+ source: '直播',
+ prices: {},
+ title: '',
+ content: ''
+ })
+ fetchMyList()
+ } else {
+ alert(data.message || '发布失败')
+ }
+ } catch (e) {
+ alert('发布失败: ' + e.message)
+ }
+ }
+
+ // Tab切换
+ const tabs = [
+ { key: 'publish', label: '📝 发布行情' },
+ { key: 'manage', label: '📋 发布管理' }
+ ]
+
+ // 按钮选择组件
+ const ButtonGroup = ({ options, value, onChange, label }) => (
+
+ {label &&
}
+
+ {options.map(opt => (
+
+ ))}
+
+
+ )
+
+ // 价格网格
+ const PriceGrid = () => {
+ const priceItems = [
+ { key: 'price_4', label: '带4' },
+ { key: 'price_no4', label: '无4' },
+ { key: 'price_no47', label: '无47' },
+ { key: 'price_no347', label: '无347' },
+ { key: 'price_no1347', label: '无1347' },
+ { key: 'price_no247', label: '无247' },
+ { key: 'price_no1247', label: '无1247' },
+ { key: 'price_no12347', label: '无12347' }
+ ]
+
+ return (
+
+
+
+ {priceItems.map(item => (
+
+
+ setFormData({
+ ...formData,
+ prices: {...formData.prices, [item.key]: e.target.value}
+ })}
+ placeholder="¥"
+ style={{
+ width: '100%',
+ padding: '8px',
+ background: '#0f172a',
+ border: '1px solid #334155',
+ borderRadius: '4px',
+ color: '#fff',
+ boxSizing: 'border-box',
+ fontSize: '13px'
+ }}
+ />
+
+ ))}
+
+
+ )
+ }
+
+ const formatDate = (dateStr) => {
+ if (!dateStr) return '-'
+ const date = new Date(dateStr)
+ return `${date.getMonth() + 1}/${date.getDate()} ${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
+ }
+
+ return (
+
+ {/* Tab导航 */}
+
+ {tabs.map(tab => (
+
setActiveTab(tab.key)}
+ style={{
+ padding: '10px 20px',
+ borderRadius: '8px',
+ background: activeTab === tab.key ? '#3b82f6' : 'transparent',
+ color: '#fff',
+ cursor: 'pointer',
+ whiteSpace: 'nowrap',
+ fontSize: '14px'
+ }}
+ >
+ {tab.label}
+
+ ))}
+
+
+ {/* 发布行情 */}
+ {activeTab === 'publish' && (
+
+
+
+
+
+ {showPublish && (
+
+
📝 发布行情信息
+
+
setFormData({...formData, edition: v})} />
+
+
+
+
+
+
+ {['标百', '标十', '单张'].map(t => (
+
+ ))}
+
+
+
+
+
+
+
+
+
+ setFormData({...formData, category: v})} />
+
+ setFormData({...formData, source: v})} />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ )}
+
+ {/* 发布管理 */}
+ {activeTab === 'manage' && (
+
+
📋 我的发布
+ {loading ? (
+
加载中...
+ ) : myList.length === 0 ? (
+
暂无发布记录
+ ) : (
+
+ {myList.map(item => (
+
+
{item.title}
+
{formatDate(item.created_at)} | {item.info_type === 'deal' ? '💰 成交' : '🔍 求购'}
+
+ ))}
+
+ )}
+
+ )}
+
+ )
+}
diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx
index 4cc319b..47e1299 100644
--- a/frontend/src/pages/News.jsx
+++ b/frontend/src/pages/News.jsx
@@ -1,47 +1,26 @@
import React, { useState, useEffect } from 'react'
-// 资讯页面 - 寻配号、成交数据、发布中心
+// 资讯页面 - 展示寻配号和行情信息(所有人可见)
export default function News() {
- const [activeTab, setActiveTab] = useState('seek') // seek-寻配号, deal-成交, publish-发布
+ const [activeTab, setActiveTab] = useState('seek')
const [infoList, setInfoList] = useState([])
const [loading, setLoading] = useState(false)
- const [showPublish, setShowPublish] = useState(false)
- const [myList, setMyList] = useState([])
-
- // 发布表单
- const [formData, setFormData] = useState({
- info_type: 'seek',
- title: '',
- content: '',
- collection_id: '',
- expect_category: '',
- expect_version: '',
- expect_packaging: '',
- expect_number: '',
- expect_price_min: '',
- expect_price_max: '',
- deal_price: '',
- deal_date: ''
- })
-
- // 我的藏品列表(用于关联)
- const [myCollections, setMyCollections] = useState([])
const API_BASE = localStorage.getItem('API_BASE') || ''
+ // 获取资讯列表
useEffect(() => {
fetchInfoList()
- fetchMyCollections()
- fetchMyList()
}, [activeTab])
- // 获取资讯列表
const fetchInfoList = async () => {
setLoading(true)
try {
const token = localStorage.getItem('token')
- const res = await fetch(`${API_BASE}/api/information/list?info_type=${activeTab}`, {
- headers: { 'Authorization': `Bearer ${token}` }
+ // 根据tab获取不同类型的数据
+ const type = activeTab === 'seek' ? 'seek' : 'deal'
+ const res = await fetch(`${API_BASE}/api/information/list?info_type=${type}`, {
+ headers: { Authorization: `Bearer ${token}` }
})
const data = await res.json()
setInfoList(data || [])
@@ -51,141 +30,34 @@ export default function News() {
setLoading(false)
}
- // 获取我的藏品列表
- const fetchMyCollections = async () => {
- try {
- const token = localStorage.getItem('token')
- const res = await fetch(`${API_BASE}/api/collections?page=1&page_size=100`, {
- headers: { 'Authorization': `Bearer ${token}` }
- })
- const data = await res.json()
- setMyCollections(data.items || [])
- } catch (e) {
- console.error(e)
- }
- }
+ // Tab切换
+ const tabs = [
+ { key: 'seek', label: '🔍 寻配号' },
+ { key: 'deal', label: '💰 成交行情' }
+ ]
- // 获取我的发布列表
- const fetchMyList = async () => {
- try {
- const token = localStorage.getItem('token')
- const res = await fetch(`${API_BASE}/api/information/my/list`, {
- headers: { 'Authorization': `Bearer ${token}` }
- })
- const data = await res.json()
- setMyList(data || [])
- } catch (e) {
- console.error(e)
- }
- }
-
- // 发布资讯
- const handlePublish = async () => {
- if (!formData.title) {
- alert('请输入标题')
- return
- }
- try {
- const token = localStorage.getItem('token')
- const res = await fetch(`${API_BASE}/api/information/`, {
- method: 'POST',
- headers: {
- 'Authorization': `Bearer ${token}`,
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- ...formData,
- expect_price_min: formData.expect_price_min ? parseFloat(formData.expect_price_min) : null,
- expect_price_max: formData.expect_price_max ? parseFloat(formData.expect_price_max) : null,
- deal_price: formData.deal_price ? parseFloat(formData.deal_price) : null,
- })
- })
- if (res.ok) {
- alert('发布成功!')
- setShowPublish(false)
- setFormData({
- info_type: 'seek',
- title: '',
- content: '',
- collection_id: '',
- expect_category: '',
- expect_version: '',
- expect_packaging: '',
- expect_number: '',
- expect_price_min: '',
- expect_price_max: '',
- deal_price: '',
- deal_date: ''
- })
- fetchInfoList()
- fetchMyList()
- }
- } catch (e) {
- alert('发布失败')
- }
- }
-
- // 删除发布
- const handleDelete = async (id) => {
- if (!confirm('确定删除?')) return
- try {
- const token = localStorage.getItem('token')
- await fetch(`${API_BASE}/api/information/${id}`, {
- method: 'DELETE',
- headers: { 'Authorization': `Bearer ${token}` }
- })
- fetchMyList()
- } catch (e) {
- console.error(e)
- }
- }
-
- // 格式化日期
const formatDate = (dateStr) => {
- if (!dateStr) return ''
- return new Date(dateStr).toLocaleDateString('zh-CN')
- }
-
- // 资讯类型映射
- const typeMap = {
- seek: '寻配号',
- deal: '成交数据',
- publish: '发布'
+ if (!dateStr) return '-'
+ const date = new Date(dateStr)
+ return `${date.getMonth() + 1}/${date.getDate()}`
}
return (
-
- {/* 顶部标题 */}
-
-
📰 资讯中心
-
-
- {/* 二级标签切换 */}
-
- {[
- { key: 'seek', label: '🔍 寻配号' },
- { key: 'deal', label: '📈 成交数据' },
- { key: 'publish', label: '➕ 发布' }
- ].map(tab => (
+
+ {/* Tab导航 */}
+
+ {tabs.map(tab => (
setActiveTab(tab.key)}
style={{
- flex: 1,
- textAlign: 'center',
- padding: '10px',
- borderRadius: '6px',
- cursor: 'pointer',
+ padding: '10px 20px',
+ borderRadius: '8px',
background: activeTab === tab.key ? '#3b82f6' : 'transparent',
- color: activeTab === tab.key ? '#fff' : '#94a3b8',
- fontSize: '14px',
- fontWeight: activeTab === tab.key ? 'bold' : 'normal'
+ color: '#fff',
+ cursor: 'pointer',
+ whiteSpace: 'nowrap',
+ fontSize: '14px'
}}
>
{tab.label}
@@ -193,508 +65,38 @@ export default function News() {
))}
- {/* 发布按钮 */}
- {activeTab !== 'publish' && (
-
-
-
- )}
-
- {/* 发布表单 */}
- {showPublish && (
-
-
发布信息
-
- {/* 选择类型 */}
-
-
-
-
-
- {/* 标题 */}
-
-
- setFormData({...formData, title: e.target.value})}
- placeholder="请输入标题"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
- {/* 内容 */}
-
-
-
-
- {/* 关联藏品 */}
- {formData.info_type === 'seek' && (
-
-
-
-
- )}
-
- {/* 寻配号条件 */}
- {formData.info_type === 'seek' && (
- <>
-
-
-
- setFormData({...formData, expect_category: e.target.value})}
- placeholder="如:纪念钞"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
-
- setFormData({...formData, expect_version: e.target.value})}
- placeholder="如:2024版"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
-
-
-
- setFormData({...formData, expect_packaging: e.target.value})}
- placeholder="如:散钞"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
-
- setFormData({...formData, expect_number: e.target.value})}
- placeholder="如:888"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
-
-
-
- setFormData({...formData, expect_price_min: e.target.value})}
- placeholder="0"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
-
- setFormData({...formData, expect_price_max: e.target.value})}
- placeholder="0"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
- >
- )}
-
- {/* 成交数据 */}
- {formData.info_type === 'deal' && (
-
-
-
- setFormData({...formData, deal_price: e.target.value})}
- placeholder="成交金额"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
-
- setFormData({...formData, expect_version: e.target.value})}
- placeholder="如:2024版"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
-
- setFormData({...formData, expect_packaging: e.target.value})}
- placeholder="如:标十"
- style={{
- width: '100%',
- padding: '10px',
- background: '#0f172a',
- border: '1px solid #334155',
- borderRadius: '6px',
- color: '#fff',
- marginTop: '4px',
- boxSizing: 'border-box'
- }}
- />
-
-
- )}
-
-
-
- )}
-
{/* 资讯列表 */}
- {activeTab === 'publish' ? (
- // 我的发布
-
-
我的发布
- {loading ? (
-
加载中...
- ) : myList.length === 0 ? (
-
暂无发布
- ) : (
- myList.map(item => (
-
-
-
- {typeMap[item.info_type]}
-
-
+
+
+ {activeTab === 'seek' ? '🔍 寻配号信息' : '💰 成交行情信息'}
+
+
+ {loading ? (
+
加载中...
+ ) : infoList.length === 0 ? (
+
+ 暂无{activeTab === 'seek' ? '寻配号' : '成交行情'}信息
+
+ ) : (
+
+ {infoList.map(item => (
+
+
+ {item.title}
-
{item.title}
-
{item.content}
-
- {formatDate(item.created_at)} · 浏览 {item.view_count} · 联系 {item.contact_count}
+
+ {formatDate(item.created_at)} | {item.info_source || '系统'}
-
- ))
- )}
-
- ) : (
- // 寻配号 / 成交数据列表
-
- {loading ? (
-
加载中...
- ) : infoList.length === 0 ? (
-
- 暂无{activeTab === 'seek' ? '寻配号' : '成交数据'}信息
-
- ) : (
- infoList.map(item => (
-
- {/* 用户信息 */}
-
-
- {item.user_name ? item.user_name[0] : '?'}
-
-
{item.user_name}
-
- {formatDate(item.created_at)}
-
-
-
- {/* 标题 */}
-
{item.title}
-
- {/* 内容 */}
{item.content && (
-
{item.content}
- )}
-
- {/* 寻配号条件 */}
- {item.info_type === 'seek' && (item.expect_category || item.expect_version || item.expect_number) && (
-
-
期望条件
-
- {item.expect_category && (
- 类别: {item.expect_category}
- )}
- {item.expect_version && (
- 版别: {item.expect_version}
- )}
- {item.expect_packaging && (
- 包装: {item.expect_packaging}
- )}
- {item.expect_number && (
- 号码: {item.expect_number}
- )}
- {(item.expect_price_min || item.expect_price_max) && (
-
- 价格: {item.expect_price_min || '?'} - {item.expect_price_max || '?'}元
-
- )}
-
+
+ {item.content}
)}
-
- {/* 成交数据 */}
- {item.info_type === 'deal' && (
-
-
-
- ¥{item.deal_price || '-'}
-
-
- {item.expect_version} / {item.expect_packaging}
-
-
-
- )}
-
- {/* 关联藏品 */}
- {item.collection_name && (
-
- 关联藏品:
- {item.collection_name}
-
- )}
-
- {/* 统计 */}
-
- 👁 {item.view_count}
- 📞 {item.contact_count}
-
- ))
- )}
-
- )}
+ ))}
+
+ )}
+
)
}