feat: 表拆分seek_info和deal_info,优化显示和分页
This commit is contained in:
parent
ea6528b7f3
commit
aec6ef7c79
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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())
|
||||||
|
|
@ -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": "删除成功"}
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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": "删除成功"}
|
||||||
|
|
@ -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}` }
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -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}`
|
// 使用新的独立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`
|
||||||
|
|
||||||
// 成交行情tab,获取500条数据
|
// 成交行情和寻配号每页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号', '永恒号', '天马号', '天马王', '金山号', '金山王', '钻石号', '朦胧号', '朦胧王', '金马号', '金马王', '倒置号', '圆圆号']
|
// 优先按日期倒序,同日按编号倒序
|
||||||
// 标准化分类名称(通货->带4号,无4->带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() } }}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue