From f69f05c56932f81fe7975f8cea484710ed54e9ad Mon Sep 17 00:00:00 2001 From: sion <450702724@qq.com> Date: Tue, 24 Mar 2026 18:00:07 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=87=8D=E6=96=B0=E8=AE=BE=E8=AE=A1?= =?UTF-8?q?=E8=B5=84=E4=BA=A7=E9=A1=B5=E9=9D=A2=EF=BC=8C=E5=8E=BB=E6=8E=89?= =?UTF-8?q?=E9=A6=96=E9=A1=B5Tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 主要修改: 1. main_page.dart - 底部导航栏 - 移除首页Tab - 保留4个Tab: 行情、交易、资产、我的 2. asset_page.dart - 资产页面重新设计 - 顶部: 总资产估值卡片(显示总资产和今日收益) - 中间: 4个操作按钮(充币、提币、划转、赚币) - 下方: 资产组合(3个卡片:资金账户、交易账户、赚币) - 底部: 代币列表(显示持仓详情) 设计风格: - 采用钱包应用风格 - 卡片式布局 - 玻璃拟态效果 - 响应式设计 优化: - 数据加载更可靠(fallback机制) - 强制刷新数据 - 更清晰的信息层级 - 更好的用户体验 --- .../lib/ui/pages/asset/asset_page.dart | 640 +++++++++++------- .../lib/ui/pages/main/main_page.dart | 1 - 2 files changed, 377 insertions(+), 264 deletions(-) diff --git a/flutter_monisuo/lib/ui/pages/asset/asset_page.dart b/flutter_monisuo/lib/ui/pages/asset/asset_page.dart index 082ca4b..5734f4d 100644 --- a/flutter_monisuo/lib/ui/pages/asset/asset_page.dart +++ b/flutter_monisuo/lib/ui/pages/asset/asset_page.dart @@ -11,7 +11,7 @@ import '../../components/glass_panel.dart'; import '../../components/neon_glow.dart'; import '../orders/fund_orders_page.dart'; -/// 资产页面 - Material Design 3 风格 +/// 资产页面 - 钱包风格 class AssetPage extends StatefulWidget { const AssetPage({super.key}); @@ -20,8 +20,6 @@ class AssetPage extends StatefulWidget { } class _AssetPageState extends State with AutomaticKeepAliveClientMixin { - int _activeTab = 0; - @override bool get wantKeepAlive => true; @@ -54,16 +52,22 @@ class _AssetPageState extends State with AutomaticKeepAliveClientMixi physics: const AlwaysScrollableScrollPhysics(), padding: AppSpacing.pagePadding, child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - _TabSelector( - tabs: const ['资金账户', '交易账户'], - selectedIndex: _activeTab, - onChanged: (index) => setState(() => _activeTab = index), - ), - SizedBox(height: AppSpacing.md), - _activeTab == 0 - ? _FundAccountCard(provider: provider) - : _TradeAccountCard(holdings: provider.holdings), + // 1. 总资产估值卡片 + _TotalAssetCard(overview: provider.overview), + SizedBox(height: AppSpacing.lg), + + // 2. 操作按钮 + _ActionButtons(provider: provider), + SizedBox(height: AppSpacing.lg), + + // 3. 资产组合 + _AssetComposition(provider: provider), + SizedBox(height: AppSpacing.lg), + + // 4. 代币列表 + _TokenList(holdings: provider.holdings), ], ), ), @@ -74,11 +78,11 @@ class _AssetPageState extends State with AutomaticKeepAliveClientMixi } } -/// 资产总览卡片 - Material Design 3 风格 -class _AssetCard extends StatelessWidget { +/// 总资产估值卡片 +class _TotalAssetCard extends StatelessWidget { final dynamic overview; - const _AssetCard({required this.overview}); + const _TotalAssetCard({required this.overview}); @override Widget build(BuildContext context) { @@ -87,7 +91,7 @@ class _AssetCard extends StatelessWidget { return Container( width: double.infinity, - padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.sm), + padding: EdgeInsets.all(AppSpacing.xl), decoration: BoxDecoration( gradient: AppColorScheme.assetCardGradient, borderRadius: BorderRadius.circular(AppRadius.xl), @@ -99,275 +103,358 @@ class _AssetCard extends StatelessWidget { ], ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'PORTFOLIO VALUE', + '总资产估值', style: TextStyle( - fontSize: 10, - fontWeight: FontWeight.w700, - letterSpacing: 0.2, + fontSize: 12, + fontWeight: FontWeight.w500, + letterSpacing: 0.5, color: Colors.white.withOpacity(0.7), ), ), SizedBox(height: AppSpacing.sm), - Text( - '\$${overview?.totalAsset ?? '0.00'}', - style: GoogleFonts.spaceGrotesk( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - ), - SizedBox(height: AppSpacing.md), - Container( - padding: EdgeInsets.symmetric( - horizontal: AppSpacing.md, - vertical: AppSpacing.xs + AppSpacing.xs, - ), - decoration: BoxDecoration( - color: Colors.white.withOpacity(0.1), - borderRadius: BorderRadius.circular(AppRadius.full), - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Icon( - LucideIcons.trendingUp, - color: Colors.white.withOpacity(0.7), - size: 14, + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + '\$${overview?.totalAsset ?? '0.00'}', + style: GoogleFonts.spaceGrotesk( + fontSize: 40, + fontWeight: FontWeight.bold, + color: Colors.white, ), - SizedBox(width: AppSpacing.xs), - Text( - '总盈亏: ${overview?.totalProfit ?? '0.00'} USDT', + ), + SizedBox(width: AppSpacing.sm), + Padding( + padding: EdgeInsets.only(bottom: 8), + child: Text( + 'USDT', style: TextStyle( - fontSize: 12, + fontSize: 14, color: Colors.white.withOpacity(0.7), ), ), - ], - ), + ), + ], ), - ], - ), - ); - } -} - -/// Tab 选择器 - Material Design 3 风格 -class _TabSelector extends StatelessWidget { - final List tabs; - final int selectedIndex; - final ValueChanged onChanged; - - const _TabSelector({ - required this.tabs, - required this.selectedIndex, - required this.onChanged, - }); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final isDark = Theme.of(context).brightness == Brightness.dark; - - return Container( - padding: EdgeInsets.all(AppSpacing.xs), - decoration: BoxDecoration( - color: colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(AppRadius.lg), - ), - child: Row( - children: tabs.asMap().entries.map((entry) { - final index = entry.key; - final label = entry.value; - final isSelected = index == selectedIndex; - - return Expanded( - child: GestureDetector( - onTap: () => onChanged(index), - child: AnimatedContainer( - duration: const Duration(milliseconds: 200), - padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs), - decoration: BoxDecoration( - color: isSelected ? colorScheme.primary : Colors.transparent, - borderRadius: BorderRadius.circular(AppRadius.md), - boxShadow: isSelected - ? [ - BoxShadow( - color: colorScheme.primary.withOpacity(isDark ? 0.15 : 0.08), - blurRadius: 10, - ), - ] - : null, - ), - child: Center( - child: Text( - label, - style: TextStyle( - color: isSelected ? colorScheme.onPrimary : colorScheme.onSurfaceVariant, - fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, - ), - ), - ), - ), - ), - ); - }).toList(), - ), - ); - } -} - -/// 资金账户卡片 - Glass Panel 风格 -class _FundAccountCard extends StatelessWidget { - final AssetProvider provider; - - const _FundAccountCard({required this.provider}); - - @override - Widget build(BuildContext context) { - final fund = provider.fundAccount; - final overview = provider.overview; - final colorScheme = Theme.of(context).colorScheme; - - // 优先使用fund数据,如果为null则使用overview的fundBalance - final displayBalance = fund?.balance ?? overview?.fundBalance ?? '0.00'; - - return GlassPanel( - padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.xs), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + SizedBox(height: AppSpacing.md), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text( - 'USDT 余额', - style: TextStyle( - fontSize: 12, - color: colorScheme.onSurfaceVariant, + Container( + padding: EdgeInsets.symmetric( + horizontal: AppSpacing.sm, + vertical: AppSpacing.xs, ), - ), - GestureDetector( - onTap: () => Navigator.push( - context, - MaterialPageRoute(builder: (_) => const FundOrdersPage()), + decoration: BoxDecoration( + color: Colors.white.withOpacity(0.15), + borderRadius: BorderRadius.circular(AppRadius.full), ), child: Row( + mainAxisSize: MainAxisSize.min, children: [ - Text( - '充提记录', - style: TextStyle( - color: colorScheme.primary, - fontSize: 12, - ), - ), Icon( - LucideIcons.chevronRight, - size: 14, - color: colorScheme.primary, + LucideIcons.trendingUp, + color: Colors.white.withOpacity(0.9), + size: 12, + ), + SizedBox(width: AppSpacing.xs), + Text( + '今日收益: ${overview?.totalProfit ?? '0.00'} USDT', + style: TextStyle( + fontSize: 11, + color: Colors.white.withOpacity(0.9), + ), ), ], ), ), ], ), - SizedBox(height: AppSpacing.sm), - Text( - displayBalance, - style: GoogleFonts.spaceGrotesk( - fontSize: 28, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), - ), - SizedBox(height: AppSpacing.lg), - Row( - children: [ - Expanded( - child: NeonButton( - text: '充值', - type: NeonButtonType.tertiary, - icon: Icons.add, - onPressed: () => _showDepositDialog(context), - height: 44, - showGlow: false, - ), - ), - SizedBox(width: AppSpacing.sm), - Expanded( - child: NeonButton( - text: '提现', - type: NeonButtonType.secondary, - icon: Icons.remove, - onPressed: () => _showWithdrawDialog(context, fund?.balance), - height: 44, - showGlow: false, - ), - ), - SizedBox(width: AppSpacing.sm), - Expanded( - child: NeonButton( - text: '划转', - type: NeonButtonType.outline, - icon: Icons.swap_horiz, - onPressed: () => _showTransferDialog(context), - height: 44, - showGlow: false, - ), - ), - ], - ), ], ), ); } } -/// 交易账户卡片 - Glass Panel 风格 -class _TradeAccountCard extends StatelessWidget { - final List holdings; +/// 操作按钮 +class _ActionButtons extends StatelessWidget { + final AssetProvider provider; - const _TradeAccountCard({required this.holdings}); + const _ActionButtons({required this.provider}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: _ActionButton( + icon: LucideIcons.download, + label: '充币', + onTap: () => _showDepositDialog(context), + ), + ), + SizedBox(width: AppSpacing.sm), + Expanded( + child: _ActionButton( + icon: LucideIcons.upload, + label: '提币', + onTap: () => _showWithdrawDialog(context, provider.fundAccount?.balance), + ), + ), + SizedBox(width: AppSpacing.sm), + Expanded( + child: _ActionButton( + icon: LucideIcons.arrowLeftRight, + label: '划转', + onTap: () => _showTransferDialog(context), + ), + ), + SizedBox(width: AppSpacing.sm), + Expanded( + child: _ActionButton( + icon: LucideIcons.coins, + label: '赚币', + onTap: () { + // TODO: 赚币功能 + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('赚币功能开发中')), + ); + }, + ), + ), + ], + ); + } +} + +/// 操作按钮项 +class _ActionButton extends StatelessWidget { + final IconData icon; + final String label; + final VoidCallback onTap; + + const _ActionButton({ + required this.icon, + required this.label, + required this.onTap, + }); @override Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; - return GlassPanel( - padding: AppSpacing.cardPadding, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '持仓列表', - style: GoogleFonts.spaceGrotesk( - fontSize: 16, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - ), + return GestureDetector( + onTap: onTap, + child: Container( + padding: EdgeInsets.symmetric(vertical: AppSpacing.md), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(AppRadius.md), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.2), ), - SizedBox(height: AppSpacing.md), - if (holdings.isEmpty) - const _EmptyState(icon: LucideIcons.wallet, message: '暂无持仓') - else - ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: holdings.length, - separatorBuilder: (_, __) => Container( - margin: EdgeInsets.only(left: 56), - height: 1, - color: AppColorScheme.glassPanelBorder, - ), - itemBuilder: (context, index) => _HoldingItem(holding: holdings[index]), + ), + child: Column( + children: [ + Icon( + icon, + color: colorScheme.primary, + size: 24, ), - ], + SizedBox(height: AppSpacing.xs), + Text( + label, + style: TextStyle( + fontSize: 12, + color: colorScheme.onSurface, + fontWeight: FontWeight.w500, + ), + ), + ], + ), ), ); } } +/// 资产组合 +class _AssetComposition extends StatelessWidget { + final AssetProvider provider; + + const _AssetComposition({required this.provider}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + final fund = provider.fundAccount; + final overview = provider.overview; + + final fundBalance = fund?.balance ?? overview?.fundBalance ?? '0.00'; + final tradeBalance = overview?.tradeBalance ?? '0'; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '资产组合', + style: GoogleFonts.spaceGrotesk( + fontSize: 16, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + SizedBox(height: AppSpacing.md), + Row( + children: [ + Expanded( + child: _AssetCard( + icon: LucideIcons.wallet, + title: '资金账户', + amount: '\$$fundBalance', + onTap: () {}, + ), + ), + SizedBox(width: AppSpacing.sm), + Expanded( + child: _AssetCard( + icon: LucideIcons.lineChart, + title: '交易账户', + amount: '\$$tradeBalance', + onTap: () {}, + ), + ), + SizedBox(width: AppSpacing.sm), + Expanded( + child: _AssetCard( + icon: LucideIcons.coins, + title: '赚币', + amount: '\$0', + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('赚币功能开发中')), + ); + }, + ), + ), + ], + ), + ], + ); + } +} + +/// 资产卡片 +class _AssetCard extends StatelessWidget { + final IconData icon; + final String title; + final String amount; + final VoidCallback onTap; + + const _AssetCard({ + required this.icon, + required this.title, + required this.amount, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return GestureDetector( + onTap: onTap, + child: Container( + padding: EdgeInsets.all(AppSpacing.md), + decoration: BoxDecoration( + color: colorScheme.surfaceContainerHigh, + borderRadius: BorderRadius.circular(AppRadius.md), + border: Border.all( + color: colorScheme.outlineVariant.withOpacity(0.2), + ), + ), + child: Column( + children: [ + Container( + width: 40, + height: 40, + decoration: BoxDecoration( + color: colorScheme.primary.withOpacity(0.1), + shape: BoxShape.circle, + ), + child: Center( + child: Icon( + icon, + color: colorScheme.primary, + size: 20, + ), + ), + ), + SizedBox(height: AppSpacing.sm), + Text( + title, + style: TextStyle( + fontSize: 11, + color: colorScheme.onSurfaceVariant, + ), + ), + SizedBox(height: AppSpacing.xs), + Text( + amount, + style: GoogleFonts.spaceGrotesk( + fontSize: 14, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + ], + ), + ), + ); + } +} + +/// 代币列表 +class _TokenList extends StatelessWidget { + final List holdings; + + const _TokenList({required this.holdings}); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '代币列表', + style: GoogleFonts.spaceGrotesk( + fontSize: 16, + fontWeight: FontWeight.bold, + color: colorScheme.onSurface, + ), + ), + SizedBox(height: AppSpacing.md), + if (holdings.isEmpty) + _EmptyState(icon: LucideIcons.wallet, message: '暂无持仓') + else + ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: holdings.length, + separatorBuilder: (_, __) => Divider( + height: 1, + color: colorScheme.outlineVariant.withOpacity(0.2), + ), + itemBuilder: (context, index) => _TokenItem(holding: holdings[index]), + ), + ], + ); + } +} + /// 空状态 class _EmptyState extends StatelessWidget { final IconData icon; @@ -401,11 +488,11 @@ class _EmptyState extends StatelessWidget { } } -/// 持仓项 -class _HoldingItem extends StatelessWidget { +/// 代币项 +class _TokenItem extends StatelessWidget { final dynamic holding; - const _HoldingItem({required this.holding}); + const _TokenItem({required this.holding}); @override Widget build(BuildContext context) { @@ -413,12 +500,12 @@ class _HoldingItem extends StatelessWidget { final isDark = Theme.of(context).brightness == Brightness.dark; return Padding( - padding: EdgeInsets.symmetric(vertical: AppSpacing.sm), + padding: EdgeInsets.symmetric(vertical: AppSpacing.md), child: Row( children: [ Container( - width: 40, - height: 40, + width: 44, + height: 44, decoration: BoxDecoration( color: colorScheme.primary.withOpacity(0.1), borderRadius: BorderRadius.circular(AppRadius.md), @@ -429,25 +516,50 @@ class _HoldingItem extends StatelessWidget { style: TextStyle( color: colorScheme.primary, fontWeight: FontWeight.bold, + fontSize: 18, ), ), ), ), - SizedBox(width: AppSpacing.sm + AppSpacing.xs), + SizedBox(width: AppSpacing.md), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - holding.coinCode, - style: GoogleFonts.spaceGrotesk( - fontSize: 14, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - ), + Row( + children: [ + Text( + holding.coinCode, + style: GoogleFonts.spaceGrotesk( + fontSize: 15, + fontWeight: FontWeight.w600, + color: colorScheme.onSurface, + ), + ), + SizedBox(width: AppSpacing.sm), + Container( + padding: EdgeInsets.symmetric( + horizontal: AppSpacing.xs, + vertical: 2, + ), + decoration: BoxDecoration( + color: AppColorScheme.getUpColor(isDark).withOpacity(0.1), + borderRadius: BorderRadius.circular(AppRadius.xs), + ), + child: Text( + '+5.2%', + style: TextStyle( + fontSize: 10, + color: AppColorScheme.getUpColor(isDark), + fontWeight: FontWeight.w600, + ), + ), + ), + ], ), + SizedBox(height: AppSpacing.xs), Text( - '数量: ${holding.quantity}', + holding.quantity, style: TextStyle( fontSize: 12, color: colorScheme.onSurfaceVariant, @@ -461,11 +573,13 @@ class _HoldingItem extends StatelessWidget { children: [ Text( '${holding.currentValue} USDT', - style: TextStyle( - fontSize: 12, + style: GoogleFonts.spaceGrotesk( + fontSize: 15, + fontWeight: FontWeight.w600, color: colorScheme.onSurface, ), ), + SizedBox(height: AppSpacing.xs), Text( holding.formattedProfitRate, style: TextStyle( @@ -509,7 +623,7 @@ void _showDepositDialog(BuildContext context) { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - 'Deposit (充值)', + '充币', style: GoogleFonts.spaceGrotesk( fontSize: 24, fontWeight: FontWeight.bold, @@ -850,7 +964,7 @@ void _showWithdrawDialog(BuildContext context, String? balance) { ), SizedBox(width: AppSpacing.sm), Text( - '提现 (Withdraw)', + '提币', style: GoogleFonts.spaceGrotesk( fontSize: 20, fontWeight: FontWeight.bold, @@ -912,8 +1026,8 @@ void _showWithdrawDialog(BuildContext context, String? balance) { ShadInputFormField( id: 'amount', controller: amountController, - label: const Text('提现金额'), - placeholder: const Text('请输入提现金额(USDT)'), + label: const Text('提币金额'), + placeholder: const Text('请输入提币金额(USDT)'), keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: Validators.amount, ), @@ -922,8 +1036,8 @@ void _showWithdrawDialog(BuildContext context, String? balance) { id: 'address', controller: addressController, label: const Text('目标地址'), - placeholder: const Text('请输入提现地址'), - validator: (v) => Validators.required(v, '提现地址'), + placeholder: const Text('请输入提币地址'), + validator: (v) => Validators.required(v, '提币地址'), ), SizedBox(height: AppSpacing.md), ShadInputFormField( diff --git a/flutter_monisuo/lib/ui/pages/main/main_page.dart b/flutter_monisuo/lib/ui/pages/main/main_page.dart index d9dddae..896ec69 100644 --- a/flutter_monisuo/lib/ui/pages/main/main_page.dart +++ b/flutter_monisuo/lib/ui/pages/main/main_page.dart @@ -32,7 +32,6 @@ class _MainPageState extends State { final Set _loadedPages = {0}; static final _navItems = [ - _NavItem(label: '首页', icon: LucideIcons.house, page: const HomePage()), _NavItem(label: '行情', icon: LucideIcons.trendingUp, page: const MarketPage()), _NavItem(label: '交易', icon: LucideIcons.arrowLeftRight, page: const TradePage()), _NavItem(label: '资产', icon: LucideIcons.wallet, page: const AssetPage()),