2026-03-25 10:58:00 +08:00
|
|
|
|
import React, { useState, useEffect } from 'react'
|
2026-04-15 21:42:52 +08:00
|
|
|
|
import YichensBoard from './YichensBoard'
|
2026-03-31 19:56:09 +08:00
|
|
|
|
|
|
|
|
|
|
// 本地获取用户手机号 - 添加异常处理
|
2026-03-29 16:20:51 +08:00
|
|
|
|
const getUserPhone = () => {
|
|
|
|
|
|
try {
|
2026-03-31 19:56:09 +08:00
|
|
|
|
const userStr = localStorage.getItem('user')
|
|
|
|
|
|
if (!userStr) return ''
|
|
|
|
|
|
const user = JSON.parse(userStr)
|
2026-03-29 16:20:51 +08:00
|
|
|
|
return user.phone || user.phoneNumber || user.mobile || user.tel || ''
|
|
|
|
|
|
} catch { return '' }
|
|
|
|
|
|
}
|
2026-03-25 10:58:00 +08:00
|
|
|
|
|
2026-03-31 19:56:09 +08:00
|
|
|
|
// 本地获取用户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 }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 15:32:27 +08:00
|
|
|
|
// 资讯页面 - 展示寻配号和行情信息(所有人可见)
|
2026-03-25 10:58:00 +08:00
|
|
|
|
export default function News() {
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const [activeTab, setActiveTab] = useState(localStorage.getItem('news_activeTab') || 'yichen')
|
2026-03-27 00:28:24 +08:00
|
|
|
|
const [showSeekPublish, setShowSeekPublish] = useState(false)
|
2026-03-27 16:39:36 +08:00
|
|
|
|
const [showMySeeks, setShowMySeeks] = useState(false) // 我的寻号
|
2026-03-28 08:24:08 +08:00
|
|
|
|
const [showContactId, setShowContactId] = useState(null) // 显示联系方式的寻号ID
|
|
|
|
|
|
const [showCommentId, setShowCommentId] = useState(null) // 显示评论输入的寻号ID
|
|
|
|
|
|
const [commentText, setCommentText] = useState('') // 评论内容
|
|
|
|
|
|
const [comments, setComments] = useState({}) // 存储各寻号的评论
|
|
|
|
|
|
const [matchedStatus, setMatchedStatus] = useState({}) // 存储各寻号的匹配状态
|
2026-03-27 16:39:36 +08:00
|
|
|
|
const [showMatchList, setShowMatchList] = useState(false)
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const [dealVersion, setDealVersion] = useState('龙钞')
|
|
|
|
|
|
const [dealDate, setDealDate] = useState('')
|
|
|
|
|
|
const [dealDetailItems, setDealDetailItems] = useState(null) // 弹窗显示的详情列表
|
2026-03-27 16:39:36 +08:00
|
|
|
|
const [matchCollections, setMatchCollections] = useState([])
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const [networkMatchCollections, setNetworkMatchCollections] = useState([])
|
|
|
|
|
|
const [showNetworkMatchList, setShowNetworkMatchList] = useState(false)
|
2026-03-29 16:20:51 +08:00
|
|
|
|
const [customModal, setCustomModal] = useState({show: false, title: '', content: ''})
|
2026-03-27 00:28:24 +08:00
|
|
|
|
const [seekForm, setSeekForm] = useState({
|
2026-03-29 16:20:51 +08:00
|
|
|
|
edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: getUserPhone() || ''
|
2026-03-27 00:28:24 +08:00
|
|
|
|
})
|
2026-03-25 10:58:00 +08:00
|
|
|
|
const [infoList, setInfoList] = useState([])
|
2026-03-28 11:10:23 +08:00
|
|
|
|
const [viewMode, setViewMode] = useState('all')
|
2026-03-31 01:22:34 +08:00
|
|
|
|
const [expandedItems, setExpandedItems] = useState({}) // 展开状态
|
2026-03-25 10:58:00 +08:00
|
|
|
|
const [loading, setLoading] = useState(false)
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const [currentPage, setCurrentPage] = useState(1)
|
|
|
|
|
|
const [totalPages, setTotalPages] = useState(1)
|
|
|
|
|
|
|
|
|
|
|
|
// 从URL获取userId参数(用于管理员查看指定用户的行情)
|
|
|
|
|
|
const urlParams = new URLSearchParams(window.location.hash.split('?')[1] || '')
|
|
|
|
|
|
const filterUserId = urlParams.get('userId')
|
2026-03-25 10:58:00 +08:00
|
|
|
|
|
|
|
|
|
|
const API_BASE = localStorage.getItem('API_BASE') || ''
|
2026-03-28 08:24:08 +08:00
|
|
|
|
const currentUser = localStorage.getItem('token') ? { id: 'logged_in' } : null
|
2026-03-25 10:58:00 +08:00
|
|
|
|
|
2026-03-31 01:22:34 +08:00
|
|
|
|
// 切换展开/收起
|
|
|
|
|
|
const toggleExpand = (itemId) => {
|
|
|
|
|
|
setExpandedItems(prev => ({
|
|
|
|
|
|
...prev,
|
|
|
|
|
|
[itemId]: !prev[itemId]
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 15:32:27 +08:00
|
|
|
|
// 获取资讯列表
|
2026-03-25 10:58:00 +08:00
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
fetchInfoList()
|
2026-04-15 21:42:52 +08:00
|
|
|
|
}, [activeTab, dealDate])
|
2026-03-25 10:58:00 +08:00
|
|
|
|
|
2026-03-27 00:28:24 +08:00
|
|
|
|
// 自动生成寻号标题
|
|
|
|
|
|
useEffect(() => {
|
2026-03-28 08:24:08 +08:00
|
|
|
|
const title = `「寻号 ${seekForm.edition || '龙钞,马钞,蛇钞'} J0${seekForm.features || 'XXXXXXXX'}」`
|
2026-03-27 00:28:24 +08:00
|
|
|
|
setSeekForm(prev => ({...prev, title}))
|
2026-03-28 08:24:08 +08:00
|
|
|
|
}, [seekForm.edition, seekForm.features])
|
2026-03-27 00:28:24 +08:00
|
|
|
|
|
2026-03-25 10:58:00 +08:00
|
|
|
|
const fetchInfoList = async () => {
|
|
|
|
|
|
setLoading(true)
|
|
|
|
|
|
try {
|
|
|
|
|
|
const token = localStorage.getItem('token')
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const type = activeTab === 'seek' ? 'seek' : activeTab === 'deal' ? 'deal' : 'yichen'
|
2026-03-27 16:39:36 +08:00
|
|
|
|
const headers = token ? { Authorization: `Bearer ${token}` } : {}
|
2026-04-15 21:42:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 构建URL参数
|
|
|
|
|
|
// 使用新的独立API(seek和deal已拆分)
|
|
|
|
|
|
let url = activeTab === 'yichen'
|
|
|
|
|
|
? `${API_BASE}/api/information/list?info_type=${activeTab}`
|
|
|
|
|
|
: activeTab === 'seek'
|
|
|
|
|
|
? `${API_BASE}/api/seek/list`
|
|
|
|
|
|
: `${API_BASE}/api/deal/list`
|
|
|
|
|
|
|
|
|
|
|
|
// 成交行情和寻配号每页500条
|
|
|
|
|
|
if (activeTab === 'deal') {
|
|
|
|
|
|
url += (url.includes('?') ? '&' : '?') + 'page_size=500'
|
|
|
|
|
|
if (dealDate) {
|
|
|
|
|
|
url += '&deal_date=' + dealDate
|
|
|
|
|
|
}
|
|
|
|
|
|
if (filterUserId) {
|
|
|
|
|
|
url += '&user_id=' + filterUserId
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (activeTab === 'seek') {
|
|
|
|
|
|
url += (url.includes("?") ? "&" : "?") + "page=" + currentPage + "&page_size=500"
|
|
|
|
|
|
if (filterUserId) {
|
|
|
|
|
|
url += '&user_id=' + filterUserId
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (activeTab === 'yichen' && filterUserId) {
|
|
|
|
|
|
// 行情tab也需要支持user_id过滤
|
|
|
|
|
|
url += (url.includes('?') ? '&' : '?') + 'user_id=' + filterUserId
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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))
|
2026-03-31 20:02:24 +08:00
|
|
|
|
// 检查响应状态
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
|
console.error('获取资讯列表失败:', res.status)
|
|
|
|
|
|
setInfoList([])
|
|
|
|
|
|
setLoading(false)
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
2026-03-25 10:58:00 +08:00
|
|
|
|
const data = await res.json()
|
2026-03-27 16:39:36 +08:00
|
|
|
|
console.log('资讯列表:', data)
|
2026-04-15 21:42:52 +08:00
|
|
|
|
|
|
|
|
|
|
// 成交行情按分类排序
|
|
|
|
|
|
let sortedData = data || []
|
|
|
|
|
|
if (activeTab === 'deal' || activeTab === 'seek') {
|
|
|
|
|
|
// 优先按日期倒序,同日按编号倒序
|
|
|
|
|
|
sortedData = [...(data || [])].sort((a, b) => {
|
|
|
|
|
|
const dateA = a.deal_date || a.created_at?.split('T')[0] || ""
|
|
|
|
|
|
const dateB = b.deal_date || b.created_at?.split('T')[0] || ""
|
|
|
|
|
|
if (dateA !== dateB) return dateB.localeCompare(dateA)
|
|
|
|
|
|
const noA = a.deal_no || ""
|
|
|
|
|
|
const noB = b.deal_no || ""
|
|
|
|
|
|
return noB.localeCompare(noA)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
setInfoList(sortedData)
|
2026-03-25 10:58:00 +08:00
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.error(e)
|
2026-03-31 20:02:24 +08:00
|
|
|
|
setInfoList([])
|
2026-03-25 10:58:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
setLoading(false)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:39:36 +08:00
|
|
|
|
// 获取匹配藏品列表
|
2026-03-28 08:24:08 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 获取寻号评论
|
|
|
|
|
|
const fetchComments = async (infoId) => {
|
2026-03-28 10:15:54 +08:00
|
|
|
|
// 每次都重新获取评论
|
2026-03-28 08:24:08 +08:00
|
|
|
|
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()
|
2026-03-28 10:15:54 +08:00
|
|
|
|
if (data.message === '留言成功' || data.message === '评论成功') {
|
2026-03-28 08:24:08 +08:00
|
|
|
|
setCommentText('')
|
2026-03-28 10:15:54 +08:00
|
|
|
|
// 强制刷新评论列表,先清空缓存
|
|
|
|
|
|
setComments(prev => {
|
|
|
|
|
|
const newComments = {...prev}
|
|
|
|
|
|
delete newComments[infoId]
|
|
|
|
|
|
return newComments
|
|
|
|
|
|
})
|
|
|
|
|
|
// 然后重新获取
|
|
|
|
|
|
setTimeout(() => fetchComments(infoId), 100)
|
|
|
|
|
|
// 确保停留在寻号 tab
|
|
|
|
|
|
if (activeTab !== 'seek') {
|
|
|
|
|
|
setActiveTab('seek')
|
|
|
|
|
|
}
|
2026-03-28 08:24:08 +08:00
|
|
|
|
alert('留言成功!')
|
2026-03-28 10:15:54 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
alert(data.detail || '留言失败')
|
2026-03-28 08:24:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
2026-03-28 10:15:54 +08:00
|
|
|
|
console.error('留言失败:', e)
|
|
|
|
|
|
alert('留言失败,请重试')
|
2026-03-28 08:24:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 匹配并联系藏友
|
|
|
|
|
|
const matchAndContact = async (infoId) => {
|
|
|
|
|
|
const token = localStorage.getItem('token')
|
|
|
|
|
|
if (!token) { alert('请先登录'); return }
|
|
|
|
|
|
try {
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const phone = getUserPhone()
|
2026-03-28 08:24:08 +08:00
|
|
|
|
const res = await fetch(`${API_BASE}/api/information/seek/match-confirm`, {
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
2026-04-15 21:42:52 +08:00
|
|
|
|
body: JSON.stringify({ info_id: infoId, contact: phone || '用户已同意' })
|
2026-03-28 08:24:08 +08:00
|
|
|
|
})
|
|
|
|
|
|
const data = await res.json()
|
|
|
|
|
|
if (data.message === '匹配成功,已通知发布者') {
|
|
|
|
|
|
setMatchedStatus(prev => ({...prev, [infoId]: 'matched'}))
|
|
|
|
|
|
setShowContactId(infoId) // 显示联系方式
|
2026-03-29 16:20:51 +08:00
|
|
|
|
setCustomModal({show: true, title: '匹配成功', content: '已通知发布者,请等待对方联系'})
|
2026-03-28 08:24:08 +08:00
|
|
|
|
fetchInfoList() // 刷新列表
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
alert('操作失败: ' + e.message)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-03-28 14:30:02 +08:00
|
|
|
|
const fetchMatchedUserInfo = async (infoId) => {
|
2026-03-29 16:20:51 +08:00
|
|
|
|
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 }
|
|
|
|
|
|
}
|
2026-03-28 14:30:02 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`${API_BASE}/api/information/seek/matched-user/${infoId}`, { headers: { Authorization: `Bearer ${token}` } })
|
2026-03-29 16:20:51 +08:00
|
|
|
|
console.log('Matched user response:', res.status)
|
2026-03-28 14:30:02 +08:00
|
|
|
|
const data = await res.json()
|
2026-03-29 16:20:51 +08:00
|
|
|
|
console.log('Matched user data:', data)
|
2026-04-15 21:42:52 +08:00
|
|
|
|
if (data.user_name) setCustomModal({show: true, title: '匹配者信息', content: `用户名: ${data.user_name}\n手机号: ${data.phone || '未提供'}\n\n免责声明及风险提示:所有用户信息仅作参考,交易请走正规平台,如产生经济损失与本站无关,后果自负。`})
|
2026-03-29 16:20:51 +08:00
|
|
|
|
else if (data.detail) alert(data.detail)
|
2026-03-28 14:30:02 +08:00
|
|
|
|
} catch (e) { console.error(e) }
|
|
|
|
|
|
}
|
|
|
|
|
|
const fetchPublisherInfo = async (infoId) => {
|
2026-03-29 16:20:51 +08:00
|
|
|
|
console.log('fetchPublisherInfo called with:', infoId)
|
2026-03-28 14:30:02 +08:00
|
|
|
|
const token = localStorage.getItem('token')
|
2026-03-29 16:20:51 +08:00
|
|
|
|
if (!token) { alert('请先登录'); return }
|
2026-03-28 14:30:02 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const res = await fetch(`${API_BASE}/api/information/seek/publisher/${infoId}`, { headers: { Authorization: `Bearer ${token}` } })
|
2026-03-29 16:20:51 +08:00
|
|
|
|
console.log('Publisher response:', res.status)
|
2026-03-28 14:30:02 +08:00
|
|
|
|
const data = await res.json()
|
2026-03-29 16:20:51 +08:00
|
|
|
|
console.log('Publisher data:', data)
|
2026-04-15 21:42:52 +08:00
|
|
|
|
if (data.user_name) setCustomModal({show: true, title: '发布者信息', content: `用户名: ${data.user_name}\n手机号: ${data.phone || '未提供'}\n\n免责声明及风险提示:所有用户信息仅作参考,交易请走正规平台,如产生经济损失与本站无关,后果自负。`})
|
2026-03-29 16:20:51 +08:00
|
|
|
|
else if (data.detail) alert(data.detail)
|
2026-03-28 14:30:02 +08:00
|
|
|
|
} catch (e) { console.error(e) }
|
|
|
|
|
|
}
|
2026-03-27 16:39:36 +08:00
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-04-15 21:42:52 +08:00
|
|
|
|
// 获取网络数据匹配列表
|
|
|
|
|
|
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)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-28 11:10:23 +08:00
|
|
|
|
// 获取当前用户ID
|
2026-03-31 19:56:09 +08:00
|
|
|
|
const currentUserId = getStoredUserId()
|
2026-03-28 11:10:23 +08:00
|
|
|
|
|
2026-03-27 16:39:36 +08:00
|
|
|
|
// 获取我的寻号列表
|
|
|
|
|
|
const [mySeekList, setMySeekList] = useState([])
|
2026-03-28 11:10:23 +08:00
|
|
|
|
const [myMatchedList, setMyMatchedList] = useState([])
|
2026-03-27 16:39:36 +08:00
|
|
|
|
const [editingSeek, setEditingSeek] = useState(null)
|
|
|
|
|
|
const fetchMySeeks = async () => {
|
|
|
|
|
|
const token = localStorage.getItem('token')
|
|
|
|
|
|
if (!token) { alert('请先登录'); return }
|
|
|
|
|
|
try {
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const res = await fetch(`${API_BASE}/api/seek/list`, {
|
2026-03-27 16:39:36 +08:00
|
|
|
|
headers: { Authorization: `Bearer ${token}` }
|
|
|
|
|
|
})
|
|
|
|
|
|
const data = await res.json()
|
2026-04-15 21:42:52 +08:00
|
|
|
|
// 筛选当前用户
|
|
|
|
|
|
const seeks = Array.isArray(data) ? data.filter(item => item.user_id === currentUserId) : []
|
2026-03-27 16:39:36 +08:00
|
|
|
|
setMySeekList(seeks)
|
2026-03-28 11:10:23 +08:00
|
|
|
|
setInfoList(seeks) // 在主列表显示
|
|
|
|
|
|
} catch (e) { console.error(e) }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取我已匹配的寻号列表
|
|
|
|
|
|
const fetchMyMatches = async () => {
|
|
|
|
|
|
const token = localStorage.getItem('token')
|
|
|
|
|
|
if (!token) { alert('请先登录'); return }
|
|
|
|
|
|
try {
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const res = await fetch(`${API_BASE}/api/seek/list`, {
|
2026-03-28 11:10:23 +08:00
|
|
|
|
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) // 在主列表显示
|
2026-03-27 16:39:36 +08:00
|
|
|
|
} 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('更新失败') }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-29 16:20:51 +08:00
|
|
|
|
const handleSeekPublish = async () => {
|
|
|
|
|
|
const phone = getUserPhone()
|
|
|
|
|
|
const finalContact = seekForm.contact || phone || ''
|
2026-03-28 15:21:07 +08:00
|
|
|
|
if (!finalContact) { alert('请填写联系方式'); return }
|
2026-03-29 16:20:51 +08:00
|
|
|
|
const content = (seekForm.content ? seekForm.content + '\n' : '') + (seekForm.price ? `价格: ${seekForm.price}` : '') + (seekForm.features ? '\n号码特征: J0' + seekForm.features : '') + '\n联系方式: ' + finalContact
|
2026-03-27 00:28:24 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const token = localStorage.getItem('token')
|
2026-03-27 16:39:36 +08:00
|
|
|
|
// 从edition映射到category
|
|
|
|
|
|
const categoryMap = { '龙钞': '龙钞', '马钞': '马钞', '蛇钞': '蛇钞' }
|
2026-03-27 00:28:24 +08:00
|
|
|
|
const res = await fetch(`${API_BASE}/api/information/`, {
|
|
|
|
|
|
method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
|
2026-03-27 16:39:36 +08:00
|
|
|
|
body: JSON.stringify({
|
|
|
|
|
|
title: seekForm.title,
|
|
|
|
|
|
content,
|
|
|
|
|
|
info_type: 'seek',
|
|
|
|
|
|
expect_category: seekForm.edition,
|
|
|
|
|
|
expect_number: seekForm.features ? 'J0' + seekForm.features : None
|
|
|
|
|
|
})
|
2026-03-27 00:28:24 +08:00
|
|
|
|
})
|
|
|
|
|
|
const data = await res.json()
|
|
|
|
|
|
if (data.id || data.code === 0) {
|
2026-03-29 15:03:52 +08:00
|
|
|
|
alert('发布成功!')
|
2026-03-27 00:28:24 +08:00
|
|
|
|
setShowSeekPublish(false)
|
2026-03-29 16:20:51 +08:00
|
|
|
|
setSeekForm({ edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: getUserPhone() || '' })
|
2026-03-27 00:28:24 +08:00
|
|
|
|
fetchInfoList()
|
|
|
|
|
|
} else { alert(data.message || '发布失败') }
|
|
|
|
|
|
} catch (e) { alert('发布失败: ' + e.message) }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-26 15:32:27 +08:00
|
|
|
|
// Tab切换
|
|
|
|
|
|
const tabs = [
|
2026-04-15 21:42:52 +08:00
|
|
|
|
{ key: 'seek', label: '寻配号' },
|
|
|
|
|
|
{ key: 'deal', label: '成交行情' },
|
|
|
|
|
|
{ key: 'yichen', label: '一尘看板' }
|
2026-03-26 15:32:27 +08:00
|
|
|
|
]
|
2026-03-25 10:58:00 +08:00
|
|
|
|
|
|
|
|
|
|
const formatDate = (dateStr) => {
|
2026-03-26 15:32:27 +08:00
|
|
|
|
if (!dateStr) return '-'
|
|
|
|
|
|
const date = new Date(dateStr)
|
|
|
|
|
|
return `${date.getMonth() + 1}/${date.getDate()}`
|
2026-03-25 10:58:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-03-26 15:32:27 +08:00
|
|
|
|
<div style={{ minHeight: '100vh', background: '#0f172a', paddingBottom: '60px' }}>
|
|
|
|
|
|
{/* Tab导航 */}
|
|
|
|
|
|
<div style={{ display: 'flex', padding: '12px 16px', background: '#1e293b', gap: '8px', overflowX: 'auto' }}>
|
|
|
|
|
|
{tabs.map(tab => (
|
2026-03-25 10:58:00 +08:00
|
|
|
|
<div
|
|
|
|
|
|
key={tab.key}
|
2026-03-28 10:15:54 +08:00
|
|
|
|
onClick={() => { setActiveTab(tab.key); localStorage.setItem('news_activeTab', tab.key); }}
|
2026-03-25 10:58:00 +08:00
|
|
|
|
style={{
|
2026-03-26 15:32:27 +08:00
|
|
|
|
padding: '10px 20px',
|
|
|
|
|
|
borderRadius: '8px',
|
2026-03-25 10:58:00 +08:00
|
|
|
|
background: activeTab === tab.key ? '#3b82f6' : 'transparent',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
cursor: 'pointer',
|
2026-03-26 15:32:27 +08:00
|
|
|
|
whiteSpace: 'nowrap',
|
2026-03-25 10:58:00 +08:00
|
|
|
|
fontSize: '14px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-03-26 15:32:27 +08:00
|
|
|
|
{tab.label}
|
2026-03-25 10:58:00 +08:00
|
|
|
|
</div>
|
2026-03-26 15:32:27 +08:00
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
2026-03-25 10:58:00 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 资讯列表 */}
|
2026-03-26 15:32:27 +08:00
|
|
|
|
<div style={{ padding: '16px' }}>
|
2026-03-27 00:28:24 +08:00
|
|
|
|
{showSeekPublish && activeTab === 'seek' && (
|
|
|
|
|
|
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '16px', marginBottom: '16px' }}>
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}><label style={{ color: '#94a3b8', fontSize: '12px' }}>版别</label>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', marginTop: '6px' }}>
|
2026-03-27 01:20:03 +08:00
|
|
|
|
{['龙钞','马钞','蛇钞'].map(item => {
|
|
|
|
|
|
const isSelected = seekForm.edition ? seekForm.edition.split(',').includes(item) : false
|
|
|
|
|
|
return (
|
|
|
|
|
|
<button key={item} onClick={() => {
|
|
|
|
|
|
const eds = seekForm.edition ? seekForm.edition.split(',').filter(x => x) : []
|
|
|
|
|
|
if (isSelected) {
|
|
|
|
|
|
const idx = eds.indexOf(item)
|
|
|
|
|
|
if (idx > -1) eds.splice(idx, 1)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
eds.push(item)
|
|
|
|
|
|
}
|
|
|
|
|
|
setSeekForm({...seekForm, edition: eds.join(',')})
|
|
|
|
|
|
}}
|
|
|
|
|
|
style={{ flex: 1, padding: '8px', borderRadius: '6px', border: isSelected?'2px solid #10b981':'1px solid #334155', background: isSelected?'rgba(16,185,129,0.2)':'#0f172a', color: '#fff', fontSize: '12px' }}>{item}</button>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
2026-03-27 00:28:24 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}><label style={{ color: '#94a3b8', fontSize: '12px' }}>价格(选填)</label>
|
|
|
|
|
|
<input type="text" value={seekForm.price} onChange={(e) => 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' }} />
|
|
|
|
|
|
</div>
|
2026-03-27 01:20:03 +08:00
|
|
|
|
<div style={{ marginBottom: '8px' }}><label style={{ color: '#94a3b8', fontSize: '12px' }}>号码特征(10位)</label>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '4px', marginTop: '6px', flexWrap: 'wrap' }}>
|
|
|
|
|
|
{[0,1,2,3,4,5,6,7,8,9].map(i => {
|
|
|
|
|
|
const isFixed = i < 2
|
2026-03-27 16:39:36 +08:00
|
|
|
|
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 <input key={i} ref={el => { if (el) el.dataset.index = i }} type="text" maxLength={1}
|
|
|
|
|
|
value={char}
|
|
|
|
|
|
readOnly={isFixed}
|
2026-03-27 01:20:03 +08:00
|
|
|
|
onChange={(e) => {
|
|
|
|
|
|
if (i < 2) return
|
2026-04-15 21:42:52 +08:00
|
|
|
|
const val = e.target.value.toUpperCase().replace(/[^0-9XABCDEFG]/g, '')
|
2026-03-27 16:39:36 +08:00
|
|
|
|
const newFeatures = (seekForm.features || '').split('')
|
|
|
|
|
|
while (newFeatures.length < 8) newFeatures.push('')
|
|
|
|
|
|
newFeatures[i - 2] = val
|
|
|
|
|
|
setSeekForm({...seekForm, features: newFeatures.join('')})
|
|
|
|
|
|
// 自动跳转下一个
|
|
|
|
|
|
if (val && i < 9) {
|
2026-03-27 01:20:03 +08:00
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
const nextInput = document.querySelector(`input[data-index="${i+1}"]`)
|
|
|
|
|
|
if (nextInput) nextInput.focus()
|
2026-03-27 16:39:36 +08:00
|
|
|
|
}, 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)
|
2026-03-27 01:20:03 +08:00
|
|
|
|
}
|
2026-03-27 16:39:36 +08:00
|
|
|
|
}}
|
|
|
|
|
|
style={{ width: '32px', height: '36px', textAlign: 'center', padding: '6px', background: '#0f172a', border: '1px solid #334155', borderRadius: '4px', color: letterColor, fontSize: '14px', boxSizing: 'border-box' }} />
|
2026-03-27 01:20:03 +08:00
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
2026-03-27 16:39:36 +08:00
|
|
|
|
<div style={{ color: '#64748b', fontSize: '10px', marginTop: '6px' }}>
|
|
|
|
|
|
备注说明:X=任意数字 A=非4 B=非47 C=非347 D=非247 E=非2347 F=非23457 G=非123457
|
|
|
|
|
|
</div>
|
2026-03-27 00:28:24 +08:00
|
|
|
|
</div>
|
2026-03-28 08:24:08 +08:00
|
|
|
|
<div style={{ marginBottom: '12px' }}><label style={{ color: '#94a3b8', fontSize: '12px' }}>标题(自动生成)</label>
|
|
|
|
|
|
<input type="text" value={seekForm.title} readOnly
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', marginTop: '6px', background: '#0f172a', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '13px', boxSizing: 'border-box' }} onChange={(e) => setSeekForm({...seekForm, title: e.target.value})} />
|
|
|
|
|
|
</div>
|
2026-03-27 00:28:24 +08:00
|
|
|
|
<div style={{ marginBottom: '12px' }}><label style={{ color: '#94a3b8', fontSize: '12px' }}>正文</label>
|
|
|
|
|
|
<textarea value={seekForm.content || ''} onChange={(e) => setSeekForm({...seekForm, content: 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', resize: 'vertical', minHeight: '60px' }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ marginBottom: '12px' }}><label style={{ color: '#ef4444', fontSize: '12px' }}>联系方式 *</label>
|
|
|
|
|
|
<input type="text" value={seekForm.contact || ''} onChange={(e) => setSeekForm({...seekForm, contact: 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' }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '12px' }}>
|
|
|
|
|
|
<button onClick={handleSeekPublish} style={{ flex: 1, padding: '12px', background: '#10b981', border: 'none', borderRadius: '8px', color: '#fff', fontSize: '14px', fontWeight: 'bold', cursor: 'pointer' }}>发布寻号</button>
|
|
|
|
|
|
<button onClick={() => setShowSeekPublish(false)} style={{ flex: 1, padding: '12px', background: 'transparent', border: '1px solid #334155', borderRadius: '8px', color: '#94a3b8', fontSize: '14px', cursor: 'pointer' }}>取消</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-26 15:32:27 +08:00
|
|
|
|
<h3 style={{ color: '#fff', marginBottom: '16px' }}>
|
2026-04-15 21:42:52 +08:00
|
|
|
|
{activeTab === 'seek' ? '寻配号信息' : activeTab === 'deal' ? `成交行情信息 (${dealDate})` : '一尘看板'}
|
2026-03-27 00:28:24 +08:00
|
|
|
|
{activeTab === 'seek' && (
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', marginLeft: 'auto' }}>
|
2026-03-28 11:10:23 +08:00
|
|
|
|
<button onClick={() => { setViewMode('all'); fetchInfoList(); }} style={{ padding: '6px 12px', background: viewMode==='all'?'#3b82f6':'#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '12px' }}>全部</button>
|
|
|
|
|
|
<button onClick={() => { setViewMode('mySeeks'); fetchMySeeks(); }} style={{ padding: '6px 12px', background: viewMode==='mySeeks'?'#3b82f6':'#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '12px' }}>我的寻号</button>
|
|
|
|
|
|
<button onClick={() => { setViewMode('myMatches'); fetchMyMatches(); }} style={{ padding: '6px 12px', background: viewMode==='myMatches'?'#8b5cf6':'#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '12px' }}>我的配号</button>
|
|
|
|
|
|
<button onClick={() => setShowSeekPublish(!showSeekPublish)} style={{ padding: '6px 12px', background: showSeekPublish ? '#6b7280' : '#10b981', border: 'none', borderRadius: '6px', color: '#fff', fontSize: '12px', cursor: 'pointer' }}>发布寻号</button>
|
2026-03-27 00:28:24 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-04-15 21:42:52 +08:00
|
|
|
|
{activeTab === 'deal' && (
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', marginLeft: 'auto' }}>
|
|
|
|
|
|
<input type="date" value={dealDate} onChange={e => setDealDate(e.target.value)}
|
|
|
|
|
|
style={{ padding: '6px 12px', background: '#1e293b', border: '1px solid #334155', borderRadius: '6px', color: '#fff', fontSize: '12px' }} />
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-26 15:32:27 +08:00
|
|
|
|
</h3>
|
|
|
|
|
|
|
2026-04-15 21:42:52 +08:00
|
|
|
|
{/* 一尘看板独立渲染 */}
|
|
|
|
|
|
{activeTab === 'yichen' ? (
|
|
|
|
|
|
<YichensBoard />
|
|
|
|
|
|
) : loading ? (
|
2026-03-26 15:32:27 +08:00
|
|
|
|
<div style={{ color: '#94a3b8', textAlign: 'center', padding: '40px' }}>加载中...</div>
|
|
|
|
|
|
) : infoList.length === 0 ? (
|
|
|
|
|
|
<div style={{ color: '#94a3b8', textAlign: 'center', padding: '40px' }}>
|
2026-04-15 21:42:52 +08:00
|
|
|
|
暂无{activeTab === 'seek' ? '寻配号' : activeTab === 'deal' ? '成交行情' : '一尘看板'}信息
|
2026-03-26 15:32:27 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
2026-04-15 21:42:52 +08:00
|
|
|
|
{/* 成交行情 - 价格统计表格 */}
|
|
|
|
|
|
{activeTab === 'deal' && (() => {
|
|
|
|
|
|
const versions = ['龙钞', '马钞', '蛇钞', '其他']
|
|
|
|
|
|
const packagings = ['标百', '标十', '单张']
|
|
|
|
|
|
const categories = ['带4号', '带7号', '永恒号', '永恒', '钻石号', '钻石', '如意号', '朦胧号', '朦胧王', '天马号', '金山号', '金马号', '天马王', '金山王', '金马王', '倒置号', '圆圆号', '通货', '无4']
|
|
|
|
|
|
|
|
|
|
|
|
// 分类名称标准化
|
|
|
|
|
|
const normalizeCat = (c) => {
|
|
|
|
|
|
if (c === '通货') return '带4号'
|
|
|
|
|
|
if (c === '无4') return '带7号'
|
|
|
|
|
|
return c
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const filteredData = infoList.filter(item => {
|
|
|
|
|
|
const serial = (item.title || '').split('-')[0] || ''
|
|
|
|
|
|
let version = '其他'
|
|
|
|
|
|
if (serial.startsWith('J0')) version = '龙钞'
|
|
|
|
|
|
else if (serial.startsWith('J1')) version = '马钞'
|
|
|
|
|
|
else if (serial.startsWith('J3')) version = '蛇钞'
|
|
|
|
|
|
if (version !== dealVersion) return false
|
|
|
|
|
|
return true
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
const calcAvg = (pkg, cat) => {
|
|
|
|
|
|
const items = filteredData.filter(item => {
|
|
|
|
|
|
const content = item.content || ''
|
|
|
|
|
|
const p = content.includes('包装:') ? content.split('包装:')[1].split('\n')[0].trim() : (item.packaging || '')
|
|
|
|
|
|
let c = content.includes('分类:') ? content.split('分类:')[1].split('\n')[0].trim() : (item.category || '')
|
|
|
|
|
|
c = normalizeCat(c)
|
|
|
|
|
|
return p === pkg && c === cat
|
|
|
|
|
|
})
|
|
|
|
|
|
if (items.length === 0) return null
|
|
|
|
|
|
const sum = items.reduce((a, b) => a + (b.deal_price || 0), 0)
|
|
|
|
|
|
return { avg: Math.round(sum / items.length), count: items.length, items }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '16px', marginBottom: '12px' }}>
|
|
|
|
|
|
<div style={{ marginBottom: '16px' }}>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '12px', flexWrap: 'wrap', marginBottom: '12px' }}>
|
|
|
|
|
|
{versions.map(v => (
|
|
|
|
|
|
<button key={v} onClick={() => setDealVersion(v)}
|
|
|
|
|
|
style={{ padding: '6px 16px', borderRadius: '8px', border: 'none', cursor: 'pointer',
|
|
|
|
|
|
background: dealVersion === v ? '#3b82f6' : 'rgba(255,255,255,0.1)',
|
|
|
|
|
|
color: '#fff', fontSize: '13px', fontWeight: dealVersion === v ? '600' : '400' }}>
|
|
|
|
|
|
{v}
|
|
|
|
|
|
</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div style={{ overflowX: 'auto' }}>
|
|
|
|
|
|
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '13px' }}>
|
|
|
|
|
|
<thead>
|
|
|
|
|
|
<tr>
|
|
|
|
|
|
<th style={{ padding: '8px', textAlign: 'left', color: '#94a3b8', borderBottom: '1px solid rgba(255,255,255,0.1)' }}></th>
|
|
|
|
|
|
{packagings.map(p => (
|
|
|
|
|
|
<th key={p} style={{ padding: '8px', textAlign: 'center', color: '#94a3b8', borderBottom: '1px solid rgba(255,255,255,0.1)' }}>{p}</th>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
</thead>
|
|
|
|
|
|
<tbody>
|
|
|
|
|
|
{(() => {
|
|
|
|
|
|
// 计算每个分类的平均价格
|
|
|
|
|
|
const catAvg = categories.map(cat => {
|
|
|
|
|
|
const prices = []
|
|
|
|
|
|
packagings.forEach(pkg => {
|
|
|
|
|
|
const d = calcAvg(pkg, cat)
|
|
|
|
|
|
if (d) prices.push(d.avg)
|
|
|
|
|
|
})
|
|
|
|
|
|
const avg = prices.length > 0 ? Math.round(prices.reduce((a,b) => a+b, 0) / prices.length) : 0
|
|
|
|
|
|
return { cat, avg }
|
|
|
|
|
|
}).filter(c => c.avg > 0)
|
|
|
|
|
|
// 分类名称标准化并按固定顺序排序
|
|
|
|
|
|
catAvg.sort((a, b) => a.avg - b.avg)
|
|
|
|
|
|
const categoryOrder = ['带4号', '带7号', '永恒号', '天马号', '天马王', '金山号', '金山王', '钻石号', '朦胧号', '朦胧王', '金马号', '金马王', '倒置号', '圆圆号']
|
|
|
|
|
|
const sortedCats = categoryOrder.filter(cat => catAvg.some(c => c.cat === cat))
|
|
|
|
|
|
.concat(catAvg.filter(c => !categoryOrder.includes(c.cat)).map(c => c.cat))
|
|
|
|
|
|
|
|
|
|
|
|
return sortedCats.map(cat => {
|
|
|
|
|
|
const rowData = packagings.map(pkg => calcAvg(pkg, cat))
|
|
|
|
|
|
const hasData = rowData.some(d => d !== null)
|
|
|
|
|
|
if (!hasData) return null
|
|
|
|
|
|
return (
|
|
|
|
|
|
<tr key={cat}>
|
|
|
|
|
|
<td style={{ padding: '8px', color: '#fbbf24', fontWeight: '500', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>{cat}</td>
|
|
|
|
|
|
{rowData.map((d, i) => (
|
|
|
|
|
|
<td key={i} style={{ padding: '8px', textAlign: 'center', borderBottom: '1px solid rgba(255,255,255,0.05)' }}>
|
|
|
|
|
|
{d ? (
|
|
|
|
|
|
<div style={{ color: '#22c55e', fontWeight: '600', cursor: 'pointer' }}
|
|
|
|
|
|
onClick={() => setDealDetailItems(d.items)}>
|
|
|
|
|
|
¥{d.avg.toLocaleString()}
|
|
|
|
|
|
<span style={{ color: '#64748b', fontSize: '11px', marginLeft: '4px' }}>({d.count})</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : <span style={{ color: '#475569' }}>-</span>}
|
|
|
|
|
|
</td>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</tr>
|
|
|
|
|
|
)
|
|
|
|
|
|
})
|
|
|
|
|
|
})()}
|
|
|
|
|
|
</tbody>
|
|
|
|
|
|
</table>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
})()}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 成交行情详情弹窗 */}
|
|
|
|
|
|
{dealDetailItems && (
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
|
|
|
|
|
|
background: 'rgba(0,0,0,0.8)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
|
|
|
|
|
padding: '20px'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
background: '#1e293b', borderRadius: '12px', padding: '16px',
|
|
|
|
|
|
maxWidth: '600px', maxHeight: '80vh', overflow: 'auto', width: '100%'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
|
|
|
|
|
<div style={{ color: '#fff', fontSize: '16px', fontWeight: '600' }}>成交详情列表</div>
|
|
|
|
|
|
<button onClick={() => setDealDetailItems(null)}
|
|
|
|
|
|
style={{ background: 'none', border: 'none', color: '#94a3b8', fontSize: '20px', cursor: 'pointer' }}>✕</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
|
|
|
|
{([...dealDetailItems].sort((a, b) => (a.deal_price || 0) - (b.deal_price || 0))).map((item, idx) => {
|
|
|
|
|
|
const content = item.content || ''
|
|
|
|
|
|
const grade = content.includes('评级:') ? content.split('评级:')[1].split('\n')[0].trim() : (item.grading_score || '')
|
|
|
|
|
|
const sizeMatch = content.match(/大小号:\s*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const size = sizeMatch ? sizeMatch[1].trim() : ''
|
|
|
|
|
|
const platformMatch = content.match(/平台:\s*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const platform = platformMatch ? platformMatch[1].trim() : '-'
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={idx} style={{
|
|
|
|
|
|
background: 'rgba(255,255,255,0.05)', borderRadius: '8px', padding: '12px',
|
|
|
|
|
|
display: 'flex', justifyContent: 'space-between', alignItems: 'center'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{ color: '#fbbf24', fontWeight: '600', marginBottom: '4px' }}>
|
|
|
|
|
|
{(item.title || '').split('-')[0]}
|
|
|
|
|
|
{size && <span style={{ color: '#94a3b8', marginLeft: '8px' }}>| {size}</span>}
|
|
|
|
|
|
{grade && <span style={{ color: '#06b6d4', marginLeft: '8px' }}>| {grade}</span>}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ color: '#94a3b8', fontSize: '12px' }}>
|
|
|
|
|
|
{item.deal_date} | {item.category} | {item.packaging} | {platform}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ color: '#22c55e', fontSize: '18px', fontWeight: '700' }}>
|
|
|
|
|
|
¥{item.deal_price?.toLocaleString()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-03-27 16:39:36 +08:00
|
|
|
|
{infoList.map(item => {
|
2026-04-15 21:42:52 +08:00
|
|
|
|
// 成交行情 tab - 按维度汇总展示
|
|
|
|
|
|
if (activeTab === 'deal') {
|
|
|
|
|
|
const content = item.content || ''
|
|
|
|
|
|
const categoryMatch = content.match(/分类:\s*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const packagingMatch = content.match(/包装:\s*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const platformMatch = content.match(/平台:\s*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const sellerMatch = content.match(/出售者:\s*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const dateMatch = content.match(/日期:\s*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const gradeMatch = content.match(/评级:\s*(.+?)(?:\n|$)/)
|
|
|
|
|
|
|
|
|
|
|
|
const category = categoryMatch ? categoryMatch[1].trim() : item.category || '-'
|
|
|
|
|
|
const packaging = packagingMatch ? packagingMatch[1].trim() : item.packaging || '-'
|
|
|
|
|
|
const platform = platformMatch ? platformMatch[1].trim() : '-'
|
|
|
|
|
|
const seller = sellerMatch ? sellerMatch[1].trim() : '-'
|
|
|
|
|
|
const dealDate = dateMatch ? dateMatch[1].trim() : item.deal_date || '-'
|
|
|
|
|
|
const grade = gradeMatch ? gradeMatch[1].trim() : (item.grading_score || '')
|
|
|
|
|
|
const gradingCompany = item.grading_company || ''
|
|
|
|
|
|
|
|
|
|
|
|
const serial = (item.title || '').split('-')[0] || ''
|
|
|
|
|
|
let version = '其他'
|
|
|
|
|
|
if (serial.startsWith('J0')) version = '龙钞'
|
|
|
|
|
|
else if (serial.startsWith('J1')) version = '马钞'
|
|
|
|
|
|
else if (serial.startsWith('J3')) version = '蛇钞'
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={item.id} style={{ borderBottom: '1px solid #334155', padding: '12px 0' }}>
|
|
|
|
|
|
{/* 标题行:冠字号 + 价格 */}
|
|
|
|
|
|
<div style={{ fontSize: '15px', fontWeight: '500', marginBottom: '6px' }}>
|
|
|
|
|
|
<span style={{ color: '#fbbf24' }}>{serial}</span>
|
|
|
|
|
|
<span style={{ color: '#22c55e', marginLeft: '12px' }}>¥{item.deal_price?.toLocaleString()}</span>
|
|
|
|
|
|
<span style={{ color: '#64748b', marginLeft: '12px', fontSize: '12px' }}>{item.deal_no}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/* 信息行:版别 | 包装 | 分类 | 评级 | 分数 | 平台 */}
|
|
|
|
|
|
<div style={{ fontSize: '12px', color: '#94a3b8' }}>
|
|
|
|
|
|
<span style={{ color: '#fff' }}>{version}</span>
|
|
|
|
|
|
<span style={{ margin: '0 8px' }}>|</span>
|
|
|
|
|
|
<span style={{ color: '#3b82f6' }}>{packaging}</span>
|
|
|
|
|
|
<span style={{ margin: '0 8px' }}>|</span>
|
|
|
|
|
|
<span style={{ color: '#fbbf24' }}>{category}</span>
|
|
|
|
|
|
{gradingCompany && <><span style={{ margin: '0 8px' }}>|</span><span>{gradingCompany}</span></>}
|
|
|
|
|
|
{grade && <><span style={{ margin: '0 8px' }}>|</span><span>{grade}</span></>}
|
|
|
|
|
|
<span style={{ margin: '0 8px' }}>|</span>
|
|
|
|
|
|
<span>{platform}</span>
|
|
|
|
|
|
<span style={{ margin: '0 8px' }}>|</span>
|
|
|
|
|
|
<span>{dealDate}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-27 16:39:36 +08:00
|
|
|
|
// 解析正文中的号码特征和联系方式
|
|
|
|
|
|
const content = item.content || ''
|
|
|
|
|
|
const featuresMatch = content.match(/号码特征[:\s]*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const contactMatch = content.match(/联系方式[:\s]*(.+?)(?:\n|$)/)
|
|
|
|
|
|
const features = featuresMatch ? featuresMatch[1].trim() : ''
|
|
|
|
|
|
const contact = contactMatch ? contactMatch[1].trim() : ''
|
|
|
|
|
|
// 去除正文中的价格、号码特征、联系方式
|
|
|
|
|
|
const cleanContent = content.replace(/价格[:\s]*.+?(\n|$)/g, '').replace(/号码特征[:\s]*.+?(\n|$)/g, '').replace(/联系方式[:\s]*.+?(\n|$)/g, '').trim()
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={item.id} style={{ background: '#1e293b', borderRadius: '12px', padding: '16px' }}>
|
|
|
|
|
|
{/* 标题 */}
|
|
|
|
|
|
<div style={{ color: '#fbbf24', fontSize: '15px', fontWeight: '600', marginBottom: '8px' }}>
|
|
|
|
|
|
{item.title}
|
2026-03-25 10:58:00 +08:00
|
|
|
|
</div>
|
2026-03-27 16:39:36 +08:00
|
|
|
|
{/* 创建日期 + 用户名 */}
|
|
|
|
|
|
<div style={{ color: '#64748b', fontSize: '12px', marginBottom: '8px' }}>
|
2026-04-15 21:42:52 +08:00
|
|
|
|
📅 {formatDate(item.created_at)} | 👤 {(item.content && item.content.match(/发布者: (.+)/)?.[1]) || item.user_name || '匿名用户'}
|
2026-03-27 16:39:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
{/* 号码特征 */}
|
|
|
|
|
|
{features && (
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<span style={{ color: '#94a3b8', fontSize: '13px' }}>号码特征:</span>
|
|
|
|
|
|
<span style={{ fontFamily: 'monospace', fontSize: '14px' }}>
|
|
|
|
|
|
{/* 如果features已包含J0则不再重复添加 */}
|
|
|
|
|
|
{features.startsWith('J0') ? (
|
|
|
|
|
|
features.split('').map((char, i) => {
|
|
|
|
|
|
const isDigit = /[0-9]/.test(char)
|
|
|
|
|
|
const letterColor = isDigit ? '#fff' : ({ X: '#94a3b8', A: '#f472b6', B: '#60a5fa', C: '#34d399', D: '#fbbf24', E: '#a78bfa', F: '#fb923c', G: '#22d3ee' }[char] || '#fff')
|
|
|
|
|
|
return <span key={i} style={{ color: i < 2 ? '#10b981' : letterColor }}>{char}</span>
|
|
|
|
|
|
})
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<>
|
|
|
|
|
|
{'J0'.split('').map((char, i) => (
|
|
|
|
|
|
<span key={i} style={{ color: '#10b981' }}>{char}</span>
|
|
|
|
|
|
))}
|
|
|
|
|
|
{features.split('').map((char, i) => {
|
|
|
|
|
|
const isDigit = /[0-9]/.test(char)
|
|
|
|
|
|
const letterColor = isDigit ? '#fff' : ({ X: '#94a3b8', A: '#f472b6', B: '#60a5fa', C: '#34d399', D: '#fbbf24', E: '#a78bfa', F: '#fb923c', G: '#22d3ee' }[char] || '#fff')
|
|
|
|
|
|
return <span key={i + 2} style={{ color: letterColor }}>{char}</span>
|
|
|
|
|
|
})}
|
|
|
|
|
|
</>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</span>
|
|
|
|
|
|
{/* 动态显示用到的字母说明 */}
|
|
|
|
|
|
{features.match(/[XABCFG]/) && (
|
|
|
|
|
|
<div style={{ color: '#64748b', fontSize: '10px', marginTop: '4px' }}>
|
|
|
|
|
|
备注说明:{(() => {
|
|
|
|
|
|
const used = []
|
|
|
|
|
|
if (features.includes('X')) used.push('X=任意数字')
|
|
|
|
|
|
if (features.includes('A')) used.push('A=非4')
|
|
|
|
|
|
if (features.includes('B')) used.push('B=非47')
|
|
|
|
|
|
if (features.includes('C')) used.push('C=非347')
|
|
|
|
|
|
if (features.includes('D')) used.push('D=非247')
|
|
|
|
|
|
if (features.includes('E')) used.push('E=非2347')
|
|
|
|
|
|
if (features.includes('F')) used.push('F=非23457')
|
|
|
|
|
|
if (features.includes('G')) used.push('G=非123457')
|
|
|
|
|
|
return used.join(' | ')
|
|
|
|
|
|
})()}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{/* 配号结果 - 仅寻配号显示 */}
|
|
|
|
|
|
{activeTab === 'seek' && (
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
2026-04-15 21:42:52 +08:00
|
|
|
|
<span
|
2026-03-27 16:39:36 +08:00
|
|
|
|
style={{ color: '#10b981', fontSize: '13px', fontWeight: 'bold', cursor: 'pointer' }}
|
|
|
|
|
|
onClick={() => fetchMatchCollections(item.id)}
|
|
|
|
|
|
>
|
2026-04-15 21:42:52 +08:00
|
|
|
|
自有{item.matched_count || 0}条藏品匹配成功
|
2026-03-27 16:39:36 +08:00
|
|
|
|
</span>
|
2026-04-15 21:42:52 +08:00
|
|
|
|
{item.network_matched_count !== undefined && (
|
|
|
|
|
|
<span
|
|
|
|
|
|
style={{ color: '#3b82f6', fontSize: '13px', fontWeight: 'bold', cursor: 'pointer', marginLeft: '12px' }}
|
|
|
|
|
|
onClick={() => fetchNetworkMatchCollections(item.id)}
|
|
|
|
|
|
>
|
|
|
|
|
|
网络数据{item.network_matched_count}条匹配成功
|
|
|
|
|
|
</span>
|
|
|
|
|
|
)}
|
2026-03-27 16:39:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-31 01:22:34 +08:00
|
|
|
|
{/* 正文 - 默认收起,点击展开 */}
|
2026-03-27 16:39:36 +08:00
|
|
|
|
{cleanContent && (
|
2026-03-31 01:22:34 +08:00
|
|
|
|
<div style={{ marginTop: '8px' }}>
|
|
|
|
|
|
{/* 展开收起按钮 */}
|
|
|
|
|
|
<div
|
|
|
|
|
|
onClick={() => toggleExpand(item.id)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
color: '#10b981',
|
|
|
|
|
|
fontSize: '13px',
|
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
|
display: 'flex',
|
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
|
gap: '6px',
|
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
|
background: 'rgba(16,185,129,0.1)',
|
|
|
|
|
|
borderRadius: '6px',
|
|
|
|
|
|
border: '1px solid rgba(16,185,129,0.2)'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<span style={{ fontSize: '16px' }}>{expandedItems[item.id] ? '▼' : '▶'}</span>
|
|
|
|
|
|
<span>{expandedItems[item.id] ? '收起详情' : '展开查看详情'}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{/* 展开后的内容 */}
|
|
|
|
|
|
{expandedItems[item.id] && (
|
|
|
|
|
|
<div style={{
|
|
|
|
|
|
color: '#e2e8f0',
|
|
|
|
|
|
fontSize: '14px',
|
|
|
|
|
|
lineHeight: '1.8',
|
|
|
|
|
|
marginTop: '12px',
|
|
|
|
|
|
background: 'rgba(255,255,255,0.03)',
|
|
|
|
|
|
padding: '12px',
|
|
|
|
|
|
borderRadius: '8px',
|
|
|
|
|
|
borderLeft: '3px solid #10b981'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{cleanContent.split('\n').map((line, i) => {
|
|
|
|
|
|
const isHighlight = line.includes('涨价') || line.includes('下跌') || line.includes('稀缺') || line.includes('热门')
|
|
|
|
|
|
return (
|
|
|
|
|
|
<div key={i} style={{
|
|
|
|
|
|
marginBottom: i < cleanContent.split('\n').length - 1 ? '8px' : 0,
|
|
|
|
|
|
color: isHighlight ? '#fbbf24' : '#e2e8f0',
|
|
|
|
|
|
fontWeight: isHighlight ? 'bold' : 'normal'
|
|
|
|
|
|
}}>
|
|
|
|
|
|
{line || ' '}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-27 16:39:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-28 08:24:08 +08:00
|
|
|
|
{/* 联系方式 - 默认隐藏,显示*** */}
|
2026-03-27 16:39:36 +08:00
|
|
|
|
{contact && (
|
2026-03-28 08:24:08 +08:00
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<div style={{ color: '#94a3b8', fontSize: '12px', marginBottom: '4px' }}>联系方式</div>
|
|
|
|
|
|
<div style={{ color: '#f97316', fontSize: '13px', padding: '8px', background: 'rgba(249,115,22,0.1)', borderRadius: '6px' }}>
|
2026-03-28 14:30:02 +08:00
|
|
|
|
联系方式:{showContactId === item.id ? contact : '匹配成功后可查看'}
|
2026-03-28 08:24:08 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{/* 是否匹配按钮 - 仅对已登录用户显示 */}
|
|
|
|
|
|
{currentUser && (
|
|
|
|
|
|
<div style={{ marginTop: '8px' }}>
|
|
|
|
|
|
<span style={{
|
2026-03-28 14:30:02 +08:00
|
|
|
|
padding: '5px 14px',
|
2026-03-28 08:24:08 +08:00
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
fontSize: '12px',
|
2026-03-28 14:30:02 +08:00
|
|
|
|
background: (item.is_matched === 'matched' || matchedStatus[item.id] === 'matched')
|
|
|
|
|
|
? ((item.user_id === currentUserId || item.matched_user_id === currentUserId) ? '#10b981' : '#6b7280')
|
|
|
|
|
|
: '#6b7280',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
opacity: (item.is_matched === 'matched' || matchedStatus[item.id] === 'matched')
|
|
|
|
|
|
? ((item.user_id === currentUserId || item.matched_user_id === currentUserId) ? 1 : 0.5)
|
|
|
|
|
|
: 1
|
2026-03-28 08:24:08 +08:00
|
|
|
|
}}>
|
|
|
|
|
|
{(item.is_matched === 'matched' || matchedStatus[item.id] === 'matched') ? '已经匹配' : '尚未匹配'}
|
2026-03-29 15:03:52 +08:00
|
|
|
|
{(item.is_matched === 'matched' || matchedStatus[item.id] === 'matched') && <button onClick={() => item.user_id === currentUserId ? fetchMatchedUserInfo(item.id) : fetchPublisherInfo(item.id)} style={{padding:'4px 10px',borderRadius:'4px',fontSize:'12px',background:(item.user_id === currentUserId || item.matched_user_id === currentUserId) ? '#10b981' : '#6b7280',border:'none',color:(item.user_id === currentUserId || item.matched_user_id === currentUserId) ? '#fb923c' : '#9ca3af',fontWeight:'bold',cursor:(item.user_id === currentUserId || item.matched_user_id === currentUserId) ? 'pointer' : 'not-allowed',marginLeft:'4px'}}>{item.user_id === currentUserId ? '匹配者信息' : '发布者信息'}</button>}
|
2026-03-28 08:24:08 +08:00
|
|
|
|
</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 匹配并联系藏友 / 留言藏友 按钮 - 有匹配藏品才可点击 */}
|
|
|
|
|
|
{currentUser && (
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', marginTop: '8px' }}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
2026-04-15 21:42:52 +08:00
|
|
|
|
if (item.is_matched === 'matched') return
|
2026-03-28 08:24:08 +08:00
|
|
|
|
if (item.matched_count > 0) {
|
|
|
|
|
|
matchAndContact(item.id)
|
|
|
|
|
|
} else {
|
2026-03-29 16:20:51 +08:00
|
|
|
|
setCustomModal({show: true, title: '提示', content: '暂无匹配藏品,无法匹配'})
|
2026-03-28 08:24:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
}}
|
2026-04-15 21:42:52 +08:00
|
|
|
|
disabled={item.matched_count === 0 || item.is_matched === 'matched'}
|
2026-03-28 08:24:08 +08:00
|
|
|
|
style={{
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
|
borderRadius: '6px',
|
|
|
|
|
|
border: 'none',
|
|
|
|
|
|
fontSize: '12px',
|
2026-04-15 21:42:52 +08:00
|
|
|
|
background: (item.matched_count > 0 && item.is_matched !== 'matched') ? '#3b82f6' : '#4b5563',
|
2026-03-28 08:24:08 +08:00
|
|
|
|
color: '#fff',
|
2026-04-15 21:42:52 +08:00
|
|
|
|
cursor: (item.matched_count > 0 && item.is_matched !== 'matched') ? 'pointer' : 'not-allowed',
|
|
|
|
|
|
opacity: (item.matched_count > 0 && item.is_matched !== 'matched') ? 1 : 0.5
|
2026-03-28 08:24:08 +08:00
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
匹配并联系藏友
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setShowCommentId(showCommentId === item.id ? null : item.id)
|
|
|
|
|
|
fetchComments(item.id)
|
|
|
|
|
|
}}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
padding: '8px 12px',
|
|
|
|
|
|
borderRadius: '6px',
|
|
|
|
|
|
border: '1px solid #334155',
|
|
|
|
|
|
fontSize: '12px',
|
|
|
|
|
|
background: '#1e293b',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
留言藏友
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 留言区域 */}
|
|
|
|
|
|
{showCommentId === item.id && (
|
|
|
|
|
|
<div style={{ marginTop: '8px', padding: '12px', background: '#0f172a', borderRadius: '6px' }}>
|
|
|
|
|
|
{/* 评论列表 */}
|
|
|
|
|
|
{comments[item.id] && comments[item.id].length > 0 && (
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
{comments[item.id].map((c, idx) => (
|
|
|
|
|
|
<div key={c.id || idx} style={{ padding: '8px 0', borderBottom: '1px solid #334155' }}>
|
|
|
|
|
|
<div style={{ color: '#3b82f6', fontSize: '12px', marginBottom: '4px' }}>{c.user_name || '匿名用户'}</div>
|
|
|
|
|
|
<div style={{ color: '#e2e8f0', fontSize: '13px' }}>{c.content}</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
{/* 评论输入框 */}
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px' }}>
|
|
|
|
|
|
<input
|
|
|
|
|
|
type="text"
|
|
|
|
|
|
value={commentText}
|
|
|
|
|
|
onChange={(e) => setCommentText(e.target.value)}
|
|
|
|
|
|
placeholder="输入留言..."
|
|
|
|
|
|
style={{
|
|
|
|
|
|
flex: 1,
|
|
|
|
|
|
padding: '8px',
|
|
|
|
|
|
background: '#1e293b',
|
|
|
|
|
|
border: '1px solid #334155',
|
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
fontSize: '13px'
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => addComment(item.id)}
|
|
|
|
|
|
style={{
|
|
|
|
|
|
padding: '8px 16px',
|
|
|
|
|
|
background: '#10b981',
|
|
|
|
|
|
border: 'none',
|
|
|
|
|
|
borderRadius: '4px',
|
|
|
|
|
|
color: '#fff',
|
|
|
|
|
|
fontSize: '13px',
|
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
发送
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-27 16:39:36 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
})}
|
2026-03-26 15:32:27 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
2026-03-27 16:39:36 +08:00
|
|
|
|
|
|
|
|
|
|
{/* 匹配藏品列表弹窗 */}
|
|
|
|
|
|
{showMatchList && (
|
|
|
|
|
|
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
|
|
|
|
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '20px', width: '90%', maxHeight: '80vh', overflow: 'auto' }}>
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
|
|
|
|
|
<h3 style={{ color: '#fff', margin: 0 }}>匹配藏品清单</h3>
|
|
|
|
|
|
<button onClick={() => setShowMatchList(false)} style={{ background: 'transparent', border: 'none', color: '#94a3b8', fontSize: '20px', cursor: 'pointer' }}>×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{matchCollections.length === 0 ? (
|
|
|
|
|
|
<div style={{ color: '#94a3b8', textAlign: 'center', padding: '20px' }}>暂无匹配藏品</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
|
|
|
|
{matchCollections.map(c => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={c.id}
|
|
|
|
|
|
style={{ background: '#0f172a', borderRadius: '8px', padding: '12px', cursor: 'pointer' }}
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
setShowMatchList(false)
|
|
|
|
|
|
// 跳转到藏品列表页并定位到该藏品
|
|
|
|
|
|
window.location.hash = `#/list?filter=id&value=${c.id}`
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ color: '#e2e8f0', fontSize: '13px' }}>
|
|
|
|
|
|
<span style={{ color: '#94a3b8' }}>编号: </span>{c.code || c.id.substring(0,8)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ color: '#e2e8f0', fontSize: '13px', marginTop: '4px' }}>
|
|
|
|
|
|
<span style={{ color: '#94a3b8' }}>冠字号: </span>{c.number}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ color: '#e2e8f0', fontSize: '13px', marginTop: '4px' }}>
|
|
|
|
|
|
<span style={{ color: '#94a3b8' }}>状态: </span>{c.status === 'in_collection' ? '持仓中' : c.status}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-04-15 21:42:52 +08:00
|
|
|
|
{/* 网络数据匹配藏品列表弹窗 */}
|
|
|
|
|
|
{showNetworkMatchList && (
|
|
|
|
|
|
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
|
|
|
|
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '20px', width: '90%', maxHeight: '80vh', overflow: 'auto' }}>
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
|
|
|
|
|
<h3 style={{ color: '#fff', margin: 0 }}>匹配藏品清单(网络数据)</h3>
|
|
|
|
|
|
<button onClick={() => setShowNetworkMatchList(false)} style={{ background: 'transparent', border: 'none', color: '#94a3b8', fontSize: '20px', cursor: 'pointer' }}>×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{networkMatchCollections.length === 0 ? (
|
|
|
|
|
|
<div style={{ color: '#94a3b8', textAlign: 'center', padding: '20px' }}>暂无匹配藏品</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
|
|
|
|
|
|
{networkMatchCollections.map(c => (
|
|
|
|
|
|
<div
|
|
|
|
|
|
key={c.id}
|
|
|
|
|
|
style={{ background: '#0f172a', borderRadius: '8px', padding: '12px', cursor: 'pointer' }}
|
|
|
|
|
|
onClick={() => {
|
|
|
|
|
|
if (c.post_url) {
|
|
|
|
|
|
window.open(c.post_url, '_blank')
|
|
|
|
|
|
}
|
|
|
|
|
|
setShowNetworkMatchList(false)
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<div style={{ color: '#e2e8f0', fontSize: '13px' }}>
|
|
|
|
|
|
<span style={{ color: '#94a3b8' }}>名称: </span>{c.name || '-'}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ color: '#e2e8f0', fontSize: '13px', marginTop: '4px' }}>
|
|
|
|
|
|
<span style={{ color: '#94a3b8' }}>冠字号: </span>{c.crown_code}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{c.price && (
|
|
|
|
|
|
<div style={{ color: '#e2e8f0', fontSize: '13px', marginTop: '4px' }}>
|
|
|
|
|
|
<span style={{ color: '#94a3b8' }}>价格: </span>¥{c.price}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
<div style={{ color: '#3b82f6', fontSize: '12px', marginTop: '6px' }}>
|
|
|
|
|
|
点击查看原帖 →
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{/* 分页组件 */}
|
|
|
|
|
|
{infoList.length > 0 && (
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px', padding: '16px', marginTop: '8px' }}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => { if (currentPage > 1) { setCurrentPage(currentPage - 1); fetchInfoList() } }}
|
|
|
|
|
|
disabled={currentPage === 1}
|
|
|
|
|
|
style={{ padding: '8px 16px', borderRadius: '6px', border: 'none', background: currentPage === 1 ? '#374151' : '#3b82f6', color: currentPage === 1 ? '#6b7280' : '#fff', cursor: currentPage === 1 ? 'not-allowed' : 'pointer', fontSize: '13px' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
上一页
|
|
|
|
|
|
</button>
|
|
|
|
|
|
<span style={{ color: '#94a3b8', fontSize: '13px' }}>
|
|
|
|
|
|
第 {currentPage} / {totalPages} 页
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => { if (currentPage < totalPages) { setCurrentPage(currentPage + 1); fetchInfoList() } }}
|
|
|
|
|
|
disabled={currentPage >= totalPages}
|
|
|
|
|
|
style={{ padding: '8px 16px', borderRadius: '6px', border: 'none', background: currentPage >= totalPages ? '#374151' : '#3b82f6', color: currentPage >= totalPages ? '#6b7280' : '#fff', cursor: currentPage >= totalPages ? 'not-allowed' : 'pointer', fontSize: '13px' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
下一页
|
|
|
|
|
|
</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
2026-03-27 16:39:36 +08:00
|
|
|
|
{/* 我的寻号弹窗 */}
|
|
|
|
|
|
{showMySeeks && (
|
|
|
|
|
|
<div style={{ position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, background: 'rgba(0,0,0,0.8)', zIndex: 1000, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
|
|
|
|
|
|
<div style={{ background: '#1e293b', borderRadius: '12px', padding: '20px', width: '90%', maxHeight: '80vh', overflow: 'auto' }}>
|
|
|
|
|
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
|
|
|
|
|
|
<h3 style={{ color: '#fff', margin: 0 }}>我发布的寻号</h3>
|
|
|
|
|
|
<button onClick={() => setShowMySeeks(false)} style={{ background: 'transparent', border: 'none', color: '#94a3b8', fontSize: '20px', cursor: 'pointer' }}>×</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
{mySeekList.length === 0 ? (
|
|
|
|
|
|
<div style={{ color: '#94a3b8', textAlign: 'center', padding: '20px' }}>暂无发布的寻号</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
|
|
|
|
|
{mySeekList.map(item => (
|
|
|
|
|
|
<div key={item.id} style={{ background: '#0f172a', borderRadius: '8px', padding: '12px' }}>
|
|
|
|
|
|
{editingSeek && editingSeek.id === item.id ? (
|
|
|
|
|
|
// 编辑模式
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<label style={{ color: '#94a3b8', fontSize: '12px' }}>版别</label>
|
|
|
|
|
|
<select
|
|
|
|
|
|
value={editingSeek.edition || '龙钞'}
|
|
|
|
|
|
onChange={(e) => setEditingSeek({...editingSeek, edition: e.target.value})}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '4px', color: '#fff', marginTop: '4px' }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<option value="龙钞">龙钞</option>
|
|
|
|
|
|
<option value="马钞">马钞</option>
|
|
|
|
|
|
<option value="蛇钞">蛇钞</option>
|
|
|
|
|
|
</select>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<label style={{ color: '#94a3b8', fontSize: '12px' }}>类型</label>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', marginTop: '4px' }}>
|
|
|
|
|
|
{['标百', '标十', '单张'].map(t => (
|
|
|
|
|
|
<button
|
|
|
|
|
|
key={t}
|
|
|
|
|
|
onClick={() => setEditingSeek({...editingSeek, type: t})}
|
|
|
|
|
|
style={{ flex: 1, padding: '8px', background: editingSeek.type === t ? '#10b981' : '#1e293b', border: '1px solid #334155', borderRadius: '4px', color: '#fff', cursor: 'pointer' }}
|
|
|
|
|
|
>{t}</button>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<label style={{ color: '#94a3b8', fontSize: '12px' }}>匹配度</label>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px', marginTop: '4px' }}>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setEditingSeek({...editingSeek, matchType: '精准'})}
|
|
|
|
|
|
style={{ flex: 1, padding: '8px', background: editingSeek.matchType === '精准' ? '#10b981' : '#1e293b', border: '1px solid #334155', borderRadius: '4px', color: '#fff', cursor: 'pointer' }}
|
|
|
|
|
|
>精准</button>
|
|
|
|
|
|
<button
|
|
|
|
|
|
onClick={() => setEditingSeek({...editingSeek, matchType: '模糊'})}
|
|
|
|
|
|
style={{ flex: 1, padding: '8px', background: editingSeek.matchType === '模糊' ? '#10b981' : '#1e293b', border: '1px solid #334155', borderRadius: '4px', color: '#fff', cursor: 'pointer' }}
|
|
|
|
|
|
>模糊</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<label style={{ color: '#94a3b8', fontSize: '12px' }}>价格(选填)</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
value={editingSeek.price || ''}
|
|
|
|
|
|
onChange={(e) => setEditingSeek({...editingSeek, price: e.target.value})}
|
|
|
|
|
|
placeholder="期望价格"
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '4px', color: '#fff', marginTop: '4px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<label style={{ color: '#94a3b8', fontSize: '12px' }}>正文</label>
|
|
|
|
|
|
<textarea
|
|
|
|
|
|
value={editingSeek.content || ''}
|
|
|
|
|
|
onChange={(e) => setEditingSeek({...editingSeek, content: e.target.value})}
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '4px', color: '#fff', minHeight: '60px', marginTop: '4px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ marginBottom: '8px' }}>
|
|
|
|
|
|
<label style={{ color: '#94a3b8', fontSize: '12px' }}>联系方式(必填)</label>
|
|
|
|
|
|
<input
|
|
|
|
|
|
value={editingSeek.contact || ''}
|
|
|
|
|
|
onChange={(e) => setEditingSeek({...editingSeek, contact: e.target.value})}
|
|
|
|
|
|
placeholder="手机号或微信"
|
|
|
|
|
|
style={{ width: '100%', padding: '8px', background: '#1e293b', border: '1px solid #334155', borderRadius: '4px', color: '#fff', marginTop: '4px' }}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ display: 'flex', gap: '8px' }}>
|
|
|
|
|
|
<button onClick={handleUpdateSeek} style={{ flex: 1, padding: '10px', background: '#10b981', border: 'none', borderRadius: '6px', color: '#fff', cursor: 'pointer' }}>保存</button>
|
|
|
|
|
|
<button onClick={() => setEditingSeek(null)} style={{ flex: 1, padding: '10px', background: '#6b7280', border: 'none', borderRadius: '6px', color: '#fff', cursor: 'pointer' }}>取消</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
// 显示模式
|
|
|
|
|
|
<div>
|
|
|
|
|
|
<div style={{ color: '#fbbf24', fontSize: '14px', fontWeight: '500', marginBottom: '4px' }}>{item.title}</div>
|
|
|
|
|
|
<div style={{ color: '#64748b', fontSize: '12px', marginBottom: '4px' }}>创建于: {new Date(item.created_at).toLocaleDateString()}</div>
|
|
|
|
|
|
<div style={{ color: '#10b981', fontSize: '12px', marginBottom: '8px' }}>配号结果: {item.matched_count || 0}条藏品匹配成功</div>
|
|
|
|
|
|
<button onClick={() => {
|
|
|
|
|
|
// 解析内容提取字段
|
|
|
|
|
|
const content = item.content || ''
|
|
|
|
|
|
const contactMatch = content.match(/联系方式[:\s]*(.+?)$/m)
|
|
|
|
|
|
const priceMatch = content.match(/价格[:\s]*(.+?)$/m)
|
|
|
|
|
|
setEditingSeek({
|
|
|
|
|
|
id: item.id,
|
|
|
|
|
|
title: item.title,
|
|
|
|
|
|
content: content.split('\n联系方式:')[0],
|
|
|
|
|
|
edition: item.expect_category || '龙钞',
|
|
|
|
|
|
type: item.title.match(/标百|标十|单张/)?.[0] || '标百',
|
|
|
|
|
|
matchType: item.title.includes('精准') ? '精准' : '模糊',
|
|
|
|
|
|
price: priceMatch ? priceMatch[1].trim() : '',
|
|
|
|
|
|
contact: contactMatch ? contactMatch[1].trim() : ''
|
|
|
|
|
|
})
|
|
|
|
|
|
}} style={{ padding: '4px 8px', background: '#3b82f6', border: 'none', borderRadius: '4px', color: '#fff', fontSize: '12px', cursor: 'pointer' }}>编辑</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-29 16:20:51 +08:00
|
|
|
|
{customModal.show && (
|
|
|
|
|
|
<div style={{position:'fixed',top:0,left:0,right:0,bottom:0,background:'rgba(0,0,0,0.7)',zIndex:2000,display:'flex',alignItems:'center',justifyContent:'center'}}>
|
|
|
|
|
|
<div style={{background:'linear-gradient(135deg, #1e293b 0%, #0f172a 100%)',borderRadius:'16px',padding:'24px',width:'85%',maxWidth:'380px',boxShadow:'0 20px 60px rgba(0,0,0,0.5)',border:'1px solid rgba(255,255,255,0.1)'}}>
|
|
|
|
|
|
<div style={{textAlign:'center',marginBottom:'20px'}}>
|
|
|
|
|
|
<div style={{width:'60px',height:'60px',background:'linear-gradient(135deg, #10b981 0%, #059669 100%)',borderRadius:'50%',display:'flex',alignItems:'center',justifyContent:'center',margin:'0 auto 12px'}}>
|
|
|
|
|
|
<span style={{fontSize:'28px'}}>ℹ️</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<h3 style={{color:'#10b981',fontSize:'20px',fontWeight:'bold',margin:'0'}}>{customModal.title}</h3>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{background:'rgba(0,0,0,0.3)',borderRadius:'12px',padding:'16px',marginBottom:'20px'}}>
|
|
|
|
|
|
{customModal.content.split('\n').map((line, i) => (
|
|
|
|
|
|
<div key={i} style={{color:'#e2e8f0',fontSize:'15px',lineHeight:'2',display:'flex',justifyContent:'space-between'}}>
|
|
|
|
|
|
<span style={{color:'#94a3b8'}}>{line.split(':')[0]}</span>
|
|
|
|
|
|
<span style={{color:'#fbbf24',fontWeight:'600'}}>{line.split(':')[1]}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
))}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<button onClick={()=>setCustomModal({...customModal,show:false})} style={{width:'100%',padding:'14px',background:'linear-gradient(135deg, #10b981 0%, #059669 100%)',border:'none',borderRadius:'10px',color:'#fff',fontSize:'16px',fontWeight:'bold',cursor:'pointer',boxShadow:'0 4px 15px rgba(16,185,129,0.4)'}}>知道了</button>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
)}
|
2026-03-25 10:58:00 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|