修复寻配号三个问题:1.限制冠字号最多8位 2.显示真实用户名 3.添加匹配数量计算
This commit is contained in:
parent
d47a002495
commit
7d0f92b1eb
File diff suppressed because it is too large
Load Diff
|
|
@ -1,384 +1,301 @@
|
|||
# seek - 寻号匹配路由
|
||||
# Version: 0.0.1
|
||||
# 更新:
|
||||
|
||||
from fastapi import APIRouter, Depends, Query, HTTPException
|
||||
# 更新:
|
||||
# Version: 1.2.x
|
||||
# 更新:
|
||||
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
|
||||
# 更新:
|
||||
from app.models.models import User, Collection, Information
|
||||
from sqlalchemy import text
|
||||
|
||||
# 更新:
|
||||
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]
|
||||
# 更新:
|
||||
user_name: Optional[str] = None # 发布者用户名
|
||||
matched_count: Optional[int] = 0 # 自有匹配数量
|
||||
network_matched_count: Optional[int] = 0 # 网络匹配数量
|
||||
|
||||
# 更新:
|
||||
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=1000),
|
||||
# 更新:
|
||||
user_only: bool = Query(False),
|
||||
# 更新:
|
||||
current_user: Optional = Depends(get_current_user),
|
||||
# 更新:
|
||||
db: Session = Depends(get_db)
|
||||
# 更新:
|
||||
):
|
||||
# 更新:
|
||||
"""获取寻配号列表"""
|
||||
# 更新:
|
||||
query = db.query(SeekInfo).filter(SeekInfo.status == status)
|
||||
# 更新:
|
||||
|
||||
# 更新:
|
||||
# 我的寻配号:只查看自己的
|
||||
# 更新:
|
||||
if user_only and current_user:
|
||||
# 更新:
|
||||
query = query.filter(SeekInfo.user_id == current_user.f99_90_id)
|
||||
# 更新:
|
||||
|
||||
# 更新:
|
||||
# 排序
|
||||
# 更新:
|
||||
query = query.order_by(SeekInfo.created_at.desc())
|
||||
# 更新:
|
||||
|
||||
# 更新:
|
||||
# 分页
|
||||
# 更新:
|
||||
offset = (page - 1) * page_size
|
||||
# 更新:
|
||||
items = query.offset(offset).limit(page_size).all()
|
||||
# 更新:
|
||||
|
||||
# 更新:
|
||||
return items
|
||||
# 更新:
|
||||
# 添加用户名和匹配数量
|
||||
from app.routers.information import match_collections_count, match_collections_count_from_coolbot
|
||||
result = []
|
||||
for item in items:
|
||||
user = db.query(User).filter(User.f99_90_id == item.user_id).first()
|
||||
user_name = user.f01_01_name if user else '匿名用户'
|
||||
|
||||
# 计算匹配数量
|
||||
matched_count = 0
|
||||
network_matched_count = 0
|
||||
if item.expect_number and len(item.expect_number) == 10:
|
||||
if current_user and current_user.f99_90_id:
|
||||
matched_count = match_collections_count(db, current_user.f99_90_id, item.expect_number)
|
||||
network_matched_count = match_collections_count_from_coolbot(item.expect_number)
|
||||
|
||||
# 构建响应
|
||||
result.append(SeekInfoResponse(
|
||||
id=item.id,
|
||||
user_id=item.user_id,
|
||||
title=item.title,
|
||||
content=item.content,
|
||||
expect_category=item.expect_category,
|
||||
expect_version=item.expect_version,
|
||||
expect_packaging=item.expect_packaging,
|
||||
expect_number=item.expect_number,
|
||||
expect_price_min=item.expect_price_min,
|
||||
expect_price_max=item.expect_price_max,
|
||||
status=item.status,
|
||||
is_matched=item.is_matched,
|
||||
matched_user_id=item.matched_user_id,
|
||||
matched_contact=item.matched_contact,
|
||||
view_count=item.view_count,
|
||||
contact_count=item.contact_count,
|
||||
created_at=item.created_at,
|
||||
updated_at=item.updated_at,
|
||||
user_name=user_name,
|
||||
matched_count=matched_count,
|
||||
network_matched_count=network_matched_count
|
||||
))
|
||||
|
||||
return result
|
||||
|
||||
# 更新:
|
||||
@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": "删除成功"}
|
||||
# 更新:
|
||||
# 获取自有藏品匹配列表
|
||||
@router.get("/my-match")
|
||||
def get_seek_match(
|
||||
info_id: str,
|
||||
current_user = Depends(get_current_user),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取符合条件的我的藏品推荐"""
|
||||
if not current_user:
|
||||
raise HTTPException(status_code=401, detail="请先登录")
|
||||
|
||||
info = db.query(SeekInfo).filter(SeekInfo.id == info_id).first()
|
||||
if not info:
|
||||
raise HTTPException(status_code=404, detail="寻配号信息不存在")
|
||||
|
||||
# 获取用户所有藏品
|
||||
collections = db.query(Collection).filter(
|
||||
Collection.f99_91_user_id == current_user.f99_90_id,
|
||||
Collection.f01_04_status == "in_collection"
|
||||
).all()
|
||||
|
||||
# 按号码特征模式匹配
|
||||
from app.routers.information import match_pattern
|
||||
matched = []
|
||||
if info.expect_number and len(info.expect_number) == 10:
|
||||
pattern = info.expect_number[2:]
|
||||
for c in collections:
|
||||
number = c.f02_10_prefix_serial or ''
|
||||
if len(number) >= 10 and number.startswith('J0'):
|
||||
col_pattern = number[2:10]
|
||||
if match_pattern(col_pattern, pattern):
|
||||
matched.append(c)
|
||||
elif len(number) >= 8:
|
||||
col_pattern = number[:8]
|
||||
if match_pattern(col_pattern, pattern):
|
||||
matched.append(c)
|
||||
else:
|
||||
matched = collections
|
||||
|
||||
return {
|
||||
"matched_count": len(matched),
|
||||
"collections": [
|
||||
{"id": c.f99_90_id, "code": c.f01_02_code or '', "name": c.f01_01_name,
|
||||
"number": c.f02_10_prefix_serial, "status": c.f01_04_status}
|
||||
for c in matched[:20]
|
||||
]
|
||||
}
|
||||
|
||||
# 获取网络数据匹配列表
|
||||
@router.get("/network-match/{info_id}")
|
||||
def get_network_match(
|
||||
info_id: str,
|
||||
limit: int = Query(20, ge=1, le=100),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""获取一尘数据库中匹配的藏品列表"""
|
||||
from app.routers.information import match_collections_list_from_coolbot
|
||||
|
||||
info = db.query(SeekInfo).filter(SeekInfo.id == info_id).first()
|
||||
if not info:
|
||||
raise HTTPException(status_code=404, detail="寻配号信息不存在")
|
||||
|
||||
if not info.expect_number:
|
||||
return {"matched_count": 0, "collections": []}
|
||||
|
||||
matched = match_collections_list_from_coolbot(info.expect_number, limit=limit)
|
||||
return {"matched_count": len(matched), "collections": matched}
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
VERSION=v0.0.3
|
||||
VERSION=v0.0.5
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<title>甲辰收藏 v=0.0.3</title>
|
||||
<title>甲辰收藏 v=0.0.5</title>
|
||||
|
||||
<!-- Favicon -->
|
||||
<link rel="icon" type="image/png" href="/static/icons/favicon.png" />
|
||||
|
|
|
|||
|
|
@ -71,9 +71,10 @@ export default function News() {
|
|||
fetchInfoList()
|
||||
}, [activeTab, dealDate])
|
||||
|
||||
// 自动生成寻号标题
|
||||
// 自动生成寻号标题(限制8位冠字号)
|
||||
useEffect(() => {
|
||||
const title = `「寻号 ${seekForm.edition || '龙钞,马钞,蛇钞'} J0${seekForm.features || 'XXXXXXXX'}」`
|
||||
const features = (seekForm.features || '').slice(0, 8).padEnd(8, 'X')
|
||||
const title = `「寻号 ${seekForm.edition || '龙钞,马钞,蛇钞'} J0${features}」`
|
||||
setSeekForm(prev => ({...prev, title}))
|
||||
}, [seekForm.edition, seekForm.features])
|
||||
|
||||
|
|
@ -274,7 +275,7 @@ export default function News() {
|
|||
// 获取网络数据匹配列表
|
||||
const fetchNetworkMatchCollections = async (infoId) => {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE}/api/information/seek/network-match/${infoId}`)
|
||||
const res = await fetch(`${API_BASE}/api/seek/network-match/${infoId}`)
|
||||
const data = await res.json()
|
||||
console.log('网络数据匹配结果:', data)
|
||||
setNetworkMatchCollections(data.collections || [])
|
||||
|
|
@ -451,12 +452,22 @@ export default function News() {
|
|||
onChange={(e) => {
|
||||
if (i < 2) return
|
||||
const val = e.target.value.toUpperCase().replace(/[^0-9XABCDEFG]/g, '')
|
||||
if (!val) {
|
||||
// 删除操作
|
||||
const newFeatures = (seekForm.features || '').split('')
|
||||
newFeatures[i - 2] = ''
|
||||
setSeekForm({...seekForm, features: newFeatures.join('').slice(0, 8)})
|
||||
return
|
||||
}
|
||||
// 限制最多8位
|
||||
let currentLen = (seekForm.features || '').length
|
||||
if (currentLen >= 8 && i - 2 >= currentLen) return // 超过8位不再输入
|
||||
const newFeatures = (seekForm.features || '').slice(0, 8).split('')
|
||||
while (newFeatures.length < 8) newFeatures.push('')
|
||||
newFeatures[i - 2] = val
|
||||
setSeekForm({...seekForm, features: newFeatures.join('')})
|
||||
setSeekForm({...seekForm, features: newFeatures.join('').slice(0, 8)})
|
||||
// 自动跳转下一个
|
||||
if (val && i < 9) {
|
||||
if (i < 9) {
|
||||
setTimeout(() => {
|
||||
const nextInput = document.querySelector(`input[data-index="${i+1}"]`)
|
||||
if (nextInput) nextInput.focus()
|
||||
|
|
@ -752,7 +763,7 @@ export default function News() {
|
|||
</div>
|
||||
{/* 创建日期 + 用户名 */}
|
||||
<div style={{ color: '#64748b', fontSize: '12px', marginBottom: '8px' }}>
|
||||
📅 {formatDate(item.created_at)} | 👤 {(item.content && item.content.match(/发布者: (.+)/)?.[1]) || item.user_name || '匿名用户'}
|
||||
📅 {formatDate(item.created_at)} | 👤 {item.user_name || '匿名用户'}
|
||||
</div>
|
||||
{/* 号码特征 */}
|
||||
{features && (
|
||||
|
|
|
|||
Loading…
Reference in New Issue