diff --git a/VERSION b/VERSION
index dd91a8b..a67e110 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-1.2.98
+1.2.99
diff --git a/backend/VERSION b/backend/VERSION
index dd91a8b..a67e110 100644
--- a/backend/VERSION
+++ b/backend/VERSION
@@ -1 +1 @@
-1.2.98
+1.2.99
diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py
index 7fb9484..46ed3b8 100644
--- a/backend/app/routers/information.py
+++ b/backend/app/routers/information.py
@@ -7,6 +7,8 @@ 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 sqlalchemy import text
from app.models.models import User, Information, Collection
router = APIRouter(prefix="/api/information", tags=["资讯"])
@@ -182,6 +184,88 @@ 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:]
+ if not pattern:
+ return 0
+
+ query = 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%'
+ """)
+
+ try:
+ with coolbot_engine.connect() as conn:
+ result = conn.execute(query)
+ 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:]
+ if not pattern:
+ return []
+
+ 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": str(row[8]) 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 = 任意数字
@@ -891,3 +975,91 @@ def get_publisher_info(
"contact": contact,
"created_at": info.created_at.isoformat() if info.created_at else None
}
+
+
+# 获取网络数据匹配列表
+@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
+ }
+
+
+# 获取所有seek列表(包含网络匹配数量)
+@router.get("/seek/list")
+def get_seek_list_all(
+ status: str = Query("active"),
+ user_id: Optional[str] = None,
+ user_only: bool = Query(False),
+ current_user: Optional[User] = Depends(get_current_user),
+ db: Session = Depends(get_db)
+):
+ """获取寻配号列表(包含网络匹配数量)"""
+ query = db.query(Information).filter(
+ Information.info_type == "seek",
+ Information.status == status
+ )
+
+ if user_only and current_user:
+ query = query.filter(Information.user_id == current_user.f99_90_id)
+
+ if user_id:
+ query = query.filter(Information.user_id == user_id)
+
+ items = query.order_by(Information.created_at.desc()).all()
+
+ result = []
+ for item in items:
+ # 计算自有匹配数量
+ matched_count = 0
+ if item.expect_number:
+ matched_count = match_collections_count(db, current_user.f99_90_id if current_user else "", item.expect_number)
+
+ # 计算网络匹配数量
+ network_matched_count = 0
+ if item.expect_number:
+ network_matched_count = match_collections_count_from_coolbot(item.expect_number)
+
+ result.append({
+ "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.isoformat() if item.created_at else None,
+ "user_name": item.user.f01_01_name if item.user else None,
+ "matched_count": matched_count,
+ "network_matched_count": network_matched_count
+ })
+
+ return result
diff --git a/frontend/VERSION b/frontend/VERSION
index dd91a8b..a67e110 100644
--- a/frontend/VERSION
+++ b/frontend/VERSION
@@ -1 +1 @@
-1.2.98
+1.2.99
diff --git a/frontend/index.html b/frontend/index.html
index 7335504..ae69927 100644
--- a/frontend/index.html
+++ b/frontend/index.html
@@ -4,7 +4,7 @@
-
甲辰收藏 v=1.2.98
+ 甲辰收藏 v=1.2.99
diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx
index f6912a9..cc9365b 100644
--- a/frontend/src/pages/Home.jsx
+++ b/frontend/src/pages/Home.jsx
@@ -9,6 +9,9 @@ export default function Home() {
const [seekStats, setSeekStats] = useState({ seekCount: 0, matchedCount: 0 })
const [recentPosts, setRecentPosts] = useState([])
const [dragonStats, setDragonStats] = useState({})
+ const [dealVersion, setDealVersion] = useState('龙钞')
+ const [dealCategoryStats, setDealCategoryStats] = useState([])
+ const [dealInfoList, setDealInfoList] = useState([])
const currentPath = window.location.hash.slice(1) || '/'
useEffect(() => {
@@ -78,8 +81,16 @@ export default function Home() {
}).catch(() => {})
// 获取寻配号统计数据
- fetch('/api/information/seek/stats').then(res => res.json()).then(data => {
- setSeekStats(data || {})
+ // 获取寻配号统计数据
+ fetch('/api/seek/stats').then(res => res.json()).then(data => {
+ setSeekStats({ seekCount: data.total || 0, userMatchedCount: data.matched || 0, totalMatchedCount: data.unmatched || 0 })
+ }).catch(() => {})
+
+ // 获取成交行情分类统计(需要登录获取全部数据)
+ const token = localStorage.getItem('token')
+ const headers = token ? { 'Authorization': 'Bearer ' + token } : {}
+ fetch('/api/deal/list?page_size=500', { headers }).then(res => res.json()).then(data => {
+ setDealInfoList(Array.isArray(data) ? data : (data.items || []))
}).catch(() => {})
}, [])
@@ -172,7 +183,7 @@ export default function Home() {
快捷操作
window.location.hash = '#/add?mode=ocr'} style={{
- background: 'linear-gradient(135deg, #22c55e 0%, #16a34a 100%)',
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%)', border: '1px solid rgba(255,255,255,0.08)',
borderRadius: '12px',
padding: '12px 16px',
cursor: 'pointer',
@@ -186,7 +197,7 @@ export default function Home() {
AI识别添加
window.location.hash = '#/add?mode=deal'} style={{
- background: 'linear-gradient(135deg, #ec4899 0%, #db2777 100%)',
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%)', border: '1px solid rgba(255,255,255,0.08)',
borderRadius: '12px',
padding: '12px 16px',
cursor: 'pointer',
@@ -200,7 +211,7 @@ export default function Home() {
录入成交行情
window.location.hash = '#/news?type=seek'} style={{
- background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%)', border: '1px solid rgba(255,255,255,0.08)',
borderRadius: '12px',
padding: '12px 16px',
cursor: 'pointer',
@@ -214,7 +225,7 @@ export default function Home() {
发布寻配需求
window.location.hash = '#/add?mode=manual'} style={{
- background: 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)',
+ background: 'linear-gradient(135deg, rgba(255,255,255,0.08) 0%, rgba(255,255,255,0.02) 100%)', border: '1px solid rgba(255,255,255,0.08)',
borderRadius: '12px',
padding: '12px 16px',
cursor: 'pointer',
@@ -258,31 +269,141 @@ export default function Home() {
+ {/* 成交行情信息 */}
+
+ {(() => {
+ const versions = ['龙钞', '马钞', '蛇钞', '其他']
+ const packagings = ['标百', '标十', '单张']
+ const categories = ['带4号', '带7号', '永恒号', '永恒', '钻石号', '钻石', '如意号', '朦胧号', '朦胧王', '天马号', '金山号', '金马号', '天马王', '金山王', '金马王', '倒置号', '圆圆号', '通货', '无4']
+
+ const normalizeCat = (c) => {
+ if (c === '通货') return '带4号'
+ if (c === '无4') return '带7号'
+ return c
+ }
+
+ const filteredData = dealInfoList.filter(item => {
+ const serial = (item.title || '').split('-')[0] || ''
+ let version = '其他'
+ if (serial.startsWith('J0')) version = '龙钞'
+ else if (serial.startsWith('J1')) version = '马钞'
+ else if (serial.startsWith('J3')) version = '蛇钞'
+ if (version !== dealVersion) return false
+ return true
+ })
+
+ const calcAvg = (pkg, cat) => {
+ const items = filteredData.filter(item => {
+ const content = item.content || ''
+ const p = content.includes('包装:') ? content.split('包装:')[1].split('\n')[0].trim() : (item.packaging || '')
+ let c = content.includes('分类:') ? content.split('分类:')[1].split('\n')[0].trim() : (item.category || '')
+ c = normalizeCat(c)
+ return p === pkg && c === cat
+ })
+ if (items.length === 0) return null
+ const sum = items.reduce((a, b) => a + (b.deal_price || 0), 0)
+ return { avg: Math.round(sum / items.length), count: items.length, items }
+ }
+
+ return (
+
+
💰 龙钞成交数据分类汇总(均价)
+
+
+
+ {versions.map(v => (
+
+ ))}
+
+
+
+
+
+
+
+ |
+ {packagings.map(p => (
+ {p} |
+ ))}
+
+
+
+ {(() => {
+ const catAvg = categories.map(cat => {
+ const prices = []
+ packagings.forEach(pkg => {
+ const d = calcAvg(pkg, cat)
+ if (d) prices.push(d.avg)
+ })
+ const avg = prices.length > 0 ? Math.round(prices.reduce((a,b) => a+b, 0) / prices.length) : 0
+ return { cat, avg }
+ }).filter(c => c.avg > 0)
+
+ catAvg.sort((a, b) => a.avg - b.avg)
+ const categoryOrder = ['带4号', '带7号', '永恒号', '天马号', '天马王', '金山号', '金山王', '钻石号', '朦胧号', '朦胧王', '金马号', '金马王', '倒置号', '圆圆号']
+ const sortedCats = categoryOrder.filter(cat => catAvg.some(c => c.cat === cat))
+ .concat(catAvg.filter(c => !categoryOrder.includes(c.cat)).map(c => c.cat))
+
+ return sortedCats.map(cat => {
+ const rowData = packagings.map(pkg => calcAvg(pkg, cat))
+ const hasData = rowData.some(d => d !== null)
+ if (!hasData) return null
+ return (
+
+ | {cat} |
+ {rowData.map((d, i) => (
+
+ {d ? (
+
+ ¥{d.avg.toLocaleString()}
+ ({d.count})
+
+ ) : -}
+ |
+ ))}
+
+ )
+ })
+ })()}
+
+
+
+
+
+ )
+ })()}
+
+
{/* 一尘今日数据 */}
📊 一尘今日连体纪念钞数据
-
+
{yichensStats.total || 0}
总帖子
-
+
{yichensStats.deals || 0}
出售
-
+
{yichensStats.wants || 0}
求购
-
+
{yichensStats.dragons || 0}
龙钞
-
+
{yichensStats.horses || 0}
马钞
-
+
{yichensStats.tianma || 0}
天马
@@ -292,43 +413,43 @@ export default function Home() {
{/* 今日龙钞帖子数据统计 */}
🐉 今日龙钞帖子数据统计
-
+
{/* 表头 */}
{/* 数据行 */}
-
带4
-
{dragonStats.dai4?.deals || 0}
-
{dragonStats.dai4?.wants || 0}
+
带4
+
{dragonStats.dai4?.deals || 0}
+
{dragonStats.dai4?.wants || 0}
{(dragonStats.dai4?.deals || 0) + (dragonStats.dai4?.wants || 0)}
-
带7号
-
{dragonStats.wu4?.deals || 0}
-
{dragonStats.wu4?.wants || 0}
+
带7号
+
{dragonStats.wu4?.deals || 0}
+
{dragonStats.wu4?.wants || 0}
{(dragonStats.wu4?.deals || 0) + (dragonStats.wu4?.wants || 0)}
-
无47
-
{dragonStats.wu47?.deals || 0}
-
{dragonStats.wu47?.wants || 0}
+
无47
+
{dragonStats.wu47?.deals || 0}
+
{dragonStats.wu47?.wants || 0}
{(dragonStats.wu47?.deals || 0) + (dragonStats.wu47?.wants || 0)}
-
无247
-
{dragonStats.wu247?.deals || 0}
-
{dragonStats.wu247?.wants || 0}
+
无247
+
{dragonStats.wu247?.deals || 0}
+
{dragonStats.wu247?.wants || 0}
{(dragonStats.wu247?.deals || 0) + (dragonStats.wu247?.wants || 0)}
-
无347
-
{dragonStats.wu347?.deals || 0}
-
{dragonStats.wu347?.wants || 0}
+
无347
+
{dragonStats.wu347?.deals || 0}
+
{dragonStats.wu347?.wants || 0}
{(dragonStats.wu347?.deals || 0) + (dragonStats.wu347?.wants || 0)}
diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx
index e54e7c9..7f827d9 100644
--- a/frontend/src/pages/News.jsx
+++ b/frontend/src/pages/News.jsx
@@ -87,7 +87,7 @@ export default function News() {
let url = activeTab === 'yichen'
? `${API_BASE}/api/information/list?info_type=${activeTab}`
: activeTab === 'seek'
- ? `${API_BASE}/api/seek/list`
+ ? `${API_BASE}/api/information/seek/list`
: `${API_BASE}/api/deal/list`
// 成交行情和寻配号每页500条
@@ -303,7 +303,7 @@ export default function News() {
const token = localStorage.getItem('token')
if (!token) { alert('请先登录'); return }
try {
- const res = await fetch(`${API_BASE}/api/seek/list`, {
+ const res = await fetch(`${API_BASE}/api/information/seek/list`, {
headers: { Authorization: `Bearer ${token}` }
})
const data = await res.json()
@@ -319,7 +319,7 @@ export default function News() {
const token = localStorage.getItem('token')
if (!token) { alert('请先登录'); return }
try {
- const res = await fetch(`${API_BASE}/api/seek/list`, {
+ const res = await fetch(`${API_BASE}/api/information/seek/list`, {
headers: { Authorization: `Bearer ${token}` }
})
const data = await res.json()