diff --git a/backend/app/models/models.py b/backend/app/models/models.py index 049c25f..b4ba6e7 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -182,6 +182,12 @@ class Information(Base): # 状态: active-有效, closed-已关闭, expired-已过期 status = Column(String(20), default="active", index=True) + # 匹配状态: pending-尚未匹配, matched-已经匹配 + is_matched = Column(String(20), default="pending", index=True) + + # 匹配的用户ID(当用户愿意交换联系方式时) + matched_user_id = Column(String(36), ForeignKey("users.f99_90_id", ondelete="SET NULL"), nullable=True) + # 浏览/联系次数 view_count = Column(Integer, default=0) contact_count = Column(Integer, default=0) @@ -193,6 +199,31 @@ class Information(Base): collection = relationship("Collection") +# 资讯评论/留言 +class InformationComment(Base): + __tablename__ = "information_comments" + + id = Column(String(36), primary_key=True, default=generate_uuid) + information_id = Column(String(36), ForeignKey("information.id", ondelete="CASCADE"), nullable=False, index=True) + user_id = Column(String(36), ForeignKey("users.f99_90_id", ondelete="CASCADE"), nullable=False, index=True) + content = Column(Text, nullable=False) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + user = relationship("User") + + +# 资讯联系方式查看记录 +class InformationContactView(Base): + __tablename__ = "information_contact_views" + + id = Column(String(36), primary_key=True, default=generate_uuid) + information_id = Column(String(36), ForeignKey("information.id", ondelete="CASCADE"), nullable=False, index=True) + viewer_id = Column(String(36), ForeignKey("users.f99_90_id", ondelete="CASCADE"), nullable=False, index=True) + created_at = Column(DateTime(timezone=True), server_default=func.now()) + + viewer = relationship("User") + + # 资讯关联用户 (收藏/点赞) class InformationLike(Base): __tablename__ = "information_likes" diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index ef65024..94a3d4f 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -681,3 +681,82 @@ def get_my_information( "created_at": i.created_at.isoformat() if i.created_at else None } for i in infos] } + + +# ============ 匹配寻号 ============ +class MatchSeekRequest(BaseModel): + info_id: str + collection_id: Optional[str] = None + + +@router.post("/seek/match-confirm") +def match_seek( + request: MatchSeekRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """确认匹配寻号 - 用户愿意交换联系方式给发布者""" + info = db.query(Information).filter( + Information.id == request.info_id, + Information.info_type == "seek", + Information.status == "active" + ).first() + + if not info: + raise HTTPException(status_code=404, detail="寻配号信息不存在") + + # 更新匹配状态 + info.is_matched = "matched" + info.matched_user_id = current_user.f99_90_id + + # 更新发布寻号者的内容,显示有藏品被匹配 + original_content = info.content or "" + # 添加匹配信息:藏品被XX藏友匹配,联系方式为:xxx + match_info = f"\n藏品被{current_user.f01_01_name}匹配成功!联系方式:{request.collection_id or '已交换'}" + info.content = original_content + match_info + + db.commit() + + return {"message": "匹配成功,已通知发布者", "is_matched": "matched"} + + +# ============ 添加留言 ============ +class CommentRequest(BaseModel): + information_id: str + content: str + + +@router.post("/comment") +def add_comment( + request: CommentRequest, + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """添加留言""" + info = db.query(Information).filter( + Information.id == request.information_id + ).first() + + if not info: + raise HTTPException(status_code=404, detail="资讯不存在") + + # 创建留言 + from app.models.models import InformationComment + comment = InformationComment( + information_id=request.information_id, + user_id=current_user.f99_90_id, + content=request.content + ) + db.add(comment) + db.commit() + + return { + "message": "留言成功", + "comment": { + "id": comment.id, + "content": comment.content, + "user_name": current_user.f01_01_name, + "user_avatar": current_user.avatar, + "created_at": comment.created_at + } + } diff --git a/frontend/index.html b/frontend/index.html index 9190919..7cebb1a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.18 + 甲辰收藏 v1.2.17 diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx index 231066a..2628b9a 100644 --- a/frontend/src/pages/News.jsx +++ b/frontend/src/pages/News.jsx @@ -5,6 +5,11 @@ export default function News() { const [activeTab, setActiveTab] = useState('deal') const [showSeekPublish, setShowSeekPublish] = useState(false) const [showMySeeks, setShowMySeeks] = useState(false) // 我的寻号 + const [showContactId, setShowContactId] = useState(null) // 显示联系方式的寻号ID + const [showCommentId, setShowCommentId] = useState(null) // 显示评论输入的寻号ID + const [commentText, setCommentText] = useState('') // 评论内容 + const [comments, setComments] = useState({}) // 存储各寻号的评论 + const [matchedStatus, setMatchedStatus] = useState({}) // 存储各寻号的匹配状态 const [showMatchList, setShowMatchList] = useState(false) const [matchCollections, setMatchCollections] = useState([]) const [seekForm, setSeekForm] = useState({ @@ -14,6 +19,7 @@ export default function News() { const [loading, setLoading] = useState(false) const API_BASE = localStorage.getItem('API_BASE') || '' + const currentUser = localStorage.getItem('token') ? { id: 'logged_in' } : null // 获取资讯列表 useEffect(() => { @@ -22,9 +28,9 @@ export default function News() { // 自动生成寻号标题 useEffect(() => { - const title = `「寻号 ${seekForm.edition || '龙钞,马钞,蛇钞'} ${seekForm.type || '不限'} J0${seekForm.features || 'XXXXXXXX'} ${seekForm.matchType}」` + const title = `「寻号 ${seekForm.edition || '龙钞,马钞,蛇钞'} J0${seekForm.features || 'XXXXXXXX'}」` setSeekForm(prev => ({...prev, title})) - }, [seekForm.edition, seekForm.type, seekForm.features, seekForm.matchType]) + }, [seekForm.edition, seekForm.features]) const fetchInfoList = async () => { setLoading(true) @@ -46,6 +52,63 @@ export default function News() { } // 获取匹配藏品列表 + + + // 获取寻号评论 + const fetchComments = async (infoId) => { + if (comments[infoId]) return; // 已加载则跳过 + try { + const res = await fetch(`${API_BASE}/api/information/comments/${infoId}`) + const data = await res.json() + setComments(prev => ({...prev, [infoId]: data || []})) + } catch (e) { + console.error('获取评论失败:', e) + } + } + + // 添加评论 + const addComment = async (infoId) => { + if (!commentText.trim()) { alert('请输入评论内容'); return } + const token = localStorage.getItem('token') + if (!token) { alert('请先登录'); return } + try { + const res = await fetch(`${API_BASE}/api/information/comment`, { + method: 'POST', + headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ information_id: infoId, content: commentText }) + }) + const data = await res.json() + if (data.message === '留言成功') { + setCommentText('') + setComments(prev => ({...prev, [infoId]: [...(prev[infoId] || []), data.comment]})) + alert('留言成功!') + } + } catch (e) { + alert('留言失败: ' + e.message) + } + } + + // 匹配并联系藏友 + const matchAndContact = async (infoId) => { + const token = localStorage.getItem('token') + if (!token) { alert('请先登录'); return } + try { + const res = await fetch(`${API_BASE}/api/information/seek/match-confirm`, { + method: 'POST', + headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' }, + body: JSON.stringify({ info_id: infoId }) + }) + const data = await res.json() + if (data.message === '匹配成功,已通知发布者') { + setMatchedStatus(prev => ({...prev, [infoId]: 'matched'})) + setShowContactId(infoId) // 显示联系方式 + alert('匹配成功!') + fetchInfoList() // 刷新列表 + } + } catch (e) { + alert('操作失败: ' + e.message) + } + } const fetchMatchCollections = async (infoId) => { const token = localStorage.getItem('token') if (!token) { alert('请先登录'); return } @@ -191,34 +254,10 @@ export default function News() { })} -
-
- {['标百','标十','单张'].map(e => ( - - ))} -
-
-
-
- {['求购','出售','寻号'].map(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' }} />
-
-
- {['精准','模糊'].map(e => ( - - ))} -
-
{[0,1,2,3,4,5,6,7,8,9].map(i => { @@ -260,6 +299,10 @@ export default function News() { 备注说明:X=任意数字 A=非4 B=非47 C=非347 D=非247 E=非2347 F=非23457 G=非123457
+
+ setSeekForm({...seekForm, title: e.target.value})} /> +