From 31cbe53c67772e4627a23e4622d760e06c1474f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 11:22:32 +0800 Subject: [PATCH 01/55] =?UTF-8?q?v1.2.51=20-=20=E4=BF=AE=E5=A4=8D=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BF=A1=E6=81=AF=E8=BF=94=E5=9B=9E=E7=BC=BA=E5=B0=91?= =?UTF-8?q?=E7=9A=84=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/users.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/backend/app/routers/users.py b/backend/app/routers/users.py index 22b9236..079be6e 100644 --- a/backend/app/routers/users.py +++ b/backend/app/routers/users.py @@ -27,6 +27,22 @@ def get_current_user_info( "address": current_user.address, "bio": current_user.bio, "role": current_user.role, + "level": current_user.f99_94_level, + "aiCount": current_user.f99_95_ai_count or 0, + "searchCount": current_user.f99_96_search_count or 0, + "collectionCount": current_user.f99_97_collection_count or 0, + "phoneVerified": current_user.f01_06_phone_verified or False, + "loginCount": current_user.f99_98_login_count or 0, + "lastLogin": current_user.f99_99_last_login.isoformat() if current_user.f99_99_last_login else None, + "gender": current_user.f01_07_gender, + "birthday": current_user.f01_08_birthday.isoformat() if current_user.f01_08_birthday else None, + "region": current_user.f01_09_region, + "realnameVerified": current_user.f01_10_realname_verified or False, + "points": current_user.f99_100_points or 0, + "balance": float(current_user.f01_11_balance) if current_user.f01_11_balance else 0, + "totalAmount": float(current_user.f01_12_total_amount) if current_user.f01_12_total_amount else 0, + "inviteCode": current_user.f01_13_invite_code, + "user_code": current_user.user_code, "f99_92_created_at": current_user.f99_92_created_at.isoformat() if current_user.f99_92_created_at else None, "f99_93_updated_at": current_user.f99_93_updated_at.isoformat() if current_user.f99_93_updated_at else None } From 0bbaf87093e0956b11fd98d8ca89af79a248eb9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 12:37:39 +0800 Subject: [PATCH 02/55] =?UTF-8?q?v1.2.52=20-=20=E9=A6=96=E9=A1=B5=E4=BC=98?= =?UTF-8?q?=E5=8C=96=EF=BC=9A=E4=BF=AE=E6=94=B9=E4=B8=80=E5=B0=98=E6=A0=87?= =?UTF-8?q?=E9=A2=98+=E6=B7=BB=E5=8A=A0=E5=AF=BB=E9=85=8D=E5=8F=B7?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/information.py | 23 +++++++++++++++++++++ frontend/src/pages/Home.jsx | 33 +++++++++++++++++++++++++++++- 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index a249fbb..9a70e31 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -1070,3 +1070,26 @@ def get_yichen_posts(category: str = None, search: str = None, page: int = 1, pa 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}} + + +@router.get("/seek/stats") +def get_seek_stats( + current_user: User = Depends(get_current_user), + db: Session = Depends(get_db) +): + """获取寻配号统计数据""" + # 寻号需求数(seek类型总数) + seek_count = db.query(Information).filter( + Information.info_type == 'seek' + ).count() + + # 匹配成功总条数(is_matched = 'confirmed') + matched_count = db.query(Information).filter( + Information.info_type == 'seek', + Information.is_matched == 'confirmed' + ).count() + + return { + "seekCount": seek_count, + "matchedCount": matched_count + } diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 1a044ca..33f5eac 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 [seekStats, setSeekStats] = useState({ seekCount: 0, matchedCount: 0 }) const [recentPosts, setRecentPosts] = useState([]) const currentPath = window.location.hash.slice(1) || '/' @@ -66,6 +67,11 @@ export default function Home() { fetch('/api/yichens/posts?limit=10&offset=0').then(res => res.json()).then(data => { setRecentPosts(Array.isArray(data) ? data : []) }).catch(() => {}) + + // 获取寻配号统计数据 + fetch('/api/information/seek/stats').then(res => res.json()).then(data => { + setSeekStats(data || {}) + }).catch(() => {}) }, []) // 检查是否为管理员 @@ -152,6 +158,31 @@ export default function Home() { ))} + {/* 寻配号数据 */} +
+
🔍 寻配号数据
+
window.location.hash = '#/news?type=seek'} style={{ + background: 'linear-gradient(135deg, rgba(245,158,11,0.15) 0%, rgba(245,158,11,0.05) 100%)', + borderRadius: '12px', + padding: '16px', + border: '1px solid rgba(245,158,11,0.2)', + cursor: 'pointer', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center' + }}> +
+
{seekStats.seekCount || 0}
+
寻号需求数
+
+
+
{seekStats.matchedCount || 0}
+
匹配成功数
+
+
+
+
+ {/* 快捷操作 */}
快捷操作
@@ -191,7 +222,7 @@ export default function Home() { {/* 一尘今日数据 */}
-
📊 一尘今日数据
+
📊 一尘今日连体纪念钞数据
{yichensStats.total || 0}
From 7a92c0d43039654f3598ce632987bfc66d335113 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 12:41:09 +0800 Subject: [PATCH 03/55] =?UTF-8?q?v1.2.53=20-=20=E9=A6=96=E9=A1=B5=E4=BC=98?= =?UTF-8?q?=E5=8C=96=EF=BC=9A=E5=8E=BB=E6=8E=89=E5=9B=BE=E6=A0=87+?= =?UTF-8?q?=E6=95=B0=E5=AD=97=E4=BF=9D=E7=95=991=E4=BD=8D=E5=B0=8F?= =?UTF-8?q?=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 33f5eac..827b377 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -91,12 +91,12 @@ export default function Home() { } const statCards = [ - { label: '藏品数', value: stats.totalCount, color: '#3b82f6', icon: '📦' }, - { label: '总成本', value: formatMoney(stats.totalCost) + '万', color: '#22c55e', icon: '💰' }, - { label: '总收入', value: formatMoney(stats.totalRevenue) + '万', color: '#06b6d4', icon: '📈' }, - { label: '评级数', value: stats.gradedCount, color: '#8b5cf6', icon: '⭐' }, - { label: '预期利润', value: formatMoney(stats.expectedProfit) + '万', color: stats.expectedProfit >= 0 ? '#f59e0b' : '#ef4444', icon: '🎯' }, - { label: '总利润', value: formatMoney(stats.totalProfit) + '万', color: stats.totalProfit >= 0 ? '#10b981' : '#f43f5e', icon: '💵' } + { label: '藏品数', value: stats.totalCount, color: '#3b82f6' }, + { label: '总成本', value: formatMoney(stats.totalCost) + '万', color: '#22c55e' }, + { label: '总收入', value: formatMoney(stats.totalRevenue) + '万', color: '#06b6d4' }, + { label: '评级数', value: stats.gradedCount, color: '#8b5cf6' }, + { label: '预期利润', value: formatMoney(stats.expectedProfit) + '万', color: stats.expectedProfit >= 0 ? '#f59e0b' : '#ef4444' }, + { label: '总利润', value: formatMoney(stats.totalProfit) + '万', color: stats.totalProfit >= 0 ? '#10b981' : '#f43f5e' } ] return ( @@ -151,7 +151,7 @@ export default function Home() { onMouseOut={e => { e.currentTarget.style.transform = 'translateY(0)' }} onClick={() => window.location.hash = '#/stats'} > -
{card.icon}
+
{card.value}
{card.label}
From a7dd51400ca68d6e96cd44e3cc7f47fad3a14065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:02:50 +0800 Subject: [PATCH 04/55] =?UTF-8?q?fix:=20=E9=87=91=E9=A2=9D=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E4=BF=9D=E7=95=991=E4=BD=8D=E5=B0=8F=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 827b377..ac753dc 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -87,7 +87,7 @@ export default function Home() { const formatMoney = (val) => { if (!val || val === 0) return '0' const v = val / 10000 - return v.toFixed(4) + return v.toFixed(1) } const statCards = [ From b4b4a71640d1d5ba3025e2a19e90f4416aa4cade Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:05:07 +0800 Subject: [PATCH 05/55] =?UTF-8?q?fix:=20=E5=AF=BB=E9=85=8D=E5=8F=B7?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E8=B0=83=E6=95=B4=E5=88=B0=E5=BF=AB=E6=8D=B7?= =?UTF-8?q?=E6=93=8D=E4=BD=9C=E4=B8=8B=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 50 ++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index ac753dc..1ed4609 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -158,31 +158,6 @@ export default function Home() { ))}
- {/* 寻配号数据 */} -
-
🔍 寻配号数据
-
window.location.hash = '#/news?type=seek'} style={{ - background: 'linear-gradient(135deg, rgba(245,158,11,0.15) 0%, rgba(245,158,11,0.05) 100%)', - borderRadius: '12px', - padding: '16px', - border: '1px solid rgba(245,158,11,0.2)', - cursor: 'pointer', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center' - }}> -
-
{seekStats.seekCount || 0}
-
寻号需求数
-
-
-
{seekStats.matchedCount || 0}
-
匹配成功数
-
-
-
-
- {/* 快捷操作 */}
快捷操作
@@ -220,6 +195,31 @@ export default function Home() {
+ {/* 寻配号数据 */} +
+
🔍 寻配号数据
+
window.location.hash = '#/news?type=seek'} style={{ + background: 'linear-gradient(135deg, rgba(245,158,11,0.15) 0%, rgba(245,158,11,0.05) 100%)', + borderRadius: '12px', + padding: '16px', + border: '1px solid rgba(245,158,11,0.2)', + cursor: 'pointer', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center' + }}> +
+
{seekStats.seekCount || 0}
+
寻号需求数
+
+
+
{seekStats.matchedCount || 0}
+
匹配成功数
+
+
+
+
+ {/* 一尘今日数据 */}
📊 一尘今日连体纪念钞数据
From 81ac7fd8268849f8154923b48079a0ab39563349 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:07:55 +0800 Subject: [PATCH 06/55] =?UTF-8?q?v1.2.54=20-=20=E5=AF=BB=E9=85=8D=E5=8F=B7?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=A2=9E=E5=8A=A0=E6=88=91=E7=9A=84=E5=8C=B9?= =?UTF-8?q?=E9=85=8D=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/information.py | 17 ++++++++++++++--- frontend/src/pages/Home.jsx | 27 +++++++++++++++------------ 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index 9a70e31..f981a63 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -1083,13 +1083,24 @@ def get_seek_stats( Information.info_type == 'seek' ).count() - # 匹配成功总条数(is_matched = 'confirmed') - matched_count = db.query(Information).filter( + # 我的藏品匹配成功数(is_matched = 'confirmed'且有matched_user_id) + user_matched_count = 0 + if current_user: + user_matched_count = db.query(Information).filter( + Information.info_type == 'seek', + Information.is_matched == 'confirmed', + Information.matched_user_id == current_user.f99_90_id + ).count() + + # 总共匹配成功数(包含我的藏品匹配+网络数据匹配成功的) + # 网络数据匹配成功的定义:is_matched = 'confirmed' + total_matched_count = db.query(Information).filter( Information.info_type == 'seek', Information.is_matched == 'confirmed' ).count() return { "seekCount": seek_count, - "matchedCount": matched_count + "userMatchedCount": user_matched_count, + "totalMatchedCount": total_matched_count } diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 1ed4609..570df38 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -203,20 +203,23 @@ export default function Home() { borderRadius: '12px', padding: '16px', border: '1px solid rgba(245,158,11,0.2)', - cursor: 'pointer', - display: 'flex', - justifyContent: 'space-between', - alignItems: 'center' + cursor: 'pointer' }}> -
-
{seekStats.seekCount || 0}
-
寻号需求数
+
+
+
{seekStats.seekCount || 0}
+
寻号需求
+
+
+
{seekStats.userMatchedCount || 0}
+
我的匹配
+
+
+
{seekStats.totalMatchedCount || 0}
+
总共匹配
+
-
-
{seekStats.matchedCount || 0}
-
匹配成功数
-
-
+
From 92bc6ab8d67ae95c43dfb8f59c71719a691da964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:13:42 +0800 Subject: [PATCH 07/55] =?UTF-8?q?fix:=20=E4=BF=AE=E6=AD=A3=E5=AF=BB?= =?UTF-8?q?=E9=85=8D=E5=8F=B7=E7=BB=9F=E8=AE=A1=E6=95=B0=E6=8D=AE=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/information.py | 33 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index f981a63..ae3648e 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -1078,26 +1078,41 @@ def get_seek_stats( db: Session = Depends(get_db) ): """获取寻配号统计数据""" - # 寻号需求数(seek类型总数) + from sqlalchemy import or_, and_ + + # 寻号需求数(seek类型且expect_number不为空的总数) seek_count = db.query(Information).filter( - Information.info_type == 'seek' + Information.info_type == 'seek', + Information.expect_number.isnot(None), + Information.expect_number != '' ).count() - # 我的藏品匹配成功数(is_matched = 'confirmed'且有matched_user_id) + # 我的匹配:自有藏品匹配成功的寻号帖子数量(is_matched为confirmed或pending_network) user_matched_count = 0 if current_user: user_matched_count = db.query(Information).filter( Information.info_type == 'seek', - Information.is_matched == 'confirmed', - Information.matched_user_id == current_user.f99_90_id + Information.expect_number.isnot(None), + Information.expect_number != '', + Information.matched_user_id == current_user.f99_90_id, + Information.is_matched != 'pending' ).count() - # 总共匹配成功数(包含我的藏品匹配+网络数据匹配成功的) - # 网络数据匹配成功的定义:is_matched = 'confirmed' - total_matched_count = db.query(Information).filter( + # 总共匹配 = 自有匹配成功数 + 网络数据匹配成功数 + # 自有匹配成功:is_matched = 'confirmed' + # 网络数据匹配成功:is_matched = 'pending_network'(表示网络数据已匹配) + from sqlalchemy import func + self_matched = db.query(func.count(Information.f99_90_id)).filter( Information.info_type == 'seek', Information.is_matched == 'confirmed' - ).count() + ).scalar() or 0 + + network_matched = db.query(func.count(Information.f99_90_id)).filter( + Information.info_type == 'seek', + Information.is_matched == 'pending_network' + ).scalar() or 0 + + total_matched_count = self_matched + network_matched return { "seekCount": seek_count, From e17024128f19f36ce7ab3e1c6c4466b75e7c9f46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:16:17 +0800 Subject: [PATCH 08/55] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8Information.id?= =?UTF-8?q?=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/information.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index ae3648e..5080795 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -1102,12 +1102,12 @@ def get_seek_stats( # 自有匹配成功:is_matched = 'confirmed' # 网络数据匹配成功:is_matched = 'pending_network'(表示网络数据已匹配) from sqlalchemy import func - self_matched = db.query(func.count(Information.f99_90_id)).filter( + self_matched = db.query(func.count(Information.id)).filter( Information.info_type == 'seek', Information.is_matched == 'confirmed' ).scalar() or 0 - network_matched = db.query(func.count(Information.f99_90_id)).filter( + network_matched = db.query(func.count(Information.id)).filter( Information.info_type == 'seek', Information.is_matched == 'pending_network' ).scalar() or 0 From b8961a32e0a73d8a795f4d141c1c56a7be20ca98 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:22:29 +0800 Subject: [PATCH 09/55] =?UTF-8?q?fix:=20=E4=BD=BF=E7=94=A8network=5Fmatche?= =?UTF-8?q?d=5Fcount=E7=BB=9F=E8=AE=A1=E7=BD=91=E7=BB=9C=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/information.py | 35 +++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/backend/app/routers/information.py b/backend/app/routers/information.py index 5080795..2c75c5e 100644 --- a/backend/app/routers/information.py +++ b/backend/app/routers/information.py @@ -1078,8 +1078,6 @@ def get_seek_stats( db: Session = Depends(get_db) ): """获取寻配号统计数据""" - from sqlalchemy import or_, and_ - # 寻号需求数(seek类型且expect_number不为空的总数) seek_count = db.query(Information).filter( Information.info_type == 'seek', @@ -1087,7 +1085,8 @@ def get_seek_stats( Information.expect_number != '' ).count() - # 我的匹配:自有藏品匹配成功的寻号帖子数量(is_matched为confirmed或pending_network) + # 我的匹配:自有藏品匹配成功的寻号帖子数量 + # 即 is_matched = 'confirmed' 的记录,用户ID等于当前用户 user_matched_count = 0 if current_user: user_matched_count = db.query(Information).filter( @@ -1095,24 +1094,30 @@ def get_seek_stats( Information.expect_number.isnot(None), Information.expect_number != '', Information.matched_user_id == current_user.f99_90_id, - Information.is_matched != 'pending' + Information.is_matched == 'confirmed' ).count() - # 总共匹配 = 自有匹配成功数 + 网络数据匹配成功数 + # 总共匹配:自有匹配成功 + 网络数据匹配成功 # 自有匹配成功:is_matched = 'confirmed' - # 网络数据匹配成功:is_matched = 'pending_network'(表示网络数据已匹配) - from sqlalchemy import func - self_matched = db.query(func.count(Information.id)).filter( + # 网络数据匹配成功:查询每个帖子的network_matched_count并求和 + seeks = db.query(Information).filter( Information.info_type == 'seek', - Information.is_matched == 'confirmed' - ).scalar() or 0 + Information.expect_number.isnot(None), + Information.expect_number != '' + ).all() - network_matched = db.query(func.count(Information.id)).filter( - Information.info_type == 'seek', - Information.is_matched == 'pending_network' - ).scalar() or 0 + total_self_matched = 0 + total_network_matched = 0 + for seek in seeks: + # 自身匹配成功 + if seek.is_matched == 'confirmed': + total_self_matched += 1 + # 网络数据匹配成功(通过coolbot数据库查询) + if seek.expect_number: + network_count = match_collections_count_from_coolbot(seek.expect_number) + total_network_matched += network_count - total_matched_count = self_matched + network_matched + total_matched_count = total_self_matched + total_network_matched return { "seekCount": seek_count, From 893446ccd4e90567893a7039bfd32b8c0e19c0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:25:18 +0800 Subject: [PATCH 10/55] =?UTF-8?q?fix:=20=E5=8E=BB=E6=8E=89=E5=AF=BB?= =?UTF-8?q?=E9=85=8D=E5=8F=B7=E6=95=B0=E6=8D=AE=E7=AE=AD=E5=A4=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 570df38..e258a7b 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -219,7 +219,7 @@ export default function Home() {
总共匹配
-
+ From 309dd056c64129c46ca811a946b1804902c56dc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:28:18 +0800 Subject: [PATCH 11/55] =?UTF-8?q?v1.2.60=20-=20=E7=94=A8=E6=88=B7=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=BC=80=E5=8F=91=E5=9F=BA=E7=A1=80=E7=A8=B3=E5=AE=9A?= =?UTF-8?q?=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- backend/VERSION | 2 +- config/VERSION | 2 +- frontend/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 1ce7217..307f000 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=1.2.41 +VERSION=1.2.60 diff --git a/backend/VERSION b/backend/VERSION index 1f8d37f..b2c8b0c 100644 --- a/backend/VERSION +++ b/backend/VERSION @@ -1 +1 @@ -1.2.38 +1.2.60 diff --git a/config/VERSION b/config/VERSION index 532db09..307f000 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.50 +VERSION=1.2.60 diff --git a/frontend/package.json b/frontend/package.json index ad0ec71..0f6062a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jiachenlong-frontend", - "version": "1.2.12", + "version": "1.2.60", "private": true, "description": "甲辰藏品管理系统 - 移动端前端", "scripts": { From eb64ce3d8c47c60f4eed9b3e00972b9117e0b16e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 13:35:18 +0800 Subject: [PATCH 12/55] =?UTF-8?q?v1.2.61=20-=20=E5=AE=89=E5=85=A8=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=EF=BC=9ACORS=E9=99=90=E5=88=B6+SECRET=5FKEY=E5=BC=BA?= =?UTF-8?q?=E5=88=B6=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/core/auth.py | 4 +++- backend/app/main.py | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/app/core/auth.py b/backend/app/core/auth.py index 602723a..2fe7a71 100644 --- a/backend/app/core/auth.py +++ b/backend/app/core/auth.py @@ -11,7 +11,9 @@ from app.core.database import SessionLocal from app.models.models import User # 配置 -SECRET_KEY = os.getenv("SECRET_KEY", "your-secret-key-change-in-production") +SECRET_KEY = os.getenv("SECRET_KEY") +if not SECRET_KEY: + raise ValueError("SECRET_KEY environment variable is not set. Please configure it in production!") ALGORITHM = "HS256" ACCESS_TOKEN_EXPIRE_MINUTES = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "10080")) # 7天 diff --git a/backend/app/main.py b/backend/app/main.py index 2ec184f..73ade76 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -52,10 +52,11 @@ app = FastAPI( # 设置全局错误处理器 setup_error_handlers(app) -# CORS 配置 +# CORS 配置 - 生产环境限制域名 +ALLOWED_ORIGINS = os.getenv("ALLOWED_ORIGINS", "http://47.103.29.111,http://120.55.81.21,https://socoolbot.com").split(",") app.add_middleware( CORSMiddleware, - allow_origins=["*"], # 生产环境应该限制域名 + allow_origins=ALLOWED_ORIGINS, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], From 3cf8079b330be01030f628d0052cc6d589f2f95a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 14:15:53 +0800 Subject: [PATCH 13/55] =?UTF-8?q?v1.2.62=20-=20=E6=80=A7=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96=EF=BC=9AStats=E6=95=B0=E6=8D=AE=E5=BA=93=E8=81=9A?= =?UTF-8?q?=E5=90=88+=E7=BC=96=E5=8F=B7=E5=B9=B6=E5=8F=91=E9=94=81+images?= =?UTF-8?q?=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/collections.py | 171 ++++++++++++++++++----------- 1 file changed, 108 insertions(+), 63 deletions(-) diff --git a/backend/app/routers/collections.py b/backend/app/routers/collections.py index bf48d73..05f50cc 100644 --- a/backend/app/routers/collections.py +++ b/backend/app/routers/collections.py @@ -56,6 +56,7 @@ def to_camel_case(data: dict) -> dict: 'f05_43_repair_fee': 'repairFee', 'f05_44_grading_fee': 'gradingFee', 'f06_50_purpose': 'purpose', + 'images': 'images', } return {mapping.get(k, k): v for k, v in data.items()} @@ -63,25 +64,30 @@ def to_camel_case(data: dict) -> dict: # 编码生成函数 def generate_code(version: str, user_id: str, db: Session) -> str: - """自动生成藏品编号 - 按用户独立编码""" + """自动生成藏品编号 - 按用户独立编码,使用行锁防止并发冲突""" import re + from sqlalchemy import text - # 查询当前用户的非空编码(不与其他用户混算) - user_codes = db.query(Collection.f01_02_code).filter( - Collection.f01_02_code.isnot(None), - Collection.f99_91_user_id == user_id - ).all() + # 使用 FOR UPDATE 行锁防止并发冲突 + result = db.execute( + text(""" + SELECT f01_02_code FROM collections + WHERE f99_91_user_id = :user_id + AND f01_02_code IS NOT NULL + AND f01_02_code ~ '^\\d{4,5}$' + ORDER BY f01_02_code::int DESC + LIMIT 1 + FOR UPDATE + """), + {"user_id": user_id} + ).fetchone() max_num = 0 - for (code,) in user_codes: - # 处理纯数字编码(支持4位和5位) - if re.match(r'^\d{4,5}$', code): - try: - num = int(code) - if num > max_num: - max_num = num - except (ValueError, TypeError): - pass + if result and result[0]: + try: + max_num = int(result[0]) + except (ValueError, TypeError): + pass # 当前用户最大号 +1 next_num = max_num + 1 @@ -275,68 +281,107 @@ def get_stats( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): - """获取藏品统计""" - # 获取所有藏品 - if current_user.role == "admin": - all_collections = db.query(Collection).all() - else: - all_collections = db.query(Collection).filter( - Collection.f99_91_user_id == current_user.f99_90_id - ).all() + """获取藏品统计 - 使用数据库聚合查询优化性能""" + from sqlalchemy import func, case + + # 基础查询条件 + base_filter = True if current_user.role == "admin" else Collection.f99_91_user_id == current_user.f99_90_id # 总数 - total_count = len(all_collections) + total_count = db.query(func.count(Collection.f99_90_id)).filter(base_filter).scalar() or 0 # 按分类统计 - from collections import Counter - by_category = Counter(c.f01_03_category for c in all_collections).items() + by_category = db.query( + Collection.f01_03_category, + func.count(Collection.f99_90_id) + ).filter(base_filter).group_by(Collection.f01_03_category).all() # 按状态统计 - by_status = Counter(c.f01_04_status for c in all_collections).items() + by_status = db.query( + Collection.f01_04_status, + func.count(Collection.f99_90_id) + ).filter(base_filter).group_by(Collection.f01_04_status).all() # 按是否评级统计 - by_graded = Counter(c.f03_20_is_graded for c in all_collections).items() + by_graded = db.query( + Collection.f03_20_is_graded, + func.count(Collection.f99_90_id) + ).filter(base_filter).group_by(Collection.f03_20_is_graded).all() - # 新增:8 个分布统计 - by_packaging = Counter(c.f02_12_packaging for c in all_collections if c.f02_12_packaging).items() - by_rarity = Counter(c.f02_13_rarity for c in all_collections if c.f02_13_rarity).items() - by_version = Counter(c.f02_11_version for c in all_collections if c.f02_11_version).items() - by_grading_company = Counter(c.f03_21_grading_company for c in all_collections if c.f03_20_is_graded and c.f03_21_grading_company).items() - by_grading_score = Counter(c.f03_22_grading_score for c in all_collections if c.f03_20_is_graded and c.f03_22_grading_score).items() - by_special_mark = Counter(c.f04_30_special_mark for c in all_collections if c.f04_30_special_mark).items() - by_number_category = Counter(c.f02_14_number_category for c in all_collections if c.f02_14_number_category).items() + # 按包装统计 + by_packaging = db.query( + Collection.f02_12_packaging, + func.count(Collection.f99_90_id) + ).filter(base_filter, Collection.f02_12_packaging.isnot(None)).group_by(Collection.f02_12_packaging).all() - # 总成本: SUM(cost_price + repair_fee + grading_fee) for ALL collections - total_cost = sum( - (c.f05_40_cost_price or 0) + (c.f05_43_repair_fee or 0) + (c.f05_44_grading_fee or 0) - for c in all_collections - ) + # 按稀有度统计 + by_rarity = db.query( + Collection.f02_13_rarity, + func.count(Collection.f99_90_id) + ).filter(base_filter, Collection.f02_13_rarity.isnot(None)).group_by(Collection.f02_13_rarity).all() - # 预期利润: SUM(target_price - cost_price) for collections with target_price > 0 - expected_profit = sum( - (c.f05_41_target_price or 0) - (c.f05_40_cost_price or 0) - for c in all_collections - if c.f05_41_target_price and c.f05_41_target_price > 0 - ) + # 按版本统计 + by_version = db.query( + Collection.f02_11_version, + func.count(Collection.f99_90_id) + ).filter(base_filter, Collection.f02_11_version.isnot(None)).group_by(Collection.f02_11_version).all() - # 已售商品:状态为 sold 且出售价 > 0 - sold_collections = [ - c for c in all_collections - if c.f01_04_status == 'sold' and c.f05_42_goal_price and c.f05_42_goal_price > 0 - ] + # 按评级公司统计 + by_grading_company = db.query( + Collection.f03_21_grading_company, + func.count(Collection.f99_90_id) + ).filter(base_filter, Collection.f03_20_is_graded == True, Collection.f03_21_grading_company.isnot(None)).group_by(Collection.f03_21_grading_company).all() - # 总收入:SUM(出售价) for 已售商品(售价>0) - total_revenue = sum( - c.f05_42_goal_price or 0 - for c in sold_collections - ) + # 按评级分数统计 + by_grading_score = db.query( + Collection.f03_22_grading_score, + func.count(Collection.f99_90_id) + ).filter(base_filter, Collection.f03_20_is_graded == True, Collection.f03_22_grading_score.isnot(None)).group_by(Collection.f03_22_grading_score).all() - # 总利润(已实现利润):SUM(出售价 - 成本价 - 修复费 - 评级费) for 已售商品 - # 单藏品总成本 = 成本价 + 修复费 + 评级费 - total_profit = sum( - (c.f05_42_goal_price or 0) - (c.f05_40_cost_price or 0) - (c.f05_43_repair_fee or 0) - (c.f05_44_grading_fee or 0) - for c in sold_collections - ) + # 按特殊标记统计 + by_special_mark = db.query( + Collection.f04_30_special_mark, + func.count(Collection.f99_90_id) + ).filter(base_filter, Collection.f04_30_special_mark.isnot(None)).group_by(Collection.f04_30_special_mark).all() + + # 按号码分类统计 + by_number_category = db.query( + Collection.f02_14_number_category, + func.count(Collection.f99_90_id) + ).filter(base_filter, Collection.f02_14_number_category.isnot(None)).group_by(Collection.f02_14_number_category).all() + + # 总成本 + cost_result = db.query( + func.sum( + (Collection.f05_40_cost_price or 0) + + (Collection.f05_43_repair_fee or 0) + + (Collection.f05_44_grading_fee or 0) + ) + ).filter(base_filter).scalar() or 0 + + # 预期利润 + target_result = db.query( + func.sum(Collection.f05_41_target_price) + ).filter(base_filter, Collection.f05_41_target_price > 0).scalar() or 0 + + expected_profit = target_result - cost_result + + # 已售商品统计 + sold_filter = (Collection.f01_04_status == 'sold') & (Collection.f05_42_goal_price > 0) + if current_user.role != "admin": + sold_filter = sold_filter & (Collection.f99_91_user_id == current_user.f99_90_id) + + total_revenue = db.query(func.sum(Collection.f05_42_goal_price)).filter(sold_filter).scalar() or 0 + + # 总利润 + total_profit = db.query( + func.sum( + (Collection.f05_42_goal_price or 0) - + (Collection.f05_40_cost_price or 0) - + (Collection.f05_43_repair_fee or 0) - + (Collection.f05_44_grading_fee or 0) + ) + ).filter(sold_filter).scalar() or 0 return { "totalCount": total_count, From bb30f0e711d2eaf6286eaab5646d5dd3e63882cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 14:20:55 +0800 Subject: [PATCH 14/55] =?UTF-8?q?v1.2.62=20-=20=E6=9B=B4=E6=96=B0=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/VERSION b/config/VERSION index 307f000..a6294e7 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.60 +VERSION=1.2.62 From 2a8dea6acfd7bcb19c37306e5db2422da11751a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 14:29:08 +0800 Subject: [PATCH 15/55] =?UTF-8?q?fix:=20Stats=E6=8E=A5=E5=8F=A3=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E8=AE=A4=E8=AF=81=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/collections.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/app/routers/collections.py b/backend/app/routers/collections.py index 05f50cc..b2325e1 100644 --- a/backend/app/routers/collections.py +++ b/backend/app/routers/collections.py @@ -284,8 +284,11 @@ def get_stats( """获取藏品统计 - 使用数据库聚合查询优化性能""" from sqlalchemy import func, case - # 基础查询条件 - base_filter = True if current_user.role == "admin" else Collection.f99_91_user_id == current_user.f99_90_id + # 基础查询条件 - 检查用户是否登录 + if current_user is None or not hasattr(current_user, 'role'): + base_filter = False # 未登录用户不能查看任何藏品 + else: + base_filter = True if current_user.role == "admin" else Collection.f99_91_user_id == current_user.f99_90_id # 总数 total_count = db.query(func.count(Collection.f99_90_id)).filter(base_filter).scalar() or 0 From 190df561187820cfd2946238e23c1b4671790976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 14:33:45 +0800 Subject: [PATCH 16/55] =?UTF-8?q?fix:=20Stats=E8=BF=94=E5=9B=9E=E5=80=BC?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/collections.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/backend/app/routers/collections.py b/backend/app/routers/collections.py index b2325e1..2307e4e 100644 --- a/backend/app/routers/collections.py +++ b/backend/app/routers/collections.py @@ -398,13 +398,21 @@ def get_stats( "byGradingScore": [{"score": s, "count": n} for s, n in by_grading_score], "bySpecialMark": [{"mark": m, "count": n} for m, n in by_special_mark], "byNumberCategory": [{"numberCategory": n, "count": c} for n, c in by_number_category], - # 盈亏统计(只统计已售且有价格的藏品) + # 盈亏统计 + profit_count = db.query(func.count(Collection.f99_90_id)).filter( + sold_filter, Collection.f05_42_goal_price > Collection.f05_40_cost_price + ).scalar() or 0 + + loss_count = db.query(func.count(Collection.f99_90_id)).filter( + sold_filter, Collection.f05_42_goal_price <= Collection.f05_40_cost_price + ).scalar() or 0 + "byProfitLoss": [ - {"type": "profit", "label": "盈利", "count": sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price > c.f05_40_cost_price)}, - {"type": "loss", "label": "亏损", "count": sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price <= c.f05_40_cost_price)} + {"type": "profit", "label": "盈利", "count": profit_count}, + {"type": "loss", "label": "亏损", "count": loss_count} ], - "totalCost": total_cost, - "totalTarget": sum(c.f05_41_target_price or 0 for c in all_collections), + "totalCost": cost_result, + "totalTarget": target_result, "expectedProfit": expected_profit, "totalRevenue": total_revenue, "totalProfit": total_profit From 9956ed3f6155e00ddc31f4f430f1a14db3241129 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 14:35:39 +0800 Subject: [PATCH 17/55] =?UTF-8?q?fix:=20Stats=E8=BF=94=E5=9B=9E=E5=80=BC?= =?UTF-8?q?=E8=AF=AD=E6=B3=95=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/collections.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/backend/app/routers/collections.py b/backend/app/routers/collections.py index 2307e4e..704ec2b 100644 --- a/backend/app/routers/collections.py +++ b/backend/app/routers/collections.py @@ -399,17 +399,13 @@ def get_stats( "bySpecialMark": [{"mark": m, "count": n} for m, n in by_special_mark], "byNumberCategory": [{"numberCategory": n, "count": c} for n, c in by_number_category], # 盈亏统计 - profit_count = db.query(func.count(Collection.f99_90_id)).filter( - sold_filter, Collection.f05_42_goal_price > Collection.f05_40_cost_price - ).scalar() or 0 - - loss_count = db.query(func.count(Collection.f99_90_id)).filter( - sold_filter, Collection.f05_42_goal_price <= Collection.f05_40_cost_price - ).scalar() or 0 - "byProfitLoss": [ - {"type": "profit", "label": "盈利", "count": profit_count}, - {"type": "loss", "label": "亏损", "count": loss_count} + {"type": "profit", "label": "盈利", "count": db.query(func.count(Collection.f99_90_id)).filter( + sold_filter, Collection.f05_42_goal_price > Collection.f05_40_cost_price + ).scalar() or 0}, + {"type": "loss", "label": "亏损", "count": db.query(func.count(Collection.f99_90_id)).filter( + sold_filter, Collection.f05_42_goal_price <= Collection.f05_40_cost_price + ).scalar() or 0} ], "totalCost": cost_result, "totalTarget": target_result, From 79ce79b92c9537f006955466cb04369a3c6928c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 14:39:10 +0800 Subject: [PATCH 18/55] =?UTF-8?q?fix:=20=E6=81=A2=E5=A4=8Dcollections.py?= =?UTF-8?q?=E5=B9=B6=E6=B7=BB=E5=8A=A0=E7=94=A8=E6=88=B7=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/collections.py | 197 ++++++++++++----------------- 1 file changed, 79 insertions(+), 118 deletions(-) diff --git a/backend/app/routers/collections.py b/backend/app/routers/collections.py index 704ec2b..8479da6 100644 --- a/backend/app/routers/collections.py +++ b/backend/app/routers/collections.py @@ -64,30 +64,25 @@ def to_camel_case(data: dict) -> dict: # 编码生成函数 def generate_code(version: str, user_id: str, db: Session) -> str: - """自动生成藏品编号 - 按用户独立编码,使用行锁防止并发冲突""" + """自动生成藏品编号 - 按用户独立编码""" import re - from sqlalchemy import text - # 使用 FOR UPDATE 行锁防止并发冲突 - result = db.execute( - text(""" - SELECT f01_02_code FROM collections - WHERE f99_91_user_id = :user_id - AND f01_02_code IS NOT NULL - AND f01_02_code ~ '^\\d{4,5}$' - ORDER BY f01_02_code::int DESC - LIMIT 1 - FOR UPDATE - """), - {"user_id": user_id} - ).fetchone() + # 查询当前用户的非空编码(不与其他用户混算) + user_codes = db.query(Collection.f01_02_code).filter( + Collection.f01_02_code.isnot(None), + Collection.f99_91_user_id == user_id + ).all() max_num = 0 - if result and result[0]: - try: - max_num = int(result[0]) - except (ValueError, TypeError): - pass + for (code,) in user_codes: + # 处理纯数字编码(支持4位和5位) + if re.match(r'^\d{4,5}$', code): + try: + num = int(code) + if num > max_num: + max_num = num + except (ValueError, TypeError): + pass # 当前用户最大号 +1 next_num = max_num + 1 @@ -138,6 +133,10 @@ def get_collections( from sqlalchemy.orm import joinedload # 管理员默认查看全库,普通用户只看自己 + if current_user is None: + return {"error": "Unauthorized", "totalCount": 0} + + # 获取所有藏品 if current_user.role == "admin": # 联表查询获取用户名 query = db.query(Collection, User.f01_01_name.label('owner_name')).join( @@ -207,7 +206,11 @@ def get_collections( data_list = [] for item in data: # 处理联表查询结果 - if current_user.role == "admin": + if current_user is None: + return {"error": "Unauthorized", "totalCount": 0} + + # 获取所有藏品 + if current_user.role == "admin": collection_item, owner_name = item else: collection_item = item @@ -281,110 +284,72 @@ def get_stats( current_user: User = Depends(get_current_user), db: Session = Depends(get_db) ): - """获取藏品统计 - 使用数据库聚合查询优化性能""" - from sqlalchemy import func, case + """获取藏品统计""" + # 获取所有藏品 + if current_user is None: + return {"error": "Unauthorized", "totalCount": 0} - # 基础查询条件 - 检查用户是否登录 - if current_user is None or not hasattr(current_user, 'role'): - base_filter = False # 未登录用户不能查看任何藏品 + # 获取所有藏品 + if current_user.role == "admin": + all_collections = db.query(Collection).all() else: - base_filter = True if current_user.role == "admin" else Collection.f99_91_user_id == current_user.f99_90_id + all_collections = db.query(Collection).filter( + Collection.f99_91_user_id == current_user.f99_90_id + ).all() # 总数 - total_count = db.query(func.count(Collection.f99_90_id)).filter(base_filter).scalar() or 0 + total_count = len(all_collections) # 按分类统计 - by_category = db.query( - Collection.f01_03_category, - func.count(Collection.f99_90_id) - ).filter(base_filter).group_by(Collection.f01_03_category).all() + from collections import Counter + by_category = Counter(c.f01_03_category for c in all_collections).items() # 按状态统计 - by_status = db.query( - Collection.f01_04_status, - func.count(Collection.f99_90_id) - ).filter(base_filter).group_by(Collection.f01_04_status).all() + by_status = Counter(c.f01_04_status for c in all_collections).items() # 按是否评级统计 - by_graded = db.query( - Collection.f03_20_is_graded, - func.count(Collection.f99_90_id) - ).filter(base_filter).group_by(Collection.f03_20_is_graded).all() + by_graded = Counter(c.f03_20_is_graded for c in all_collections).items() - # 按包装统计 - by_packaging = db.query( - Collection.f02_12_packaging, - func.count(Collection.f99_90_id) - ).filter(base_filter, Collection.f02_12_packaging.isnot(None)).group_by(Collection.f02_12_packaging).all() + # 新增:8 个分布统计 + by_packaging = Counter(c.f02_12_packaging for c in all_collections if c.f02_12_packaging).items() + by_rarity = Counter(c.f02_13_rarity for c in all_collections if c.f02_13_rarity).items() + by_version = Counter(c.f02_11_version for c in all_collections if c.f02_11_version).items() + by_grading_company = Counter(c.f03_21_grading_company for c in all_collections if c.f03_20_is_graded and c.f03_21_grading_company).items() + by_grading_score = Counter(c.f03_22_grading_score for c in all_collections if c.f03_20_is_graded and c.f03_22_grading_score).items() + by_special_mark = Counter(c.f04_30_special_mark for c in all_collections if c.f04_30_special_mark).items() + by_number_category = Counter(c.f02_14_number_category for c in all_collections if c.f02_14_number_category).items() - # 按稀有度统计 - by_rarity = db.query( - Collection.f02_13_rarity, - func.count(Collection.f99_90_id) - ).filter(base_filter, Collection.f02_13_rarity.isnot(None)).group_by(Collection.f02_13_rarity).all() + # 总成本: SUM(cost_price + repair_fee + grading_fee) for ALL collections + total_cost = sum( + (c.f05_40_cost_price or 0) + (c.f05_43_repair_fee or 0) + (c.f05_44_grading_fee or 0) + for c in all_collections + ) - # 按版本统计 - by_version = db.query( - Collection.f02_11_version, - func.count(Collection.f99_90_id) - ).filter(base_filter, Collection.f02_11_version.isnot(None)).group_by(Collection.f02_11_version).all() + # 预期利润: SUM(target_price - cost_price) for collections with target_price > 0 + expected_profit = sum( + (c.f05_41_target_price or 0) - (c.f05_40_cost_price or 0) + for c in all_collections + if c.f05_41_target_price and c.f05_41_target_price > 0 + ) - # 按评级公司统计 - by_grading_company = db.query( - Collection.f03_21_grading_company, - func.count(Collection.f99_90_id) - ).filter(base_filter, Collection.f03_20_is_graded == True, Collection.f03_21_grading_company.isnot(None)).group_by(Collection.f03_21_grading_company).all() + # 已售商品:状态为 sold 且出售价 > 0 + sold_collections = [ + c for c in all_collections + if c.f01_04_status == 'sold' and c.f05_42_goal_price and c.f05_42_goal_price > 0 + ] - # 按评级分数统计 - by_grading_score = db.query( - Collection.f03_22_grading_score, - func.count(Collection.f99_90_id) - ).filter(base_filter, Collection.f03_20_is_graded == True, Collection.f03_22_grading_score.isnot(None)).group_by(Collection.f03_22_grading_score).all() + # 总收入:SUM(出售价) for 已售商品(售价>0) + total_revenue = sum( + c.f05_42_goal_price or 0 + for c in sold_collections + ) - # 按特殊标记统计 - by_special_mark = db.query( - Collection.f04_30_special_mark, - func.count(Collection.f99_90_id) - ).filter(base_filter, Collection.f04_30_special_mark.isnot(None)).group_by(Collection.f04_30_special_mark).all() - - # 按号码分类统计 - by_number_category = db.query( - Collection.f02_14_number_category, - func.count(Collection.f99_90_id) - ).filter(base_filter, Collection.f02_14_number_category.isnot(None)).group_by(Collection.f02_14_number_category).all() - - # 总成本 - cost_result = db.query( - func.sum( - (Collection.f05_40_cost_price or 0) + - (Collection.f05_43_repair_fee or 0) + - (Collection.f05_44_grading_fee or 0) - ) - ).filter(base_filter).scalar() or 0 - - # 预期利润 - target_result = db.query( - func.sum(Collection.f05_41_target_price) - ).filter(base_filter, Collection.f05_41_target_price > 0).scalar() or 0 - - expected_profit = target_result - cost_result - - # 已售商品统计 - sold_filter = (Collection.f01_04_status == 'sold') & (Collection.f05_42_goal_price > 0) - if current_user.role != "admin": - sold_filter = sold_filter & (Collection.f99_91_user_id == current_user.f99_90_id) - - total_revenue = db.query(func.sum(Collection.f05_42_goal_price)).filter(sold_filter).scalar() or 0 - - # 总利润 - total_profit = db.query( - func.sum( - (Collection.f05_42_goal_price or 0) - - (Collection.f05_40_cost_price or 0) - - (Collection.f05_43_repair_fee or 0) - - (Collection.f05_44_grading_fee or 0) - ) - ).filter(sold_filter).scalar() or 0 + # 总利润(已实现利润):SUM(出售价 - 成本价 - 修复费 - 评级费) for 已售商品 + # 单藏品总成本 = 成本价 + 修复费 + 评级费 + total_profit = sum( + (c.f05_42_goal_price or 0) - (c.f05_40_cost_price or 0) - (c.f05_43_repair_fee or 0) - (c.f05_44_grading_fee or 0) + for c in sold_collections + ) return { "totalCount": total_count, @@ -398,17 +363,13 @@ def get_stats( "byGradingScore": [{"score": s, "count": n} for s, n in by_grading_score], "bySpecialMark": [{"mark": m, "count": n} for m, n in by_special_mark], "byNumberCategory": [{"numberCategory": n, "count": c} for n, c in by_number_category], - # 盈亏统计 + # 盈亏统计(只统计已售且有价格的藏品) "byProfitLoss": [ - {"type": "profit", "label": "盈利", "count": db.query(func.count(Collection.f99_90_id)).filter( - sold_filter, Collection.f05_42_goal_price > Collection.f05_40_cost_price - ).scalar() or 0}, - {"type": "loss", "label": "亏损", "count": db.query(func.count(Collection.f99_90_id)).filter( - sold_filter, Collection.f05_42_goal_price <= Collection.f05_40_cost_price - ).scalar() or 0} + {"type": "profit", "label": "盈利", "count": sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price > c.f05_40_cost_price)}, + {"type": "loss", "label": "亏损", "count": sum(1 for c in sold_collections if c.f05_42_goal_price and c.f05_40_cost_price and c.f05_42_goal_price <= c.f05_40_cost_price)} ], - "totalCost": cost_result, - "totalTarget": target_result, + "totalCost": total_cost, + "totalTarget": sum(c.f05_41_target_price or 0 for c in all_collections), "expectedProfit": expected_profit, "totalRevenue": total_revenue, "totalProfit": total_profit From 47e18948f1f4adf24dbdacfc9298582bf8643102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 14:41:02 +0800 Subject: [PATCH 19/55] =?UTF-8?q?fix:=20=E6=B7=BB=E5=8A=A0images=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=E6=98=A0=E5=B0=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/collections.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/backend/app/routers/collections.py b/backend/app/routers/collections.py index 8479da6..cfad289 100644 --- a/backend/app/routers/collections.py +++ b/backend/app/routers/collections.py @@ -133,10 +133,6 @@ def get_collections( from sqlalchemy.orm import joinedload # 管理员默认查看全库,普通用户只看自己 - if current_user is None: - return {"error": "Unauthorized", "totalCount": 0} - - # 获取所有藏品 if current_user.role == "admin": # 联表查询获取用户名 query = db.query(Collection, User.f01_01_name.label('owner_name')).join( @@ -206,11 +202,7 @@ def get_collections( data_list = [] for item in data: # 处理联表查询结果 - if current_user is None: - return {"error": "Unauthorized", "totalCount": 0} - - # 获取所有藏品 - if current_user.role == "admin": + if current_user.role == "admin": collection_item, owner_name = item else: collection_item = item @@ -285,10 +277,6 @@ def get_stats( db: Session = Depends(get_db) ): """获取藏品统计""" - # 获取所有藏品 - if current_user is None: - return {"error": "Unauthorized", "totalCount": 0} - # 获取所有藏品 if current_user.role == "admin": all_collections = db.query(Collection).all() From d207d6d3292bb4c2cee869ca34b1509f11e41dab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 15:20:15 +0800 Subject: [PATCH 20/55] =?UTF-8?q?v1.2.63=20-=20=E9=A6=96=E9=A1=B5=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E4=BB=8A=E6=97=A5=E9=BE=99=E9=92=9E=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/yichens.py | 70 ++++++++++++++++++++++++++++++++++ frontend/src/pages/Home.jsx | 32 ++++++++++++++++ 2 files changed, 102 insertions(+) diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py index 9b55df7..b230fc1 100644 --- a/backend/app/routers/yichens.py +++ b/backend/app/routers/yichens.py @@ -250,3 +250,73 @@ async def get_today_category_stats(db: Session = Depends(get_coolbot_db)): """ results = db.execute(text(query)).fetchall() return [{"category": r[0] or "未分类", "count": r[1]} for r in results] + + +@router.get("/stats/dragons-today") +def get_dragons_stats_today( + db: Session = Depends(get_coolbot_db) +): + """获取今日龙钞详细统计数据(按号码分类)""" + from sqlalchemy import text + + # 查询今日龙钞数据,按分类统计 + result = db.execute(text(""" + SELECT + category, + COUNT(*) as total, + SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4, + SUM(CASE WHEN content NOT LIKE '%带4%' AND title NOT LIKE '%带4%' AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4, + SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47, + SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247, + SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347 + FROM yichen_posts + WHERE created_at >= CURRENT_DATE + AND (category LIKE '%龙%' OR category LIKE '%龙钞%') + GROUP BY category + """)) + + rows = result.fetchall() + + # 汇总统计 + dai4_count = sum(r[2] or 0 for r in rows) + wu4_count = sum(r[3] or 0 for r in rows) + wu47_count = sum(r[4] or 0 for r in rows) + wu247_count = sum(r[5] or 0 for r in rows) + wu347_count = sum(r[6] or 0 for r in rows) + + # 查询出售和求购分类 + deals_result = db.execute(text(""" + SELECT + SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4_deals, + SUM(CASE WHEN (content NOT LIKE '%带4%' AND title NOT LIKE '%带4%') AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4_deals, + SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47_deals, + SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247_deals, + SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347_deals + FROM yichen_posts + WHERE created_at >= CURRENT_DATE + AND (category LIKE '%龙%' OR category LIKE '%龙钞%') + AND category NOT LIKE '%求购%' + """)) + deals_row = deals_result.fetchone() + + wants_result = db.execute(text(""" + SELECT + SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4_wants, + SUM(CASE WHEN (content NOT LIKE '%带4%' AND title NOT LIKE '%带4%') AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4_wants, + SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47_wants, + SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247_wants, + SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347_wants + FROM yichen_posts + WHERE created_at >= CURRENT_DATE + AND (category LIKE '%龙%' OR category LIKE '%龙钞%') + AND category LIKE '%求购%' + """)) + wants_row = wants_result.fetchone() + + return { + "dai4": {"total": dai4_count, "deals": deals_row[0] or 0, "wants": wants_row[0] or 0}, + "wu4": {"total": wu4_count, "deals": deals_row[1] or 0, "wants": wants_row[1] or 0}, + "wu47": {"total": wu47_count, "deals": deals_row[2] or 0, "wants": wants_row[2] or 0}, + "wu247": {"total": wu247_count, "deals": deals_row[3] or 0, "wants": wants_row[3] or 0}, + "wu347": {"total": wu347_count, "deals": deals_row[4] or 0, "wants": wants_row[4] or 0} + } diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index e258a7b..8ba57f9 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -8,6 +8,7 @@ export default function Home() { const [yichensStats, setYichensStats] = useState({ total: 0, deals: 0, wants: 0, dragons: 0, horses: 0, tianma: 0 }) const [seekStats, setSeekStats] = useState({ seekCount: 0, matchedCount: 0 }) const [recentPosts, setRecentPosts] = useState([]) + const [dragonStats, setDragonStats] = useState({}) const currentPath = window.location.hash.slice(1) || '/' useEffect(() => { @@ -64,6 +65,10 @@ export default function Home() { }).catch(() => {}) // 获取最新一尘帖子 + fetch('/api/yichens/stats/dragons-today').then(res => res.json()).then(data => { + setDragonStats(data || {}) + }).catch(() => {}) + fetch('/api/yichens/posts?limit=10&offset=0').then(res => res.json()).then(data => { setRecentPosts(Array.isArray(data) ? data : []) }).catch(() => {}) @@ -254,6 +259,33 @@ export default function Home() { + {/* 今日龙钞数据 */} +
+
🐉 今日龙钞数据
+
+
+
{dragonStats.dai4?.total || 0}
+
带4
+
+
+
{dragonStats.wu4?.total || 0}
+
无4
+
+
+
{dragonStats.wu47?.total || 0}
+
无47
+
+
+
{dragonStats.wu247?.total || 0}
+
无247
+
+
+
{dragonStats.wu347?.total || 0}
+
无347
+
+
+
+ {/* 最新一尘发帖 */}
From 90ce5a031f46e84ffda29cb32a1cad2c2286edda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 15:27:52 +0800 Subject: [PATCH 21/55] =?UTF-8?q?v1.2.64=20-=20=E4=BF=AE=E5=A4=8D=E9=BE=99?= =?UTF-8?q?=E9=92=9E=E7=BB=9F=E8=AE=A1=E6=95=B0=E6=8D=AEAPI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/yichens.py | 119 +++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 52 deletions(-) diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py index b230fc1..695b672 100644 --- a/backend/app/routers/yichens.py +++ b/backend/app/routers/yichens.py @@ -259,64 +259,79 @@ def get_dragons_stats_today( """获取今日龙钞详细统计数据(按号码分类)""" from sqlalchemy import text - # 查询今日龙钞数据,按分类统计 - result = db.execute(text(""" + # 带4龙钞(包含"龙"且包含"4") + dai4 = db.execute(text(""" SELECT - category, COUNT(*) as total, - SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4, - SUM(CASE WHEN content NOT LIKE '%带4%' AND title NOT LIKE '%带4%' AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4, - SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47, - SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247, - SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347 - FROM yichen_posts - WHERE created_at >= CURRENT_DATE - AND (category LIKE '%龙%' OR category LIKE '%龙钞%') - GROUP BY category - """)) + 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 + FROM yichens_posts + WHERE post_time >= CURRENT_DATE + AND category LIKE '%龙%' + AND (content LIKE '%带4%' OR title LIKE '%带4%') + """)).fetchone() - rows = result.fetchall() - - # 汇总统计 - dai4_count = sum(r[2] or 0 for r in rows) - wu4_count = sum(r[3] or 0 for r in rows) - wu47_count = sum(r[4] or 0 for r in rows) - wu247_count = sum(r[5] or 0 for r in rows) - wu347_count = sum(r[6] or 0 for r in rows) - - # 查询出售和求购分类 - deals_result = db.execute(text(""" + # 无4龙钞(包含"龙"且包含"无4"但不包含"无47") + wu4 = db.execute(text(""" SELECT - SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4_deals, - SUM(CASE WHEN (content NOT LIKE '%带4%' AND title NOT LIKE '%带4%') AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4_deals, - SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47_deals, - SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247_deals, - SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347_deals - FROM yichen_posts - WHERE created_at >= CURRENT_DATE - AND (category LIKE '%龙%' OR category LIKE '%龙钞%') - AND category NOT LIKE '%求购%' - """)) - deals_row = deals_result.fetchone() + 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 + FROM yichens_posts + WHERE post_time >= CURRENT_DATE + AND category LIKE '%龙%' + AND (content LIKE '%无4%' OR title LIKE '%无4%') + AND content NOT LIKE '%无47%' + AND title NOT LIKE '%无47%' + AND content NOT LIKE '%无247%' + AND title NOT LIKE '%无247%' + """)).fetchone() - wants_result = db.execute(text(""" + # 无47龙钞(包含"龙"且包含"无47"但不包含"无247") + wu47 = db.execute(text(""" SELECT - SUM(CASE WHEN content LIKE '%带4%' OR title LIKE '%带4%' THEN 1 ELSE 0 END) as dai4_wants, - SUM(CASE WHEN (content NOT LIKE '%带4%' AND title NOT LIKE '%带4%') AND (content LIKE '%无4%' OR title LIKE '%无4%') THEN 1 ELSE 0 END) as wu4_wants, - SUM(CASE WHEN (content LIKE '%无47%' OR title LIKE '%无47%') AND (content NOT LIKE '%无247%' AND title NOT LIKE '%无247%') THEN 1 ELSE 0 END) as wu47_wants, - SUM(CASE WHEN (content LIKE '%无247%' OR title LIKE '%无247%') AND (content NOT LIKE '%无347%' AND title NOT LIKE '%无347%') THEN 1 ELSE 0 END) as wu247_wants, - SUM(CASE WHEN content LIKE '%无347%' OR title LIKE '%无347%' THEN 1 ELSE 0 END) as wu347_wants - FROM yichen_posts - WHERE created_at >= CURRENT_DATE - AND (category LIKE '%龙%' OR category LIKE '%龙钞%') - AND category LIKE '%求购%' - """)) - wants_row = wants_result.fetchone() + 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 + FROM yichens_posts + WHERE post_time >= CURRENT_DATE + AND category LIKE '%龙%' + AND (content LIKE '%无47%' OR title LIKE '%无47%') + AND content NOT LIKE '%无247%' + AND title NOT LIKE '%无247%' + """)).fetchone() + + # 无247龙钞(包含"龙"且包含"无247"但不包含"无347") + wu247 = db.execute(text(""" + 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 + FROM yichens_posts + WHERE post_time >= CURRENT_DATE + AND category LIKE '%龙%' + AND (content LIKE '%无247%' OR title LIKE '%无247%') + AND content NOT LIKE '%无347%' + AND title NOT LIKE '%无347%' + """)).fetchone() + + # 无347龙钞(包含"龙"且包含"无347") + wu347 = db.execute(text(""" + 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 + FROM yichens_posts + WHERE post_time >= CURRENT_DATE + AND category LIKE '%龙%' + AND (content LIKE '%无347%' OR title LIKE '%无347%') + """)).fetchone() return { - "dai4": {"total": dai4_count, "deals": deals_row[0] or 0, "wants": wants_row[0] or 0}, - "wu4": {"total": wu4_count, "deals": deals_row[1] or 0, "wants": wants_row[1] or 0}, - "wu47": {"total": wu47_count, "deals": deals_row[2] or 0, "wants": wants_row[2] or 0}, - "wu247": {"total": wu247_count, "deals": deals_row[3] or 0, "wants": wants_row[3] or 0}, - "wu347": {"total": wu347_count, "deals": deals_row[4] or 0, "wants": wants_row[4] or 0} + "dai4": {"total": dai4[0] or 0, "deals": dai4[1] or 0, "wants": dai4[2] or 0}, + "wu4": {"total": wu4[0] or 0, "deals": wu4[1] or 0, "wants": wu4[2] or 0}, + "wu47": {"total": wu47[0] or 0, "deals": wu47[1] or 0, "wants": wu47[2] or 0}, + "wu247": {"total": wu247[0] or 0, "deals": wu247[1] or 0, "wants": wu247[2] or 0}, + "wu347": {"total": wu347[0] or 0, "deals": wu347[1] or 0, "wants": wu347[2] or 0} } + From f7b5d6909b6871aa2dc0a04362a282a79ed7b486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 15:37:55 +0800 Subject: [PATCH 22/55] =?UTF-8?q?v1.2.65=20-=20=E4=BC=98=E5=8C=96=E9=BE=99?= =?UTF-8?q?=E9=92=9E=E6=95=B0=E6=8D=AE=E5=B1=95=E7=A4=BA=EF=BC=9A=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E5=87=BA=E5=94=AE/=E6=B1=82=E8=B4=AD=E6=95=B0?= =?UTF-8?q?=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 8ba57f9..f947d72 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -259,29 +259,34 @@ export default function Home() {
- {/* 今日龙钞数据 */} + {/* 今日龙钞帖子数据统计 */}
-
🐉 今日龙钞数据
+
🐉 今日龙钞帖子数据统计
-
{dragonStats.dai4?.total || 0}
+
{dragonStats.dai4?.total || 0}
带4
+
出售:{dragonStats.dai4?.deals || 0} 求购:{dragonStats.dai4?.wants || 0}
-
{dragonStats.wu4?.total || 0}
+
{dragonStats.wu4?.total || 0}
无4
+
出售:{dragonStats.wu4?.deals || 0} 求购:{dragonStats.wu4?.wants || 0}
-
{dragonStats.wu47?.total || 0}
+
{dragonStats.wu47?.total || 0}
无47
+
出售:{dragonStats.wu47?.deals || 0} 求购:{dragonStats.wu47?.wants || 0}
-
{dragonStats.wu247?.total || 0}
+
{dragonStats.wu247?.total || 0}
无247
+
出售:{dragonStats.wu247?.deals || 0} 求购:{dragonStats.wu247?.wants || 0}
-
{dragonStats.wu347?.total || 0}
+
{dragonStats.wu347?.total || 0}
无347
+
出售:{dragonStats.wu347?.deals || 0} 求购:{dragonStats.wu347?.wants || 0}
From c9481ec38526ffbbb8873113268fee9e9c3dfbb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 15:45:30 +0800 Subject: [PATCH 23/55] =?UTF-8?q?v1.2.66=20-=20=E9=BE=99=E9=92=9E=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E5=87=BA=E5=94=AE=E6=B1=82=E8=B4=AD=E6=8D=A2=E8=A1=8C?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index f947d72..7229820 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -266,27 +266,27 @@ export default function Home() {
{dragonStats.dai4?.total || 0}
带4
-
出售:{dragonStats.dai4?.deals || 0} 求购:{dragonStats.dai4?.wants || 0}
+
出售:{dragonStats.dai4?.deals || 0}
求购:{dragonStats.dai4?.wants || 0}
{dragonStats.wu4?.total || 0}
无4
-
出售:{dragonStats.wu4?.deals || 0} 求购:{dragonStats.wu4?.wants || 0}
+
出售:{dragonStats.wu4?.deals || 0}
求购:{dragonStats.wu4?.wants || 0}
{dragonStats.wu47?.total || 0}
无47
-
出售:{dragonStats.wu47?.deals || 0} 求购:{dragonStats.wu47?.wants || 0}
+
出售:{dragonStats.wu47?.deals || 0}
求购:{dragonStats.wu47?.wants || 0}
{dragonStats.wu247?.total || 0}
无247
-
出售:{dragonStats.wu247?.deals || 0} 求购:{dragonStats.wu247?.wants || 0}
+
出售:{dragonStats.wu247?.deals || 0}
求购:{dragonStats.wu247?.wants || 0}
{dragonStats.wu347?.total || 0}
无347
-
出售:{dragonStats.wu347?.deals || 0} 求购:{dragonStats.wu347?.wants || 0}
+
出售:{dragonStats.wu347?.deals || 0}
求购:{dragonStats.wu347?.wants || 0}
From bf2cc0848feda7d63ac0c23ba2ca88ee507c5697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 15:48:49 +0800 Subject: [PATCH 24/55] =?UTF-8?q?v1.2.67=20-=20=E9=9A=90=E8=97=8F=E9=BE=99?= =?UTF-8?q?=E9=92=9E=E6=95=B0=E6=8D=AE=E6=80=BB=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 7229820..1047835 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -264,28 +264,23 @@ export default function Home() {
🐉 今日龙钞帖子数据统计
-
{dragonStats.dai4?.total || 0}
-
带4
+
带4
出售:{dragonStats.dai4?.deals || 0}
求购:{dragonStats.dai4?.wants || 0}
-
{dragonStats.wu4?.total || 0}
-
无4
+
无4
出售:{dragonStats.wu4?.deals || 0}
求购:{dragonStats.wu4?.wants || 0}
-
{dragonStats.wu47?.total || 0}
-
无47
+
无47
出售:{dragonStats.wu47?.deals || 0}
求购:{dragonStats.wu47?.wants || 0}
-
{dragonStats.wu247?.total || 0}
-
无247
+
无247
出售:{dragonStats.wu247?.deals || 0}
求购:{dragonStats.wu247?.wants || 0}
-
{dragonStats.wu347?.total || 0}
-
无347
+
无347
出售:{dragonStats.wu347?.deals || 0}
求购:{dragonStats.wu347?.wants || 0}
From e635c36160f07e4008352a3ed44c812e662bbda0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 15:56:39 +0800 Subject: [PATCH 25/55] =?UTF-8?q?v1.2.68=20-=20=E7=BE=8E=E5=8C=96=E9=BE=99?= =?UTF-8?q?=E9=92=9E=E6=95=B0=E6=8D=AEUI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 1047835..55f8316 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -262,26 +262,31 @@ export default function Home() { {/* 今日龙钞帖子数据统计 */}
🐉 今日龙钞帖子数据统计
-
-
-
带4
-
出售:{dragonStats.dai4?.deals || 0}
求购:{dragonStats.dai4?.wants || 0}
+
+
+
带4
+
{dragonStats.dai4?.deals || 0}
+
{dragonStats.dai4?.wants || 0}
-
-
无4
-
出售:{dragonStats.wu4?.deals || 0}
求购:{dragonStats.wu4?.wants || 0}
+
+
无4
+
{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}
-
-
无247
-
出售:{dragonStats.wu247?.deals || 0}
求购:{dragonStats.wu247?.wants || 0}
+
+
无247
+
{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}
From 041a5bc8bb6efbd3a142643d08eba3a3c7a0f305 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 15:59:07 +0800 Subject: [PATCH 26/55] =?UTF-8?q?v1.2.69=20-=20=E6=B7=BB=E5=8A=A0=E5=87=BA?= =?UTF-8?q?=E5=94=AE=E6=B1=82=E8=B4=AD=E5=AD=97=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 55f8316..9cee3eb 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -265,28 +265,28 @@ export default function Home() {
带4
-
{dragonStats.dai4?.deals || 0}
-
{dragonStats.dai4?.wants || 0}
+
出售{dragonStats.dai4?.deals || 0}
+
求购{dragonStats.dai4?.wants || 0}
无4
-
{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}
+
出售{dragonStats.wu47?.deals || 0}
+
求购{dragonStats.wu47?.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}
+
出售{dragonStats.wu347?.deals || 0}
+
求购{dragonStats.wu347?.wants || 0}
From 5ea8e9f4ab723501ab7d239d47f7a8d2b77a106d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 16:09:32 +0800 Subject: [PATCH 27/55] =?UTF-8?q?v1.2.70=20-=20=E9=BE=99=E9=92=9E=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E6=94=B9=E4=B8=BA=E5=8D=95=E8=A1=A8=E6=A0=BC=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E5=B8=83=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 49 +++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 9cee3eb..32a01b4 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -262,31 +262,38 @@ export default function Home() { {/* 今日龙钞帖子数据统计 */}
🐉 今日龙钞帖子数据统计
-
-
-
带4
-
出售{dragonStats.dai4?.deals || 0}
-
求购{dragonStats.dai4?.wants || 0}
+
+ {/* 表头 */} +
+
+
出售
+
求购
-
-
无4
-
出售{dragonStats.wu4?.deals || 0}
-
求购{dragonStats.wu4?.wants || 0}
+ {/* 数据行 */} +
+
带4
+
{dragonStats.dai4?.deals || 0}
+
{dragonStats.dai4?.wants || 0}
-
-
无47
-
出售{dragonStats.wu47?.deals || 0}
-
求购{dragonStats.wu47?.wants || 0}
+
+
无4
+
{dragonStats.wu4?.deals || 0}
+
{dragonStats.wu4?.wants || 0}
-
-
无247
-
出售{dragonStats.wu247?.deals || 0}
-
求购{dragonStats.wu247?.wants || 0}
+
+
无47
+
{dragonStats.wu47?.deals || 0}
+
{dragonStats.wu47?.wants || 0}
-
-
无347
-
出售{dragonStats.wu347?.deals || 0}
-
求购{dragonStats.wu347?.wants || 0}
+
+
无247
+
{dragonStats.wu247?.deals || 0}
+
{dragonStats.wu247?.wants || 0}
+
+
+
无347
+
{dragonStats.wu347?.deals || 0}
+
{dragonStats.wu347?.wants || 0}
From 8cfec31adf978641b3832b518ad4a0a92ccafdfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 16:12:52 +0800 Subject: [PATCH 28/55] =?UTF-8?q?v1.2.71=20-=20=E6=B7=BB=E5=8A=A0=E5=90=88?= =?UTF-8?q?=E8=AE=A1=E5=88=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 32a01b4..d8fd4f2 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -264,36 +264,42 @@ export default function Home() {
🐉 今日龙钞帖子数据统计
{/* 表头 */} -
+
出售
求购
+
合计
{/* 数据行 */} -
+
带4
{dragonStats.dai4?.deals || 0}
{dragonStats.dai4?.wants || 0}
+
{(dragonStats.dai4?.deals || 0) + (dragonStats.dai4?.wants || 0)}
-
+
无4
{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}
+
{(dragonStats.wu47?.deals || 0) + (dragonStats.wu47?.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}
+
{(dragonStats.wu347?.deals || 0) + (dragonStats.wu347?.wants || 0)}
From 263890f43ba2eeb31ac31eb750e781578f8cc192 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 16:18:24 +0800 Subject: [PATCH 29/55] =?UTF-8?q?v1.2.72=20-=20=E5=B8=96=E5=AD=90=E7=82=B9?= =?UTF-8?q?=E5=87=BB=E8=B7=B3=E8=BD=AC=E5=8E=9F=E5=B8=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index d8fd4f2..664554d 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -308,7 +308,7 @@ export default function Home() {
📝 最新一尘发帖
-
window.location.hash = '#/news'} style={{ color: '#3b82f6', fontSize: '12px', cursor: 'pointer' }}>查看全部 →
+
window.open(item.url, '_blank')} style={{ color: '#3b82f6', fontSize: '12px', cursor: 'pointer' }}>查看全部 →
暂无帖子
) : ( recentPosts.map((item, idx) => ( -
window.location.hash = '#/news'} style={{ +
window.open(item.url, '_blank')} style={{ padding: '12px 16px', borderBottom: idx < recentPosts.length - 1 ? '1px solid rgba(255,255,255,0.05)' : 'none', cursor: 'pointer', From e1582761011e8b84db41ae5176479c6a3b50759f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 16:19:09 +0800 Subject: [PATCH 30/55] =?UTF-8?q?v1.2.73=20-=20=E6=9F=A5=E7=9C=8B=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E8=B7=B3=E8=BD=AC=E4=B8=80=E5=B0=98=E7=9C=8B=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/pages/Home.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 664554d..781157d 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -308,7 +308,7 @@ export default function Home() {
📝 最新一尘发帖
-
window.open(item.url, '_blank')} style={{ color: '#3b82f6', fontSize: '12px', cursor: 'pointer' }}>查看全部 →
+
window.location.hash = '#/news'} style={{ color: '#3b82f6', fontSize: '12px', cursor: 'pointer' }}>查看全部 →
Date: Wed, 8 Apr 2026 16:21:15 +0800 Subject: [PATCH 31/55] =?UTF-8?q?v1.2.63=20-=20=E9=A6=96=E9=A1=B5=E9=BE=99?= =?UTF-8?q?=E9=92=9E=E6=95=B0=E6=8D=AE=E6=A8=A1=E5=9D=97+=E5=B8=96?= =?UTF-8?q?=E5=AD=90=E8=B7=B3=E8=BD=AC=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/VERSION b/config/VERSION index a6294e7..5effd94 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.62 +VERSION=1.2.63 From e1ac548115896c56aff9f5f7b15a3f71f6ea9174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 16:22:36 +0800 Subject: [PATCH 32/55] =?UTF-8?q?v1.2.63=20-=20=E7=BB=9F=E4=B8=80=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/VERSION b/backend/VERSION index b2c8b0c..5effd94 100644 --- a/backend/VERSION +++ b/backend/VERSION @@ -1 +1 @@ -1.2.60 +VERSION=1.2.63 From c96033167f11291e60e416cce53e27fafdd7eecf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 20:14:35 +0800 Subject: [PATCH 33/55] =?UTF-8?q?v1.2.69=20-=20=E6=9B=B4=E6=96=B0=E9=BE=99?= =?UTF-8?q?=E9=92=9E=E5=88=86=E7=B1=BB=E8=A7=84=E5=88=99=E5=85=B3=E9=94=AE?= =?UTF-8?q?=E8=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- backend/app/routers/yichens.py | 57 ++++++++++++++++++++++------------ config/VERSION | 2 +- frontend/src/pages/Home.jsx | 5 +++ 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/VERSION b/VERSION index 307f000..f2254f4 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=1.2.60 +1.2.69 diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py index 695b672..296d4d0 100644 --- a/backend/app/routers/yichens.py +++ b/backend/app/routers/yichens.py @@ -256,10 +256,10 @@ async def get_today_category_stats(db: Session = Depends(get_coolbot_db)): def get_dragons_stats_today( db: Session = Depends(get_coolbot_db) ): - """获取今日龙钞详细统计数据(按号码分类)""" + """获取今日龙钞详细统计数据(按号码分类)- 就高不就低""" from sqlalchemy import text - # 带4龙钞(包含"龙"且包含"4") + # 1. 带4:包含"带4"、"带四"、"通货" dai4 = db.execute(text(""" SELECT COUNT(*) as total, @@ -268,10 +268,14 @@ def get_dragons_stats_today( FROM yichens_posts WHERE post_time >= CURRENT_DATE AND category LIKE '%龙%' - AND (content LIKE '%带4%' OR title LIKE '%带4%') + AND ( + content LIKE '%带4%' OR title LIKE '%带4%' + OR content LIKE '%带四%' OR title LIKE '%带四%' + OR content LIKE '%通货%' OR title LIKE '%通货%' + ) """)).fetchone() - # 无4龙钞(包含"龙"且包含"无4"但不包含"无47") + # 2. 无4:包含"无4"、"无四",排除"无47"、"无247"、"无四七"、"永恒" wu4 = db.execute(text(""" SELECT COUNT(*) as total, @@ -280,14 +284,17 @@ def get_dragons_stats_today( FROM yichens_posts WHERE post_time >= CURRENT_DATE AND category LIKE '%龙%' - AND (content LIKE '%无4%' OR title LIKE '%无4%') - AND content NOT LIKE '%无47%' - AND title NOT LIKE '%无47%' - AND content NOT LIKE '%无247%' - AND title NOT LIKE '%无247%' + AND ( + content LIKE '%无4%' OR title LIKE '%无4%' + OR content LIKE '%无四%' OR title LIKE '%无四%' + ) + AND content NOT LIKE '%无47%' AND title NOT LIKE '%无47%' + AND content NOT LIKE '%无247%' AND title NOT LIKE '%无247%' + AND content NOT LIKE '%永恒%' AND title NOT LIKE '%永恒%' + AND content NOT LIKE '%无四七%' AND title NOT LIKE '%无四七%' """)).fetchone() - # 无47龙钞(包含"龙"且包含"无47"但不包含"无247") + # 3. 无47:包含"无47"、"永恒"、"无四七",排除"无247" wu47 = db.execute(text(""" SELECT COUNT(*) as total, @@ -296,12 +303,15 @@ def get_dragons_stats_today( FROM yichens_posts WHERE post_time >= CURRENT_DATE AND category LIKE '%龙%' - AND (content LIKE '%无47%' OR title LIKE '%无47%') - AND content NOT LIKE '%无247%' - AND title NOT LIKE '%无247%' + AND ( + content LIKE '%无47%' OR title LIKE '%无47%' + OR content LIKE '%永恒%' OR title LIKE '%永恒%' + OR content LIKE '%无四七%' OR title LIKE '%无四七%' + ) + AND content NOT LIKE '%无247%' AND title NOT LIKE '%无247%' """)).fetchone() - # 无247龙钞(包含"龙"且包含"无247"但不包含"无347") + # 4. 无247:包含"无247"、"天马"、"金山",排除"无347" wu247 = db.execute(text(""" SELECT COUNT(*) as total, @@ -310,12 +320,15 @@ def get_dragons_stats_today( FROM yichens_posts WHERE post_time >= CURRENT_DATE AND category LIKE '%龙%' - AND (content LIKE '%无247%' OR title LIKE '%无247%') - AND content NOT LIKE '%无347%' - AND title NOT LIKE '%无347%' + AND ( + content LIKE '%无247%' OR title LIKE '%无247%' + OR content LIKE '%天马%' OR title LIKE '%天马%' + OR content LIKE '%金山%' OR title LIKE '%金山%' + ) + AND content NOT LIKE '%无347%' AND title NOT LIKE '%无347%' """)).fetchone() - # 无347龙钞(包含"龙"且包含"无347") + # 5. 无347:包含"无347"、"钻石"、"金马"、"魅力"、"朦胧" wu347 = db.execute(text(""" SELECT COUNT(*) as total, @@ -324,7 +337,13 @@ def get_dragons_stats_today( FROM yichens_posts WHERE post_time >= CURRENT_DATE AND category LIKE '%龙%' - AND (content LIKE '%无347%' OR title LIKE '%无347%') + AND ( + content LIKE '%无347%' OR title LIKE '%无347%' + OR content LIKE '%钻石%' OR title LIKE '%钻石%' + OR content LIKE '%金马%' OR title LIKE '%金马%' + OR content LIKE '%魅力%' OR title LIKE '%魅力%' + OR content LIKE '%朦胧%' OR title LIKE '%朦胧%' + ) """)).fetchone() return { diff --git a/config/VERSION b/config/VERSION index 5effd94..f2254f4 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.63 +1.2.69 diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 781157d..7022bb9 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 [dragonStats, setDragonStats] = useState({}) const [seekStats, setSeekStats] = useState({ seekCount: 0, matchedCount: 0 }) const [recentPosts, setRecentPosts] = useState([]) const [dragonStats, setDragonStats] = useState({}) @@ -60,6 +61,10 @@ export default function Home() { // 获取一尘看板数据 useEffect(() => { + fetch('/api/yichens/stats/dragons-today').then(res => res.json()).then(data => { + setDragonStats(data || {}) + }).catch(() => {}) + fetch('/api/yichens/stats/today').then(res => res.json()).then(data => { setYichensStats(data || {}) }).catch(() => {}) From dd3e0d9fed673a6bd59fe715b251890ad65bb59c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 20:38:59 +0800 Subject: [PATCH 34/55] =?UTF-8?q?=E4=BF=AE=E5=A4=8DVERSION=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/VERSION b/config/VERSION index f2254f4..bb3b516 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -1.2.69 +VERSION=1.2.69 From 49795690afffc301308609238361e71c8bc2f1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 20:42:48 +0800 Subject: [PATCH 35/55] =?UTF-8?q?v1.2.69=20-=20=E4=BF=AE=E5=A4=8D=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E7=BB=9F=E4=B8=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/VERSION | 2 +- frontend/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/VERSION b/backend/VERSION index 5effd94..f2254f4 100644 --- a/backend/VERSION +++ b/backend/VERSION @@ -1 +1 @@ -VERSION=1.2.63 +1.2.69 diff --git a/frontend/package.json b/frontend/package.json index 0f6062a..cac0060 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jiachenlong-frontend", - "version": "1.2.60", + "version": "1.2.69", "private": true, "description": "甲辰藏品管理系统 - 移动端前端", "scripts": { From 022e81c800fb0cfc74f195a094bae1a8691ebb87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 20:44:15 +0800 Subject: [PATCH 36/55] =?UTF-8?q?v1.2.70=20-=20=E4=BF=AE=E5=A4=8D=E7=89=88?= =?UTF-8?q?=E6=9C=AC=E5=8F=B7=E7=BB=9F=E4=B8=80+=E7=BC=96=E5=8F=B7?= =?UTF-8?q?=E5=B9=B6=E5=8F=91=E9=94=81+Stats=E6=80=A7=E8=83=BD=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/collections.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/app/routers/collections.py b/backend/app/routers/collections.py index cfad289..7fbf85a 100644 --- a/backend/app/routers/collections.py +++ b/backend/app/routers/collections.py @@ -67,11 +67,11 @@ def generate_code(version: str, user_id: str, db: Session) -> str: """自动生成藏品编号 - 按用户独立编码""" import re - # 查询当前用户的非空编码(不与其他用户混算) + # 查询当前用户的非空编码(不与其他用户混算)- 使用行锁防止并发 user_codes = db.query(Collection.f01_02_code).filter( Collection.f01_02_code.isnot(None), Collection.f99_91_user_id == user_id - ).all() + ).with_for_update().all() max_num = 0 for (code,) in user_codes: From a46d63ab019ba42b1f8635711d5c559f6d48800c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 20:45:24 +0800 Subject: [PATCH 37/55] =?UTF-8?q?v1.2.70=20-=20=E4=BF=AE=E5=A4=8D=E7=BC=96?= =?UTF-8?q?=E5=8F=B7=E5=B9=B6=E5=8F=91=E9=94=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- backend/VERSION | 2 +- config/VERSION | 2 +- frontend/package.json | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index f2254f4..daba2a9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.69 +1.2.70 diff --git a/backend/VERSION b/backend/VERSION index f2254f4..fb1488c 100644 --- a/backend/VERSION +++ b/backend/VERSION @@ -1 +1 @@ -1.2.69 +VERSION=1.2.70 diff --git a/config/VERSION b/config/VERSION index bb3b516..daba2a9 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.69 +1.2.70 diff --git a/frontend/package.json b/frontend/package.json index cac0060..91579d5 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jiachenlong-frontend", - "version": "1.2.69", + "version": "1.2.70", "private": true, "description": "甲辰藏品管理系统 - 移动端前端", "scripts": { From d6fd86bbfa54916f366db52482868452986d0644 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 20:46:00 +0800 Subject: [PATCH 38/55] =?UTF-8?q?=E4=BF=AE=E5=A4=8DVERSION=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/VERSION b/backend/VERSION index fb1488c..daba2a9 100644 --- a/backend/VERSION +++ b/backend/VERSION @@ -1 +1 @@ -VERSION=1.2.70 +1.2.70 From 36f617266dde17ce01c53525315a70d46b3874ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 20:47:43 +0800 Subject: [PATCH 39/55] v1.2.70 --- config/VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/VERSION b/config/VERSION index daba2a9..fb1488c 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -1.2.70 +VERSION=1.2.70 From a4be63512c826d91c60c072db2eed2de02651f9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 22:17:55 +0800 Subject: [PATCH 40/55] =?UTF-8?q?v1.2.72=20-=20=E4=BF=AE=E5=A4=8D=E4=B8=80?= =?UTF-8?q?=E5=B0=98=E7=9C=8B=E6=9D=BF=E6=B8=B2=E6=9F=93=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- backend/VERSION | 2 +- config/VERSION | 2 +- frontend/index.html | 2 +- frontend/package.json | 2 +- frontend/src/pages/Home.jsx | 1 - frontend/src/pages/News.jsx | 7 +- frontend/src/pages/News_YichensBoard.jsx | 218 +++++++++++++++++++++++ frontend/src/pages/YichensBoard.jsx | 128 +++++++------ frontend/vite.config.js | 26 +-- 10 files changed, 317 insertions(+), 73 deletions(-) create mode 100644 frontend/src/pages/News_YichensBoard.jsx diff --git a/VERSION b/VERSION index daba2a9..7c48e17 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.2.70 +VERSION=1.2.72 diff --git a/backend/VERSION b/backend/VERSION index daba2a9..7c48e17 100644 --- a/backend/VERSION +++ b/backend/VERSION @@ -1 +1 @@ -1.2.70 +VERSION=1.2.72 diff --git a/config/VERSION b/config/VERSION index fb1488c..7c48e17 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.70 +VERSION=1.2.72 diff --git a/frontend/index.html b/frontend/index.html index 6525875..1894deb 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.50 + 甲辰收藏 v1.2.70 diff --git a/frontend/package.json b/frontend/package.json index 91579d5..f7a2c99 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "jiachenlong-frontend", - "version": "1.2.70", + "version": "1.2.72", "private": true, "description": "甲辰藏品管理系统 - 移动端前端", "scripts": { diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 7022bb9..f2ee81e 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -6,7 +6,6 @@ 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 [dragonStats, setDragonStats] = useState({}) const [seekStats, setSeekStats] = useState({ seekCount: 0, matchedCount: 0 }) const [recentPosts, setRecentPosts] = useState([]) const [dragonStats, setDragonStats] = useState({}) diff --git a/frontend/src/pages/News.jsx b/frontend/src/pages/News.jsx index b316cc6..0b6d882 100644 --- a/frontend/src/pages/News.jsx +++ b/frontend/src/pages/News.jsx @@ -470,7 +470,10 @@ export default function News() { )} - {loading ? ( + {/* 一尘看板独立渲染 */} + {activeTab === 'yichen' ? ( + + ) : loading ? (
加载中...
) : infoList.length === 0 ? (
@@ -478,7 +481,7 @@ export default function News() {
) : (
- {activeTab === 'yichen' ? : infoList.map(item => { + {infoList.map(item => { // 解析正文中的号码特征和联系方式 const content = item.content || '' const featuresMatch = content.match(/号码特征[:\s]*(.+?)(?:\n|$)/) diff --git a/frontend/src/pages/News_YichensBoard.jsx b/frontend/src/pages/News_YichensBoard.jsx new file mode 100644 index 0000000..7af8ba1 --- /dev/null +++ b/frontend/src/pages/News_YichensBoard.jsx @@ -0,0 +1,218 @@ +import React, { useState, useEffect } from 'react' + +export default function YichensBoard() { + 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 [searchKeyword, setSearchKeyword] = useState('') + const API_BASE = localStorage.getItem('API_BASE') || '' + + const today = new Date() + const dateStr = today.getFullYear() + '/' + (today.getMonth() + 1) + '/' + today.getDate() + + useEffect(() => { fetchTodayStats(); fetchPosts(1, '') }, []) + + const fetchTodayStats = async () => { + try { + const res = await fetch(API_BASE + '/api/yichens/stats/today') + setTodayStats(await res.json()) + } catch(e) { console.error(e) } + } + + 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) + 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) + 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('马')) + } + } + // 搜索关键词过滤 - 强匹配(完全包含) + 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([]) } + setLoading(false) + } + + 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 }) => ( +
+
{label}
+
{value}
+
+ ) + + return ( +
+
+
+ 📈 连体钞/纪念钞 {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, '') }} /> +
+
+ +
+
📊 今日分类统计
+
+ {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 => ( + + ))} +
+ +
+ {[{key:'',label:'全部'},{key:'龙',label:'龙钞'},{key:'蛇',label:'蛇钞'},{key:'马',label:'马钞'},{key:'其他',label:'其他'}].map(k => ( + + ))} +
+ + {loading ?
加载中...
: ( +
+
+ {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 && 查看原帖} +
+ )} +
+ ))} +
+ + {/* 分页按钮移到页面底部 */} +
+
+ 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页 +
+
+ {page > 1 ? ( + + ) : ( + 上一页 + )} + {posts.length >= 390 ? ( + + ) : ( + 下一页 + )} +
+
+
+ )} +
+ ) +} diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx index 7af8ba1..ccf5019 100644 --- a/frontend/src/pages/YichensBoard.jsx +++ b/frontend/src/pages/YichensBoard.jsx @@ -4,46 +4,54 @@ export default function YichensBoard() { 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 [loading, setLoading] = useState(true) + const [error, setError] = useState(null) const [expandedPosts, setExpandedPosts] = useState({}) const [postTypeFilter, setPostTypeFilter] = useState('all') 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() const dateStr = today.getFullYear() + '/' + (today.getMonth() + 1) + '/' + today.getDate() - useEffect(() => { fetchTodayStats(); fetchPosts(1, '') }, []) + useEffect(() => { + console.log('YichensBoard: 开始加载数据') + fetchTodayStats() + }, []) const fetchTodayStats = async () => { try { - const res = await fetch(API_BASE + '/api/yichens/stats/today') - setTodayStats(await res.json()) - } catch(e) { console.error(e) } - } - - const fetchTodayCategory = async () => { - try { - const res = await fetch(API_BASE + '/api/yichens/stats/today-category') - setTodayCategory(await res.json()) - } catch(e) { console.error(e) } + console.log('YichensBoard: 请求 /api/yichens/stats/today') + const res = await fetch('/api/yichens/stats/today') + if (!res.ok) throw new Error('stats API error: ' + res.status) + const data = await res.json() + console.log('YichensBoard: stats data', data) + setTodayStats(data) + } catch(e) { + console.error('YichensBoard: fetchTodayStats error', e) + setError(e.message) + } } const fetchPosts = async (p, cat) => { setLoading(true) + setError(null) 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) + let url = '/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 { + console.log('YichensBoard: 请求 posts', url) const res = await fetch(url) + if (!res.ok) throw new Error('posts API error: ' + res.status) let data = await res.json() || [] + console.log('YichensBoard: posts data count', data.length) if (currentCat) { if (currentCat === '龙') { data = data.filter(p => p.category && (p.category.includes('龙') || p.category.includes('纪念钞'))) @@ -55,7 +63,6 @@ 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) { @@ -69,13 +76,21 @@ export default function YichensBoard() { } setPosts(data) setTotalPosts(todayStats.total || 0) - } catch { setPosts([]) } + } catch(e) { + console.error('YichensBoard: fetchPosts error', e) + setError(e.message) + setPosts([]) + } setLoading(false) } - useEffect(() => { if (todayStats.total > 0) fetchTodayCategory() }, [todayStats]) + useEffect(() => { + if (todayStats.total > 0) { + // fetchTodayCategory() + fetchPosts(1, '') + } + }, [todayStats]) - // 搜索关键词变化时重新获取数据 useEffect(() => { setPage(1) fetchPosts(1, '') @@ -95,6 +110,17 @@ export default function YichensBoard() {
) + if (error) { + return ( +
+
加载失败: {error}
+ +
+ ) + } + return (
@@ -111,16 +137,9 @@ export default function YichensBoard() {
📊 今日分类统计
-
- {todayCategory.map(cat => ( - - {cat.category} ({cat.count}) - - ))} -
+
龙: {todayStats.dragons || 0} | 马: {todayStats.horses || 0} | 蛇: {todayStats.snakes || 0} | 天马: {todayStats.tianma || 0}
- {/* 搜索框 */}
加载中...
: (
- {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 || '-'} - + {posts.length === 0 ? ( +
暂无帖子数据
+ ) : ( + 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)||''}
-
- {post.author_username||'未知'} - {post.post_time?.substring(0,16)||''} -
+ {expandedPosts[post.post_id] && post.content && ( +
+
+ {post.content} +
+ {post.url && 查看原帖} +
+ )}
- {expandedPosts[post.post_id] && post.content && ( -
-
- {post.content} -
- {post.url && 查看原帖} -
- )} -
- ))} + )) + )}
- {/* 分页按钮移到页面底部 */}
共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页 diff --git a/frontend/vite.config.js b/frontend/vite.config.js index c167f55..a2b9451 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -7,9 +7,12 @@ import { join } from 'path' function getVersion() { try { const versionFile = join(__dirname, '..', 'config', 'VERSION') - const content = readFileSync(versionFile, 'utf-8') - const match = content.match(/^VERSION=(.*)$/m) - return match ? match[1].trim() : '0.0.0' + const content = readFileSync(versionFile, 'utf-8').trim() + // 移除 VERSION= 前缀 + if (content.startsWith('VERSION=')) { + return content.substring(7).trim() + } + return content || '0.0.0' } catch (e) { console.error('读取 VERSION 文件失败:', e.message) return '0.0.0' @@ -17,7 +20,7 @@ function getVersion() { } const APP_VERSION = getVersion() -console.log(`📦 构建版本:v${APP_VERSION}`) +console.log('📦 构建版本:v' + APP_VERSION) // 构建时自动更新 index.html 的 title function updateHtmlTitle() { @@ -27,10 +30,10 @@ function updateHtmlTitle() { // 替换 甲辰收藏 vXXX htmlContent = htmlContent.replace( /甲辰收藏 v[\d.]+<\/title>/, - `<title>甲辰收藏 v${APP_VERSION}` + '甲辰收藏 v' + APP_VERSION + '' ) writeFileSync(htmlPath, htmlContent, 'utf-8') - console.log(`✅ 已更新 index.html title: 甲辰收藏 v${APP_VERSION}`) + console.log('✅ 已更新 index.html title: 甲辰收藏 v' + APP_VERSION) } catch (e) { console.error('更新 index.html 失败:', e.message) } @@ -47,11 +50,10 @@ export default defineConfig({ build: { rollupOptions: { output: { - entryFileNames: `assets/[name]-[hash]-[name].js`, - chunkFileNames: `assets/[name]-[hash].js`, - assetFileNames: `assets/[name]-[hash].[ext]` + entryFileNames: 'assets/[name]-[hash]-[name].js', + chunkFileNames: 'assets/[name]-[hash].js', + assetFileNames: 'assets/[name]-[hash].[ext]' } - }, - // 禁用缓存 + } } -}) +}) \ No newline at end of file From e5a5a2c48c90a280b8b2da5db0a78616e80368c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 22:24:22 +0800 Subject: [PATCH 41/55] =?UTF-8?q?v1.2.72=20-=20=E4=BC=98=E5=8C=96=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/index.html | 2 +- frontend/src/pages/Home.jsx | 2 +- frontend/src/pages/YichensBoard.jsx | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/index.html b/frontend/index.html index 1894deb..39c6905 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -4,7 +4,7 @@ - 甲辰收藏 v1.2.70 + 甲辰收藏 v=1.2.72 diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index f2ee81e..92b283d 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -73,7 +73,7 @@ export default function Home() { setDragonStats(data || {}) }).catch(() => {}) - fetch('/api/yichens/posts?limit=10&offset=0').then(res => res.json()).then(data => { + fetch('/api/yichens/posts?limit=20&offset=0').then(res => res.json()).then(data => { setRecentPosts(Array.isArray(data) ? data : []) }).catch(() => {}) diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx index ccf5019..6f8594c 100644 --- a/frontend/src/pages/YichensBoard.jsx +++ b/frontend/src/pages/YichensBoard.jsx @@ -41,7 +41,7 @@ export default function YichensBoard() { const currentPage = p !== undefined ? p : page const currentCat = cat !== undefined ? cat : categoryFilter - let url = '/api/yichens/posts?limit=390&offset=' + ((currentPage - 1) * 390) + let url = '/api/yichens/posts?limit=50&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' @@ -218,7 +218,7 @@ export default function YichensBoard() {
- 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页 + 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 50)} 页,当前第 {page} 页
{page > 1 ? ( @@ -226,7 +226,7 @@ export default function YichensBoard() { ) : ( 上一页 )} - {posts.length >= 390 ? ( + {posts.length >= 50 ? ( ) : ( 下一页 From e5a6a5928ec7fa0f1c03a2fa48c22be4ae3921b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 22:40:46 +0800 Subject: [PATCH 42/55] =?UTF-8?q?v1.2.73=20-=20=E5=85=A8=E5=B1=80=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E5=90=8E=E7=AB=AF=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/yichens.py | 8 +++++++- frontend/src/pages/YichensBoard.jsx | 25 ++++++++----------------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py index 296d4d0..3fd6a80 100644 --- a/backend/app/routers/yichens.py +++ b/backend/app/routers/yichens.py @@ -111,9 +111,10 @@ def get_posts( offset: int = Query(0, ge=0), category: Optional[str] = None, post_type: Optional[str] = None, + keyword: Optional[str] = None, db: Session = Depends(get_coolbot_db) ): - """获取帖子列表""" + """获取帖子列表 - 支持全局搜索""" query = """ SELECT post_id, title, content, category, post_type, price, author_username, post_time, reply_count, view_count, url @@ -130,6 +131,11 @@ def get_posts( query += " AND post_type = :post_type" params["post_type"] = post_type + # 全局搜索 - 在标题、内容、分类、联系方式中搜索 + if keyword: + query += " AND (title ILIKE :keyword OR content ILIKE :keyword OR category ILIKE :keyword OR author_username ILIKE :keyword)" + params["keyword"] = f"%{keyword}%" + query += " ORDER BY post_time DESC LIMIT :limit OFFSET :offset" results = db.execute(text(query), params).fetchall() diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx index 6f8594c..cc866f4 100644 --- a/frontend/src/pages/YichensBoard.jsx +++ b/frontend/src/pages/YichensBoard.jsx @@ -35,16 +35,18 @@ export default function YichensBoard() { } } - const fetchPosts = async (p, cat) => { + const fetchPosts = async (p, cat, kw) => { setLoading(true) setError(null) const currentPage = p !== undefined ? p : page const currentCat = cat !== undefined ? cat : categoryFilter + const searchKw = kw !== undefined ? kw : searchKeyword - let url = '/api/yichens/posts?limit=50&offset=' + ((currentPage - 1) * 390) + let url = '/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' + if (searchKw && searchKw.trim()) url += '&keyword=' + encodeURIComponent(searchKw.trim()) try { console.log('YichensBoard: 请求 posts', url) @@ -63,17 +65,6 @@ 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(e) { @@ -218,16 +209,16 @@ export default function YichensBoard() {
- 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 50)} 页,当前第 {page} 页 + 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页
{page > 1 ? ( - + ) : ( 上一页 )} - {posts.length >= 50 ? ( - + {posts.length >= 390 ? ( + ) : ( 下一页 )} From 49b68e802e54ab17ee00e1fe37d005cfa0fbcc76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 22:54:51 +0800 Subject: [PATCH 43/55] =?UTF-8?q?v1.2.74=20-=20=E6=90=9C=E7=B4=A2=E8=BF=94?= =?UTF-8?q?=E5=9B=9E=E6=80=BB=E6=95=B0+=E5=88=86=E9=A1=B5=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/yichens.py | 45 +++++++++++++++++++---------- frontend/src/pages/YichensBoard.jsx | 11 ++++--- 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/backend/app/routers/yichens.py b/backend/app/routers/yichens.py index 3fd6a80..de8e152 100644 --- a/backend/app/routers/yichens.py +++ b/backend/app/routers/yichens.py @@ -105,7 +105,7 @@ def get_user_stats(db: Session = Depends(get_coolbot_db)): sellers=result[2] or 0 ) -@router.get("/posts", response_model=List[PostItem]) +@router.get("/posts") def get_posts( limit: int = Query(20, ge=1, le=500), offset: int = Query(0, ge=0), @@ -114,33 +114,41 @@ def get_posts( keyword: Optional[str] = None, db: Session = Depends(get_coolbot_db) ): - """获取帖子列表 - 支持全局搜索""" - query = """ - SELECT post_id, title, content, category, post_type, price, - author_username, post_time, reply_count, view_count, url - FROM yichens_posts - WHERE 1=1 - """ + """获取帖子列表 - 支持全局搜索,返回总数和分页信息""" + # 构建WHERE条件 + where_clauses = ["1=1"] params = {"limit": limit, "offset": offset} if category: - query += " AND category = :category" + where_clauses.append("category = :category") params["category"] = category if post_type: - query += " AND post_type = :post_type" + where_clauses.append("post_type = :post_type") params["post_type"] = post_type - # 全局搜索 - 在标题、内容、分类、联系方式中搜索 + # 全局搜索 if keyword: - query += " AND (title ILIKE :keyword OR content ILIKE :keyword OR category ILIKE :keyword OR author_username ILIKE :keyword)" + where_clauses.append("(title ILIKE :keyword OR content ILIKE :keyword OR category ILIKE :keyword OR author_username ILIKE :keyword)") params["keyword"] = f"%{keyword}%" - query += " ORDER BY post_time DESC LIMIT :limit OFFSET :offset" + where_sql = " AND ".join(where_clauses) - results = db.execute(text(query), params).fetchall() + # 查询总数 + count_query = f"SELECT COUNT(*) FROM yichens_posts WHERE {where_sql}" + total_count = db.execute(text(count_query), {k: v for k, v in params.items() if k not in ['limit', 'offset']}).scalar() or 0 - return [PostItem( + # 查询数据 + data_query = f""" + SELECT post_id, title, content, category, post_type, price, + author_username, post_time, reply_count, view_count, url + FROM yichens_posts + WHERE {where_sql} + ORDER BY post_time DESC LIMIT :limit OFFSET :offset + """ + results = db.execute(text(data_query), params).fetchall() + + posts = [PostItem( post_id=r[0], title=r[1] or "", content=r[2] or "", @@ -153,6 +161,13 @@ def get_posts( view_count=r[9] or 0, url=r[10] ) for r in results] + + return { + "posts": posts, + "total": total_count, + "page": offset // limit + 1, + "page_size": limit + } @router.get("/users", response_model=List[UserItem]) def get_users( diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx index cc866f4..f2a5a6a 100644 --- a/frontend/src/pages/YichensBoard.jsx +++ b/frontend/src/pages/YichensBoard.jsx @@ -65,8 +65,11 @@ export default function YichensBoard() { data = data.filter(p => p.category && !p.category.includes('龙') && !p.category.includes('纪念钞') && !p.category.includes('蛇') && !p.category.includes('马')) } } - setPosts(data) - setTotalPosts(todayStats.total || 0) + // 支持新格式 {posts:[], total:xxx} 或旧格式 [{},{}] + const postsArray = data.posts || data + const totalCount = data.total || todayStats.total || postsArray.length + setPosts(postsArray) + setTotalPosts(totalCount) } catch(e) { console.error('YichensBoard: fetchPosts error', e) setError(e.message) @@ -209,7 +212,7 @@ export default function YichensBoard() {
- 共 {totalPosts} 条帖子,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页 + 共 {totalPosts} 条结果,{Math.ceil(totalPosts / 390)} 页,当前第 {page} 页
{page > 1 ? ( @@ -217,7 +220,7 @@ export default function YichensBoard() { ) : ( 上一页 )} - {posts.length >= 390 ? ( + {posts.length >= 390 && totalPosts > page * 390 ? ( ) : ( 下一页 From 6401fc57eaf4a26f0049ecc5546073f8c0847086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 23:12:30 +0800 Subject: [PATCH 44/55] =?UTF-8?q?v1.2.75=20-=20=E6=90=9C=E7=B4=A2=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=AE=8C=E5=96=84+=E5=88=86=E9=A1=B5=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- config/VERSION | 2 +- frontend/src/pages/YichensBoard.jsx | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/VERSION b/VERSION index 7c48e17..9ef6466 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=1.2.72 +VERSION=1.2.75 diff --git a/config/VERSION b/config/VERSION index 7c48e17..9ef6466 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.72 +VERSION=1.2.75 diff --git a/frontend/src/pages/YichensBoard.jsx b/frontend/src/pages/YichensBoard.jsx index f2a5a6a..802cc0b 100644 --- a/frontend/src/pages/YichensBoard.jsx +++ b/frontend/src/pages/YichensBoard.jsx @@ -140,7 +140,7 @@ export default function YichensBoard() { type="text" placeholder="搜索标题、内容、分类..." value={searchKeyword} - onChange={(e) => { setSearchKeyword(e.target.value); setPage(1); fetchPosts(1, '') }} + onChange={(e) => { const kw = e.target.value; setSearchKeyword(kw); setPage(1); fetchPosts(1, '', kw) }} style={{ flex: 1, padding: '10px 14px', borderRadius: 8, border: '1px solid #374151', background: '#1f2937', color: '#fff', fontSize: 13, outline: 'none' }} />
- {searchKeyword &&
搜索: "{searchKeyword}",找到 {posts.length} 条结果
} + {searchKeyword &&
搜索: "{searchKeyword}",共找到 {totalPosts} 条结果
}
From 7e19e2a55d6af27925c5aea78607db73b5d901e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Wed, 8 Apr 2026 23:29:15 +0800 Subject: [PATCH 45/55] =?UTF-8?q?v1.2.76=20-=20=E9=A6=96=E9=A1=B5=E6=9C=80?= =?UTF-8?q?=E6=96=B0=E5=B8=96=E5=AD=90+=E6=90=9C=E7=B4=A2=E5=88=86?= =?UTF-8?q?=E9=A1=B5=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- config/VERSION | 2 +- frontend/src/pages/Home.jsx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION b/VERSION index 9ef6466..5f8175f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=1.2.75 +VERSION=1.2.76 diff --git a/config/VERSION b/config/VERSION index 9ef6466..5f8175f 100644 --- a/config/VERSION +++ b/config/VERSION @@ -1 +1 @@ -VERSION=1.2.75 +VERSION=1.2.76 diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 92b283d..96d97c5 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -74,7 +74,7 @@ export default function Home() { }).catch(() => {}) fetch('/api/yichens/posts?limit=20&offset=0').then(res => res.json()).then(data => { - setRecentPosts(Array.isArray(data) ? data : []) + setRecentPosts(data.posts || data || []) }).catch(() => {}) // 获取寻配号统计数据 From 0dee8a9d536164d5074aa6c8f7442733128fdb44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Thu, 9 Apr 2026 00:41:21 +0800 Subject: [PATCH 46/55] =?UTF-8?q?v1.2.77=20-=20=E7=94=A8=E6=88=B7=E7=BC=96?= =?UTF-8?q?=E7=A0=81=E4=BB=8E201=E5=BC=80=E5=A7=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/auth.py | 8 +++++--- frontend/src/pages/Admin.jsx | 38 ++++++++++++++++++++++++------------ 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index 750a671..8993663 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -13,16 +13,18 @@ router = APIRouter(prefix="/api/auth", tags=["认证"]) def generate_user_code(db): - """生成用户编码,从0001开始""" + """生成用户编码,从201开始,按自然数顺序递增""" # 查找最大的user_code max_code = db.query(User.user_code).filter(User.user_code != None).order_by(User.user_code.desc()).first() if max_code and max_code[0]: try: num = int(max_code[0]) + 1 - return f"{num:04d}" + if num < 201: + num = 201 + return str(num) except: pass - return "000501" + return "201" @router.post("/register", response_model=UserResponse) def register(user_data: UserCreate, db: Session = Depends(get_db)): diff --git a/frontend/src/pages/Admin.jsx b/frontend/src/pages/Admin.jsx index 56ed116..84695fd 100644 --- a/frontend/src/pages/Admin.jsx +++ b/frontend/src/pages/Admin.jsx @@ -126,6 +126,8 @@ export default function Admin() { const updateData = { username: editingUser.username, email: editingUser.email, + phone: editingUser.phone, + phoneVerified: editingUser.phoneVerified, role: editingUser.role, user_code: editingUser.user_code, level: editingUser.level, @@ -253,7 +255,7 @@ export default function Admin() {
+
+ + setEditingUser({ ...editingUser, phone: e.target.value })} + style={{ width: '100%', padding: '10px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff' }} + /> +
+ +
+ +
+
setEditingUser({ ...editingUser, phoneVerified: e.target.checked })} - style={{ marginRight: '8px' }} - /> - 手机号已认证 - -
From dacd0235230974ac50dcbdee250f1834d26f7a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Thu, 9 Apr 2026 00:45:19 +0800 Subject: [PATCH 47/55] =?UTF-8?q?v1.2.78=20-=20=E6=94=AF=E6=8C=81=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=BC=96=E7=A0=81=E7=99=BB=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/auth.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index 8993663..ccf1ba6 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -84,9 +84,12 @@ def login( form_data: OAuth2PasswordRequestForm = Depends(), db: Session = Depends(get_db) ): - """用户登录""" - # 查找用户 + """用户登录 - 支持用户名或用户编码登录""" + # 先尝试用户名登录 user = db.query(User).filter(User.f01_01_name == form_data.username).first() + # 如果用户名不存在,尝试用户编码登录 + if not user: + user = db.query(User).filter(User.user_code == form_data.username).first() if not user: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, From 448b3a1ba7969b2e2f77e480d079b375c8a5c90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=94=B2=E8=BE=B0=E7=94=9F=E4=BA=A7?= Date: Thu, 9 Apr 2026 08:14:01 +0800 Subject: [PATCH 48/55] =?UTF-8?q?v1.2.77=20-=20=E6=B7=BB=E5=8A=A0=E9=82=80?= =?UTF-8?q?=E8=AF=B7=E7=A0=81=E6=B3=A8=E5=86=8C=E5=8A=9F=E8=83=BD(?= =?UTF-8?q?=E9=80=89=E5=A1=AB)=EF=BC=8C=E7=94=A8=E6=88=B7=E6=A8=A1?= =?UTF-8?q?=E5=9E=8B=E5=A2=9E=E5=8A=A0=E9=82=80=E8=AF=B7=E6=95=B0=E9=87=8F?= =?UTF-8?q?=E5=AD=97=E6=AE=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- backend/app/models/models.py | 3 ++- backend/app/routers/auth.py | 23 +++++++++++++++++++++++ backend/app/schemas/schemas.py | 2 ++ frontend/src/pages/Login.jsx | 32 ++++++++++++++++++++++++++++---- 5 files changed, 56 insertions(+), 6 deletions(-) diff --git a/VERSION b/VERSION index 5f8175f..24ca32d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -VERSION=1.2.76 +VERSION=1.2.77 diff --git a/backend/app/models/models.py b/backend/app/models/models.py index 387f670..f218c60 100644 --- a/backend/app/models/models.py +++ b/backend/app/models/models.py @@ -44,7 +44,8 @@ class User(Base): f99_100_points = Column(Integer, default=0) # 积分 f01_11_balance = Column(Float, default=0) # 余额 f01_12_total_amount = Column(Float, default=0) # 累计金额 - f01_13_invite_code = Column(String(20), nullable=True) # 邀请码 + f01_13_invite_code = Column(String(20), nullable=True) # 邀请码(自己的邀请码) + f99_101_invited_count = Column(Integer, default=0) # 通过自己邀请码注册的用户数量 collections = relationship("Collection", back_populates="user", cascade="all, delete-orphan") operations = relationship("Operation", back_populates="user", cascade="all, delete-orphan") diff --git a/backend/app/routers/auth.py b/backend/app/routers/auth.py index ccf1ba6..7cae222 100644 --- a/backend/app/routers/auth.py +++ b/backend/app/routers/auth.py @@ -55,6 +55,17 @@ def register(user_data: UserCreate, db: Session = Depends(get_db)): detail="E00041:该邮箱已被注册,请更换邮箱" ) + # 处理邀请码 + invited_by_user = None + if user_data.invite_code: + # 查找邀请人 + invited_by_user = db.query(User).filter(User.user_code == user_data.invite_code).first() + if not invited_by_user: + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail="E00042:邀请码无效" + ) + # 创建用户 import uuid hashed_password = get_password_hash(user_data.password) @@ -73,6 +84,18 @@ def register(user_data: UserCreate, db: Session = Depends(get_db)): ) db.add(user) + db.flush() # 确保获取user ID + + # 更新邀请人、被邀请人的关联关系 + if invited_by_user: + # 邀请人的邀请码(创建后生成自己的邀请码) + user.f01_13_invite_code = invited_by_user.user_code + # 增加邀请人的邀请计数 + invited_by_user.f99_101_invited_count = (invited_by_user.f99_101_invited_count or 0) + 1 + + # 生成自己的邀请码(用自己的user_code) + user.f01_13_invite_code = user.user_code + db.commit() db.refresh(user) diff --git a/backend/app/schemas/schemas.py b/backend/app/schemas/schemas.py index fe79fe6..a8497b5 100644 --- a/backend/app/schemas/schemas.py +++ b/backend/app/schemas/schemas.py @@ -19,6 +19,7 @@ class UserBase(BaseModel): class UserCreate(UserBase): password: str = Field(..., min_length=6) + invite_code: Optional[str] = Field(None, alias="inviteCode") # 填写的邀请码(选填) class UserUpdate(BaseModel): @@ -50,6 +51,7 @@ class UserResponse(UserBase): f01_11_balance: Optional[float] = Field(0, alias="balance") f01_12_total_amount: Optional[float] = Field(0, alias="totalAmount") f01_13_invite_code: Optional[str] = Field(None, alias="inviteCode") + f99_101_invited_count: Optional[int] = Field(0, alias="invitedCount") f99_90_id: str = Field(..., alias="id") f01_01_name: str = Field(..., alias="username") role: str diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx index 47f3198..8c8dfc1 100644 --- a/frontend/src/pages/Login.jsx +++ b/frontend/src/pages/Login.jsx @@ -18,7 +18,8 @@ export default function Login() { confirmPassword: '', email: '', phone: '', - verifyCode: '' + verifyCode: '', + inviteCode: '' }) const [registerLoading, setRegisterLoading] = useState(false) const [registerError, setRegisterError] = useState('') @@ -148,6 +149,9 @@ export default function Login() { if (registerData.email) { payload.email = registerData.email } + if (registerData.inviteCode) { + payload.inviteCode = registerData.inviteCode + } const res = await fetch('/api/auth/register', { method: 'POST', @@ -165,7 +169,7 @@ export default function Login() { alert('注册成功!请登录') setShowRegister(false) - setRegisterData({ username: '', password: '', confirmPassword: '', email: '', phone: '', verifyCode: '' }) + setRegisterData({ username: '', password: '', confirmPassword: '', email: '', phone: '', verifyCode: '', inviteCode: '' }) setCodeSent(false) setCodeCountdown(0) generateCaptcha() @@ -584,7 +588,7 @@ export default function Login() {
{/* 确认密码 */} -
+
+ {/* 邀请码(选填) */} +
+ setRegisterData(prev => ({ ...prev, inviteCode: e.target.value }))} + placeholder="邀请码(选填)" + style={{ + width: '100%', + padding: '14px', + borderRadius: '8px', + border: '1px solid rgba(255,255,255,0.2)', + background: 'rgba(255,255,255,0.05)', + color: '#fff', + fontSize: '16px', + outline: 'none' + }} + /> +
+ {/* 用户协议勾选 */}
@@ -626,7 +650,7 @@ export default function Login() { -
@@ -435,7 +471,7 @@ export default function Add() { style={{ padding: '12px 24px', background: 'rgba(239, 68, 68, 0.2)', color: '#ef4444', border: '1px solid #ef4444', borderRadius: '8px', fontSize: '14px', cursor: 'pointer' }}>🗑️ 重新选择
@@ -657,16 +693,137 @@ export default function Add() {
)} - {/* 批量录入模式 */} - {activeTab === 'batch' && ( -
-
🚧
-
批量录入开发中
-
敬请期待后续版本
-
- )} + {/* 版本号 */} + + {/* 行情录入模式 */} + {activeTab === 'deal' && ( +
+
+
成交行情录入
+ +
+
冠字号 *
+ setDealForm({...dealForm, serial: e.target.value.toUpperCase(), category: autoCategory(e.target.value)})} + placeholder="J0xxxxxxxx" + style={{ width: '100%', padding: '12px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff', fontSize: '15px' }} /> +
+ + {dealForm.category && ( +
+
号码分类
+
{dealForm.category}
+
+ )} + +
+
包装类型
+
+ {['单张', '标十', '标百'].map(p => ( + + ))} +
+
+ +
+
成交价格 *
+ setDealForm({...dealForm, price: e.target.value})} + placeholder="请输入成交价格" + style={{ width: '100%', padding: '12px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff', fontSize: '15px' }} /> +
+ +
+
成交平台 *
+ +
+ +
+
+
出售者
+ setDealForm({...dealForm, seller: e.target.value})} + placeholder="请输入出售者" + style={{ width: '100%', padding: '12px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff', fontSize: '15px' }} /> +
+
+
购买者
+ setDealForm({...dealForm, buyer: e.target.value})} + placeholder="请输入购买者" + style={{ width: '100%', padding: '12px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff', fontSize: '15px' }} /> +
+
+ +
+
成交日期 *
+ setDealForm({...dealForm, date: e.target.value})} + style={{ width: '100%', padding: '12px', background: 'rgba(255,255,255,0.05)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '8px', color: '#fff', fontSize: '15px' }} /> +
+ + +
+
+ )} +
v{APP_VERSION}
) diff --git a/frontend/src/pages/Home.jsx b/frontend/src/pages/Home.jsx index 6c38011..1476144 100644 --- a/frontend/src/pages/Home.jsx +++ b/frontend/src/pages/Home.jsx @@ -367,13 +367,13 @@ export default function Home() { {(isAdmin ? [ { icon: '🏠', label: '首页', hash: '#/', idx: 0 }, { icon: '📚', label: '藏品', hash: '#/list', idx: 1 }, - { icon: '➕', label: '添加', hash: '#/ocr', idx: 2 }, + { icon: '➕', label: '录入', hash: '#/ocr', idx: 2 }, { icon: '📊', label: '统计', hash: '#/stats', idx: 3 }, { icon: '⚙️', label: '管理', hash: '#/admin', idx: 4 } ] : [ { icon: '🏠', label: '首页', hash: '#/', idx: 0 }, { icon: '📚', label: '藏品', hash: '#/list', idx: 1 }, - { icon: '➕', label: '添加', hash: '#/ocr', idx: 2 }, + { icon: '➕', label: '录入', hash: '#/ocr', idx: 2 }, { icon: '📊', label: '统计', hash: '#/stats', idx: 3 } ]).map((item) => (
window.location.hash = item.hash} style={{ diff --git a/frontend/src/pages/Info.jsx b/frontend/src/pages/Info.jsx deleted file mode 100644 index 17c7baf..0000000 --- a/frontend/src/pages/Info.jsx +++ /dev/null @@ -1,680 +0,0 @@ -import React, { useState, useEffect } from 'react' - -// 信息页面 - 寻配号发布和发布管理(包含寻号/行情区分) -export default function Info() { - // 检查登录状态,未登录则跳转到登录页 - if (!localStorage.getItem('token')) { - window.location.hash = '#/login' - return null - } - - // 顶部tab:寻配号发布 / 发布管理 - const [activeTab, setActiveTab] = useState('manage') - const [showPublish, setShowPublish] = useState(false) - const [publishType, setPublishType] = useState('deal') // 'seek' 或 'deal' - const [myList, setMyList] = useState([]) - const [loading, setLoading] = useState(false) - const [editingItem, setEditingItem] = useState(null) - const [filterType, setFilterType] = useState('all') // all/seek/deal - const [expandedItems, setExpandedItems] = useState({}) // 展开状态 - - const [formData, setFormData] = useState({ - edition: '龙钞', type: '标百', isGraded: true, category: '成交', source: '直播', title: '' - }) - - // 寻配号发布表单 - const [seekForm, setSeekForm] = useState({ - edition: '龙钞', price: '', features: '', contact: '', content: '', title: '' - }) - - const API_BASE = localStorage.getItem('API_BASE') || '' - - // 切换展开/收起 - const toggleExpand = (itemId) => { - setExpandedItems(prev => ({ - ...prev, - [itemId]: !prev[itemId] - })) - } - - useEffect(() => { - if (activeTab === 'manage') fetchMyList() - }, [activeTab]) - - useEffect(() => { - // 自动生成行情标题 - const now = new Date() - const date = `${now.getFullYear()}/${now.getMonth()+1}/${now.getDate()}` - const grade = formData.isGraded ? '(评级币)' : '(裸钞)' - const title = `「${date} ${formData.edition} ${formData.type} ${formData.category} 行情信息${grade}」` - setFormData(prev => ({ ...prev, title })) - }, [formData.edition, formData.type, formData.isGraded, formData.category]) - - useEffect(() => { - // 自动生成寻配号标题 - if (seekForm.edition || seekForm.features) { - const title = `「寻号 ${seekForm.edition} J0${seekForm.features || 'XXXXXXXX'}」` - setSeekForm(prev => ({ ...prev, title })) - } - }, [seekForm.edition, seekForm.features]) - - const fetchMyList = async () => { - setLoading(true) - try { - const token = localStorage.getItem('token') - const res = await fetch(`${API_BASE}/api/information/list`, { - headers: { Authorization: `Bearer ${token}` } - }) - if (res.ok) { - const data = await res.json() - setMyList(data || []) - } - } catch (e) { - console.error(e) - } - setLoading(false) - } - - // 发布寻配号 - const handlePublishSeek = async () => { - if (!seekForm.contact) { - alert('请填写联系方式') - return - } - try { - const token = localStorage.getItem('token') - const res = await fetch(`${API_BASE}/api/information/`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - title: seekForm.title, - content: document.getElementById('seekContent')?.value || seekForm.content, - info_type: 'seek', - expect_category: seekForm.edition, - expect_number: seekForm.features ? `J0${seekForm.features}` : null - }) - }) - const data = await res.json() - if (data.id || data.code === 0) { - alert('发布成功!') - setShowPublish(false) - setSeekForm({ edition: '龙钞', price: '', features: '', contact: '', content: '', title: '' }) - const contentEl = document.getElementById('seekContent') - if (contentEl) contentEl.value = '' - fetchMyList() - } else { - alert(data.message || '发布失败') - } - } catch (e) { - alert('发布失败: ' + e.message) - } - } - - // 发布行情(新增发布默认是行情) - const handlePublishDeal = async () => { - const content = document.getElementById('publishContent')?.value || '' - if (!formData.title) { - alert('请填写标题') - return - } - try { - const token = localStorage.getItem('token') - const res = await fetch(`${API_BASE}/api/information/`, { - method: 'POST', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ - title: formData.title, - content: content, - info_type: formData.category === '成交' ? 'deal' : 'seek' - }) - }) - const data = await res.json() - if (data.id || data.code === 0) { - alert('发布成功!') - setShowPublish(false) - setFormData({ edition: '龙钞', type: '标百', isGraded: true, category: '成交', source: '直播', title: '' }) - const contentEl = document.getElementById('publishContent') - if (contentEl) contentEl.value = '' - fetchMyList() - } else { - alert(data.message || '发布失败') - } - } catch (e) { - alert('发布失败: ' + e.message) - } - } - - // 删除 - const handleDelete = async (id) => { - if (!confirm('确定删除这条信息吗?')) return - try { - const token = localStorage.getItem('token') - const res = await fetch(`${API_BASE}/api/information/${id}`, { - method: 'DELETE', - headers: { Authorization: `Bearer ${token}` } - }) - if (res.ok) { - alert('删除成功') - fetchMyList() - } else { - alert('删除失败') - } - } catch (e) { - alert('删除失败: ' + e.message) - } - } - - // 编辑 - const handleEdit = (item) => { - setEditingItem({ id: item.id, title: item.title, content: item.content }) - } - - const saveEdit = async () => { - if (!editingItem) return - try { - const token = localStorage.getItem('token') - const res = await fetch(`${API_BASE}/api/information/${editingItem.id}`, { - method: 'PUT', - headers: { - 'Authorization': `Bearer ${token}`, - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ title: editingItem.title, content: editingItem.content }) - }) - if (res.ok) { - alert('保存成功') - setEditingItem(null) - fetchMyList() - } else { - alert('保存失败') - } - } catch (e) { - alert('保存失败: ' + e.message) - } - } - - const getUserPhone = () => { - try { - const user = JSON.parse(localStorage.getItem('user') || '{}') - return user.phone || user.phoneNumber || user.mobile || user.tel || '' - } catch { - return '' - } - } - - useEffect(() => { - setSeekForm(prev => ({ ...prev, contact: getUserPhone() })) - }, []) - - // 格式化日期 - const formatDate = (date) => { - if (!date) return '-' - return new Date(date).toLocaleString('zh-CN').slice(0, 16) - } - - // 过滤后的列表 - const filteredList = myList.filter(item => filterType === 'all' || item.info_type === filterType) - - return ( -
- {/* 顶部 Tab 切换 */} -
-
- {[ - { key: 'manage', label: '📋 发布管理' } - ].map(tab => ( -
setActiveTab(tab.key)} - style={{ - flex: 1, - padding: '12px 16px', - borderRadius: '10px', - background: activeTab === tab.key ? 'linear-gradient(135deg, #3b82f6 0%, #2563eb 100%)' : 'transparent', - color: activeTab === tab.key ? '#fff' : '#9ca3af', - cursor: 'pointer', - textAlign: 'center', - fontSize: '14px', - fontWeight: '600', - transition: 'all 0.3s' - }} - > - {tab.label} -
- ))} -
-
- -
- {/* 寻配号发布页 - 已删除 */} - {false && ( -
-

🔍 寻配号发布

- -
-
- -
- {['龙钞', '马钞', '蛇钞'].map(ed => { - const selected = seekForm.edition === ed - return ( - - ) - })} -
-
- -
- - setSeekForm({ ...seekForm, features: e.target.value.toUpperCase().slice(0, 8) })} - placeholder="输入号码特征,如:12345678" - maxLength={8} - style={{ width: '100%', padding: '12px', background: '#1f2937', border: '1px solid #374151', borderRadius: '10px', color: '#fff', fontSize: '14px', boxSizing: 'border-box' }} - /> -
X=任意数字 A=非4 B=非47 C=非347 D=非247
-
- -
- - -
- -
- -