import React, { useState, useEffect } from 'react' import YichensBoard from './YichensBoard' // 本地获取用户手机号 - 添加异常处理 const getUserPhone = () => { try { const userStr = localStorage.getItem('user') if (!userStr) return '' const user = JSON.parse(userStr) return user.phone || user.phoneNumber || user.mobile || user.tel || '' } catch { return '' } } // 本地获取用户ID - 添加异常处理 const getStoredUserId = () => { try { const userStr = localStorage.getItem('user') if (!userStr) return null const user = JSON.parse(userStr) return user.id || user.user_id || user.f99_90_id || null } catch { return null } } // 资讯页面 - 展示寻配号和行情信息(所有人可见) export default function News() { const [activeTab, setActiveTab] = useState(localStorage.getItem('news_activeTab') || 'yichen') const [showSeekPublish, setShowSeekPublish] = useState(false) const [showMySeeks, setShowMySeeks] = useState(false) // 我的寻号 const [showContactId, setShowContactId] = useState(null) // 显示联系方式的寻号ID const [showCommentId, setShowCommentId] = useState(null) // 显示评论输入的寻号ID const [commentText, setCommentText] = useState('') // 评论内容 const [comments, setComments] = useState({}) // 存储各寻号的评论 const [matchedStatus, setMatchedStatus] = useState({}) // 存储各寻号的匹配状态 const [showMatchList, setShowMatchList] = useState(false) const [dealVersion, setDealVersion] = useState('龙钞') const [dealDate, setDealDate] = useState(() => { const today = new Date() return `${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${String(today.getDate()).padStart(2, '0')}` }) const [dealDetailItems, setDealDetailItems] = useState(null) // 弹窗显示的详情列表 const [matchCollections, setMatchCollections] = useState([]) const [networkMatchCollections, setNetworkMatchCollections] = useState([]) const [showNetworkMatchList, setShowNetworkMatchList] = useState(false) const [customModal, setCustomModal] = useState({show: false, title: '', content: ''}) const [seekForm, setSeekForm] = useState({ edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: getUserPhone() || '' }) const [infoList, setInfoList] = useState([]) const [viewMode, setViewMode] = useState('all') const [expandedItems, setExpandedItems] = useState({}) // 展开状态 const [loading, setLoading] = useState(false) const [currentPage, setCurrentPage] = useState(1) const [totalPages, setTotalPages] = useState(1) const API_BASE = localStorage.getItem('API_BASE') || '' const currentUser = localStorage.getItem('token') ? { id: 'logged_in' } : null // 切换展开/收起 const toggleExpand = (itemId) => { setExpandedItems(prev => ({ ...prev, [itemId]: !prev[itemId] })) } // 获取资讯列表 useEffect(() => { fetchInfoList() }, [activeTab, dealDate]) // 自动生成寻号标题 useEffect(() => { const title = `「寻号 ${seekForm.edition || '龙钞,马钞,蛇钞'} J0${seekForm.features || 'XXXXXXXX'}」` setSeekForm(prev => ({...prev, title})) }, [seekForm.edition, seekForm.features]) const fetchInfoList = async () => { setLoading(true) try { const token = localStorage.getItem('token') const type = activeTab === 'seek' ? 'seek' : activeTab === 'deal' ? 'deal' : 'yichen' const headers = token ? { Authorization: `Bearer ${token}` } : {} // 构建URL参数 let url = `${API_BASE}/api/information/list?info_type=${type}` // 如果是成交行情tab,获取当天所有数据(不分页) if (activeTab === 'deal' && dealDate) { url += `&deal_date=${dealDate}&page_size=500` } else { url += `&page=${currentPage}&page_size=50` } const res = await fetch(url, { headers }) // 从响应头获取总页数 const total = res.headers.get('X-Total-Pages') || res.headers.get('x-total-pages') if (total) setTotalPages(parseInt(total)) // 检查响应状态 if (!res.ok) { console.error('获取资讯列表失败:', res.status) setInfoList([]) setLoading(false) return } const data = await res.json() console.log('资讯列表:', data) setInfoList(data || []) } catch (e) { console.error(e) setInfoList([]) } setLoading(false) } // 获取匹配藏品列表 // 获取寻号评论 const fetchComments = async (infoId) => { // 每次都重新获取评论 try { const res = await fetch(`${API_BASE}/api/information/comments/${infoId}`) const data = await res.json() setComments(prev => ({...prev, [infoId]: data || []})) } catch (e) { console.error('获取评论失败:', e) } } // 添加评论 const addComment = async (infoId) => { if (!commentText.trim()) { alert('请输入评论内容'); return } const token = localStorage.getItem('token') if (!token) { alert('请先登录'); return } try { const res = await fetch(`${API_BASE}/api/information/comment`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ information_id: infoId, content: commentText }) }) const data = await res.json() if (data.message === '留言成功' || data.message === '评论成功') { setCommentText('') // 强制刷新评论列表,先清空缓存 setComments(prev => { const newComments = {...prev} delete newComments[infoId] return newComments }) // 然后重新获取 setTimeout(() => fetchComments(infoId), 100) // 确保停留在寻号 tab if (activeTab !== 'seek') { setActiveTab('seek') } alert('留言成功!') } else { alert(data.detail || '留言失败') } } catch (e) { console.error('留言失败:', e) alert('留言失败,请重试') } } // 匹配并联系藏友 const matchAndContact = async (infoId) => { const token = localStorage.getItem('token') if (!token) { alert('请先登录'); return } try { const phone = getUserPhone() const res = await fetch(`${API_BASE}/api/information/seek/match-confirm`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ info_id: infoId, contact: phone || '用户已同意' }) }) const data = await res.json() if (data.message === '匹配成功,已通知发布者') { setMatchedStatus(prev => ({...prev, [infoId]: 'matched'})) setShowContactId(infoId) // 显示联系方式 setCustomModal({show: true, title: '匹配成功', content: '已通知发布者,请等待对方联系'}) fetchInfoList() // 刷新列表 } } catch (e) { alert('操作失败: ' + e.message) } } const fetchMatchedUserInfo = async (infoId) => { 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() console.log('Matched user data:', data) if (data.user_name) setCustomModal({show: true, title: '匹配者信息', content: `用户名: ${data.user_name}\n手机号: ${data.phone || '未提供'}\n\n免责声明及风险提示:所有用户信息仅作参考,交易请走正规平台,如产生经济损失与本站无关,后果自负。`}) 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) { 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() console.log('Publisher data:', data) if (data.user_name) setCustomModal({show: true, title: '发布者信息', content: `用户名: ${data.user_name}\n手机号: ${data.phone || '未提供'}\n\n免责声明及风险提示:所有用户信息仅作参考,交易请走正规平台,如产生经济损失与本站无关,后果自负。`}) else if (data.detail) alert(data.detail) } catch (e) { console.error(e) } } const fetchMatchCollections = async (infoId) => { const token = localStorage.getItem('token') if (!token) { alert('请先登录'); return } try { const res = await fetch(`${API_BASE}/api/information/seek/match?info_id=${infoId}`, { headers: { Authorization: `Bearer ${token}` } }) const data = await res.json() console.log('匹配结果:', data) console.log('匹配数量:', data.matched_count) console.log('藏品列表:', data.collections) setMatchCollections(data.collections || []) setShowMatchList(true) } catch (e) { console.error('获取匹配藏品失败:', e) alert('获取匹配藏品失败: ' + e.message) } } // 获取网络数据匹配列表 const fetchNetworkMatchCollections = async (infoId) => { try { const res = await fetch(`${API_BASE}/api/information/seek/network-match/${infoId}`) const data = await res.json() console.log('网络数据匹配结果:', data) setNetworkMatchCollections(data.collections || []) setShowNetworkMatchList(true) } catch (e) { console.error('获取网络匹配藏品失败:', e) alert('获取网络匹配藏品失败: ' + e.message) } } // 获取当前用户ID const currentUserId = getStoredUserId() // 获取我的寻号列表 const [mySeekList, setMySeekList] = useState([]) const [myMatchedList, setMyMatchedList] = useState([]) const [editingSeek, setEditingSeek] = useState(null) const fetchMySeeks = async () => { const token = localStorage.getItem('token') if (!token) { alert('请先登录'); return } try { const res = await fetch(`${API_BASE}/api/information/my/list`, { headers: { Authorization: `Bearer ${token}` } }) const data = await res.json() // 过滤出寻号类型的并筛选当前用户 const seeks = Array.isArray(data) ? data.filter(item => item.info_type === 'seek' && item.user_id === currentUserId) : [] setMySeekList(seeks) setInfoList(seeks) // 在主列表显示 } catch (e) { console.error(e) } } // 获取我已匹配的寻号列表 const fetchMyMatches = async () => { const token = localStorage.getItem('token') if (!token) { alert('请先登录'); return } try { const res = await fetch(`${API_BASE}/api/information/list?info_type=seek`, { headers: { Authorization: `Bearer ${token}` } }) const data = await res.json() // 筛选我已匹配的(matched_user_id等于当前用户ID) const matched = data.filter(item => item.is_matched === 'matched' && item.matched_user_id === currentUserId) setMyMatchedList(matched) setInfoList(matched) // 在主列表显示 } catch (e) { console.error(e) } } // 更新寻号 const handleUpdateSeek = async () => { if (!editingSeek) return const token = localStorage.getItem('token') try { // 解析正文中的号码特征和联系方式 const content = (editingSeek.content || '') + '\n联系方式: ' + editingSeek.contact await fetch(`${API_BASE}/api/information/${editingSeek.id}`, { method: 'PUT', headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ title: editingSeek.title, content, expect_category: editingSeek.edition, expect_number: editingSeek.features ? 'J0' + editingSeek.features : null }) }) setEditingSeek(null) fetchMySeeks() } catch (e) { alert('更新失败') } } 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') // 从edition映射到category const categoryMap = { '龙钞': '龙钞', '马钞': '马钞', '蛇钞': '蛇钞' } const res = await fetch(`${API_BASE}/api/information/`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ title: seekForm.title, content, info_type: 'seek', expect_category: seekForm.edition, expect_number: seekForm.features ? 'J0' + seekForm.features : None }) }) const data = await res.json() if (data.id || data.code === 0) { alert('发布成功!') setShowSeekPublish(false) setSeekForm({ edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: getUserPhone() || '' }) fetchInfoList() } else { alert(data.message || '发布失败') } } catch (e) { alert('发布失败: ' + e.message) } } // Tab切换 const tabs = [ { key: 'seek', label: '寻配号' }, { key: 'deal', label: '成交行情' }, { key: 'yichen', label: '一尘看板' } ] const formatDate = (dateStr) => { if (!dateStr) return '-' const date = new Date(dateStr) return `${date.getMonth() + 1}/${date.getDate()}` } return (
{/* Tab导航 */}
{tabs.map(tab => (
{ setActiveTab(tab.key); localStorage.setItem('news_activeTab', tab.key); }} style={{ padding: '10px 20px', borderRadius: '8px', background: activeTab === tab.key ? '#3b82f6' : 'transparent', color: '#fff', cursor: 'pointer', whiteSpace: 'nowrap', fontSize: '14px' }} > {tab.label}
))}
{/* 资讯列表 */}
{showSeekPublish && activeTab === 'seek' && (
{['龙钞','马钞','蛇钞'].map(item => { const isSelected = seekForm.edition ? seekForm.edition.split(',').includes(item) : false return ( ) })}
setSeekForm({...seekForm, price: e.target.value})} placeholder="请输入价格" style={{ width: '100%', padding: '8px', marginTop: '6px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '13px', boxSizing: 'border-box' }} />
{[0,1,2,3,4,5,6,7,8,9].map(i => { const isFixed = i < 2 const char = i === 0 ? 'J' : (i === 1 ? '0' : (seekForm.features[i - 2] || '')) const isDigit = /[0-9]/.test(char) const letterColor = isFixed ? '#10b981' : (isDigit ? '#fff' : ({ X: '#94a3b8', A: '#f472b6', B: '#60a5fa', C: '#34d399', D: '#fbbf24', E: '#a78bfa', F: '#fb923c', G: '#22d3ee' }[char] || '#fff')) return { if (el) el.dataset.index = i }} type="text" maxLength={1} value={char} readOnly={isFixed} onChange={(e) => { if (i < 2) return const val = e.target.value.toUpperCase().replace(/[^0-9XABCDEFG]/g, '') const newFeatures = (seekForm.features || '').split('') while (newFeatures.length < 8) newFeatures.push('') newFeatures[i - 2] = val setSeekForm({...seekForm, features: newFeatures.join('')}) // 自动跳转下一个 if (val && i < 9) { setTimeout(() => { const nextInput = document.querySelector(`input[data-index="${i+1}"]`) if (nextInput) nextInput.focus() }, 50) } }} onKeyDown={(e) => { // 按Delete或Backspace且当前为空时,跳回前一个 if ((e.key === 'Backspace' || e.key === 'Delete') && !char && i > 2) { setTimeout(() => { const prevInput = document.querySelector(`input[data-index="${i-1}"]`) if (prevInput) prevInput.focus() }, 50) } }} style={{ width: '32px', height: '36px', textAlign: 'center', padding: '6px', background: '#0f172a', border: '1px solid #334155', borderRadius: '4px', color: letterColor, fontSize: '14px', boxSizing: 'border-box' }} /> })}
备注说明:X=任意数字 A=非4 B=非47 C=非347 D=非247 E=非2347 F=非23457 G=非123457
setSeekForm({...seekForm, title: e.target.value})} />