瀏覽代碼

✨ feat(finance): IDO 概览卡片、我的团队页与锁仓账户深链

对齐 Web IDO 预售:展示团队人数/锁仓总量入口,我的团队页接团队与直推接口,
锁仓总量跳转资产页锁仓子 Tab;修复 Sliver 下 Row stretch 导致的空白布局。

Co-authored-by: Cursor <cursoragent@cursor.com>
maotouying_cc 1 月之前
父節點
當前提交
3e3bb75bcc

+ 17 - 0
lib/core/l10n/app_en.arb

@@ -85,6 +85,23 @@
       "release": {}
     }
   },
+  "financeMyTeam": "My Team",
+  "financeCurrentNodeLevel": "Current Node Level",
+  "financeDirectNumLabel": "Direct Referrals",
+  "financeTeamNumLabel": "Team Size",
+  "financeTeamPerformanceLabel": "Team Performance (iBit)",
+  "financeTotalRevenueLabel": "Total Earnings (USDT)",
+  "financeDirectListTitle": "Direct Referral List",
+  "financeDirectAddress": "Address",
+  "financeLevelCol": "Level",
+  "financeBuyAmountCol": "Purchase (iBit)",
+  "financeMyRewardCol": "My Reward (U)",
+  "financeNodeLevelNormal": "No Node",
+  "financeNodeLevelCommunity": "Community Node",
+  "financeNodeLevelCity": "City Node",
+  "financeNodeLevelApac": "APAC Node",
+  "financeNodeLevelGlobal": "Global Node",
+  "financeTeamLoadMore": "Load More",
   "financeJoinPresale": "Join Presale",
   "financeSubscribeQty": "Subscription Amount",
   "financeAvailableIbit": "Withdrawable iBit",

+ 17 - 0
lib/core/l10n/app_ja.arb

@@ -86,6 +86,23 @@
       "release": {}
     }
   },
+  "financeMyTeam": "マイチーム",
+  "financeCurrentNodeLevel": "現在のノード等級",
+  "financeDirectNumLabel": "直紹介人数",
+  "financeTeamNumLabel": "チーム人数",
+  "financeTeamPerformanceLabel": "チーム実績(iBit)",
+  "financeTotalRevenueLabel": "総収益(USDT)",
+  "financeDirectListTitle": "直紹介リスト",
+  "financeDirectAddress": "直紹介アドレス",
+  "financeLevelCol": "等級",
+  "financeBuyAmountCol": "購入量(iBit)",
+  "financeMyRewardCol": "私の報酬(U)",
+  "financeNodeLevelNormal": "ノードなし",
+  "financeNodeLevelCommunity": "コミュニティノード",
+  "financeNodeLevelCity": "シティノード",
+  "financeNodeLevelApac": "APACノード",
+  "financeNodeLevelGlobal": "グローバルノード",
+  "financeTeamLoadMore": "さらに読み込む",
   "financeJoinPresale": "プレセール参加",
   "financeSubscribeQty": "申込数量",
   "financeAvailableIbit": "出金可能 iBit",

+ 17 - 0
lib/core/l10n/app_ko.arb

@@ -85,6 +85,23 @@
       "release": {}
     }
   },
+  "financeMyTeam": "내 팀",
+  "financeCurrentNodeLevel": "현재 노드 등급",
+  "financeDirectNumLabel": "직추천 인원",
+  "financeTeamNumLabel": "팀 인원",
+  "financeTeamPerformanceLabel": "팀 실적(iBit)",
+  "financeTotalRevenueLabel": "총 수익(USDT)",
+  "financeDirectListTitle": "직추천 목록",
+  "financeDirectAddress": "직추천 주소",
+  "financeLevelCol": "등급",
+  "financeBuyAmountCol": "구매량(iBit)",
+  "financeMyRewardCol": "내 보상(U)",
+  "financeNodeLevelNormal": "노드 없음",
+  "financeNodeLevelCommunity": "커뮤니티 노드",
+  "financeNodeLevelCity": "시티 노드",
+  "financeNodeLevelApac": "APAC 노드",
+  "financeNodeLevelGlobal": "글로벌 노드",
+  "financeTeamLoadMore": "더 보기",
   "financeJoinPresale": "프리세일 참여",
   "financeSubscribeQty": "청약 수량",
   "financeAvailableIbit": "출금 가능 iBit",

+ 102 - 0
lib/core/l10n/app_localizations.dart

@@ -461,6 +461,108 @@ abstract class AppLocalizations {
   /// **'会员购买一定量平台自己的主链币,自动进入锁仓账户。锁仓{lock}个月,按{release}个月释放。'**
   String financeIdoRuleBatch(Object lock, Object release);
 
+  /// No description provided for @financeMyTeam.
+  ///
+  /// In zh, this message translates to:
+  /// **'我的团队'**
+  String get financeMyTeam;
+
+  /// No description provided for @financeCurrentNodeLevel.
+  ///
+  /// In zh, this message translates to:
+  /// **'当前节点等级'**
+  String get financeCurrentNodeLevel;
+
+  /// No description provided for @financeDirectNumLabel.
+  ///
+  /// In zh, this message translates to:
+  /// **'直推人数'**
+  String get financeDirectNumLabel;
+
+  /// No description provided for @financeTeamNumLabel.
+  ///
+  /// In zh, this message translates to:
+  /// **'团队人数'**
+  String get financeTeamNumLabel;
+
+  /// No description provided for @financeTeamPerformanceLabel.
+  ///
+  /// In zh, this message translates to:
+  /// **'团队业绩(iBit)'**
+  String get financeTeamPerformanceLabel;
+
+  /// No description provided for @financeTotalRevenueLabel.
+  ///
+  /// In zh, this message translates to:
+  /// **'总收益(USDT)'**
+  String get financeTotalRevenueLabel;
+
+  /// No description provided for @financeDirectListTitle.
+  ///
+  /// In zh, this message translates to:
+  /// **'直推列表'**
+  String get financeDirectListTitle;
+
+  /// No description provided for @financeDirectAddress.
+  ///
+  /// In zh, this message translates to:
+  /// **'直推地址'**
+  String get financeDirectAddress;
+
+  /// No description provided for @financeLevelCol.
+  ///
+  /// In zh, this message translates to:
+  /// **'等级'**
+  String get financeLevelCol;
+
+  /// No description provided for @financeBuyAmountCol.
+  ///
+  /// In zh, this message translates to:
+  /// **'购买量(iBit)'**
+  String get financeBuyAmountCol;
+
+  /// No description provided for @financeMyRewardCol.
+  ///
+  /// In zh, this message translates to:
+  /// **'我的奖励(U)'**
+  String get financeMyRewardCol;
+
+  /// No description provided for @financeNodeLevelNormal.
+  ///
+  /// In zh, this message translates to:
+  /// **'无节点'**
+  String get financeNodeLevelNormal;
+
+  /// No description provided for @financeNodeLevelCommunity.
+  ///
+  /// In zh, this message translates to:
+  /// **'社区节点'**
+  String get financeNodeLevelCommunity;
+
+  /// No description provided for @financeNodeLevelCity.
+  ///
+  /// In zh, this message translates to:
+  /// **'城市节点'**
+  String get financeNodeLevelCity;
+
+  /// No description provided for @financeNodeLevelApac.
+  ///
+  /// In zh, this message translates to:
+  /// **'亚太节点'**
+  String get financeNodeLevelApac;
+
+  /// No description provided for @financeNodeLevelGlobal.
+  ///
+  /// In zh, this message translates to:
+  /// **'全球节点'**
+  String get financeNodeLevelGlobal;
+
+  /// No description provided for @financeTeamLoadMore.
+  ///
+  /// In zh, this message translates to:
+  /// **'加载更多'**
+  String get financeTeamLoadMore;
+
   /// No description provided for @financeJoinPresale.
   ///
   /// In zh, this message translates to:

+ 51 - 0
lib/core/l10n/app_localizations_en.dart

@@ -196,6 +196,57 @@ class AppLocalizationsEn extends AppLocalizations {
     return 'Members purchase a set amount of the platform main-chain token; funds are locked automatically. Locked for $lock month(s), released over $release month(s).';
   }
 
+  @override
+  String get financeMyTeam => 'My Team';
+
+  @override
+  String get financeCurrentNodeLevel => 'Current Node Level';
+
+  @override
+  String get financeDirectNumLabel => 'Direct Referrals';
+
+  @override
+  String get financeTeamNumLabel => 'Team Size';
+
+  @override
+  String get financeTeamPerformanceLabel => 'Team Performance (iBit)';
+
+  @override
+  String get financeTotalRevenueLabel => 'Total Earnings (USDT)';
+
+  @override
+  String get financeDirectListTitle => 'Direct Referral List';
+
+  @override
+  String get financeDirectAddress => 'Address';
+
+  @override
+  String get financeLevelCol => 'Level';
+
+  @override
+  String get financeBuyAmountCol => 'Purchase (iBit)';
+
+  @override
+  String get financeMyRewardCol => 'My Reward (U)';
+
+  @override
+  String get financeNodeLevelNormal => 'No Node';
+
+  @override
+  String get financeNodeLevelCommunity => 'Community Node';
+
+  @override
+  String get financeNodeLevelCity => 'City Node';
+
+  @override
+  String get financeNodeLevelApac => 'APAC Node';
+
+  @override
+  String get financeNodeLevelGlobal => 'Global Node';
+
+  @override
+  String get financeTeamLoadMore => 'Load More';
+
   @override
   String get financeJoinPresale => 'Join Presale';
 

+ 51 - 0
lib/core/l10n/app_localizations_hi.dart

@@ -196,6 +196,57 @@ class AppLocalizationsHi extends AppLocalizations {
     return '会员购买一定量平台自己的主链币,自动进入锁仓账户。锁仓$lock个月,按$release个月释放。';
   }
 
+  @override
+  String get financeMyTeam => '我的团队';
+
+  @override
+  String get financeCurrentNodeLevel => '当前节点等级';
+
+  @override
+  String get financeDirectNumLabel => '直推人数';
+
+  @override
+  String get financeTeamNumLabel => '团队人数';
+
+  @override
+  String get financeTeamPerformanceLabel => '团队业绩(iBit)';
+
+  @override
+  String get financeTotalRevenueLabel => '总收益(USDT)';
+
+  @override
+  String get financeDirectListTitle => '直推列表';
+
+  @override
+  String get financeDirectAddress => '直推地址';
+
+  @override
+  String get financeLevelCol => '等级';
+
+  @override
+  String get financeBuyAmountCol => '购买量(iBit)';
+
+  @override
+  String get financeMyRewardCol => '我的奖励(U)';
+
+  @override
+  String get financeNodeLevelNormal => '无节点';
+
+  @override
+  String get financeNodeLevelCommunity => '社区节点';
+
+  @override
+  String get financeNodeLevelCity => '城市节点';
+
+  @override
+  String get financeNodeLevelApac => '亚太节点';
+
+  @override
+  String get financeNodeLevelGlobal => '全球节点';
+
+  @override
+  String get financeTeamLoadMore => '加载更多';
+
   @override
   String get financeJoinPresale => '参与预售';
 

+ 51 - 0
lib/core/l10n/app_localizations_id.dart

@@ -196,6 +196,57 @@ class AppLocalizationsId extends AppLocalizations {
     return '会员购买一定量平台自己的主链币,自动进入锁仓账户。锁仓$lock个月,按$release个月释放。';
   }
 
+  @override
+  String get financeMyTeam => '我的团队';
+
+  @override
+  String get financeCurrentNodeLevel => '当前节点等级';
+
+  @override
+  String get financeDirectNumLabel => '直推人数';
+
+  @override
+  String get financeTeamNumLabel => '团队人数';
+
+  @override
+  String get financeTeamPerformanceLabel => '团队业绩(iBit)';
+
+  @override
+  String get financeTotalRevenueLabel => '总收益(USDT)';
+
+  @override
+  String get financeDirectListTitle => '直推列表';
+
+  @override
+  String get financeDirectAddress => '直推地址';
+
+  @override
+  String get financeLevelCol => '等级';
+
+  @override
+  String get financeBuyAmountCol => '购买量(iBit)';
+
+  @override
+  String get financeMyRewardCol => '我的奖励(U)';
+
+  @override
+  String get financeNodeLevelNormal => '无节点';
+
+  @override
+  String get financeNodeLevelCommunity => '社区节点';
+
+  @override
+  String get financeNodeLevelCity => '城市节点';
+
+  @override
+  String get financeNodeLevelApac => '亚太节点';
+
+  @override
+  String get financeNodeLevelGlobal => '全球节点';
+
+  @override
+  String get financeTeamLoadMore => '加载更多';
+
   @override
   String get financeJoinPresale => '参与预售';
 

+ 51 - 0
lib/core/l10n/app_localizations_ja.dart

@@ -195,6 +195,57 @@ class AppLocalizationsJa extends AppLocalizations {
     return '会員が一定量のプラットフォーム主チェーンコインを購入すると、自動的にロック口座に入ります。$lockヶ月ロック後、$releaseヶ月かけて解放されます。';
   }
 
+  @override
+  String get financeMyTeam => 'マイチーム';
+
+  @override
+  String get financeCurrentNodeLevel => '現在のノード等級';
+
+  @override
+  String get financeDirectNumLabel => '直紹介人数';
+
+  @override
+  String get financeTeamNumLabel => 'チーム人数';
+
+  @override
+  String get financeTeamPerformanceLabel => 'チーム実績(iBit)';
+
+  @override
+  String get financeTotalRevenueLabel => '総収益(USDT)';
+
+  @override
+  String get financeDirectListTitle => '直紹介リスト';
+
+  @override
+  String get financeDirectAddress => '直紹介アドレス';
+
+  @override
+  String get financeLevelCol => '等級';
+
+  @override
+  String get financeBuyAmountCol => '購入量(iBit)';
+
+  @override
+  String get financeMyRewardCol => '私の報酬(U)';
+
+  @override
+  String get financeNodeLevelNormal => 'ノードなし';
+
+  @override
+  String get financeNodeLevelCommunity => 'コミュニティノード';
+
+  @override
+  String get financeNodeLevelCity => 'シティノード';
+
+  @override
+  String get financeNodeLevelApac => 'APACノード';
+
+  @override
+  String get financeNodeLevelGlobal => 'グローバルノード';
+
+  @override
+  String get financeTeamLoadMore => 'さらに読み込む';
+
   @override
   String get financeJoinPresale => 'プレセール参加';
 

+ 51 - 0
lib/core/l10n/app_localizations_ko.dart

@@ -195,6 +195,57 @@ class AppLocalizationsKo extends AppLocalizations {
     return '회원이 일정량의 플랫폼 메인체인 코인을 구매하면 자동으로 락업 계정에 들어갑니다. $lock개월 락업 후 $release개월에 걸쳐 해제됩니다.';
   }
 
+  @override
+  String get financeMyTeam => '내 팀';
+
+  @override
+  String get financeCurrentNodeLevel => '현재 노드 등급';
+
+  @override
+  String get financeDirectNumLabel => '직추천 인원';
+
+  @override
+  String get financeTeamNumLabel => '팀 인원';
+
+  @override
+  String get financeTeamPerformanceLabel => '팀 실적(iBit)';
+
+  @override
+  String get financeTotalRevenueLabel => '총 수익(USDT)';
+
+  @override
+  String get financeDirectListTitle => '직추천 목록';
+
+  @override
+  String get financeDirectAddress => '직추천 주소';
+
+  @override
+  String get financeLevelCol => '등급';
+
+  @override
+  String get financeBuyAmountCol => '구매량(iBit)';
+
+  @override
+  String get financeMyRewardCol => '내 보상(U)';
+
+  @override
+  String get financeNodeLevelNormal => '노드 없음';
+
+  @override
+  String get financeNodeLevelCommunity => '커뮤니티 노드';
+
+  @override
+  String get financeNodeLevelCity => '시티 노드';
+
+  @override
+  String get financeNodeLevelApac => 'APAC 노드';
+
+  @override
+  String get financeNodeLevelGlobal => '글로벌 노드';
+
+  @override
+  String get financeTeamLoadMore => '더 보기';
+
   @override
   String get financeJoinPresale => '프리세일 참여';
 

+ 102 - 0
lib/core/l10n/app_localizations_zh.dart

@@ -195,6 +195,57 @@ class AppLocalizationsZh extends AppLocalizations {
     return '会员购买一定量平台自己的主链币,自动进入锁仓账户。锁仓$lock个月,按$release个月释放。';
   }
 
+  @override
+  String get financeMyTeam => '我的团队';
+
+  @override
+  String get financeCurrentNodeLevel => '当前节点等级';
+
+  @override
+  String get financeDirectNumLabel => '直推人数';
+
+  @override
+  String get financeTeamNumLabel => '团队人数';
+
+  @override
+  String get financeTeamPerformanceLabel => '团队业绩(iBit)';
+
+  @override
+  String get financeTotalRevenueLabel => '总收益(USDT)';
+
+  @override
+  String get financeDirectListTitle => '直推列表';
+
+  @override
+  String get financeDirectAddress => '直推地址';
+
+  @override
+  String get financeLevelCol => '等级';
+
+  @override
+  String get financeBuyAmountCol => '购买量(iBit)';
+
+  @override
+  String get financeMyRewardCol => '我的奖励(U)';
+
+  @override
+  String get financeNodeLevelNormal => '无节点';
+
+  @override
+  String get financeNodeLevelCommunity => '社区节点';
+
+  @override
+  String get financeNodeLevelCity => '城市节点';
+
+  @override
+  String get financeNodeLevelApac => '亚太节点';
+
+  @override
+  String get financeNodeLevelGlobal => '全球节点';
+
+  @override
+  String get financeTeamLoadMore => '加载更多';
+
   @override
   String get financeJoinPresale => '参与预售';
 
@@ -3674,6 +3725,57 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
     return '會員購買一定量平台自己的主鏈幣,自動進入鎖倉帳戶。鎖倉$lock個月,按$release個月釋放。';
   }
 
+  @override
+  String get financeMyTeam => '我的團隊';
+
+  @override
+  String get financeCurrentNodeLevel => '當前節點等級';
+
+  @override
+  String get financeDirectNumLabel => '直推人數';
+
+  @override
+  String get financeTeamNumLabel => '團隊人數';
+
+  @override
+  String get financeTeamPerformanceLabel => '團隊業績(iBit)';
+
+  @override
+  String get financeTotalRevenueLabel => '總收益(USDT)';
+
+  @override
+  String get financeDirectListTitle => '直推列表';
+
+  @override
+  String get financeDirectAddress => '直推地址';
+
+  @override
+  String get financeLevelCol => '等級';
+
+  @override
+  String get financeBuyAmountCol => '購買量(iBit)';
+
+  @override
+  String get financeMyRewardCol => '我的獎勵(U)';
+
+  @override
+  String get financeNodeLevelNormal => '無節點';
+
+  @override
+  String get financeNodeLevelCommunity => '社區節點';
+
+  @override
+  String get financeNodeLevelCity => '城市節點';
+
+  @override
+  String get financeNodeLevelApac => '亞太節點';
+
+  @override
+  String get financeNodeLevelGlobal => '全球節點';
+
+  @override
+  String get financeTeamLoadMore => '加載更多';
+
   @override
   String get financeJoinPresale => '參與預售';
 

+ 17 - 0
lib/core/l10n/app_zh.arb

@@ -85,6 +85,23 @@
       "release": {}
     }
   },
+  "financeMyTeam": "我的团队",
+  "financeCurrentNodeLevel": "当前节点等级",
+  "financeDirectNumLabel": "直推人数",
+  "financeTeamNumLabel": "团队人数",
+  "financeTeamPerformanceLabel": "团队业绩(iBit)",
+  "financeTotalRevenueLabel": "总收益(USDT)",
+  "financeDirectListTitle": "直推列表",
+  "financeDirectAddress": "直推地址",
+  "financeLevelCol": "等级",
+  "financeBuyAmountCol": "购买量(iBit)",
+  "financeMyRewardCol": "我的奖励(U)",
+  "financeNodeLevelNormal": "无节点",
+  "financeNodeLevelCommunity": "社区节点",
+  "financeNodeLevelCity": "城市节点",
+  "financeNodeLevelApac": "亚太节点",
+  "financeNodeLevelGlobal": "全球节点",
+  "financeTeamLoadMore": "加载更多",
   "financeJoinPresale": "参与预售",
   "financeSubscribeQty": "认购数量",
   "financeAvailableIbit": "可提现可用 iBit",

+ 17 - 0
lib/core/l10n/app_zh_TW.arb

@@ -85,6 +85,23 @@
       "release": {}
     }
   },
+  "financeMyTeam": "我的團隊",
+  "financeCurrentNodeLevel": "當前節點等級",
+  "financeDirectNumLabel": "直推人數",
+  "financeTeamNumLabel": "團隊人數",
+  "financeTeamPerformanceLabel": "團隊業績(iBit)",
+  "financeTotalRevenueLabel": "總收益(USDT)",
+  "financeDirectListTitle": "直推列表",
+  "financeDirectAddress": "直推地址",
+  "financeLevelCol": "等級",
+  "financeBuyAmountCol": "購買量(iBit)",
+  "financeMyRewardCol": "我的獎勵(U)",
+  "financeNodeLevelNormal": "無節點",
+  "financeNodeLevelCommunity": "社區節點",
+  "financeNodeLevelCity": "城市節點",
+  "financeNodeLevelApac": "亞太節點",
+  "financeNodeLevelGlobal": "全球節點",
+  "financeTeamLoadMore": "加載更多",
   "financeJoinPresale": "參與預售",
   "financeSubscribeQty": "認購數量",
   "financeAvailableIbit": "可提现可用 iBit",

+ 13 - 1
lib/core/router/app_router.dart

@@ -60,6 +60,7 @@ import '../../presentation/screens/user/language_screen.dart';
 import '../../presentation/screens/user/protocol_screen.dart';
 import '../../presentation/screens/finance/finance_hub_screen.dart';
 import '../../presentation/screens/finance/staking_screen.dart';
+import '../../presentation/screens/finance/staking_team_screen.dart';
 import '../../presentation/screens/common/qr_scanner_screen.dart';
 import '../../presentation/widgets/common/bottom_nav_shell.dart';
 import '../../providers/auth_provider.dart';
@@ -267,7 +268,14 @@ final appRouterProvider = Provider<GoRouter>((ref) {
             routes: [
               GoRoute(
                 path: '/asset',
-                builder: (context, state) => const AssetScreen(),
+                builder: (context, state) {
+                  final tab = state.uri.queryParameters['tab'];
+                  int? initialSubTab;
+                  if (tab == 'staking' || tab == 'locked') {
+                    initialSubTab = 5;
+                  }
+                  return AssetScreen(initialSubTab: initialSubTab);
+                },
               ),
             ],
           ),
@@ -557,6 +565,10 @@ final appRouterProvider = Provider<GoRouter>((ref) {
         path: '/finance/airdrop',
         builder: (context, state) => const FinanceHubScreen(initialTab: 1),
       ),
+      GoRoute(
+        path: '/finance/team',
+        builder: (context, state) => const StakingTeamScreen(),
+      ),
       GoRoute(
         path: '/user/about',
         builder: (context, state) =>

+ 19 - 0
lib/core/utils/staking_node_level.dart

@@ -0,0 +1,19 @@
+import '../l10n/app_localizations.dart';
+
+/// 质押节点等级文案(对齐 Web `stakingNodeLevel.ts`)
+String stakingNodeLevelLabel(AppLocalizations l10n, String? level) {
+  final normalized = (level ?? 'NORMAL').trim().toUpperCase();
+  switch (normalized) {
+    case 'COMMUNITY':
+      return l10n.financeNodeLevelCommunity;
+    case 'CITY':
+      return l10n.financeNodeLevelCity;
+    case 'APAC':
+      return l10n.financeNodeLevelApac;
+    case 'GLOBAL':
+      return l10n.financeNodeLevelGlobal;
+    case 'NORMAL':
+    default:
+      return l10n.financeNodeLevelNormal;
+  }
+}

+ 77 - 0
lib/data/models/finance/staking_team_overview.dart

@@ -0,0 +1,77 @@
+/// IDO 团队总览(对齐 Web `StakingTeamOverview`)
+class StakingTeamOverview {
+  final int directNum;
+  final int teamNum;
+  final num teamPerformance;
+  final num totalRevenue;
+  final num lockTotal;
+  final String nodeLevel;
+
+  const StakingTeamOverview({
+    this.directNum = 0,
+    this.teamNum = 0,
+    this.teamPerformance = 0,
+    this.totalRevenue = 0,
+    this.lockTotal = 0,
+    this.nodeLevel = 'NORMAL',
+  });
+
+  factory StakingTeamOverview.fromJson(Map<String, dynamic> json) {
+    num parseNum(dynamic v) {
+      final n = num.tryParse('$v');
+      return n ?? 0;
+    }
+
+    return StakingTeamOverview(
+      directNum: int.tryParse('${json['directNum'] ?? 0}') ?? 0,
+      teamNum: int.tryParse('${json['teamNum'] ?? 0}') ?? 0,
+      teamPerformance: parseNum(json['teamPerformance']),
+      totalRevenue: parseNum(json['totalRevenue']),
+      lockTotal: parseNum(json['lockTotal']),
+      nodeLevel: '${json['nodeLevel'] ?? 'NORMAL'}',
+    );
+  }
+}
+
+/// 直推成员(对齐 Web `StakingDirectMember`)
+class StakingDirectMember {
+  final String directAddress;
+  final String level;
+  final num buyAmount;
+  final num reward;
+
+  const StakingDirectMember({
+    this.directAddress = '',
+    this.level = '',
+    this.buyAmount = 0,
+    this.reward = 0,
+  });
+
+  factory StakingDirectMember.fromJson(Map<String, dynamic> json) {
+    num parseNum(dynamic v) {
+      final n = num.tryParse('$v');
+      return n ?? 0;
+    }
+
+    return StakingDirectMember(
+      directAddress: '${json['directAddress'] ?? ''}',
+      level: '${json['level'] ?? ''}',
+      buyAmount: parseNum(json['buyAmount']),
+      reward: parseNum(json['reward']),
+    );
+  }
+}
+
+class StakingDirectListPage {
+  final List<StakingDirectMember> content;
+  final int totalPages;
+  final int pageNo;
+  final bool hasMore;
+
+  const StakingDirectListPage({
+    required this.content,
+    required this.totalPages,
+    required this.pageNo,
+    required this.hasMore,
+  });
+}

+ 55 - 0
lib/data/services/staking_service.dart

@@ -6,6 +6,7 @@ import '../models/finance/airdrop_eligibility.dart';
 import '../models/finance/airdrop_record_item.dart';
 import '../models/finance/staking_config.dart';
 import '../models/finance/staking_order_item.dart';
+import '../models/finance/staking_team_overview.dart';
 import '../models/finance/staking_wallet_balance.dart';
 
 class StakingService {
@@ -96,6 +97,60 @@ class StakingService {
     );
   }
 
+  /// 查询质押团队总览(对齐 Web `fetchStakingTeamOverview`)
+  Future<StakingTeamOverview> getTeamOverview() async {
+    final resp = await _dio.post<Map<String, dynamic>>(
+      'uc/staking/team',
+      data: <String, dynamic>{},
+      options: Options(contentType: Headers.formUrlEncodedContentType),
+    );
+    final raw = resp.data?['data'];
+    if (raw is Map<String, dynamic>) {
+      return StakingTeamOverview.fromJson(raw);
+    }
+    return const StakingTeamOverview();
+  }
+
+  /// 分页查询直推列表(对齐 Web `fetchStakingDirectList`)
+  Future<StakingDirectListPage> getDirectList({
+    int pageNo = 1,
+    int pageSize = 20,
+  }) async {
+    final resp = await _dio.post<Map<String, dynamic>>(
+      'uc/staking/direct/list',
+      data: {'pageNo': pageNo, 'pageSize': pageSize},
+      options: Options(contentType: Headers.formUrlEncodedContentType),
+    );
+    final raw = resp.data?['data'];
+    if (raw is! Map<String, dynamic>) {
+      return const StakingDirectListPage(
+        content: [],
+        totalPages: 0,
+        pageNo: 1,
+        hasMore: false,
+      );
+    }
+    final contentRaw = raw['content'];
+    final content = contentRaw is List
+        ? contentRaw
+            .whereType<Map<String, dynamic>>()
+            .map(StakingDirectMember.fromJson)
+            .toList()
+        : <StakingDirectMember>[];
+    final totalPages = int.tryParse('${raw['totalPages'] ?? 0}') ?? 0;
+    final rawPageNo = int.tryParse('${raw['pageNo'] ?? ''}');
+    final rawNumber = int.tryParse('${raw['number'] ?? ''}');
+    final parsedPageNo = rawPageNo ?? ((rawNumber ?? (pageNo - 1)) + 1);
+    final last = raw['last'];
+    final hasMore = last is bool ? !last : parsedPageNo < totalPages;
+    return StakingDirectListPage(
+      content: content,
+      totalPages: totalPages,
+      pageNo: parsedPageNo,
+      hasMore: hasMore,
+    );
+  }
+
   Future<StakingWalletBalance> getStakingWalletBalance(String coinUnit) async {
     final resp = await _dio.post<Map<String, dynamic>>(
       'uc/staking/wallet',

+ 59 - 3
lib/presentation/screens/asset/asset_screen.dart

@@ -17,7 +17,10 @@ import 'asset_spot_trading_tab.dart';
 import 'asset_staking_tab.dart';
 
 class AssetScreen extends ConsumerStatefulWidget {
-  const AssetScreen({super.key});
+  const AssetScreen({super.key, this.initialSubTab});
+
+  /// 资产页子 Tab 初始索引(0=总览 … 5=锁仓账户),对齐 Web `?tab=staking`
+  final int? initialSubTab;
 
   @override
   ConsumerState<AssetScreen> createState() => _AssetScreenState();
@@ -40,11 +43,25 @@ class _AssetScreenState extends ConsumerState<AssetScreen>
     ];
   }
 
+  int get _initialTabIndex {
+    final subTab = widget.initialSubTab;
+    if (subTab != null && subTab >= 0 && subTab < 6) {
+      return subTab;
+    }
+    return 0;
+  }
+
   @override
   void initState() {
     super.initState();
-    _tabController = TabController(length: 6, vsync: this);
-    _pageController = PageController();
+    final initialIndex = _initialTabIndex;
+    _tabController = TabController(
+      length: 6,
+      vsync: this,
+      initialIndex: initialIndex,
+    );
+    _pageController = PageController(initialPage: initialIndex);
+    ref.read(currentAssetSubTabProvider.notifier).state = initialIndex;
     // 拖动 PageView → 实时更新指示器 offset(平滑插值)
     _pageController.addListener(() {
       if (!_pageController.hasClients) return;
@@ -55,6 +72,45 @@ class _AssetScreenState extends ConsumerState<AssetScreen>
       }
     });
     _tabController.addListener(_onTabChanged);
+    if (initialIndex > 0) {
+      WidgetsBinding.instance.addPostFrameCallback((_) {
+        if (!mounted) {
+          return;
+        }
+        _refreshForTab(initialIndex);
+      });
+    }
+  }
+
+  @override
+  void didUpdateWidget(AssetScreen oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    final subTab = widget.initialSubTab;
+    if (subTab == null ||
+        subTab < 0 ||
+        subTab >= _tabController.length ||
+        subTab == oldWidget.initialSubTab) {
+      return;
+    }
+    WidgetsBinding.instance.addPostFrameCallback((_) {
+      if (!mounted) {
+        return;
+      }
+      _jumpToSubTab(subTab);
+    });
+  }
+
+  /// 无动画切到指定子 Tab(外部深链进入锁仓账户等)
+  void _jumpToSubTab(int index) {
+    if (_tabController.index != index) {
+      _tabController.index = index;
+    }
+    if (_pageController.hasClients) {
+      _pageController.jumpToPage(index);
+    }
+    ref.read(currentAssetSubTabProvider.notifier).state = index;
+    _refreshForTab(index);
+    setState(() {});
   }
 
   void _onTabChanged() {

+ 6 - 0
lib/presentation/screens/finance/staking_screen.dart

@@ -11,6 +11,7 @@ import '../../../data/models/finance/staking_config.dart';
 import '../../../providers/auth_provider.dart';
 import '../../../providers/market_provider.dart';
 import '../../../providers/staking_provider.dart';
+import '../../widgets/finance/ido_overview_cards.dart';
 
 bool _financeIsDark(BuildContext context) =>
     Theme.of(context).brightness == Brightness.dark;
@@ -117,6 +118,11 @@ class _StakingScreenState extends ConsumerState<StakingScreen> {
             rulesText: rulesText,
             loadingRules: state.isLoadingConfig,
           ),
+          IdoOverviewCards(
+            teamNum: state.teamNum,
+            lockedTotal: state.lockedTotal,
+            loading: state.isLoadingOverview,
+          ),
           Padding(
             padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
             child: Column(

+ 379 - 0
lib/presentation/screens/finance/staking_team_screen.dart

@@ -0,0 +1,379 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+import 'package:intl/intl.dart';
+
+import '../../../core/l10n/app_localizations.dart';
+import '../../../core/theme/app_colors.dart';
+import '../../../core/utils/staking_node_level.dart';
+import '../../../data/models/finance/staking_team_overview.dart';
+import '../../../providers/auth_provider.dart';
+import '../../../providers/staking_team_provider.dart';
+
+bool _isDark(BuildContext context) =>
+    Theme.of(context).brightness == Brightness.dark;
+
+Color _pageBg(BuildContext context) =>
+    _isDark(context) ? AppColors.darkBg : Theme.of(context).colorScheme.surface;
+
+Color _primaryText(BuildContext context) =>
+    _isDark(context) ? Colors.white : Theme.of(context).colorScheme.onSurface;
+
+Color _secondaryText(BuildContext context) => _primaryText(context).withAlpha(160);
+
+String _fmtNum(num value, {int maxFrac = 2}) {
+  if (!value.isFinite) {
+    return '0';
+  }
+  return NumberFormat('#,##0.${'#' * maxFrac}', 'en_US').format(value);
+}
+
+/// 我的团队页(对齐 Web `StakingTeamView.vue`)
+class StakingTeamScreen extends ConsumerStatefulWidget {
+  const StakingTeamScreen({super.key});
+
+  @override
+  ConsumerState<StakingTeamScreen> createState() => _StakingTeamScreenState();
+}
+
+class _StakingTeamScreenState extends ConsumerState<StakingTeamScreen> {
+  @override
+  void initState() {
+    super.initState();
+    Future.microtask(() {
+      if (!ref.read(isLoggedInProvider)) {
+        return;
+      }
+      ref.read(stakingTeamProvider.notifier).init();
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final l10n = AppLocalizations.of(context)!;
+    final isLoggedIn = ref.watch(isLoggedInProvider);
+    final state = ref.watch(stakingTeamProvider);
+
+    if (!isLoggedIn) {
+      return Scaffold(
+        backgroundColor: _pageBg(context),
+        appBar: _buildAppBar(context, l10n),
+        body: Center(
+          child: TextButton(
+            onPressed: () => context.push(
+              '/login?redirect=${Uri.encodeComponent('/finance/team')}',
+            ),
+            child: Text(l10n.login),
+          ),
+        ),
+      );
+    }
+
+    return Scaffold(
+      backgroundColor: _pageBg(context),
+      appBar: _buildAppBar(context, l10n),
+      body: state.isLoading
+          ? const Center(child: CircularProgressIndicator())
+          : state.errorMessage != null && state.overview == null
+              ? _ErrorBody(
+                  message: state.errorMessage!,
+                  onRetry: () =>
+                      ref.read(stakingTeamProvider.notifier).init(),
+                )
+              : _TeamBody(state: state),
+    );
+  }
+
+  PreferredSizeWidget _buildAppBar(
+    BuildContext context,
+    AppLocalizations l10n,
+  ) {
+    return AppBar(
+      backgroundColor: Colors.transparent,
+      foregroundColor: _primaryText(context),
+      elevation: 0,
+      centerTitle: true,
+      leading: IconButton(
+        icon: const Icon(Icons.arrow_back_ios_new, size: 20),
+        onPressed: () {
+          if (context.canPop()) {
+            context.pop();
+          } else {
+            context.go('/finance/ido');
+          }
+        },
+      ),
+      title: Text(
+        l10n.financeMyTeam,
+        style: TextStyle(
+          color: _primaryText(context),
+          fontSize: 18,
+          fontWeight: FontWeight.w600,
+        ),
+      ),
+    );
+  }
+}
+
+class _ErrorBody extends StatelessWidget {
+  const _ErrorBody({required this.message, required this.onRetry});
+
+  final String message;
+  final VoidCallback onRetry;
+
+  @override
+  Widget build(BuildContext context) {
+    final l10n = AppLocalizations.of(context)!;
+    return Center(
+      child: Padding(
+        padding: const EdgeInsets.all(24),
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            Text(
+              message,
+              textAlign: TextAlign.center,
+              style: TextStyle(color: _secondaryText(context)),
+            ),
+            const SizedBox(height: 16),
+            OutlinedButton(
+              onPressed: onRetry,
+              style: OutlinedButton.styleFrom(
+                foregroundColor: AppColors.brand,
+                side: const BorderSide(color: AppColors.brand),
+              ),
+              child: Text(l10n.retry),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+class _TeamBody extends ConsumerWidget {
+  const _TeamBody({required this.state});
+
+  final StakingTeamState state;
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final l10n = AppLocalizations.of(context)!;
+    final overview = state.overview ?? const StakingTeamOverview();
+    final statCards = [
+      (l10n.financeDirectNumLabel, _fmtNum(overview.directNum, maxFrac: 0)),
+      (l10n.financeTeamNumLabel, _fmtNum(overview.teamNum, maxFrac: 0)),
+      (l10n.financeTeamPerformanceLabel, _fmtNum(overview.teamPerformance)),
+      (l10n.financeTotalRevenueLabel, _fmtNum(overview.totalRevenue)),
+    ];
+
+    return ListView(
+      padding: const EdgeInsets.fromLTRB(16, 8, 16, 32),
+      children: [
+        _NodeBanner(
+          label: l10n.financeCurrentNodeLevel,
+          value: stakingNodeLevelLabel(l10n, overview.nodeLevel),
+        ),
+        const SizedBox(height: 12),
+        GridView.count(
+          crossAxisCount: 2,
+          shrinkWrap: true,
+          physics: const NeverScrollableScrollPhysics(),
+          mainAxisSpacing: 12,
+          crossAxisSpacing: 12,
+          childAspectRatio: 1.35,
+          children: [
+            for (final card in statCards)
+              _StatCard(label: card.$1, value: card.$2),
+          ],
+        ),
+        const SizedBox(height: 24),
+        Text(
+          l10n.financeDirectListTitle,
+          style: TextStyle(
+            color: _primaryText(context),
+            fontSize: 18,
+            fontWeight: FontWeight.w600,
+          ),
+        ),
+        const SizedBox(height: 12),
+        if (state.directList.isEmpty && !state.isLoadingMore)
+          Padding(
+            padding: const EdgeInsets.symmetric(vertical: 32),
+            child: Center(
+              child: Text(
+                l10n.noData,
+                style: TextStyle(
+                  color: _secondaryText(context),
+                  fontSize: 14,
+                ),
+              ),
+            ),
+          )
+        else
+          _DirectTable(members: state.directList),
+        if (state.hasMore) ...[
+          const SizedBox(height: 16),
+          Center(
+            child: OutlinedButton(
+              onPressed: state.isLoadingMore
+                  ? null
+                  : () => ref.read(stakingTeamProvider.notifier).loadMore(),
+              style: OutlinedButton.styleFrom(
+                foregroundColor: _secondaryText(context),
+                side: BorderSide(color: _primaryText(context).withAlpha(40)),
+                shape: const StadiumBorder(),
+              ),
+              child: state.isLoadingMore
+                  ? const SizedBox(
+                      width: 18,
+                      height: 18,
+                      child: CircularProgressIndicator(strokeWidth: 2),
+                    )
+                  : Text(l10n.financeTeamLoadMore),
+            ),
+          ),
+        ],
+      ],
+    );
+  }
+}
+
+class _NodeBanner extends StatelessWidget {
+  const _NodeBanner({required this.label, required this.value});
+
+  final String label;
+  final String value;
+
+  @override
+  Widget build(BuildContext context) {
+    return Container(
+      width: double.infinity,
+      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 18),
+      decoration: BoxDecoration(
+        borderRadius: BorderRadius.circular(12),
+        gradient: const LinearGradient(
+          colors: [Color(0xFFC9A227), Color(0xFFE8C547), Color(0xFFC9A227)],
+        ),
+      ),
+      child: Row(
+        children: [
+          Expanded(
+            child: Text(
+              label,
+              style: const TextStyle(
+                color: Color(0xFF1A1408),
+                fontSize: 15,
+                fontWeight: FontWeight.w500,
+              ),
+            ),
+          ),
+          Text(
+            value,
+            style: const TextStyle(
+              color: Color(0xFF1A1408),
+              fontSize: 16,
+              fontWeight: FontWeight.w700,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class _StatCard extends StatelessWidget {
+  const _StatCard({required this.label, required this.value});
+
+  final String label;
+  final String value;
+
+  @override
+  Widget build(BuildContext context) {
+    final bg = _isDark(context)
+        ? AppColors.darkBgSecondary
+        : Theme.of(context).colorScheme.surfaceContainerHighest;
+    return Container(
+      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
+      decoration: BoxDecoration(
+        color: bg,
+        borderRadius: BorderRadius.circular(12),
+      ),
+      child: Column(
+        mainAxisAlignment: MainAxisAlignment.center,
+        children: [
+          Text(
+            value,
+            style: const TextStyle(
+              color: AppColors.brand,
+              fontSize: 22,
+              fontWeight: FontWeight.w700,
+              fontFeatures: [FontFeature.tabularFigures()],
+            ),
+          ),
+          const SizedBox(height: 8),
+          Text(
+            label,
+            textAlign: TextAlign.center,
+            style: TextStyle(
+              color: _secondaryText(context),
+              fontSize: 13,
+              height: 1.35,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class _DirectTable extends StatelessWidget {
+  const _DirectTable({required this.members});
+
+  final List<StakingDirectMember> members;
+
+  @override
+  Widget build(BuildContext context) {
+    final l10n = AppLocalizations.of(context)!;
+    return SingleChildScrollView(
+      scrollDirection: Axis.horizontal,
+      child: DataTable(
+        headingTextStyle: TextStyle(
+          color: _secondaryText(context),
+          fontSize: 12,
+          fontWeight: FontWeight.w400,
+        ),
+        dataTextStyle: TextStyle(
+          color: _primaryText(context),
+          fontSize: 13,
+        ),
+        columns: [
+          DataColumn(label: Text(l10n.financeDirectAddress)),
+          DataColumn(label: Text(l10n.financeLevelCol)),
+          DataColumn(label: Text(l10n.financeBuyAmountCol)),
+          DataColumn(label: Text(l10n.financeMyRewardCol)),
+        ],
+        rows: [
+          for (final row in members)
+            DataRow(
+              cells: [
+                DataCell(
+                  SizedBox(
+                    width: 120,
+                    child: Text(
+                      row.directAddress.isEmpty ? '—' : row.directAddress,
+                      maxLines: 1,
+                      overflow: TextOverflow.ellipsis,
+                    ),
+                  ),
+                ),
+                DataCell(Text(stakingNodeLevelLabel(l10n, row.level))),
+                DataCell(Text(_fmtNum(row.buyAmount))),
+                DataCell(Text(_fmtNum(row.reward))),
+              ],
+            ),
+        ],
+      ),
+    );
+  }
+}

+ 175 - 0
lib/presentation/widgets/finance/ido_overview_cards.dart

@@ -0,0 +1,175 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:go_router/go_router.dart';
+import 'package:intl/intl.dart';
+
+import '../../../core/l10n/app_localizations.dart';
+import '../../../core/theme/app_colors.dart';
+import '../../../providers/asset_provider.dart';
+import '../../../providers/futures_provider.dart' show activeBottomTabProvider;
+
+/// 资产页锁仓账户子 Tab 索引
+const int kAssetStakingSubTabIndex = 5;
+
+/// IDO 预售页「我的团队 / 锁仓总量」入口卡片(对齐 Web ido-overview)
+class IdoOverviewCards extends ConsumerWidget {
+  const IdoOverviewCards({
+    super.key,
+    required this.teamNum,
+    required this.lockedTotal,
+    this.loading = false,
+  });
+
+  final int teamNum;
+  final String lockedTotal;
+  final bool loading;
+
+  static String _fmtAmount(String value, {int maxFrac = 8}) {
+    final parsed = double.tryParse(value);
+    if (parsed == null || !parsed.isFinite) {
+      return '0';
+    }
+    if (parsed == 0) {
+      return '0';
+    }
+    return NumberFormat('#,##0.${'#' * maxFrac}', 'en_US').format(parsed);
+  }
+
+  void _goToStakingAccount(BuildContext context, WidgetRef ref) {
+    ref.read(currentAssetSubTabProvider.notifier).state =
+        kAssetStakingSubTabIndex;
+    ref.read(activeBottomTabProvider.notifier).state = kAssetStakingSubTabIndex;
+    context.go('/asset?tab=staking');
+  }
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final l10n = AppLocalizations.of(context)!;
+    final isDark = Theme.of(context).brightness == Brightness.dark;
+    final primary = isDark ? Colors.white : Theme.of(context).colorScheme.onSurface;
+    final labelColor = primary.withAlpha(140);
+    final cardBg = primary.withAlpha(10);
+    final cardBorder = primary.withAlpha(26);
+
+    return Padding(
+      padding: const EdgeInsets.fromLTRB(16, 0, 16, 24),
+      child: IntrinsicHeight(
+        child: Row(
+          crossAxisAlignment: CrossAxisAlignment.stretch,
+          children: [
+            Expanded(
+              child: _OverviewCard(
+                label: l10n.financeMyTeam,
+                value: loading ? '—' : _fmtAmount('$teamNum', maxFrac: 0),
+                labelColor: labelColor,
+                valueColor: primary,
+                backgroundColor: cardBg,
+                borderColor: cardBorder,
+                onTap: () => context.push('/finance/team'),
+              ),
+            ),
+            const SizedBox(width: 12),
+            Expanded(
+              child: _OverviewCard(
+                label: l10n.assetsStakingTotal,
+                value: loading ? '—' : _fmtAmount(lockedTotal, maxFrac: 2),
+                labelColor: labelColor,
+                valueColor: primary,
+                backgroundColor: cardBg,
+                borderColor: cardBorder,
+                onTap: () => _goToStakingAccount(context, ref),
+              ),
+            ),
+          ],
+        ),
+      ),
+    );
+  }
+}
+
+class _OverviewCard extends StatelessWidget {
+  const _OverviewCard({
+    required this.label,
+    required this.value,
+    required this.labelColor,
+    required this.valueColor,
+    required this.backgroundColor,
+    required this.borderColor,
+    required this.onTap,
+  });
+
+  final String label;
+  final String value;
+  final Color labelColor;
+  final Color valueColor;
+  final Color backgroundColor;
+  final Color borderColor;
+  final VoidCallback onTap;
+
+  @override
+  Widget build(BuildContext context) {
+    return Material(
+      color: backgroundColor,
+      shape: RoundedRectangleBorder(
+        borderRadius: BorderRadius.circular(16),
+        side: BorderSide(color: borderColor),
+      ),
+      child: InkWell(
+        onTap: onTap,
+        borderRadius: BorderRadius.circular(16),
+        splashColor: AppColors.brand.withAlpha(40),
+        highlightColor: AppColors.brand.withAlpha(20),
+        child: Padding(
+          padding: const EdgeInsets.fromLTRB(20, 20, 20, 22),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              Row(
+                children: [
+                  Flexible(
+                    child: Text(
+                      label,
+                      style: TextStyle(color: labelColor, fontSize: 14),
+                      maxLines: 1,
+                      overflow: TextOverflow.ellipsis,
+                    ),
+                  ),
+                  Text(
+                    ' >',
+                    style: TextStyle(
+                      color: labelColor.withAlpha(180),
+                      fontSize: 13,
+                    ),
+                  ),
+                ],
+              ),
+              const SizedBox(height: 12),
+              SizedBox(
+                height: 26,
+                width: double.infinity,
+                child: Align(
+                  alignment: Alignment.centerLeft,
+                  child: FittedBox(
+                    fit: BoxFit.scaleDown,
+                    alignment: Alignment.centerLeft,
+                    child: Text(
+                      value,
+                      maxLines: 1,
+                      style: TextStyle(
+                        color: valueColor,
+                        fontSize: 22,
+                        fontWeight: FontWeight.w600,
+                        height: 1.1,
+                        fontFeatures: const [FontFeature.tabularFigures()],
+                      ),
+                    ),
+                  ),
+                ),
+              ),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+}

+ 67 - 2
lib/providers/staking_provider.dart

@@ -25,6 +25,9 @@ class StakingState {
   final StakingConfig? selectedConfig;
   final String fundingAvailable;
   final StakingWalletBalance stakingWallet;
+  final int teamNum;
+  final String lockedTotal;
+  final bool isLoadingOverview;
 
   const StakingState({
     this.isLoadingConfig = false,
@@ -40,6 +43,9 @@ class StakingState {
       availableBalance: '0',
       lockedBalance: '0',
     ),
+    this.teamNum = 0,
+    this.lockedTotal = '0',
+    this.isLoadingOverview = false,
   });
 
   StakingState copyWith({
@@ -53,6 +59,9 @@ class StakingState {
     bool clearSelectedConfig = false,
     String? fundingAvailable,
     StakingWalletBalance? stakingWallet,
+    int? teamNum,
+    String? lockedTotal,
+    bool? isLoadingOverview,
   }) {
     return StakingState(
       isLoadingConfig: isLoadingConfig ?? this.isLoadingConfig,
@@ -65,6 +74,9 @@ class StakingState {
           clearSelectedConfig ? null : (selectedConfig ?? this.selectedConfig),
       fundingAvailable: fundingAvailable ?? this.fundingAvailable,
       stakingWallet: stakingWallet ?? this.stakingWallet,
+      teamNum: teamNum ?? this.teamNum,
+      lockedTotal: lockedTotal ?? this.lockedTotal,
+      isLoadingOverview: isLoadingOverview ?? this.isLoadingOverview,
     );
   }
 }
@@ -123,12 +135,62 @@ class StakingNotifier extends AutoDisposeNotifier<StakingState> {
         isLoadingConfig: false,
       );
       unawaited(_refreshWithdrawableFund());
+      unawaited(refreshOverviewStats());
     } catch (e) {
       state = state.copyWith(isLoadingConfig: false, errorMessage: '$e');
     }
   }
 
-  Future<void> refreshBalances() => _refreshWithdrawableFund();
+  Future<void> refreshBalances() async {
+    await Future.wait([
+      _refreshWithdrawableFund(),
+      refreshOverviewStats(),
+    ]);
+  }
+
+  /// 对齐 Web `refreshOverviewStats`:团队人数 + 锁仓总量
+  Future<void> refreshOverviewStats() async {
+    final config = state.selectedConfig;
+    final rawUnit = config?.coinUnit.trim() ?? '';
+    final coinUnit =
+        rawUnit.isEmpty ? StakingService.stakingCoinUnit : rawUnit;
+    state = state.copyWith(isLoadingOverview: true);
+    try {
+      final team = await _stakingService.getTeamOverview();
+      var locked = team.lockTotal;
+      if (locked <= 0) {
+        try {
+          final wallet = await _stakingService.getStakingWalletBalance(coinUnit);
+          final available = double.tryParse(wallet.availableBalance) ?? 0;
+          final lockedBal = double.tryParse(wallet.lockedBalance) ?? 0;
+          locked = available + lockedBal;
+        } catch (_) {
+          locked = 0;
+        }
+      }
+      state = state.copyWith(
+        teamNum: team.teamNum,
+        lockedTotal: '$locked',
+        isLoadingOverview: false,
+      );
+    } catch (_) {
+      try {
+        final wallet = await _stakingService.getStakingWalletBalance(coinUnit);
+        final available = double.tryParse(wallet.availableBalance) ?? 0;
+        final lockedBal = double.tryParse(wallet.lockedBalance) ?? 0;
+        state = state.copyWith(
+          lockedTotal: '${available + lockedBal}',
+          isLoadingOverview: false,
+        );
+      } catch (__) {
+        state = state.copyWith(
+          teamNum: 0,
+          lockedTotal: '0',
+          isLoadingOverview: false,
+        );
+      }
+    }
+  }
 
   Future<void> selectConfig(StakingConfig config) async {
     state = state.copyWith(selectedConfig: config, errorMessage: null);
@@ -181,7 +243,10 @@ class StakingNotifier extends AutoDisposeNotifier<StakingState> {
     state = state.copyWith(isSubmitting: true, errorMessage: null);
     try {
       await _stakingService.submitStake(configId: config.id, amount: amount);
-      await _refreshWithdrawableFund();
+      await Future.wait([
+        _refreshWithdrawableFund(),
+        refreshOverviewStats(),
+      ]);
       state = state.copyWith(isSubmitting: false);
       return null;
     } catch (e) {

+ 107 - 0
lib/providers/staking_team_provider.dart

@@ -0,0 +1,107 @@
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../core/network/dio_client.dart';
+import '../data/models/finance/staking_team_overview.dart';
+import '../data/services/staking_service.dart';
+
+class StakingTeamState {
+  final bool isLoading;
+  final bool isLoadingMore;
+  final String? errorMessage;
+  final StakingTeamOverview? overview;
+  final List<StakingDirectMember> directList;
+  final int pageNo;
+  final bool hasMore;
+
+  const StakingTeamState({
+    this.isLoading = false,
+    this.isLoadingMore = false,
+    this.errorMessage,
+    this.overview,
+    this.directList = const [],
+    this.pageNo = 1,
+    this.hasMore = false,
+  });
+
+  StakingTeamState copyWith({
+    bool? isLoading,
+    bool? isLoadingMore,
+    String? errorMessage,
+    StakingTeamOverview? overview,
+    List<StakingDirectMember>? directList,
+    int? pageNo,
+    bool? hasMore,
+  }) {
+    return StakingTeamState(
+      isLoading: isLoading ?? this.isLoading,
+      isLoadingMore: isLoadingMore ?? this.isLoadingMore,
+      errorMessage: errorMessage,
+      overview: overview ?? this.overview,
+      directList: directList ?? this.directList,
+      pageNo: pageNo ?? this.pageNo,
+      hasMore: hasMore ?? this.hasMore,
+    );
+  }
+}
+
+class StakingTeamNotifier extends AutoDisposeNotifier<StakingTeamState> {
+  static const _pageSize = 20;
+
+  StakingService get _service => StakingService(ref.read(dioClientProvider));
+
+  @override
+  StakingTeamState build() => const StakingTeamState();
+
+  Future<void> init() async {
+    state = state.copyWith(isLoading: true, errorMessage: null);
+    try {
+      final results = await Future.wait([
+        _service.getTeamOverview(),
+        _service.getDirectList(pageNo: 1, pageSize: _pageSize),
+      ]);
+      final overview = results[0] as StakingTeamOverview;
+      final page = results[1] as StakingDirectListPage;
+      state = state.copyWith(
+        isLoading: false,
+        overview: overview,
+        directList: page.content,
+        pageNo: page.pageNo,
+        hasMore: page.hasMore,
+      );
+    } catch (e) {
+      state = state.copyWith(
+        isLoading: false,
+        errorMessage: '$e',
+        overview: null,
+        directList: const [],
+      );
+    }
+  }
+
+  Future<void> loadMore() async {
+    if (state.isLoadingMore || !state.hasMore) {
+      return;
+    }
+    state = state.copyWith(isLoadingMore: true);
+    try {
+      final nextPage = state.pageNo + 1;
+      final page = await _service.getDirectList(
+        pageNo: nextPage,
+        pageSize: _pageSize,
+      );
+      state = state.copyWith(
+        isLoadingMore: false,
+        directList: [...state.directList, ...page.content],
+        pageNo: page.pageNo,
+        hasMore: page.hasMore,
+      );
+    } catch (e) {
+      state = state.copyWith(isLoadingMore: false, errorMessage: '$e');
+    }
+  }
+}
+
+final stakingTeamProvider =
+    AutoDisposeNotifierProvider<StakingTeamNotifier, StakingTeamState>(
+  StakingTeamNotifier.new,
+);