feat: 表拆分seek_info和deal_info,优化显示和分页

This commit is contained in:
甲辰生产 2026-04-14 23:47:16 +08:00
parent ea6528b7f3
commit 28e499dbf9
8 changed files with 574 additions and 52 deletions

View File

@ -17,6 +17,8 @@ from app.routers import ocr as ocr_router
from app.routers import users as users_router from app.routers import users as users_router
from app.routers import information as information_router from app.routers import information as information_router
from app.routers import yichens as yichens_router from app.routers import yichens as yichens_router
from app.routers import seek as seek_router
from app.routers import deal as deal_router
# 版本信息 - 从 config/VERSION 文件读取 # 版本信息 - 从 config/VERSION 文件读取
def get_version(): def get_version():
@ -84,6 +86,8 @@ app.include_router(users_router.router) # 当前用户接口
app.include_router(users_router.admin_router) # 管理员用户管理 app.include_router(users_router.admin_router) # 管理员用户管理
app.include_router(information_router.router) app.include_router(information_router.router)
app.include_router(yichens_router.router) # 一尘看板 app.include_router(yichens_router.router) # 一尘看板
app.include_router(seek_router.router) # 寻配号
app.include_router(deal_router.router) # 成交行情
@app.get("/") @app.get("/")
@ -106,3 +110,6 @@ if __name__ == "__main__":
import uvicorn import uvicorn
port = int(os.getenv("PORT", "3000")) port = int(os.getenv("PORT", "3000"))
uvicorn.run(app, host="0.0.0.0", port=port) uvicorn.run(app, host="0.0.0.0", port=port)
from app.routers import seek as seek_router
from app.routers import deal as deal_router

View File

@ -0,0 +1,54 @@
from sqlalchemy import Column, String, Text, Float, Date, Boolean, Integer, DateTime
from sqlalchemy.sql import func
from app.core.database import Base
import uuid
def generate_uuid():
return str(uuid.uuid4())
class DealInfo(Base):
__tablename__ = "deal_info"
id = Column(String(36), primary_key=True, default=generate_uuid)
user_id = Column(String(36), nullable=True, index=True)
# 标题和内容
title = Column(String(255), nullable=False)
content = Column(Text, nullable=True)
# 成交信息
deal_price = Column(Float, nullable=True) # 成交价格
deal_date = Column(Date, nullable=True) # 成交日期
deal_no = Column(String(20), nullable=True, index=True) # 行情编号从A000001开始递增
# 包装和分类
packaging = Column(String(50), nullable=True) # 包装(标百/标十/单张)
category = Column(String(100), nullable=True) # 分类
# 评级相关
is_graded = Column(Boolean, default=False) # 是否评级
grading_company = Column(String(100), nullable=True) # 评级机构
grading_score = Column(String(50), nullable=True) # 评级分数
# 号码特征
tail_number = Column(String(10), nullable=True) # 尾号
size_type = Column(String(20), nullable=True) # 大小号
# 版别
version = Column(String(50), nullable=True) # 版别
# 交易信息
platform = Column(String(50), nullable=True) # 成交平台
seller = Column(String(100), nullable=True) # 出售者
buyer = Column(String(100), nullable=True) # 购买者
# 状态
status = Column(String(20), default="active")
# 统计
view_count = Column(Integer, default=0)
contact_count = Column(Integer, default=0)
# 时间
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())

View File

@ -0,0 +1,39 @@
from sqlalchemy import Column, String, Text, Float, Date, Boolean, Integer, DateTime
from sqlalchemy.sql import func
from app.core.database import Base
import uuid
def generate_uuid():
return str(uuid.uuid4())
class SeekInfo(Base):
__tablename__ = "seek_info"
id = Column(String(36), primary_key=True, default=generate_uuid)
user_id = Column(String(36), nullable=False, index=True)
# 标题和内容
title = Column(String(255), nullable=False)
content = Column(Text, nullable=True)
# 期望条件(求购条件)
expect_category = Column(String(100), nullable=True) # 期望类别
expect_version = Column(String(100), nullable=True) # 期望版别
expect_packaging = Column(String(100), nullable=True) # 期望包装
expect_number = Column(String(50), nullable=True) # 期望号码
expect_price_min = Column(Float, nullable=True) # 期望最低价
expect_price_max = Column(Float, nullable=True) # 期望最高价
# 匹配状态
status = Column(String(20), default="active") # active/closed/expired
is_matched = Column(String(10), default="false") # 是否已匹配
matched_user_id = Column(String(36), nullable=True) # 匹配的用户ID
matched_contact = Column(String(100), nullable=True) # 匹配的联系方式
# 统计
view_count = Column(Integer, default=0)
contact_count = Column(Integer, default=0)
# 时间
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())

243
backend/app/routers/deal.py Normal file
View File

@ -0,0 +1,243 @@
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import Optional
from datetime import datetime, date
from app.core.database import get_db
from app.core.auth import get_current_user
from app.models.deal_info import DealInfo
router = APIRouter(prefix="/api/deal", tags=["成交行情"])
# ============ Schema ============
class DealInfoCreate(BaseModel):
title: str
content: Optional[str] = None
deal_price: Optional[float] = None
deal_date: Optional[str] = None # YYYY-MM-DD
packaging: Optional[str] = None
category: Optional[str] = None
is_graded: Optional[bool] = False
grading_company: Optional[str] = None
grading_score: Optional[str] = None
tail_number: Optional[str] = None
size_type: Optional[str] = None
version: Optional[str] = None
platform: Optional[str] = None
seller: Optional[str] = None
buyer: Optional[str] = None
class DealInfoUpdate(BaseModel):
title: Optional[str] = None
content: Optional[str] = None
deal_price: Optional[float] = None
deal_date: Optional[str] = None
packaging: Optional[str] = None
category: Optional[str] = None
is_graded: Optional[bool] = None
grading_company: Optional[str] = None
grading_score: Optional[str] = None
tail_number: Optional[str] = None
size_type: Optional[str] = None
version: Optional[str] = None
platform: Optional[str] = None
seller: Optional[str] = None
buyer: Optional[str] = None
status: Optional[str] = None
class DealInfoResponse(BaseModel):
id: str
user_id: Optional[str]
title: str
content: Optional[str]
deal_price: Optional[float]
deal_date: Optional[date]
deal_no: Optional[str]
packaging: Optional[str]
category: Optional[str]
is_graded: Optional[bool]
grading_company: Optional[str]
grading_score: Optional[str]
tail_number: Optional[str]
size_type: Optional[str]
version: Optional[str]
platform: Optional[str]
seller: Optional[str]
buyer: Optional[str]
status: str
view_count: int
contact_count: int
created_at: Optional[datetime]
updated_at: Optional[datetime]
class Config:
from_attributes = True
# 生成行情编号
def generate_deal_no(db: Session):
"""生成行情编号从A000001开始递增"""
last = db.query(DealInfo).order_by(DealInfo.deal_no.desc()).first()
if last and last.deal_no:
# 例如 A000001 -> 2 -> A000002
num = int(last.deal_no[1:]) + 1
return f"A{num:06d}"
return "A000001"
# ============ API ============
@router.get("/list", response_model=list[DealInfoResponse])
def get_deal_list(
status: str = Query("active"),
deal_date: Optional[str] = Query(None),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=500),
current_user: Optional = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取成交行情列表"""
query = db.query(DealInfo).filter(DealInfo.status == status)
# 成交日期过滤
if deal_date:
query = query.filter(DealInfo.deal_date == deal_date)
# 排序:优先成交日期倒序,同日按编号倒序
query = query.order_by(DealInfo.deal_date.desc().nullslast(), DealInfo.deal_no.desc().nullslast())
# 分页
offset = (page - 1) * page_size
items = query.offset(offset).limit(page_size).all()
return items
@router.get("/stats")
def get_deal_stats(
db: Session = Depends(get_db)
):
"""获取成交行情统计"""
total = db.query(DealInfo).filter(DealInfo.status == "active").count()
# 按日期统计
from sqlalchemy import func
date_stats = db.query(
DealInfo.deal_date,
func.count(DealInfo.id).label('count')
).filter(
DealInfo.status == "active",
DealInfo.deal_date.isnot(None)
).group_by(DealInfo.deal_date).order_by(DealInfo.deal_date.desc()).limit(10).all()
return {
"total": total,
"by_date": [{"date": str(d.deal_date), "count": d.count} for d in date_stats]
}
@router.post("", response_model=DealInfoResponse)
def create_deal(
data: DealInfoCreate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""创建成交行情"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
# 生成行情编号
deal_no = generate_deal_no(db)
# 解析日期
deal_date = None
if data.deal_date:
try:
deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date()
except:
pass
deal = DealInfo(
user_id=current_user.f99_90_id if current_user else None,
title=data.title,
content=data.content,
deal_price=data.deal_price,
deal_date=deal_date,
deal_no=deal_no,
packaging=data.packaging,
category=data.category,
is_graded=data.is_graded or False,
grading_company=data.grading_company,
grading_score=data.grading_score,
tail_number=data.tail_number,
size_type=data.size_type,
version=data.version,
platform=data.platform,
seller=data.seller,
buyer=data.buyer,
status="active"
)
db.add(deal)
db.commit()
db.refresh(deal)
return deal
@router.get("/{deal_id}", response_model=DealInfoResponse)
def get_deal(
deal_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取成交行情详情"""
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
if not deal:
raise HTTPException(status_code=404, detail="成交行情不存在")
# 增加浏览数
deal.view_count += 1
db.commit()
return deal
@router.put("/{deal_id}", response_model=DealInfoResponse)
def update_deal(
deal_id: str,
data: DealInfoUpdate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""更新成交行情"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
if not deal:
raise HTTPException(status_code=404, detail="成交行情不存在")
# 处理日期
if data.deal_date:
try:
data.deal_date = datetime.strptime(data.deal_date, "%Y-%m-%d").date()
except:
data.deal_date = None
for key, value in data.model_dump(exclude_unset=True).items():
setattr(deal, key, value)
db.commit()
db.refresh(deal)
return deal
@router.delete("/{deal_id}")
def delete_deal(
deal_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""删除成交行情"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
deal = db.query(DealInfo).filter(DealInfo.id == deal_id).first()
if not deal:
raise HTTPException(status_code=404, detail="成交行情不存在")
deal.status = "deleted"
db.commit()
return {"message": "删除成功"}

View File

@ -134,7 +134,7 @@ def get_information_list(
query = query.filter(Information.deal_date == deal_date_obj) query = query.filter(Information.deal_date == deal_date_obj)
# 按创建时间倒序 # 按创建时间倒序
query = query.order_by(Information.created_at.desc()) query = query.order_by(Information.deal_date.desc().nullslast(), Information.deal_no.desc().nullslast())
# 分页 # 分页
offset = (page - 1) * page_size offset = (page - 1) * page_size

184
backend/app/routers/seek.py Normal file
View File

@ -0,0 +1,184 @@
from fastapi import APIRouter, Depends, Query, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import Optional
from datetime import datetime
from app.core.database import get_db
from app.core.auth import get_current_user
from app.models.seek_info import SeekInfo
router = APIRouter(prefix="/api/seek", tags=["寻配号"])
# ============ Schema ============
class SeekInfoCreate(BaseModel):
title: str
content: Optional[str] = None
expect_category: Optional[str] = None
expect_version: Optional[str] = None
expect_packaging: Optional[str] = None
expect_number: Optional[str] = None
expect_price_min: Optional[float] = None
expect_price_max: Optional[float] = None
class SeekInfoUpdate(BaseModel):
title: Optional[str] = None
content: Optional[str] = None
expect_category: Optional[str] = None
expect_version: Optional[str] = None
expect_packaging: Optional[str] = None
expect_number: Optional[str] = None
expect_price_min: Optional[float] = None
expect_price_max: Optional[float] = None
status: Optional[str] = None
class SeekInfoResponse(BaseModel):
id: str
user_id: str
title: str
content: Optional[str]
expect_category: Optional[str]
expect_version: Optional[str]
expect_packaging: Optional[str]
expect_number: Optional[str]
expect_price_min: Optional[float]
expect_price_max: Optional[float]
status: str
is_matched: Optional[str]
matched_user_id: Optional[str]
matched_contact: Optional[str]
view_count: int
contact_count: int
created_at: Optional[datetime]
updated_at: Optional[datetime]
class Config:
from_attributes = True
# ============ API ============
@router.get("/list", response_model=list[SeekInfoResponse])
def get_seek_list(
status: str = Query("active"),
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
current_user: Optional = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取寻配号列表"""
query = db.query(SeekInfo).filter(SeekInfo.status == status)
# 排序
query = query.order_by(SeekInfo.created_at.desc())
# 分页
offset = (page - 1) * page_size
items = query.offset(offset).limit(page_size).all()
return items
@router.get("/stats")
def get_seek_stats(
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取寻配号统计"""
total = db.query(SeekInfo).filter(SeekInfo.status == "active").count()
matched = db.query(SeekInfo).filter(SeekInfo.is_matched == "true").count()
return {
"total": total,
"matched": matched,
"unmatched": total - matched
}
@router.post("", response_model=SeekInfoResponse)
def create_seek(
data: SeekInfoCreate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""创建寻配号"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
seek = SeekInfo(
user_id=current_user.f99_90_id,
title=data.title,
content=data.content,
expect_category=data.expect_category,
expect_version=data.expect_version,
expect_packaging=data.expect_packaging,
expect_number=data.expect_number,
expect_price_min=data.expect_price_min,
expect_price_max=data.expect_price_max,
status="active"
)
db.add(seek)
db.commit()
db.refresh(seek)
return seek
@router.get("/{seek_id}", response_model=SeekInfoResponse)
def get_seek(
seek_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""获取寻配号详情"""
seek = db.query(SeekInfo).filter(SeekInfo.id == seek_id).first()
if not seek:
raise HTTPException(status_code=404, detail="寻配号不存在")
# 增加浏览数
seek.view_count += 1
db.commit()
return seek
@router.put("/{seek_id}", response_model=SeekInfoResponse)
def update_seek(
seek_id: str,
data: SeekInfoUpdate,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""更新寻配号"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
seek = db.query(SeekInfo).filter(
SeekInfo.id == seek_id,
SeekInfo.user_id == current_user.f99_90_id
).first()
if not seek:
raise HTTPException(status_code=404, detail="寻配号不存在")
for key, value in data.model_dump(exclude_unset=True).items():
setattr(seek, key, value)
db.commit()
db.refresh(seek)
return seek
@router.delete("/{seek_id}")
def delete_seek(
seek_id: str,
current_user = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""删除寻配号"""
if not current_user:
raise HTTPException(status_code=401, detail="请先登录")
seek = db.query(SeekInfo).filter(
SeekInfo.id == seek_id,
SeekInfo.user_id == current_user.f99_90_id
).first()
if not seek:
raise HTTPException(status_code=404, detail="寻配号不存在")
seek.status = "deleted"
db.commit()
return {"message": "删除成功"}

View File

@ -79,7 +79,7 @@ export default function List() {
} }
try { try {
const user = JSON.parse(userStr) const user = JSON.parse(userStr)
const res = await fetch(`${API_BASE}/api/information/list?info_type=deal&page_size=500`, { const res = await fetch(`${API_BASE}/api/deal/list?page_size=500`, {
headers: { 'Authorization': 'Bearer ' + token } headers: { 'Authorization': 'Bearer ' + token }
}) })
const data = await res.json() const data = await res.json()
@ -758,7 +758,7 @@ function DealListItem({ deal, onRefresh }) {
const saveEdit = async () => { const saveEdit = async () => {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
try { try {
await fetch(`${API_BASE}/api/information/${deal.id}`, { await fetch(`${API_BASE}/api/deal/${deal.id}`, {
method: 'PUT', method: 'PUT',
headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify(editForm) body: JSON.stringify(editForm)
@ -772,7 +772,7 @@ function DealListItem({ deal, onRefresh }) {
if (!confirm('确定删除这条行情?')) return if (!confirm('确定删除这条行情?')) return
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
try { try {
await fetch(`${API_BASE}/api/information/${deal.id}`, { await fetch(`${API_BASE}/api/deal/${deal.id}`, {
method: 'DELETE', method: 'DELETE',
headers: { 'Authorization': `Bearer ${token}` } headers: { 'Authorization': `Bearer ${token}` }
}) })

View File

@ -79,16 +79,21 @@ export default function News() {
const headers = token ? { Authorization: `Bearer ${token}` } : {} const headers = token ? { Authorization: `Bearer ${token}` } : {}
// URL // URL
let url = `${API_BASE}/api/information/list?info_type=${type}` // 使APIseekdeal
let url = activeTab === 'yichen'
? `${API_BASE}/api/information/list?info_type=${activeTab}`
: activeTab === 'seek'
? `${API_BASE}/api/seek/list`
: `${API_BASE}/api/deal/list`
// tab500 // 500
if (activeTab === 'deal') { if (activeTab === 'deal') {
url += `&page_size=500` url += (url.includes('?') ? '&' : '?') + 'page_size=500'
if (dealDate) { if (dealDate) {
url += `&deal_date=${dealDate}` url += '&deal_date=' + dealDate
} }
} else { } else if (activeTab === 'seek') {
url += `&page=${currentPage}&page_size=50` url += (url.includes("?") ? "&" : "?") + "page=" + currentPage + "&page_size=500"
} }
const res = await fetch(url, { headers }) const res = await fetch(url, { headers })
@ -107,25 +112,17 @@ export default function News() {
// //
let sortedData = data || [] let sortedData = data || []
if (activeTab === 'deal') { if (activeTab === 'deal' || activeTab === 'seek') {
const categoryOrder = ['带4号', '带7号', '永恒号', '天马号', '天马王', '金山号', '金山王', '钻石号', '朦胧号', '朦胧王', '金马号', '金马王', '倒置号', '圆圆号'] //
// ->44->7
const normalizeCat = (c) => {
if (c === '通货') return '带4号'
if (c === '无4') return '带7号'
return c
}
sortedData = [...(data || [])].sort((a, b) => { sortedData = [...(data || [])].sort((a, b) => {
const contentA = a.content || '' const dateA = a.deal_date || a.created_at?.split('T')[0] || ""
const contentB = b.content || '' const dateB = b.deal_date || b.created_at?.split('T')[0] || ""
const catA = normalizeCat(contentA.match(/分类:\s*(.+?)(?:\n|$)/)?.[1]?.trim() || a.category || '') if (dateA !== dateB) return dateB.localeCompare(dateA)
const catB = normalizeCat(contentB.match(/分类:\s*(.+?)(?:\n|$)/)?.[1]?.trim() || b.category || '') const noA = a.deal_no || ""
const idxA = categoryOrder.indexOf(catA) const noB = b.deal_no || ""
const idxB = categoryOrder.indexOf(catB) return noB.localeCompare(noA)
return (idxA === -1 ? 999 : idxA) - (idxB === -1 ? 999 : idxB)
}) })
} }
setInfoList(sortedData) setInfoList(sortedData)
} catch (e) { } catch (e) {
console.error(e) console.error(e)
@ -293,12 +290,12 @@ export default function News() {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (!token) { alert('请先登录'); return } if (!token) { alert('请先登录'); return }
try { try {
const res = await fetch(`${API_BASE}/api/information/my/list`, { const res = await fetch(`${API_BASE}/api/seek/list`, {
headers: { Authorization: `Bearer ${token}` } headers: { Authorization: `Bearer ${token}` }
}) })
const data = await res.json() const data = await res.json()
// //
const seeks = Array.isArray(data) ? data.filter(item => item.info_type === 'seek' && item.user_id === currentUserId) : [] const seeks = Array.isArray(data) ? data.filter(item => item.user_id === currentUserId) : []
setMySeekList(seeks) setMySeekList(seeks)
setInfoList(seeks) // setInfoList(seeks) //
} catch (e) { console.error(e) } } catch (e) { console.error(e) }
@ -309,7 +306,7 @@ export default function News() {
const token = localStorage.getItem('token') const token = localStorage.getItem('token')
if (!token) { alert('请先登录'); return } if (!token) { alert('请先登录'); return }
try { try {
const res = await fetch(`${API_BASE}/api/information/list?info_type=seek`, { const res = await fetch(`${API_BASE}/api/seek/list`, {
headers: { Authorization: `Bearer ${token}` } headers: { Authorization: `Bearer ${token}` }
}) })
const data = await res.json() const data = await res.json()
@ -707,28 +704,26 @@ export default function News() {
else if (serial.startsWith('J3')) version = '蛇钞' else if (serial.startsWith('J3')) version = '蛇钞'
return ( return (
<div key={item.id} style={{ background: '#1e293b', borderRadius: '8px', padding: '10px', marginBottom: '8px' }}> <div key={item.id} style={{ borderBottom: '1px solid #334155', padding: '12px 0' }}>
{/* 第一行:成交日期 | 版别 | 包装 | 分类 | 价格 */} {/* 标题行:冠字号 + 价格 */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '6px' }}> <div style={{ fontSize: '15px', fontWeight: '500', marginBottom: '6px' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '6px', flexWrap: 'wrap' }}> <span style={{ color: '#fbbf24' }}>{serial}</span>
<span style={{ background: '#64748b', color: '#fff', padding: '2px 6px', borderRadius: '3px', fontSize: '10px' }}>{dealDate}</span> <span style={{ color: '#22c55e', marginLeft: '12px' }}>¥{item.deal_price?.toLocaleString()}</span>
<span style={{ color: '#94a3b8', fontSize: '10px' }}>|</span> <span style={{ color: '#64748b', marginLeft: '12px', fontSize: '12px' }}>{item.deal_no}</span>
<span style={{ background: '#3b82f6', color: '#fff', padding: '2px 6px', borderRadius: '3px', fontSize: '10px' }}>{version}</span>
<span style={{ color: '#94a3b8', fontSize: '10px' }}>|</span>
<span style={{ background: '#8b5cf6', color: '#fff', padding: '2px 6px', borderRadius: '3px', fontSize: '10px' }}>{packaging}</span>
<span style={{ color: '#94a3b8', fontSize: '10px' }}>|</span>
<span style={{ background: '#f97316', color: '#fff', padding: '2px 6px', borderRadius: '3px', fontSize: '10px' }}>{category}</span>
</div>
<div style={{ color: '#22c55e', fontSize: '14px', fontWeight: '700' }}>¥{item.deal_price?.toLocaleString()}</div>
</div> </div>
{/* 第二行:冠字号 | 评级机构 | 分数 | 平台 */} {/* 信息行:版别 | 包装 | 分类 | 评级 | 分数 | 平台 */}
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}> <div style={{ fontSize: '12px', color: '#94a3b8' }}>
<span style={{ color: '#fbbf24', fontSize: '13px', fontWeight: '600' }}>{serial}</span> <span style={{ color: '#fff' }}>{version}</span>
<span style={{ color: '#94a3b8', fontSize: '10px' }}>|</span> <span style={{ margin: '0 8px' }}>|</span>
{gradingCompany && <><span style={{ background: '#ec4899', color: '#fff', padding: '2px 6px', borderRadius: '3px', fontSize: '10px' }}>{gradingCompany}</span><span style={{ color: '#94a3b8', fontSize: '10px' }}>|</span></>} <span style={{ color: '#3b82f6' }}>{packaging}</span>
<span style={{ background: '#06b6d4', color: '#fff', padding: '2px 6px', borderRadius: '3px', fontSize: '10px' }}>{grade}</span> <span style={{ margin: '0 8px' }}>|</span>
<span style={{ color: '#94a3b8', fontSize: '10px' }}>|</span> <span style={{ color: '#fbbf24' }}>{category}</span>
<span style={{ background: '#10b981', color: '#fff', padding: '2px 6px', borderRadius: '3px', fontSize: '10px' }}>{platform}</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>
</div> </div>
) )
@ -1085,7 +1080,7 @@ export default function News() {
)} )}
{/* 分页组件 */} {/* 分页组件 */}
{totalPages > 1 && ( {infoList.length > 0 && (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px', padding: '16px', marginTop: '8px' }}> <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '8px', padding: '16px', marginTop: '8px' }}>
<button <button
onClick={() => { if (currentPage > 1) { setCurrentPage(currentPage - 1); fetchInfoList() } }} onClick={() => { if (currentPage > 1) { setCurrentPage(currentPage - 1); fetchInfoList() } }}