From 5da876a193393c923467f5339d66c2ce15fc8cae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Mon, 6 Apr 2026 15:54:22 +0800 Subject: [PATCH 01/10] =?UTF-8?q?v1.2.41=20=E6=B7=BB=E5=8A=A0=E4=B8=80?= =?UTF-8?q?=E5=B0=98=E7=9C=8B=E6=9D=BF=E5=8A=9F=E8=83=BD=E5=88=B0=E8=B5=84?= =?UTF-8?q?=E8=AE=AF=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/information.py | 15 +++++++++++++++ frontend/index.html | 2 +- frontend/src/pages/News.jsx | 9 +++++---- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index 7fb9484..87e3baf 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -891,3 +891,18 @@ def get_publisher_info( "contact": contact, "created_at": info.created_at.isoformat() if info.created_at else None } + + +@router.get("/yichen-posts") +def get_yichen_posts(category: str = None, search: str = None, page: int = 1, page_size: int = 20): + from app.models.models import Information + from sqlalchemy import desc + query = db.query(Information).filter(Information.info_type == 'yichen') + if category: + query = query.filter(Information.expect_category == category) + if search: + query = query.filter(Information.title.contains(search)) + total = query.count() + offset = (page - 1) * page_size + items = query.order_by(desc(Information.created_at)).offset(offset).limit(page_size).all() + return {"data": items, "pagination": {"page": page, "limit": page_size, "total": total}} diff --git a/frontend/index.html b/frontend/index.html index 03c4351..0e59670 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.38 + 甲辰收藏 v0.0.0 diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx index 04cb53f..651c20d 100644 --- a/frontend/src/pages/News.jsx +++ b/frontend/src/pages/News.jsx @@ -69,7 +69,7 @@ export default function News() { // 寻配号对所有人公开,无需登录即可查看 const token = localStorage.getItem('token') // 根据tab获取不同类型的数据 - const type = activeTab === 'seek' ? 'seek' : 'deal' + const type = activeTab === 'seek' ? 'seek' : activeTab === 'deal' ? 'deal' : 'yichen' // 始终传递token,以便获取准确的matched_count const headers = token ? { Authorization: `Bearer ${token}` } : {} const res = await fetch(`${API_BASE}/api/information/list?info_type=${type}`, { headers }) @@ -315,7 +315,8 @@ export default function News() { // Tab切换 const tabs = [ { key: 'seek', label: '🔍 寻配号' }, - { key: 'deal', label: '💰 成交行情' } + { key: 'deal', label: '💰 成交行情' }, + { key: 'yichen', label: '📊 一尘看板' } ] const formatDate = (dateStr) => { @@ -435,7 +436,7 @@ export default function News() { )}

- {activeTab === 'seek' ? '🔍 寻配号信息' : '💰 成交行情信息'} + {activeTab === 'seek' ? '🔍 寻配号信息' : activeTab === 'deal' ? '💰 成交行情信息' : '📊 一尘看板'} {activeTab === 'seek' && (
@@ -450,7 +451,7 @@ export default function News() {
加载中...
) : infoList.length === 0 ? (
- 暂无{activeTab === 'seek' ? '寻配号' : '成交行情'}信息 + 暂无{activeTab === 'seek' ? '寻配号' : activeTab === 'deal' ? '成交行情' : '一尘看板'}信息
) : (
From b2b30cc54e0c8526992f59c2ef70365c9dfaa2ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Mon, 6 Apr 2026 16:21:47 +0800 Subject: [PATCH 02/10] =?UTF-8?q?v1.2.41=20=E4=BF=AE=E5=A4=8D=E4=B8=80?= =?UTF-8?q?=E5=B0=98=E7=9C=8B=E6=9D=BF=E6=98=BE=E7=A4=BA=E7=9C=9F=E5=AE=9E?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E8=80=85=E5=90=8D=E7=A7=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/src/pages/News.jsx | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 50e400b..1ce7217 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.41 +VERSION=1.2.41 diff --git a/config/VERSION b/config/VERSION index a4f8462..1ce7217 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -v1.2.41 +VERSION=1.2.41 diff --git a/frontend/index.html b/frontend/index.html index 0e59670..bf5cc5a 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v0.0.0 + 甲辰收藏 v1.2.41 diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx index 651c20d..402147d 100644 --- a/frontend/src/pages/News.jsx +++ b/frontend/src/pages/News.jsx @@ -473,7 +473,7 @@ export default function News() {
{/* 创建日期 + 用户名 */}
- 📅 {formatDate(item.created_at)}  |  👤 {item.user_name || '匿名用户'} + 📅 {formatDate(item.created_at)}  |  👤 {(item.content && item.content.match(/发布者: (.+)/)?.[1]) || item.user_name || '匿名用户'}
{/* 号码特征 */} {features && ( From 7ff0ae5c9ec9f85ff68a113e903e0514623eb7f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Mon, 6 Apr 2026 21:17:15 +0800 Subject: [PATCH 03/10] =?UTF-8?q?v1.2.42=20=E4=B8=80=E5=B0=98=E7=9C=8B?= =?UTF-8?q?=E6=9D=BF=E5=9F=BA=E7=A1=80=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/core/coolbot_db.py | 37 ++++++ backend/app/main.py | 4 +- backend/app/models/models.py | 2 +- backend/app/routers/information.py | 4 +- backend/app/routers/yichens.py | 181 ++++++++++++++++++++++++++++ config/VERSION | 2 +- frontend/src/pages/Info.jsx | 164 ++++++++++++++++++++++++- frontend/src/pages/News.jsx | 5 +- frontend/src/pages/YichensBoard.jsx | 113 +++++++++++++++++ 9 files changed, 503 insertions(+), 9 deletions(-) create mode 100644 backend/app/core/coolbot_db.py create mode 100644 backend/app/routers/yichens.py create mode 100644 frontend/src/pages/YichensBoard.jsx diff --git a/backend/app/core/coolbot_db.py b/backend/app/core/coolbot_db.py new file mode 100644 index 0000000..a0b9014 --- /dev/null +++ b/backend/app/core/coolbot_db.py @@ -0,0 +1,37 @@ +import os +from sqlalchemy import create_engine +from sqlalchemy.orm import sessionmaker +from sqlalchemy.pool import QueuePool + +COOLBOT_DB_URL = os.getenv( + "COOLBOT_DB_URL", + "postgresql://coolbot:Coolbot123@pgm-bp1t1008h019ez6cno.pg.rds.aliyuncs.com:5432/coolbot_data" +) + +coolbot_engine = create_engine( + COOLBOT_DB_URL, + poolclass=QueuePool, + pool_size=10, + max_overflow=20, + pool_timeout=30, + pool_recycle=1800, + pool_pre_ping=True, + echo=False, + connect_args={ + "connect_timeout": 10, + "application_name": "zodiac-coolbot" + } +) + +CoolbotSession = sessionmaker(autocommit=False, autoflush=False, bind=coolbot_engine) + +def get_coolbot_db(): + """获取coolbot数据库会话""" + db = CoolbotSession() + try: + yield db + except Exception: + db.rollback() + raise + finally: + db.close() diff --git a/backend/app/main.py b/backend/app/main.py index 7624c39..e43e400 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -16,6 +16,7 @@ from app.routers import auth, collections, operations from app.routers import ocr as ocr_router from app.routers import users as users_router from app.routers import information as information_router +from app.routers import yichens as yichens_router # 版本信息 - 从 config/VERSION 文件读取 def get_version(): @@ -80,7 +81,8 @@ app.include_router(operations.router) app.include_router(ocr_router.router) # OCR 识别 app.include_router(users_router.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.get("/") diff --git a/backend/app/models/models.py b/backend/app/models/models.py index 40811e7..387f670 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -163,7 +163,7 @@ class Information(Base): # 内容描述 content = Column(Text, nullable=True) - + # 关联藏品ID collection_id = Column(String(36), ForeignKey("collections.f99_90_id", ondelete="SET NULL"), nullable=True) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index 87e3baf..2987785 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -16,7 +16,7 @@ router = APIRouter(prefix="/api/information", tags=["资讯"]) class InformationCreate(BaseModel): info_type: str # seek-寻配号, deal-成交数据, publish-发布 title: str - content: Optional[str] = None + content: Optional[str] collection_id: Optional[str] = None expect_category: Optional[str] = None expect_version: Optional[str] = None @@ -30,7 +30,7 @@ class InformationCreate(BaseModel): class InformationUpdate(BaseModel): title: Optional[str] = None - content: Optional[str] = None + content: Optional[str] status: Optional[str] = None expect_category: Optional[str] = None expect_version: Optional[str] = None diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py new file mode 100644 index 0000000..e163b4a --- /dev/null +++ b/backend/app/routers/yichens.py @@ -0,0 +1,181 @@ +from fastapi import APIRouter, Depends, Query +from sqlalchemy import func, text +from sqlalchemy.orm import Session +from pydantic import BaseModel +from typing import Optional, List +from datetime import datetime, date +from app.core.coolbot_db import get_coolbot_db + +router = APIRouter(prefix="/api/yichens", tags=["一尘看板"]) + +# ============ 数据模型 ============ +class YichensPostStats(BaseModel): + total_posts: int + total_deals: int # 出售 + total_wants: int # 求购 + total_replies: int + total_views: int + avg_price: Optional[float] + +class CategoryStat(BaseModel): + category: str + count: int + +class PostItem(BaseModel): + post_id: str + title: str + category: Optional[str] + post_type: str + price: Optional[float] + author_username: str + post_time: str + reply_count: int + view_count: int + url: Optional[str] + +class UserStat(BaseModel): + total_users: int + new_users_today: int + sellers: int + +class UserItem(BaseModel): + user_id: str + username: str + avatar_url: Optional[str] + credit_level: Optional[str] + credit_score: Optional[int] + post_count: int + is_seller: bool + registration_date: Optional[str] + +# ============ 统计接口 ============ +@router.get("/stats/posts", response_model=YichensPostStats) +def get_post_stats(days: int = Query(30, ge=1, le=365), db: Session = Depends(get_coolbot_db)): + """获取帖子统计""" + result = db.execute(text(""" + SELECT + COUNT(*) as total_posts, + COUNT(*) FILTER (WHERE post_type = 'deal') as total_deals, + COUNT(*) FILTER (WHERE post_type = 'want') as total_wants, + COALESCE(SUM(reply_count), 0) as total_replies, + COALESCE(SUM(view_count), 0) as total_views, + AVG(price) as avg_price + FROM yichens_posts + WHERE post_time >= NOW() - INTERVAL '1 day' * :days + """), {"days": days}).fetchone() + + return YichensPostStats( + total_posts=result[0] or 0, + total_deals=result[1] or 0, + total_wants=result[2] or 0, + total_replies=result[3] or 0, + total_views=result[4] or 0, + avg_price=float(result[5]) if result[5] else None + ) + +@router.get("/stats/categories", response_model=List[CategoryStat]) +def get_category_stats(days: int = Query(30, ge=1, le=365), db: Session = Depends(get_coolbot_db)): + """按分类统计帖子数量""" + results = db.execute(text(""" + SELECT category, COUNT(*) as count + FROM yichens_posts + WHERE post_time >= NOW() - INTERVAL '1 day' * :days + GROUP BY category + ORDER BY count DESC + """), {"days": days}).fetchall() + + return [CategoryStat(category=r[0] or "未知", count=r[1]) for r in results] + +@router.get("/stats/users", response_model=UserStat) +def get_user_stats(db: Session = Depends(get_coolbot_db)): + """获取用户统计""" + result = db.execute(text(""" + SELECT + COUNT(*) as total_users, + COUNT(*) FILTER (WHERE created_at::date = CURRENT_DATE) as new_users_today, + COUNT(*) FILTER (WHERE is_seller = true) as sellers + FROM yichens_users + """)).fetchone() + + return UserStat( + total_users=result[0] or 0, + new_users_today=result[1] or 0, + sellers=result[2] or 0 + ) + +@router.get("/posts", response_model=List[PostItem]) +def get_posts( + limit: int = Query(20, ge=1, le=100), + offset: int = Query(0, ge=0), + category: Optional[str] = None, + post_type: Optional[str] = None, + db: Session = Depends(get_coolbot_db) +): + """获取帖子列表""" + query = """ + SELECT post_id, title, category, post_type, price, + author_username, post_time, reply_count, view_count, url + FROM yichens_posts + WHERE 1=1 + """ + params = {"limit": limit, "offset": offset} + + if category: + query += " AND category = :category" + params["category"] = category + + if post_type: + query += " AND post_type = :post_type" + params["post_type"] = post_type + + query += " ORDER BY post_time DESC LIMIT :limit OFFSET :offset" + + results = db.execute(text(query), params).fetchall() + + return [PostItem( + post_id=r[0], + title=r[1] or "", + category=r[2], + post_type=r[3] or "", + price=float(r[4]) if r[4] else None, + author_username=r[5] or "", + post_time=str(r[6]) if r[6] else "", + reply_count=r[7] or 0, + view_count=r[8] or 0, + url=r[9] + ) for r in results] + +@router.get("/users", response_model=List[UserItem]) +def get_users( + limit: int = Query(20, ge=1, le=100), + offset: int = Query(0, ge=0), + is_seller: Optional[bool] = None, + db: Session = Depends(get_coolbot_db) +): + """获取用户列表""" + query = """ + SELECT user_id, username, avatar_url, credit_level, credit_score, + post_count, is_seller, registration_date + FROM yichens_users + WHERE 1=1 + """ + params = {"limit": limit, "offset": offset} + + if is_seller is not None: + query += " AND is_seller = :is_seller" + params["is_seller"] = is_seller + + query += " ORDER BY post_count DESC LIMIT :limit OFFSET :offset" + + results = db.execute(text(query), params).fetchall() + + return [UserItem( + user_id=r[0], + username=r[1] or "", + avatar_url=r[2], + credit_level=r[3], + credit_score=r[4], + post_count=r[5] or 0, + is_seller=r[6] or False, + registration_date=str(r[7]) if r[7] else None + ) for r in results] diff --git a/config/VERSION b/config/VERSION index 1ce7217..4aee9f9 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.41 +VERSION=1.2.42 diff --git a/frontend/src/pages/Info.jsx b/frontend/src/pages/Info.jsx index 390bdb8..2804236 100644 --- a/frontend/src/pages/Info.jsx +++ b/frontend/src/pages/Info.jsx @@ -9,7 +9,7 @@ export default function Info() { } // 顶部tab:寻配号发布 / 发布管理 - const [activeTab, setActiveTab] = useState('manage') + const [activeTab, setActiveTab] = useState('yichens') const [showPublish, setShowPublish] = useState(false) const [publishType, setPublishType] = useState('deal') // 'seek' 或 'deal' const [myList, setMyList] = useState([]) @@ -227,7 +227,8 @@ export default function Info() {
{[ - { key: 'manage', label: '📋 发布管理' } + { key: 'manage', label: '📋 发布管理' }, + { key: 'yichens', label: '📊 一尘看板' } ].map(tab => (
)} + + {/* 一尘看板页 */} + {activeTab === 'yichens' && ( + + )} +
+ ) +} + + +// ============ 一尘看板组件 ============ +function YichensBoard() { + const [stats, setStats] = useState({ posts: null, categories: [], users: null }) + const [posts, setPosts] = useState([]) + const [loading, setLoading] = useState(false) + const [expandedPosts, setExpandedPosts] = useState({}) + const [postTypeFilter, setPostTypeFilter] = useState('all') + + const API_BASE = localStorage.getItem('API_BASE') || '' + + useEffect(() => { + fetchStats() + fetchPosts() + }, []) + + const fetchStats = async () => { + try { + const [postsRes, catRes, usersRes] = await Promise.all([ + fetch(`${API_BASE}/api/yichens/stats/posts?days=30`), + fetch(`${API_BASE}/api/yichens/stats/categories?days=30`), + fetch(`${API_BASE}/api/yichens/stats/users`) + ]) + const postsData = await postsRes.json() + const catData = await catRes.json() + const usersData = await usersRes.json() + setStats({ posts: postsData, categories: catData, users: usersData }) + } catch (e) { + console.error('获取统计失败:', e) + } + } + + const fetchPosts = async () => { + setLoading(true) + try { + let url = `${API_BASE}/api/yichens/posts?limit=50` + if (postTypeFilter !== 'all') url += `&post_type=${postTypeFilter}` + const res = await fetch(url) + const data = await res.json() + setPosts(data || []) + } catch (e) { + console.error('获取帖子失败:', e) + } + setLoading(false) + } + + const togglePostExpand = (postId) => { + setExpandedPosts(prev => ({ ...prev, [postId]: !prev[postId] })) + } + + const filteredPosts = postTypeFilter === 'all' + ? posts + : posts.filter(p => p.post_type === postTypeFilter) + + return ( +
+ {stats.posts && ( +
+
+
30天帖子
+
{stats.posts.total_posts}
+
出售{stats.posts.total_deals} 求购{stats.posts.total_wants}
+
+
+
浏览量
+
{(stats.posts.total_views / 10000).toFixed(1)}万
+
回复{stats.posts.total_replies}
+
+
+
用户数
+
{stats.users?.total_users || 0}
+
今日新增{stats.users?.new_users_today || 0}
+
+
+ )} + +
+
📊 分类统计
+
+ {stats.categories.map(cat => ( + + {cat.category} ({cat.count}) + + ))} +
+
+ +
+ {[ + { key: 'all', label: '全部' }, + { key: 'deal', label: '出售' }, + { key: 'want', label: '求购' } + ].map(ft => ( + + ))} +
+ + {loading ? ( +
加载中...
+ ) : ( +
+ {filteredPosts.map(post => ( +
+
togglePostExpand(post.post_id)} style={{ cursor: 'pointer' }}> +
+ {post.title || '无标题'} + + {post.post_type === 'deal' ? '出售' : post.post_type === 'want' ? '求购' : '普通'} + +
+
+ {post.author_username || '未知'} + {post.post_time ? post.post_time.substring(0, 16) : ''} +
+
+ + {expandedPosts[post.post_id] && ( +
+
+
分类: {post.category || '-'}
+
价格: {post.price ? post.price.toLocaleString() + '元' : '待询'}
+
浏览: {post.view_count || 0}
+
回复: {post.reply_count || 0}
+
+ {post.url && ( + + 查看原帖 + + )} +
+ )} +
+ ))} +
+ )}
) } + diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx index 402147d..73b2d09 100644 --- a/frontend/src/pages/News.jsx +++ b/frontend/src/pages/News.jsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react' +import YichensBoard from './YichensBoard' // 本地获取用户手机号 - 添加异常处理 const getUserPhone = () => { @@ -22,7 +23,7 @@ const getStoredUserId = () => { // 资讯页面 - 展示寻配号和行情信息(所有人可见) export default function News() { - const [activeTab, setActiveTab] = useState(localStorage.getItem('news_activeTab') || 'seek') + const [activeTab, setActiveTab] = useState(localStorage.getItem('news_activeTab') || 'yichen') const [showSeekPublish, setShowSeekPublish] = useState(false) const [showMySeeks, setShowMySeeks] = useState(false) // 我的寻号 const [showContactId, setShowContactId] = useState(null) // 显示联系方式的寻号ID @@ -455,7 +456,7 @@ export default function News() {
) : (
- {infoList.map(item => { + {activeTab === 'yichen' ? : infoList.map(item => { // 解析正文中的号码特征和联系方式 const content = item.content || '' const featuresMatch = content.match(/号码特征[:\s]*(.+?)(?:\n|$)/) diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx new file mode 100644 index 0000000..f105ce4 --- /dev/null +++ b/frontend/src/pages/YichensBoard.jsx @@ -0,0 +1,113 @@ +import React, { useState, useEffect } from 'react' + +export default function YichensBoard() { + const [stats, setStats] = useState({ posts: null, categories: [], users: null }) + const [posts, setPosts] = useState([]) + const [loading, setLoading] = useState(false) + const [expandedPosts, setExpandedPosts] = useState({}) + const [postTypeFilter, setPostTypeFilter] = useState('all') + const API_BASE = localStorage.getItem('API_BASE') || '' + + useEffect(() => { fetchStats(); fetchPosts() }, []) + + const fetchStats = async () => { + try { + const [p, c, u] = await Promise.all([ + fetch(`${API_BASE}/api/yichens/stats/posts?days=30`), + fetch(`${API_BASE}/api/yichens/stats/categories?days=30`), + fetch(`${API_BASE}/api/yichens/stats/users`) + ]) + setStats({ posts: await p.json(), categories: await c.json(), users: await u.json() }) + } catch(e) { console.error(e) } + } + + const fetchPosts = async () => { + setLoading(true) + let url = `${API_BASE}/api/yichens/posts?limit=50` + if (postTypeFilter !== 'all') url += `&post_type=${postTypeFilter}` + try { + const res = await fetch(url) + setPosts((await res.json()) || []) + } catch { setPosts([]) } + setLoading(false) + } + + const togglePost = id => setExpandedPosts(p => ({ ...p, [id]: !p[id] })) + const filtered = postTypeFilter === 'all' ? posts : posts.filter(p => p.post_type === postTypeFilter) + + return ( +
+ {stats.posts && ( +
+
+
30天帖子
+
{stats.posts.total_posts}
+
出售{stats.posts.total_deals} 求购{stats.posts.total_wants}
+
+
+
浏览量
+
{(stats.posts.total_views/10000).toFixed(1)}万
+
回复{stats.posts.total_replies}
+
+
+
用户数
+
{stats.users?.total_users||0}
+
今日新增{stats.users?.new_users_today||0}
+
+
+ )} + +
+
📊 分类统计
+
+ {stats.categories.map(cat => ( + + {cat.category} ({cat.count}) + + ))} +
+
+ +
+ {[{key:'all',label:'全部'},{key:'deal',label:'出售'},{key:'want',label:'求购'}].map(k => ( + + ))} +
+ + {loading ?
加载中...
: +
+ {filtered.map(post => ( +
+
togglePost(post.post_id)} style={{ cursor: 'pointer' }}> +
+ {post.title||'无标题'} + + {post.post_type==='deal'?'出售':post.post_type==='want'?'求购':'普通'} + +
+
+ {post.author_username||'未知'} + {post.post_time?.substring(0,16)||''} +
+
+ {expandedPosts[post.post_id] && ( +
+
+
分类: {post.category||'-'}
+
价格: {post.price?post.price.toLocaleString()+'元':'待询'}
+
浏览: {post.view_count||0}
+
回复: {post.reply_count||0}
+
+ {post.url && 查看原帖} +
+ )} +
+ ))} +
} +
+ ) +} From 9d1b59a42120804c91604f7d7dd3c35fbc0f5125 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Mon, 6 Apr 2026 23:22:57 +0800 Subject: [PATCH 04/10] =?UTF-8?q?v1.2.43=20=E4=B8=80=E5=B0=98=E7=9C=8B?= =?UTF-8?q?=E6=9D=BF=E5=AE=8C=E5=96=84=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/yichens.py | 91 +++++++++++-- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/src/pages/YichensBoard.jsx | 195 ++++++++++++++++++---------- 4 files changed, 212 insertions(+), 78 deletions(-) diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py index e163b4a..331be20 100644 --- a/backend/app/routers/yichens.py +++ b/backend/app/routers/yichens.py @@ -32,6 +32,7 @@ class PostItem(BaseModel): reply_count: int view_count: int url: Optional[str] + content: Optional[str] class UserStat(BaseModel): total_users: int @@ -42,6 +43,7 @@ class UserItem(BaseModel): user_id: str username: str avatar_url: Optional[str] + content: Optional[str] credit_level: Optional[str] credit_score: Optional[int] post_count: int @@ -105,7 +107,7 @@ def get_user_stats(db: Session = Depends(get_coolbot_db)): @router.get("/posts", response_model=List[PostItem]) def get_posts( - limit: int = Query(20, ge=1, le=100), + limit: int = Query(20, ge=1, le=500), offset: int = Query(0, ge=0), category: Optional[str] = None, post_type: Optional[str] = None, @@ -113,7 +115,7 @@ def get_posts( ): """获取帖子列表""" query = """ - SELECT post_id, title, category, post_type, price, + SELECT post_id, title, content, category, post_type, price, author_username, post_time, reply_count, view_count, url FROM yichens_posts WHERE 1=1 @@ -135,19 +137,20 @@ def get_posts( return [PostItem( post_id=r[0], title=r[1] or "", - category=r[2], - post_type=r[3] or "", - price=float(r[4]) if r[4] else None, - author_username=r[5] or "", - post_time=str(r[6]) if r[6] else "", - reply_count=r[7] or 0, - view_count=r[8] or 0, - url=r[9] + content=r[2] or "", + category=r[3], + post_type=r[4] or "", + price=float(r[5]) if r[5] else None, + author_username=r[6] or "", + post_time=str(r[7]) if r[7] else "", + reply_count=r[8] or 0, + view_count=r[9] or 0, + url=r[10] ) for r in results] @router.get("/users", response_model=List[UserItem]) def get_users( - limit: int = Query(20, ge=1, le=100), + limit: int = Query(20, ge=1, le=500), offset: int = Query(0, ge=0), is_seller: Optional[bool] = None, db: Session = Depends(get_coolbot_db) @@ -179,3 +182,69 @@ def get_users( is_seller=r[6] or False, registration_date=str(r[7]) if r[7] else None ) for r in results] + + +@router.get("/stats/today") +async def get_today_stats(db: Session = Depends(get_coolbot_db)): + """获取今日新增帖子统计""" + query = """ + SELECT + COUNT(*) as total, + SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, + SUM(CASE WHEN post_type = 'want' THEN 1 ELSE 0 END) as wants, + SUM(CASE WHEN post_type = 'normal' THEN 1 ELSE 0 END) as others, + SUM(CASE WHEN category LIKE '%龙%' THEN 1 ELSE 0 END) as dragons, + SUM(CASE WHEN category LIKE '%马%' THEN 1 ELSE 0 END) as horses, + SUM(CASE WHEN category LIKE '%蛇%' THEN 1 ELSE 0 END) as snakes + FROM yichens_posts + WHERE post_time >= CURRENT_DATE + """ + result = db.execute(text(query)).fetchone() + return { + "total": result[0] or 0, + "deals": result[1] or 0, + "wants": result[2] or 0, + "others": result[3] or 0, + "dragons": result[3] or 0, + "horses": result[4] or 0, + "snakes": result[5] or 0 + } + +@router.get("/stats/hour") +async def get_hour_stats(db: Session = Depends(get_coolbot_db)): + """获取近一个小时新增帖子统计""" + query = """ + SELECT + COUNT(*) as total, + SUM(CASE WHEN post_type = 'deal' THEN 1 ELSE 0 END) as deals, + SUM(CASE WHEN post_type = 'want' THEN 1 ELSE 0 END) as wants, + SUM(CASE WHEN post_type = 'normal' THEN 1 ELSE 0 END) as others, + SUM(CASE WHEN category LIKE '%龙%' THEN 1 ELSE 0 END) as dragons, + SUM(CASE WHEN category LIKE '%马%' THEN 1 ELSE 0 END) as horses, + SUM(CASE WHEN category LIKE '%蛇%' THEN 1 ELSE 0 END) as snakes + FROM yichens_posts + WHERE post_time >= NOW() - INTERVAL '1 hour' + """ + result = db.execute(text(query)).fetchone() + return { + "total": result[0] or 0, + "deals": result[1] or 0, + "wants": result[2] or 0, + "others": result[3] or 0, + "dragons": result[3] or 0, + "horses": result[4] or 0, + "snakes": result[5] or 0 + } + +@router.get("/stats/today-category") +async def get_today_category_stats(db: Session = Depends(get_coolbot_db)): + """获取今日帖子分类统计""" + query = """ + SELECT category, COUNT(*) as count + FROM yichens_posts + WHERE post_time >= CURRENT_DATE + GROUP BY category + ORDER BY count DESC + """ + results = db.execute(text(query)).fetchall() + return [{"category": r[0] or "未分类", "count": r[1]} for r in results] diff --git a/config/VERSION b/config/VERSION index 4aee9f9..79ee983 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.42 +VERSION=1.2.43 diff --git a/frontend/index.html b/frontend/index.html index bf5cc5a..42efbe9 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.41 + 甲辰收藏 v1.2.42 diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx index f105ce4..0785a51 100644 --- a/frontend/src/pages/YichensBoard.jsx +++ b/frontend/src/pages/YichensBoard.jsx @@ -1,66 +1,99 @@ import React, { useState, useEffect } from 'react' export default function YichensBoard() { - const [stats, setStats] = useState({ posts: null, categories: [], users: null }) + const [todayStats, setTodayStats] = useState({ total: 0, deals: 0, wants: 0, others: 0 }) + const [todayCategory, setTodayCategory] = useState([]) const [posts, setPosts] = useState([]) const [loading, setLoading] = useState(false) const [expandedPosts, setExpandedPosts] = useState({}) const [postTypeFilter, setPostTypeFilter] = useState('all') + const [categoryFilter, setCategoryFilter] = useState('') + const [page, setPage] = useState(1) + const [totalPosts, setTotalPosts] = useState(0) const API_BASE = localStorage.getItem('API_BASE') || '' - useEffect(() => { fetchStats(); fetchPosts() }, []) + const today = new Date() + const dateStr = today.getFullYear() + '/' + (today.getMonth() + 1) + '/' + today.getDate() - const fetchStats = async () => { + useEffect(() => { fetchTodayStats(); fetchPosts(1, '') }, []) + + const fetchTodayStats = async () => { try { - const [p, c, u] = await Promise.all([ - fetch(`${API_BASE}/api/yichens/stats/posts?days=30`), - fetch(`${API_BASE}/api/yichens/stats/categories?days=30`), - fetch(`${API_BASE}/api/yichens/stats/users`) - ]) - setStats({ posts: await p.json(), categories: await c.json(), users: await u.json() }) + const res = await fetch(API_BASE + '/api/yichens/stats/today') + setTodayStats(await res.json()) } catch(e) { console.error(e) } } - const fetchPosts = async () => { + const fetchTodayCategory = async () => { + try { + const res = await fetch(API_BASE + '/api/yichens/stats/today-category') + setTodayCategory(await res.json()) + } catch(e) { console.error(e) } + } + + const fetchPosts = async (p, cat) => { setLoading(true) - let url = `${API_BASE}/api/yichens/posts?limit=50` - if (postTypeFilter !== 'all') url += `&post_type=${postTypeFilter}` + const currentPage = p !== undefined ? p : page + const currentCat = cat !== undefined ? cat : categoryFilter + + let url = API_BASE + '/api/yichens/posts?limit=390&offset=' + ((currentPage - 1) * 390) + if (postTypeFilter === 'deal') url += '&post_type=deal' + else if (postTypeFilter === 'want') url += '&post_type=want' + else if (postTypeFilter === 'other') url += '&post_type=normal' try { const res = await fetch(url) - setPosts((await res.json()) || []) + let data = await res.json() || [] + if (currentCat) { + if (currentCat === '龙') { + data = data.filter(p => p.category && (p.category.includes('龙') || p.category.includes('纪念钞'))) + } else if (currentCat === '蛇') { + data = data.filter(p => p.category && p.category.includes('蛇')) + } else if (currentCat === '马') { + data = data.filter(p => p.category && p.category.includes('马')) + } else if (currentCat === '其他') { + data = data.filter(p => p.category && !p.category.includes('龙') && !p.category.includes('纪念钞') && !p.category.includes('蛇') && !p.category.includes('马')) + } + } + setPosts(data) + setTotalPosts(todayStats.total || 0) } catch { setPosts([]) } setLoading(false) } + useEffect(() => { if (todayStats.total > 0) fetchTodayCategory() }, [todayStats]) + const togglePost = id => setExpandedPosts(p => ({ ...p, [id]: !p[id] })) - const filtered = postTypeFilter === 'all' ? posts : posts.filter(p => p.post_type === postTypeFilter) + + const StatCard = ({ label, value, color, onClick }) => ( +
+
{label}
+
{value}
+
+ ) return (
- {stats.posts && ( -
-
-
30天帖子
-
{stats.posts.total_posts}
-
出售{stats.posts.total_deals} 求购{stats.posts.total_wants}
-
-
-
浏览量
-
{(stats.posts.total_views/10000).toFixed(1)}万
-
回复{stats.posts.total_replies}
-
-
-
用户数
-
{stats.users?.total_users||0}
-
今日新增{stats.users?.new_users_today||0}
-
+
+
+ 📈 连体钞/纪念钞 {dateStr}
- )} +
+ { setPostTypeFilter('all'); setCategoryFilter(''); setPage(1); fetchPosts(1, '') }} /> + { setPostTypeFilter('deal'); setCategoryFilter(''); setPage(1); fetchPosts(1, '') }} /> + { setPostTypeFilter('want'); setCategoryFilter(''); setPage(1); fetchPosts(1, '') }} /> + { setPostTypeFilter('other'); setCategoryFilter(''); setPage(1); fetchPosts(1, '') }} /> +
+
-
📊 分类统计
+
📊 今日分类统计
- {stats.categories.map(cat => ( + {todayCategory.map(cat => ( {cat.category} ({cat.count}) @@ -68,9 +101,9 @@ export default function YichensBoard() {
-
- {[{key:'all',label:'全部'},{key:'deal',label:'出售'},{key:'want',label:'求购'}].map(k => ( -
- {loading ?
加载中...
: -
- {filtered.map(post => ( -
-
togglePost(post.post_id)} style={{ cursor: 'pointer' }}> -
- {post.title||'无标题'} - - {post.post_type==='deal'?'出售':post.post_type==='want'?'求购':'普通'} - -
-
- {post.author_username||'未知'} - {post.post_time?.substring(0,16)||''} -
-
- {expandedPosts[post.post_id] && ( -
-
-
分类: {post.category||'-'}
-
价格: {post.price?post.price.toLocaleString()+'元':'待询'}
-
浏览: {post.view_count||0}
-
回复: {post.reply_count||0}
-
- {post.url && 查看原帖} -
+
+ {[{key:'',label:'全部'},{key:'龙',label:'龙钞'},{key:'蛇',label:'蛇钞'},{key:'马',label:'马钞'},{key:'其他',label:'其他'}].map(k => ( + + ))} +
+ + {loading ?
加载中...
: ( +
+
+
+ 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页 +
+
+ {page > 1 ? ( + + ) : ( + 上一页 + )} + {posts.length >= 390 ? ( + + ) : ( + 下一页 )}
- ))} -
} +
+
+ {posts.map(post => ( +
+
togglePost(post.post_id)} style={{ cursor: 'pointer' }}> +
+ {post.title||'无标题'} +
+ + {post.post_type==='deal'?'出售':post.post_type==='want'?'求购':post.post_type==='normal'?'其他':'普通'} + + + {post.category || '-'} + +
+
+
+ {post.author_username||'未知'} + {post.post_time?.substring(0,16)||''} +
+
+ {expandedPosts[post.post_id] && post.content && ( +
+
+ {post.content} +
+ {post.url && 查看原帖} +
+ )} +
+ ))} +
+
+ )}
) } From f11c4a0be06784009094d7cd92300cbce7d126cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Tue, 7 Apr 2026 13:07:20 +0800 Subject: [PATCH 05/10] =?UTF-8?q?v1.2.45=20=E4=B8=80=E5=B0=98=E7=9C=8B?= =?UTF-8?q?=E6=9D=BF=E4=BC=98=E5=8C=96=E7=89=88=EF=BC=9A=E9=A6=96=E9=A1=B5?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=B8=80=E5=B0=98=E6=95=B0=E6=8D=AE=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E3=80=81=E4=B8=80=E5=B0=98=E7=9C=8B=E6=9D=BF=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=8A=9F=E8=83=BD=E3=80=81=E5=88=86=E7=B1=BB=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/yichens.py | 10 ++-- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/src/pages/Home.jsx | 39 ++++++++++++++ frontend/src/pages/YichensBoard.jsx | 82 +++++++++++++++++++++-------- 5 files changed, 108 insertions(+), 27 deletions(-) diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py index 331be20..9b55df7 100644 --- a/backend/app/routers/yichens.py +++ b/backend/app/routers/yichens.py @@ -195,7 +195,8 @@ async def get_today_stats(db: Session = Depends(get_coolbot_db)): SUM(CASE WHEN post_type = 'normal' THEN 1 ELSE 0 END) as others, SUM(CASE WHEN category LIKE '%龙%' THEN 1 ELSE 0 END) as dragons, SUM(CASE WHEN category LIKE '%马%' THEN 1 ELSE 0 END) as horses, - SUM(CASE WHEN category LIKE '%蛇%' THEN 1 ELSE 0 END) as snakes + SUM(CASE WHEN category LIKE '%蛇%' THEN 1 ELSE 0 END) as snakes, + SUM(CASE WHEN title LIKE '%天马%' OR content LIKE '%天马%' THEN 1 ELSE 0 END) as tianma FROM yichens_posts WHERE post_time >= CURRENT_DATE """ @@ -205,9 +206,10 @@ async def get_today_stats(db: Session = Depends(get_coolbot_db)): "deals": result[1] or 0, "wants": result[2] or 0, "others": result[3] or 0, - "dragons": result[3] or 0, - "horses": result[4] or 0, - "snakes": result[5] or 0 + "dragons": result[4] or 0, + "horses": result[5] or 0, + "snakes": result[6] or 0, + "tianma": result[7] or 0 } @router.get("/stats/hour") diff --git a/config/VERSION b/config/VERSION index 79ee983..f6594fa 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.43 +VERSION=1.2.45 diff --git a/frontend/index.html b/frontend/index.html index 42efbe9..cdce529 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.42 + 甲辰收藏 v1.2.45 diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index dc0262b..19770b4 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -5,6 +5,7 @@ export default function Home() { const [user, setUser] = useState(null) const [stats, setStats] = useState({ totalCount: 0, totalCost: 0, totalRevenue: 0, expectedProfit: 0, totalProfit: 0, gradedCount: 0 }) const [recentCollections, setRecentCollections] = useState([]) + const [yichensStats, setYichensStats] = useState({ total: 0, deals: 0, wants: 0, dragons: 0, horses: 0, tianma: 0 }) const currentPath = window.location.hash.slice(1) || '/' useEffect(() => { @@ -54,6 +55,13 @@ export default function Home() { } }, []) + // 获取一尘看板数据 + useEffect(() => { + fetch('/api/yichens/stats/today').then(res => res.json()).then(data => { + setYichensStats(data || {}) + }).catch(() => {}) + }, []) + // 检查是否为管理员 const isAdmin = user && user.role === 'admin' @@ -138,6 +146,37 @@ export default function Home() { ))}
+ {/* 一尘看板数据 */} +
+
📊 一尘今日数据
+
+
+
{yichensStats.total || 0}
+
总帖子
+
+
+
{yichensStats.deals || 0}
+
出售
+
+
+
{yichensStats.wants || 0}
+
求购
+
+
+
{yichensStats.dragons || 0}
+
龙钞
+
+
+
{yichensStats.horses || 0}
+
马钞
+
+
+
{yichensStats.tianma || 0}
+
天马
+
+
+
+ {/* 快捷操作 */}
快捷操作
diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx index 0785a51..7af8ba1 100644 --- a/frontend/src/pages/YichensBoard.jsx +++ b/frontend/src/pages/YichensBoard.jsx @@ -10,6 +10,7 @@ export default function YichensBoard() { const [categoryFilter, setCategoryFilter] = useState('') const [page, setPage] = useState(1) const [totalPosts, setTotalPosts] = useState(0) + const [searchKeyword, setSearchKeyword] = useState('') const API_BASE = localStorage.getItem('API_BASE') || '' const today = new Date() @@ -54,6 +55,18 @@ export default function YichensBoard() { data = data.filter(p => p.category && !p.category.includes('龙') && !p.category.includes('纪念钞') && !p.category.includes('蛇') && !p.category.includes('马')) } } + // 搜索关键词过滤 - 强匹配(完全包含) + if (searchKeyword) { + const kw = searchKeyword.trim() + if (kw) { + data = data.filter(p => + (p.title && p.title.includes(kw)) || + (p.content && p.content.includes(kw)) || + (p.category && p.category.includes(kw)) || + (p.contact && p.contact.includes(kw)) + ) + } + } setPosts(data) setTotalPosts(todayStats.total || 0) } catch { setPosts([]) } @@ -62,6 +75,12 @@ export default function YichensBoard() { useEffect(() => { if (todayStats.total > 0) fetchTodayCategory() }, [todayStats]) + // 搜索关键词变化时重新获取数据 + useEffect(() => { + setPage(1) + fetchPosts(1, '') + }, [searchKeyword]) + const togglePost = id => setExpandedPosts(p => ({ ...p, [id]: !p[id] })) const StatCard = ({ label, value, color, onClick }) => ( @@ -90,17 +109,36 @@ export default function YichensBoard() {
-
-
📊 今日分类统计
-
+
+
📊 今日分类统计
+
{todayCategory.map(cat => ( - + {cat.category} ({cat.count}) ))}
+ {/* 搜索框 */} +
+
+ { setSearchKeyword(e.target.value); setPage(1); fetchPosts(1, '') }} + style={{ flex: 1, padding: '10px 14px', borderRadius: 8, border: '1px solid #374151', background: '#1f2937', color: '#fff', fontSize: 13, outline: 'none' }} + /> + +
+ {searchKeyword &&
搜索: "{searchKeyword}",找到 {posts.length} 条结果
} +
+
{[{key:'all',label:'全部'},{key:'deal',label:'出售'},{key:'want',label:'求购'},{key:'other',label:'其他'}].map(k => ( - ) : ( - 上一页 - )} - {posts.length >= 390 ? ( - - ) : ( - 下一页 - )} -
-
{posts.map(post => (
@@ -171,6 +192,25 @@ export default function YichensBoard() {
))}
+ + {/* 分页按钮移到页面底部 */} +
+
+ 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页 +
+
+ {page > 1 ? ( + + ) : ( + 上一页 + )} + {posts.length >= 390 ? ( + + ) : ( + 下一页 + )} +
+
)}
From 3c3072159af0ed6698bb3fea7384a6b439325f78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Tue, 7 Apr 2026 13:14:51 +0800 Subject: [PATCH 06/10] =?UTF-8?q?v1.2.46=20=E4=B8=80=E5=B0=98=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E9=A6=96=E9=A1=B5=E4=BC=98=E5=8C=96=E7=89=88=EF=BC=9A?= =?UTF-8?q?=E9=A6=96=E9=A1=B5=E5=B8=83=E5=B1=80=E8=B0=83=E6=95=B4=E3=80=81?= =?UTF-8?q?=E6=9C=80=E6=96=B0=E4=B8=80=E5=B0=98=E5=8F=91=E5=B8=96=E5=B1=95?= =?UTF-8?q?=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/src/pages/Home.jsx | 100 ++++++++++++++++++++---------------- 3 files changed, 58 insertions(+), 46 deletions(-) diff --git a/config/VERSION b/config/VERSION index f6594fa..0c9e143 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.45 +VERSION=1.2.46 diff --git a/frontend/index.html b/frontend/index.html index cdce529..81ca7b3 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.45 + 甲辰收藏 v1.2.46 diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 19770b4..816ccb6 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -6,6 +6,7 @@ export default function Home() { const [stats, setStats] = useState({ totalCount: 0, totalCost: 0, totalRevenue: 0, expectedProfit: 0, totalProfit: 0, gradedCount: 0 }) const [recentCollections, setRecentCollections] = useState([]) const [yichensStats, setYichensStats] = useState({ total: 0, deals: 0, wants: 0, dragons: 0, horses: 0, tianma: 0 }) + const [recentPosts, setRecentPosts] = useState([]) const currentPath = window.location.hash.slice(1) || '/' useEffect(() => { @@ -60,6 +61,11 @@ export default function Home() { fetch('/api/yichens/stats/today').then(res => res.json()).then(data => { setYichensStats(data || {}) }).catch(() => {}) + + // 获取最新一尘帖子 + fetch('/api/yichens/posts?limit=10&offset=0').then(res => res.json()).then(data => { + setRecentPosts(Array.isArray(data) ? data : []) + }).catch(() => {}) }, []) // 检查是否为管理员 @@ -146,37 +152,6 @@ export default function Home() { ))}
- {/* 一尘看板数据 */} -
-
📊 一尘今日数据
-
-
-
{yichensStats.total || 0}
-
总帖子
-
-
-
{yichensStats.deals || 0}
-
出售
-
-
-
{yichensStats.wants || 0}
-
求购
-
-
-
{yichensStats.dragons || 0}
-
龙钞
-
-
-
{yichensStats.horses || 0}
-
马钞
-
-
-
{yichensStats.tianma || 0}
-
天马
-
-
-
- {/* 快捷操作 */}
快捷操作
@@ -214,11 +189,42 @@ export default function Home() {
- {/* 最近藏品 */} + {/* 一尘今日数据 */} +
+
📊 一尘今日数据
+
+
+
{yichensStats.total || 0}
+
总帖子
+
+
+
{yichensStats.deals || 0}
+
出售
+
+
+
{yichensStats.wants || 0}
+
求购
+
+
+
{yichensStats.dragons || 0}
+
龙钞
+
+
+
{yichensStats.horses || 0}
+
马钞
+
+
+
{yichensStats.tianma || 0}
+
天马
+
+
+
+ + {/* 最新一尘发帖 */}
-
最近藏品
-
window.location.hash = '#/list'} style={{ color: '#3b82f6', fontSize: '12px', cursor: 'pointer' }}>查看全部 →
+
📝 最新一尘发帖
+
window.location.hash = '#/news'} style={{ color: '#3b82f6', fontSize: '12px', cursor: 'pointer' }}>查看全部 →
- {recentCollections.length === 0 ? ( -
暂无藏品
+ {recentPosts.length === 0 ? ( +
暂无帖子
) : ( - recentCollections.map((item, idx) => ( -
window.location.hash = '#/detail?id=' + item.id} style={{ + recentPosts.map((item, idx) => ( +
window.location.hash = '#/news'} style={{ padding: '12px 16px', - borderBottom: idx < recentCollections.length - 1 ? '1px solid rgba(255,255,255,0.05)' : 'none', + borderBottom: idx < recentPosts.length - 1 ? '1px solid rgba(255,255,255,0.05)' : 'none', cursor: 'pointer', display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}> -
-
{item.code || '-'} {item.prefixSerial || ''}
-
{item.version || '-'} · {item.status === 'sold' ? '已售' : item.status === 'in_collection' ? '收藏中' : item.status}
+
+
{item.title || '无标题'}
+
{item.category || '-'} · {item.author_username || '未知'} · {item.post_time?.substring(0, 16) || ''}
-
- {item.costPrice ? '¥' + item.costPrice : '-'} +
+ {item.post_type === 'deal' ? '出售' : item.post_type === 'want' ? '求购' : '其他'}
)) From 97cc72754769e75b448c1a0c2f9d9896a748d527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Tue, 7 Apr 2026 14:01:55 +0800 Subject: [PATCH 07/10] =?UTF-8?q?v1.2.47=20=E4=BF=AE=E5=A4=8Dlogo=20404?= =?UTF-8?q?=E9=97=AE=E9=A2=98=EF=BC=9A=E7=A7=BB=E9=99=A4=E7=89=88=E6=9C=AC?= =?UTF-8?q?=E5=8F=82=E6=95=B0=EF=BC=8C=E4=BF=AE=E5=A4=8Dstatic=E8=B7=AF?= =?UTF-8?q?=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/main.py | 8 ++++---- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/src/pages/Home.jsx | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/app/main.py b/backend/app/main.py index e43e400..2ec184f 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -66,10 +66,10 @@ uploads_dir = "uploads" os.makedirs(uploads_dir, exist_ok=True) app.mount("/uploads", StaticFiles(directory=uploads_dir), name="uploads") -# 挂载项目静态资源目录(可选,生产环境建议用 Nginx) -# static_dir = Path(__file__).parent.parent.parent / "static" -# if static_dir.exists(): -# app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") +# 挂载项目静态资源目录 +static_dir = Path(__file__).parent.parent / "static" +if static_dir.exists(): + app.mount("/static", StaticFiles(directory=str(static_dir)), name="static") # 添加日志中间件 app.middleware("http")(logging_middleware) diff --git a/config/VERSION b/config/VERSION index 0c9e143..5306092 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.46 +VERSION=1.2.47 diff --git a/frontend/index.html b/frontend/index.html index 81ca7b3..44beab4 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.46 + 甲辰收藏 v1.2.47 diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 816ccb6..1a044ca 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -110,7 +110,7 @@ export default function Home() { backdropFilter: 'blur(10px)' }}>
- 甲辰收藏 + 甲辰收藏
{user?.username || '用户'}
欢迎回来 👋
From 195d779e79d67bac9f7c3d1a6bb377d929b9b5ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Tue, 7 Apr 2026 14:11:35 +0800 Subject: [PATCH 08/10] =?UTF-8?q?v1.2.48=20=E4=B8=80=E5=B0=98=E7=9C=8B?= =?UTF-8?q?=E6=9D=BF=E9=98=B6=E6=AE=B5=E6=80=A7=E7=A8=B3=E5=AE=9A=E7=89=88?= =?UTF-8?q?=EF=BC=9A=E5=88=A0=E9=99=A4Info=E9=9D=A2=E6=9D=BF=E4=B8=80?= =?UTF-8?q?=E5=B0=98=E7=9C=8B=E6=9D=BFtab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/src/pages/Info.jsx | 161 +----------------------------------- 3 files changed, 4 insertions(+), 161 deletions(-) diff --git a/config/VERSION b/config/VERSION index 5306092..85b0ff0 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.47 +VERSION=1.2.48 diff --git a/frontend/index.html b/frontend/index.html index 44beab4..d6edbe5 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.47 + 甲辰收藏 v1.2.48 diff --git a/frontend/src/pages/Info.jsx b/frontend/src/pages/Info.jsx index 2804236..17c7baf 100644 --- a/frontend/src/pages/Info.jsx +++ b/frontend/src/pages/Info.jsx @@ -9,7 +9,7 @@ export default function Info() { } // 顶部tab:寻配号发布 / 发布管理 - const [activeTab, setActiveTab] = useState('yichens') + const [activeTab, setActiveTab] = useState('manage') const [showPublish, setShowPublish] = useState(false) const [publishType, setPublishType] = useState('deal') // 'seek' 或 'deal' const [myList, setMyList] = useState([]) @@ -227,8 +227,7 @@ export default function Info() {
{[ - { key: 'manage', label: '📋 发布管理' }, - { key: 'yichens', label: '📊 一尘看板' } + { key: 'manage', label: '📋 发布管理' } ].map(tab => (
)} - - {/* 一尘看板页 */} - {activeTab === 'yichens' && ( - - )}
) } -// ============ 一尘看板组件 ============ -function YichensBoard() { - const [stats, setStats] = useState({ posts: null, categories: [], users: null }) - const [posts, setPosts] = useState([]) - const [loading, setLoading] = useState(false) - const [expandedPosts, setExpandedPosts] = useState({}) - const [postTypeFilter, setPostTypeFilter] = useState('all') - - const API_BASE = localStorage.getItem('API_BASE') || '' - - useEffect(() => { - fetchStats() - fetchPosts() - }, []) - - const fetchStats = async () => { - try { - const [postsRes, catRes, usersRes] = await Promise.all([ - fetch(`${API_BASE}/api/yichens/stats/posts?days=30`), - fetch(`${API_BASE}/api/yichens/stats/categories?days=30`), - fetch(`${API_BASE}/api/yichens/stats/users`) - ]) - const postsData = await postsRes.json() - const catData = await catRes.json() - const usersData = await usersRes.json() - setStats({ posts: postsData, categories: catData, users: usersData }) - } catch (e) { - console.error('获取统计失败:', e) - } - } - - const fetchPosts = async () => { - setLoading(true) - try { - let url = `${API_BASE}/api/yichens/posts?limit=50` - if (postTypeFilter !== 'all') url += `&post_type=${postTypeFilter}` - const res = await fetch(url) - const data = await res.json() - setPosts(data || []) - } catch (e) { - console.error('获取帖子失败:', e) - } - setLoading(false) - } - - const togglePostExpand = (postId) => { - setExpandedPosts(prev => ({ ...prev, [postId]: !prev[postId] })) - } - - const filteredPosts = postTypeFilter === 'all' - ? posts - : posts.filter(p => p.post_type === postTypeFilter) - - return ( -
- {stats.posts && ( -
-
-
30天帖子
-
{stats.posts.total_posts}
-
出售{stats.posts.total_deals} 求购{stats.posts.total_wants}
-
-
-
浏览量
-
{(stats.posts.total_views / 10000).toFixed(1)}万
-
回复{stats.posts.total_replies}
-
-
-
用户数
-
{stats.users?.total_users || 0}
-
今日新增{stats.users?.new_users_today || 0}
-
-
- )} - -
-
📊 分类统计
-
- {stats.categories.map(cat => ( - - {cat.category} ({cat.count}) - - ))} -
-
- -
- {[ - { key: 'all', label: '全部' }, - { key: 'deal', label: '出售' }, - { key: 'want', label: '求购' } - ].map(ft => ( - - ))} -
- - {loading ? ( -
加载中...
- ) : ( -
- {filteredPosts.map(post => ( -
-
togglePostExpand(post.post_id)} style={{ cursor: 'pointer' }}> -
- {post.title || '无标题'} - - {post.post_type === 'deal' ? '出售' : post.post_type === 'want' ? '求购' : '普通'} - -
-
- {post.author_username || '未知'} - {post.post_time ? post.post_time.substring(0, 16) : ''} -
-
- - {expandedPosts[post.post_id] && ( -
-
-
分类: {post.category || '-'}
-
价格: {post.price ? post.price.toLocaleString() + '元' : '待询'}
-
浏览: {post.view_count || 0}
-
回复: {post.reply_count || 0}
-
- {post.url && ( - - 查看原帖 - - )} -
- )} -
- ))} -
- )} -
- ) -} From 693e13bbab5023b27be24eacee4edb3f3613adff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Tue, 7 Apr 2026 14:18:17 +0800 Subject: [PATCH 09/10] =?UTF-8?q?v1.2.49=20=E5=8F=91=E5=B8=83=E5=AF=BB?= =?UTF-8?q?=E5=8F=B7=E4=BC=98=E5=8C=96=E7=89=88=EF=BC=9A=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E8=BE=93=E5=85=A5D/E=E5=AD=97=E7=AC=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/src/pages/News.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/VERSION b/config/VERSION index 85b0ff0..9d33ae0 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.48 +VERSION=1.2.49 diff --git a/frontend/index.html b/frontend/index.html index d6edbe5..b11aa93 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.48 + 甲辰收藏 v1.2.49 diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx index 73b2d09..adad95b 100644 --- a/frontend/src/pages/News.jsx +++ b/frontend/src/pages/News.jsx @@ -389,7 +389,7 @@ export default function News() { readOnly={isFixed} onChange={(e) => { if (i < 2) return - const val = e.target.value.toUpperCase().replace(/[^0-9XABCFG]/g, '') + const val = e.target.value.toUpperCase().replace(/[^0-9XABCDEFG]/g, '') const newFeatures = (seekForm.features || '').split('') while (newFeatures.length < 8) newFeatures.push('') newFeatures[i - 2] = val From e8c3069bfd5446de68cc4d3d6508501140999ef9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=BE=99=E5=A4=A7?= Date: Tue, 7 Apr 2026 15:49:35 +0800 Subject: [PATCH 10/10] =?UTF-8?q?v1.2.50=20=E9=98=B6=E6=AE=B5=E6=80=A7?= =?UTF-8?q?=E5=AE=8C=E5=96=84=E7=89=88=E6=9C=AC=EF=BC=9A=E5=AF=BB=E9=85=8D?= =?UTF-8?q?=E5=8F=B7=E4=BC=98=E5=8C=96=E3=80=81=E7=BD=91=E7=BB=9C=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=8C=B9=E9=85=8D=E3=80=81=E5=88=86=E9=A1=B5=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=81logo=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/information.py | 168 ++++++++++++++++++++++++++++- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/src/pages/News.jsx | 119 ++++++++++++++++++-- 4 files changed, 277 insertions(+), 14 deletions(-) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index 2987785..a249fbb 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -1,12 +1,14 @@ # 资讯API路由 from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session, joinedload +from sqlalchemy import text from typing import List, Optional from pydantic import BaseModel from datetime import datetime, date from app.core.database import get_db from app.core.auth import get_current_user +from app.core.coolbot_db import coolbot_engine from app.models.models import User, Information, Collection router = APIRouter(prefix="/api/information", tags=["资讯"]) @@ -74,6 +76,8 @@ class InformationResponse(BaseModel): collection_number: Optional[str] = None # 匹配数量(我的藏品中满足条件的数量) matched_count: Optional[int] = 0 + # 网络数据匹配数量(coolbot_data数据库中满足条件的数量) + network_matched_count: Optional[int] = 0 class Config: from_attributes = True @@ -87,7 +91,8 @@ def get_information_list( page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), current_user: Optional[User] = Depends(get_current_user), - db: Session = Depends(get_db) + db: Session = Depends(get_db), + response: Response = None ): """获取资讯列表(公开,无需登录)""" query = db.query(Information).options( @@ -142,8 +147,21 @@ def get_information_list( collection_version=item.collection.f02_11_version if item.collection else None, collection_number=item.collection.f02_10_prefix_serial if item.collection else None, matched_count=matched_count, + network_matched_count=match_collections_count_from_coolbot(item.expect_number) if item.info_type == 'seek' and item.expect_number else 0, )) + # 获取总数并设置响应头 + from fastapi import Response + total_query = db.query(Information).filter(Information.status == status) + if info_type: + total_query = total_query.filter(Information.info_type == info_type) + total_count = total_query.count() + total_pages = (total_count + page_size - 1) // page_size + + # 设置响应头 + response.headers['X-Total-Pages'] = str(total_pages) + response.headers['X-Total-Count'] = str(total_count) + return result @@ -182,6 +200,110 @@ def match_collections_count(db: Session, user_id: str, expect_number: str) -> in return count +def match_collections_count_from_coolbot(expect_number: str) -> int: + """根据号码特征计算匹配藏品数量(从coolbot_data数据库)""" + if not expect_number or len(expect_number) != 10: + return 0 + + # 固定前缀 + if not expect_number.startswith('J0'): + return 0 + + pattern = expect_number[2:] # 后8位 + if not pattern: + return 0 + + # 直接查询coolbot_data数据库 + query = text(""" + SELECT COUNT(*) FROM collections + WHERE crown_code IS NOT NULL + AND crown_code != '' + AND LENGTH(crown_code) >= 10 + AND crown_code LIKE 'J0%' + """) + + try: + with coolbot_engine.connect() as conn: + result = conn.execute(query) + total_count = result.scalar() or 0 + + # 遍历匹配 + query_all = text(""" + SELECT id, crown_code FROM collections + WHERE crown_code IS NOT NULL + AND crown_code != '' + AND LENGTH(crown_code) >= 10 + AND crown_code LIKE 'J0%' + """) + result = conn.execute(query_all) + + match_count = 0 + for row in result: + crown_code = row[1] + if crown_code and len(crown_code) >= 10: + col_pattern = crown_code[2:10] + if match_pattern(col_pattern, pattern): + match_count += 1 + + return match_count + except Exception as e: + print(f"Error querying coolbot_data: {e}") + return 0 + + +def match_collections_list_from_coolbot(expect_number: str, limit: int = 20) -> List[dict]: + """获取匹配的藏品列表(从coolbot_data数据库)""" + if not expect_number or len(expect_number) != 10: + return [] + + # 固定前缀 + if not expect_number.startswith('J0'): + return [] + + pattern = expect_number[2:] # 后8位 + if not pattern: + return [] + + # 直接查询coolbot_data数据库 + query = text(""" + SELECT id, name, category, crown_code, price, post_title, post_url, author, post_crawled_at + FROM collections + WHERE crown_code IS NOT NULL + AND crown_code != '' + AND LENGTH(crown_code) >= 10 + AND crown_code LIKE 'J0%' + """) + + try: + with coolbot_engine.connect() as conn: + result = conn.execute(query) + + matched = [] + for row in result: + crown_code = row[3] + if crown_code and len(crown_code) >= 10: + col_pattern = crown_code[2:10] + if match_pattern(col_pattern, pattern): + matched.append({ + "id": row[0], + "name": row[1], + "category": row[2], + "crown_code": crown_code, + "price": float(row[4]) if row[4] else None, + "post_title": row[5], + "post_url": row[6], + "author": row[7], + "post_crawled_at": row[8].isoformat() if row[8] else None + }) + if len(matched) >= limit: + break + + return matched + except Exception as e: + print(f"Error querying coolbot_data: {e}") + return [] + + def match_pattern(col_number: str, pattern: str) -> bool: """匹配号码特征模式""" # X = 任意数字 @@ -502,7 +624,36 @@ def get_seek_match( "cost_price": c.f05_40_cost_price, } for c in matched - ] + ], + "network_matched_count": match_collections_count_from_coolbot(info.expect_number) if info.expect_number else 0, + "network_collections": match_collections_list_from_coolbot(info.expect_number, limit=20) if info.expect_number else [] + } + + +# 获取网络数据匹配列表 +@router.get("/seek/network-match/{info_id}") +def get_network_match( + info_id: str, + limit: int = Query(20, ge=1, le=100), + db: Session = Depends(get_db) +): + """获取一尘数据库中匹配的藏品列表""" + info = db.query(Information).filter( + Information.id == info_id, + Information.info_type == "seek" + ).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 } @@ -558,8 +709,21 @@ def get_my_seeks( collection_version=item.collection.f02_11_version if item.collection else None, collection_number=item.collection.f02_10_prefix_serial if item.collection else None, matched_count=matched_count, + network_matched_count=match_collections_count_from_coolbot(item.expect_number) if item.info_type == 'seek' and item.expect_number else 0, )) + # 获取总数并设置响应头 + from fastapi import Response + total_query = db.query(Information).filter(Information.status == status) + if info_type: + total_query = total_query.filter(Information.info_type == info_type) + total_count = total_query.count() + total_pages = (total_count + page_size - 1) // page_size + + # 设置响应头 + response.headers['X-Total-Pages'] = str(total_pages) + response.headers['X-Total-Count'] = str(total_count) + return result diff --git a/config/VERSION b/config/VERSION index 9d33ae0..532db09 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.49 +VERSION=1.2.50 diff --git a/frontend/index.html b/frontend/index.html index b11aa93..6525875 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.49 + 甲辰收藏 v1.2.50 diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx index adad95b..b316cc6 100644 --- a/frontend/src/pages/News.jsx +++ b/frontend/src/pages/News.jsx @@ -33,6 +33,8 @@ export default function News() { const [matchedStatus, setMatchedStatus] = useState({}) // 存储各寻号的匹配状态 const [showMatchList, setShowMatchList] = useState(false) const [matchCollections, setMatchCollections] = useState([]) + const [networkMatchCollections, setNetworkMatchCollections] = useState([]) + const [showNetworkMatchList, setShowNetworkMatchList] = useState(false) const [customModal, setCustomModal] = useState({show: false, title: '', content: ''}) const [seekForm, setSeekForm] = useState({ edition: '龙钞', type: '', category: '寻号', price: '', matchType: '模糊', features: '', content: '', title: '', contact: getUserPhone() || '' @@ -41,6 +43,8 @@ export default function News() { const [viewMode, setViewMode] = useState('all') const [expandedItems, setExpandedItems] = useState({}) // 展开状态 const [loading, setLoading] = useState(false) + const [currentPage, setCurrentPage] = useState(1) + const [totalPages, setTotalPages] = useState(1) const API_BASE = localStorage.getItem('API_BASE') || '' const currentUser = localStorage.getItem('token') ? { id: 'logged_in' } : null @@ -73,7 +77,10 @@ export default function News() { const type = activeTab === 'seek' ? 'seek' : activeTab === 'deal' ? 'deal' : 'yichen' // 始终传递token,以便获取准确的matched_count const headers = token ? { Authorization: `Bearer ${token}` } : {} - const res = await fetch(`${API_BASE}/api/information/list?info_type=${type}`, { headers }) + const res = await fetch(`${API_BASE}/api/information/list?info_type=${type}&page=${currentPage}&page_size=50`, { headers }) + // 从响应头获取总页数 + const total = res.headers.get('X-Total-Pages') || res.headers.get('x-total-pages') + if (total) setTotalPages(parseInt(total)) // 检查响应状态 if (!res.ok) { console.error('获取资讯列表失败:', res.status) @@ -147,10 +154,11 @@ export default function News() { const token = localStorage.getItem('token') if (!token) { alert('请先登录'); return } try { + const phone = getUserPhone() 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 }) + body: JSON.stringify({ info_id: infoId, contact: phone || '用户已同意' }) }) const data = await res.json() if (data.message === '匹配成功,已通知发布者') { @@ -188,7 +196,7 @@ export default function News() { console.log('Matched user response:', res.status) const data = await res.json() console.log('Matched user data:', data) - if (data.user_name) setCustomModal({show: true, title: '匹配者信息', content: `用户名: ${data.user_name}\n联系方式: ${data.matched_contact || '未提供'}`}) + if (data.user_name) setCustomModal({show: true, title: '匹配者信息', content: `用户名: ${data.user_name}\n手机号: ${data.phone || '未提供'}\n\n免责声明及风险提示:所有用户信息仅作参考,交易请走正规平台,如产生经济损失与本站无关,后果自负。`}) else if (data.detail) alert(data.detail) } catch (e) { console.error(e) } } @@ -201,7 +209,7 @@ export default function News() { console.log('Publisher response:', res.status) const data = await res.json() console.log('Publisher data:', data) - if (data.user_name) setCustomModal({show: true, title: '发布者信息', content: `用户名: ${data.user_name}\n联系方式: ${data.contact || '未提供'}`}) + if (data.user_name) setCustomModal({show: true, title: '发布者信息', content: `用户名: ${data.user_name}\n手机号: ${data.phone || '未提供'}\n\n免责声明及风险提示:所有用户信息仅作参考,交易请走正规平台,如产生经济损失与本站无关,后果自负。`}) else if (data.detail) alert(data.detail) } catch (e) { console.error(e) } } @@ -224,6 +232,20 @@ export default function News() { } } + // 获取网络数据匹配列表 + 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) + } + } + // 获取当前用户ID const currentUserId = getStoredUserId() @@ -523,12 +545,20 @@ export default function News() { {/* 配号结果 - 仅寻配号显示 */} {activeTab === 'seek' && (
- fetchMatchCollections(item.id)} > - 配号结果: {item.matched_count || 0}条藏品匹配成功 + 自有{item.matched_count || 0}条藏品匹配成功 + {item.network_matched_count !== undefined && ( + fetchNetworkMatchCollections(item.id)} + > + 网络数据{item.network_matched_count}条匹配成功 + + )}
)} {/* 正文 - 默认收起,点击展开 */} @@ -615,23 +645,24 @@ export default function News() {
)} + {/* 网络数据匹配藏品列表弹窗 */} + {showNetworkMatchList && ( +
+
+
+

匹配藏品清单(网络数据)

+ +
+ {networkMatchCollections.length === 0 ? ( +
暂无匹配藏品
+ ) : ( +
+ {networkMatchCollections.map(c => ( +
{ + if (c.post_url) { + window.open(c.post_url, '_blank') + } + setShowNetworkMatchList(false) + }} + > +
+ 名称: {c.name || '-'} +
+
+ 冠字号: {c.crown_code} +
+ {c.price && ( +
+ 价格: ¥{c.price} +
+ )} +
+ 点击查看原帖 → +
+
+ ))} +
+ )} +
+
+ )} + + {/* 分页组件 */} + {totalPages > 1 && ( +
+ + + 第 {currentPage} / {totalPages} 页 + + +
+ )} + {/* 我的寻号弹窗 */} {showMySeeks && (