-
📋 基本信息
-
-
setEditingUser({ ...editingUser, user_code: e.target.value })} style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fbbf24', fontSize: '13px' }} />
setEditingUser({ ...editingUser, username: e.target.value })} style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }} />
-
setEditingUser({ ...editingUser, phone: e.target.value })} style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }} />
-
setEditingUser({ ...editingUser, email: e.target.value })} style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }} />
+ {/* 第一部分:基本信息 */}
+
+
📋 基本信息
+
+
+
+ setEditingUser({ ...editingUser, username: e.target.value })}
+ style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
+ />
+
+
+
+
+ setEditingUser({ ...editingUser, email: e.target.value })}
+ style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
+ />
+
+
+
+
+
-
- {/* 会员信息 */}
-
-
⭐ 会员信息
-
-
-
-
setEditingUser({ ...editingUser, points: parseInt(e.target.value) || 0 })} style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }} />
-
setEditingUser({ ...editingUser, balance: parseFloat(e.target.value) || 0 })} style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }} />
+
+ {/* 第二部分:修改密码 */}
+
+
🔐 修改密码(可选)
+
+
+
+ setEditingUser({ ...editingUser, newPassword: e.target.value })}
+ placeholder="请输入新密码(至少 6 位)"
+ style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
+ />
+
+
+
+
+ setEditingUser({ ...editingUser, confirmPassword: e.target.value })}
+ placeholder="请再次输入新密码"
+ style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }}
+ />
-
- {/* 统计信息(只读) */}
-
-
📊 统计数据(仅展示)
-
-
藏品
{editingUser.collectionCount || 0}
-
AI识别
{editingUser.aiCount || 0}
-
寻号
{editingUser.searchCount || 0}
-
登录
{editingUser.loginCount || 0}
-
-
-
- {/* 修改密码 */}
-
-
🔐 修改密码(可选)
-
-
setEditingUser({ ...editingUser, newPassword: e.target.value })} placeholder="留空则不修改" style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }} />
-
setEditingUser({ ...editingUser, confirmPassword: e.target.value })} placeholder="再次输入" style={{ width: '100%', padding: '8px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '6px', color: '#fff', fontSize: '13px' }} />
-
-
-
+
-
-
+
+
)}
+
+ {/* 版本号 - 移到表格下方,避免被底部导航遮挡 */}
+
+ v{APP_VERSION}
+
)
}
diff --git a/frontend/src/pages/AdminV2.jsx b/frontend/src/pages/AdminV2.jsx
new file mode 100644
index 0000000..1e3d355
--- /dev/null
+++ b/frontend/src/pages/AdminV2.jsx
@@ -0,0 +1,272 @@
+import React, { useState, useEffect } from 'react'
+
+// 管理后台V2
+export default function AdminV2() {
+ const [activeTab, setActiveTab] = useState('users')
+ const token = localStorage.getItem('token')
+ const API_BASE = localStorage.getItem('API_BASE') || ''
+
+ const userStr = localStorage.getItem('user')
+ let user = null
+ try { user = userStr ? JSON.parse(userStr) : null } catch (e) {}
+
+ if (!user || user.role !== 'admin') {
+ return (
+
+
+
🚫
+
无权访问
+
仅管理员可以访问管理后台
+
+
+ )
+ }
+
+ return (
+
+
+
⚙️ 管理后台
+
+
+
+ {[
+ { key: 'users', label: '👥 用户管理' },
+ { key: 'stats', label: '📊 统计分析' },
+ { key: 'publish', label: '📢 信息发布' },
+ { key: 'infoManage', label: '📋 信息管理' }
+ ].map(tab => (
+
setActiveTab(tab.key)} style={{ flex: '1 1 45%', textAlign: 'center', padding: '10px 8px', margin: '2px', borderRadius: '6px', cursor: 'pointer', background: activeTab === tab.key ? '#3b82f6' : 'transparent', color: activeTab === tab.key ? '#fff' : '#94a3b8', fontSize: '13px', fontWeight: activeTab === tab.key ? 'bold' : 'normal' }}>
+ {tab.label}
+
+ ))}
+
+
+ {activeTab === 'users' &&
}
+ {activeTab === 'stats' &&
}
+ {activeTab === 'publish' &&
}
+ {activeTab === 'infoManage' &&
}
+
+ )
+}
+
+function UserManagement({ token, API_BASE }) {
+ const [users, setUsers] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [search, setSearch] = useState('')
+ const [editingUser, setEditingUser] = useState(null)
+ const [showAddModal, setShowAddModal] = useState(false)
+ const [newUser, setNewUser] = useState({ username: '', password: '', phone: '', role: 'user' })
+
+ useEffect(() => { fetchUsers() }, [])
+
+ const fetchUsers = async () => {
+ setLoading(true)
+ try {
+ const res = await fetch(`${API_BASE}/api/admin/users?page=1&limit=100`, { headers: { 'Authorization': `Bearer ${token}` } })
+ const data = await res.json()
+ setUsers(Array.isArray(data) ? data : [])
+ } catch (e) { console.error(e) }
+ setLoading(false)
+ }
+
+ const handleAddUser = async () => {
+ if (!newUser.username || !newUser.password) { alert('请填写用户名和密码'); return }
+ try {
+ const res = await fetch(`${API_BASE}/api/admin/users`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(newUser) })
+ if (res.ok) { alert('添加成功'); setShowAddModal(false); setNewUser({ username: '', password: '', phone: '', role: 'user' }); fetchUsers() }
+ } catch (e) { alert('添加失败') }
+ }
+
+ const handleUpdateUser = async () => {
+ try {
+ const res = await fetch(`${API_BASE}/api/admin/users/${editingUser.f99_90_id}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(editingUser) })
+ if (res.ok) { alert('更新成功'); setEditingUser(null); fetchUsers() }
+ } catch (e) { alert('更新失败') }
+ }
+
+ const handleDeleteUser = async (id) => {
+ if (!confirm('确定删除该用户?')) return
+ try { await fetch(`${API_BASE}/api/admin/users/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); fetchUsers() } catch (e) { console.error(e) }
+ }
+
+ const filteredUsers = users.filter(u => u.f01_01_name?.includes(search) || u.phone?.includes(search) || u.user_code?.includes(search))
+
+ return (
+
+
+ setSearch(e.target.value)} style={{ flex: 1, padding: '10px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff' }} />
+
+
+
+ {loading ?
加载中...
: (
+
+ {filteredUsers.map(u => (
+
+
+
+
{u.f01_01_name?.[0] || '?'}
+
{u.f01_01_name}
{u.user_code || '-'}
+
+
{u.role === 'admin' ? '管理员' : '用户'}
+
+
+
📱 {u.phone || '-'}
🏷️ 等级: {u.f99_94_level || '青铜'} | 藏品: {u.f99_97_collection_count || 0}
+
🔑 登录: {u.f99_98_login_count || 0}次 | AI: {u.f99_95_ai_count || 0}次
+
+
+
+
+
+
+ ))}
+
+ )}
+
+ {showAddModal && (
+
+
+
添加用户
+
setNewUser({...newUser, username: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} />
+
setNewUser({...newUser, password: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} />
+
setNewUser({...newUser, phone: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '12px', boxSizing: 'border-box' }} />
+
+
+
+
+
+
+
+ )}
+
+ {editingUser && (
+
+
+
编辑用户
+
setEditingUser({...editingUser, phone: e.target.value})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} />
+
+
setEditingUser({...editingUser, f99_100_points: parseInt(e.target.value)})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '8px', boxSizing: 'border-box' }} />
+
setEditingUser({...editingUser, f01_11_balance: parseFloat(e.target.value)})} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginBottom: '12px', boxSizing: 'border-box' }} />
+
+
+
+
+
+
+ )}
+
+ )
+}
+
+function Statistics({ token, API_BASE }) {
+ const [stats, setStats] = useState(null)
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => { fetchStats() }, [])
+
+ const fetchStats = async () => {
+ setLoading(true)
+ try {
+ const collectionsRes = await fetch(`${API_BASE}/api/collections/stats`, { headers: { 'Authorization': `Bearer ${token}` } })
+ const collectionsData = await collectionsRes.json()
+ const usersRes = await fetch(`${API_BASE}/api/admin/users?page=1&limit=100`, { headers: { 'Authorization': `Bearer ${token}` } })
+ const usersData = await usersRes.json()
+ const totalUsers = Array.isArray(usersData) ? usersData.length : 0
+ const totalCollections = collectionsData.total || 0
+ const levelCount = {}
+ if (Array.isArray(usersData)) { usersData.forEach(u => { const level = u.f99_94_level || '青铜'; levelCount[level] = (levelCount[level] || 0) + 1 }) }
+ const statusCount = { in_collection: collectionsData.byStatus?.find(s => s.status === 'in_collection')?.count || 0, sold: collectionsData.byStatus?.find(s => s.status === 'sold')?.count || 0 }
+ const totalBalance = Array.isArray(usersData) ? usersData.reduce((sum, u) => sum + (u.f01_11_balance || 0), 0) : 0
+ setStats({ users: { total: totalUsers, levels: levelCount }, collections: { total: totalCollections, status: statusCount }, balance: { total: totalBalance } })
+ } catch (e) { console.error(e) }
+ setLoading(false)
+ }
+
+ if (loading) return
加载中...
+
+ return (
+
+
+
{stats?.users?.total || 0}
👥 用户总数
+
{stats?.collections?.total || 0}
📚 藏品总数
+
¥{stats?.balance?.total?.toFixed(2) || '0'}
💰 账户总余额
+
{stats?.collections?.status?.in_collection || 0}
✨ 收藏中
+
+
🏆 会员等级分布
{Object.entries(stats?.users?.levels || {}).map(([level, count]) => (
{level}{count}人
))}
+
📦 藏品状态
{stats?.collections?.status?.in_collection || 0}
收藏中
{stats?.collections?.status?.sold || 0}
已售出
+
+ )
+}
+
+function InfoPublish({ token, API_BASE }) {
+ const [publishType, setPublishType] = useState('system')
+ const [formData, setFormData] = useState({ title: '', content: '', expect_version: '', expect_packaging: '', deal_price: '' })
+ const [submitting, setSubmitting] = useState(false)
+
+ const handlePublish = async () => {
+ if (!formData.title) { alert('请输入标题'); return }
+ setSubmitting(true)
+ try {
+ const data = { title: formData.title, content: formData.content, info_type: publishType === 'system' ? 'publish' : 'deal', deal_price: publishType === 'deal' ? parseFloat(formData.deal_price) : null, expect_version: formData.expect_version, expect_packaging: formData.expect_packaging }
+ const res = await fetch(`${API_BASE}/api/information/`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify(data) })
+ if (res.ok) { alert('发布成功!'); setFormData({ title: '', content: '', expect_version: '', expect_packaging: '', deal_price: '' }) }
+ } catch (e) { alert('发布失败') }
+ setSubmitting(false)
+ }
+
+ return (
+
+
+
+
+
+
+
{publishType === 'system' ? '📢 发布系统通知' : '📈 发布成交数据'}
+
setFormData({...formData, title: e.target.value})} placeholder={publishType === 'system' ? '请输入通知标题' : '如:2024版纪念钞成交'} style={{ width: '100%', padding: '10px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', marginTop: '4px', boxSizing: 'border-box' }} />
+
+ {publishType === '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' }} />
)}
+
+
+
+ )
+}
+
+function InfoManage({ token, API_BASE }) {
+ const [infoList, setInfoList] = useState([])
+ const [loading, setLoading] = useState(true)
+ const [filterType, setFilterType] = useState('all')
+
+ useEffect(() => { fetchInfoList() }, [])
+
+ const fetchInfoList = async () => {
+ setLoading(true)
+ try { const res = await fetch(`${API_BASE}/api/information/list?status=active`, { headers: { 'Authorization': `Bearer ${token}` } }); const data = await res.json(); setInfoList(data || []) } catch (e) { console.error(e) }
+ setLoading(false)
+ }
+
+ const handleDelete = async (id) => { if (!confirm('确定删除该信息?')) return; try { await fetch(`${API_BASE}/api/information/${id}`, { method: 'DELETE', headers: { 'Authorization': `Bearer ${token}` } }); fetchInfoList() } catch (e) { console.error(e) } }
+
+ const handleClose = async (id) => { try { await fetch(`${API_BASE}/api/information/${id}`, { method: 'PUT', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ status: 'closed' }) }); fetchInfoList() } catch (e) { console.error(e) } }
+
+ const filteredList = filterType === 'all' ? infoList : infoList.filter(i => i.info_type === filterType)
+ const typeLabels = { seek: '🔍 寻配号', deal: '📈 成交', publish: '📝 发布' }
+
+ return (
+
+
{['all', 'seek', 'deal', 'publish'].map(type => ())}
+ {loading ?
加载中...
: filteredList.length === 0 ?
暂无信息
: (
+ filteredList.map(item => (
+
+
+ {typeLabels[item.info_type]}
+ {new Date(item.created_at).toLocaleDateString('zh-CN')}
+
+
{item.title}
+ {item.content &&
{item.content}
}
+
👤 {item.user_name} | 👁 {item.view_count} | 📞 {item.contact_count}
+
+
+ ))
+ )}
+
+ )
+}
diff --git a/frontend/src/pages/Info.jsx b/frontend/src/pages/Info.jsx
index a93782d..24e3557 100644
--- a/frontend/src/pages/Info.jsx
+++ b/frontend/src/pages/Info.jsx
@@ -1,6 +1,12 @@
import React, { useState, useEffect } from 'react'
export default function Info() {
+ // 检查登录状态,未登录则跳转到登录页
+ if (!localStorage.getItem('token')) {
+ window.location.hash = '#/login'
+ return null
+ }
+
const [activeTab, setActiveTab] = useState('publish')
const [showPublish, setShowPublish] = useState(false)
const [myList, setMyList] = useState([])
diff --git a/frontend/src/pages/List.jsx b/frontend/src/pages/List.jsx
index fa4fed1..0078943 100644
--- a/frontend/src/pages/List.jsx
+++ b/frontend/src/pages/List.jsx
@@ -3,13 +3,12 @@ import { APP_VERSION } from '../config/version'
export default function List() {
const [collections, setCollections] = useState([])
- const [highlightId, setHighlightId] = useState('')
const [loading, setLoading] = useState(true)
const [filter, setFilter] = useState('')
const [filterType, setFilterType] = useState('')
const urlParams = new URLSearchParams(window.location.hash.split('?')[1] || '')
- const urlId = urlParams.get('id') || ''
- const [userIdFilter, setUserIdFilter] = useState(urlParams.get('userId') || '')
+ const urlUserId = urlParams.get('userId') || ''
+ const [userIdFilter, setUserIdFilter] = useState(urlUserId)
const [sortField, setSortField] = useState('createdAt')
const [sortOrder, setSortOrder] = useState('desc')
const [viewMode, setViewMode] = useState('list')
@@ -32,9 +31,6 @@ export default function List() {
// 监听hash变化,重新读取筛选参数
const handleHashChange = () => {
const params = new URLSearchParams(window.location.hash.split('?')[1] || '')
- // 支持id参数 - 高亮显示指定藏品
- const idParam = params.get('id') || ''
- setHighlightId(idParam)
// 支持两种格式:filter=category&value=自持 或 filter=category=自持
let filterTypeParam = params.get('filter') || ''
let valueParam = params.get('value') || ''
@@ -71,15 +67,11 @@ export default function List() {
try {
// 从URL获取筛选参数
const params = new URLSearchParams(window.location.hash.split('?')[1] || '')
- const urlId = params.get('id') || ''
const urlFilterType = params.get('filter') || ''
const urlFilterValue = params.get('value') ? decodeURIComponent(params.get('value')) : ''
let api = '/api/collections?page=' + page + '&limit=100&sortBy=' + sortField + '&sortOrder=' + sortOrder
- // 如果有id参数,直接用id筛选(后端精确匹配)
- if (urlId) {
- api += '&id=' + encodeURIComponent(urlId)
- } else if (urlFilterType && urlFilterValue) {
+ if (urlFilterType && urlFilterValue) {
api += '&' + urlFilterType + '=' + encodeURIComponent(urlFilterValue)
}
const res = await fetch(api, {
@@ -109,7 +101,6 @@ export default function List() {
if (filterType && filter) {
const fieldMap = {
- id: 'id',
status: 'status',
category: 'category',
packaging: 'packaging',
@@ -415,8 +406,8 @@ export default function List() {
return { bg: 'linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%)', color: '#1e293b' }
}
- const ListItem = ({ item, highlight }) => (
-
goDetail(item.id)} style={{ background: 'rgba(255,255,255,0.03)', border: highlight ? '2px solid #10b981' : '1px solid rgba(255,255,255,0.05)', borderRadius: '12px', padding: '10px', marginBottom: '8px', cursor: 'pointer', boxShadow: highlight ? '0 0 10px rgba(16,185,129,0.3)' : 'none' }}>
+ const ListItem = ({ item }) => (
+
goDetail(item.id)} style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.05)', borderRadius: '12px', padding: '10px', marginBottom: '8px', cursor: 'pointer' }}>
{/* 第1行:编号 + 冠字号 + 包装(蓝色) | 状态 + 持仓类型 + 珍惜度(紫色) */}
@@ -592,7 +583,7 @@ export default function List() {
) : viewMode === 'grid' ? (
{filteredCollections.map(item => (
-
goDetail(item.id)} style={{ background: 'rgba(255,255,255,0.03)', border: item.id === highlightId ? '2px solid #10b981' : '1px solid rgba(255,255,255,0.05)', borderRadius: '12px', padding: '12px', cursor: 'pointer', boxShadow: item.id === highlightId ? '0 0 10px rgba(16,185,129,0.3)' : 'none' }}>
+
goDetail(item.id)} style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.05)', borderRadius: '12px', padding: '12px', cursor: 'pointer' }}>
🐉
{item.code || '-'}
{formatPrefixSerial(item.prefixSerial)}
@@ -604,7 +595,7 @@ export default function List() {
))}
) : (
- filteredCollections.map(item =>
)
+ filteredCollections.map(item =>
)
)}
diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx
index a5ff477..1aa3254 100644
--- a/frontend/src/pages/News.jsx
+++ b/frontend/src/pages/News.jsx
@@ -1,4 +1,13 @@
import React, { useState, useEffect } from 'react'
+// 本地获取用户手机号
+const getUserPhone = () => {
+ try {
+ const user = JSON.parse(localStorage.getItem('user') || '{}')
+ console.log('getUserPhone user:', user)
+ // Try multiple possible field names
+ return user.phone || user.phoneNumber || user.mobile || user.tel || ''
+ } catch { return '' }
+}
// 资讯页面 - 展示寻配号和行情信息(所有人可见)
export default function News() {
@@ -12,8 +21,9 @@ export default function News() {
const [matchedStatus, setMatchedStatus] = useState({}) // 存储各寻号的匹配状态
const [showMatchList, setShowMatchList] = useState(false)
const [matchCollections, setMatchCollections] = useState([])
+ const [customModal, setCustomModal] = useState({show: false, title: '', content: ''})
const [seekForm, setSeekForm] = useState({
- edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: (() => { try { const u = JSON.parse(localStorage.getItem('user') || '{}'); return u.phone || '' } catch { return '' } })() || ''
+ edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: getUserPhone() || ''
})
const [infoList, setInfoList] = useState([])
const [viewMode, setViewMode] = useState('all')
@@ -117,7 +127,7 @@ export default function News() {
if (data.message === '匹配成功,已通知发布者') {
setMatchedStatus(prev => ({...prev, [infoId]: 'matched'}))
setShowContactId(infoId) // 显示联系方式
- alert('匹配成功!')
+ setCustomModal({show: true, title: '匹配成功', content: '已通知发布者,请等待对方联系'})
fetchInfoList() // 刷新列表
}
} catch (e) {
@@ -125,21 +135,45 @@ export default function News() {
}
}
const fetchMatchedUserInfo = async (infoId) => {
- const token = localStorage.getItem('token')
- if (!token) return
+ console.log('fetchMatchedUserInfo called with:', infoId)
+ let token = localStorage.getItem('token')
+ if (!token) {
+ // Try to refresh token
+ const username = localStorage.getItem('username')
+ const password = localStorage.getItem('password')
+ if (username && password) {
+ const form = new FormData()
+ form.append('username', username)
+ form.append('password', password)
+ const loginRes = await fetch(`${API_BASE}/api/auth/login`, { method: 'POST', body: form })
+ const loginData = await loginRes.json()
+ if (loginData.access_token) {
+ token = loginData.access_token
+ localStorage.setItem('token', token)
+ }
+ }
+ if (!token) { alert('请先登录'); return }
+ }
try {
const res = await fetch(`${API_BASE}/api/information/seek/matched-user/${infoId}`, { headers: { Authorization: `Bearer ${token}` } })
+ console.log('Matched user response:', res.status)
const data = await res.json()
- if (data.matched_user_id) alert(`匹配者: ${data.user_name}, 联系方式: ${data.matched_contact || '未提供'}`)
+ console.log('Matched user data:', data)
+ if (data.user_name) setCustomModal({show: true, title: '匹配者信息', content: `用户名: ${data.user_name}\n联系方式: ${data.matched_contact || '未提供'}`})
+ else if (data.detail) alert(data.detail)
} catch (e) { console.error(e) }
}
const fetchPublisherInfo = async (infoId) => {
+ console.log('fetchPublisherInfo called with:', infoId)
const token = localStorage.getItem('token')
- if (!token) return
+ if (!token) { alert('请先登录'); return }
try {
const res = await fetch(`${API_BASE}/api/information/seek/publisher/${infoId}`, { headers: { Authorization: `Bearer ${token}` } })
+ console.log('Publisher response:', res.status)
const data = await res.json()
- if (data.user_id) alert(`发布者: ${data.user_name}, 联系方式: ${data.contact || '未提供'}`)
+ console.log('Publisher data:', data)
+ if (data.user_name) setCustomModal({show: true, title: '发布者信息', content: `用户名: ${data.user_name}\n联系方式: ${data.contact || '未提供'}`})
+ else if (data.detail) alert(data.detail)
} catch (e) { console.error(e) }
}
const fetchMatchCollections = async (infoId) => {
@@ -227,14 +261,13 @@ export default function News() {
} catch (e) { alert('更新失败') }
}
- const handleSeekPublish = async () => { console.log('handleSeekPublish called')
- const phone = (() => { try { const u = JSON.parse(localStorage.getItem('user') || '{}'); return u.phone || '' } catch { return '' } })()
- const finalContact = seekForm.contact || phone
+ const handleSeekPublish = async () => {
+ const phone = getUserPhone()
+ const finalContact = seekForm.contact || phone || ''
if (!finalContact) { alert('请填写联系方式'); return }
- // 类型改为非必选
+ const content = (seekForm.content ? seekForm.content + '\n' : '') + (seekForm.price ? `价格: ${seekForm.price}` : '') + (seekForm.features ? '\n号码特征: J0' + seekForm.features : '') + '\n联系方式: ' + finalContact
try {
const token = localStorage.getItem('token')
- const content = (seekForm.content ? seekForm.content + '\n' : '') + (seekForm.price ? `价格: ${seekForm.price}` : '') + (seekForm.features ? '\n号码特征: J0' + seekForm.features : '') + '\n联系方式: ' + finalContact
// 从edition映射到category
const categoryMap = { '龙钞': '龙钞', '马钞': '马钞', '蛇钞': '蛇钞' }
const res = await fetch(`${API_BASE}/api/information/`, {
@@ -251,7 +284,7 @@ export default function News() {
if (data.id || data.code === 0) {
alert('发布成功!')
setShowSeekPublish(false)
- setSeekForm({ edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: (() => { try { const u = JSON.parse(localStorage.getItem('user') || '{}'); return u.phone || '' } catch { return '' } })() || '' })
+ setSeekForm({ edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: getUserPhone() || '' })
fetchInfoList()
} else { alert(data.message || '发布失败') }
} catch (e) { alert('发布失败: ' + e.message) }
@@ -517,7 +550,7 @@ export default function News() {
if (item.matched_count > 0) {
matchAndContact(item.id)
} else {
- alert('暂无匹配藏品,无法匹配')
+ setCustomModal({show: true, title: '提示', content: '暂无匹配藏品,无法匹配'})
}
}}
disabled={item.matched_count === 0}
@@ -768,6 +801,27 @@ export default function News() {
)}
+ {customModal.show && (
+
+
+
+
+ ℹ️
+
+
{customModal.title}
+
+
+ {customModal.content.split('\n').map((line, i) => (
+
+ {line.split(':')[0]}
+ {line.split(':')[1]}
+
+ ))}
+
+
+
+
+ )}
)
}