import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import '../../../core/theme/app_color_scheme.dart'; import '../../../core/theme/app_spacing.dart'; import '../../../providers/asset_provider.dart'; import '../../shared/ui_constants.dart'; import '../orders/fund_orders_page.dart'; /// 资产页面 - 使用 shadcn_ui 现代化设计 class AssetPage extends StatefulWidget { const AssetPage({super.key}); @override State createState() => _AssetPageState(); } class _AssetPageState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; int _activeTab = 0; @override void initState() { super.initState(); WidgetsBinding.instance.addPostFrameCallback((_) => _loadData()); } void _loadData() { context.read().refreshAll(); } @override Widget build(BuildContext context) { super.build(context); final theme = ShadTheme.of(context); return Scaffold( backgroundColor: theme.colorScheme.background, body: Consumer( builder: (context, provider, _) { return RefreshIndicator( onRefresh: () => provider.refreshAll(force: true), color: theme.colorScheme.primary, child: SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), padding: AppSpacing.pagePadding, child: Column( children: [ _AssetCard(overview: provider.overview), SizedBox(height: AppSpacing.md), _TabSelector( tabs: const ['资金账户', '交易账户'], selectedIndex: _activeTab, onChanged: (index) => setState(() => _activeTab = index), ), SizedBox(height: AppSpacing.md), _activeTab == 0 ? _FundAccountCard(provider: provider) : _TradeAccountCard(holdings: provider.holdings), ], ), ), ); }, ), ); } } /// 资产总览卡片 class _AssetCard extends StatelessWidget { final dynamic overview; const _AssetCard({required this.overview}); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return Container( width: double.infinity, padding: EdgeInsets.all(AppSpacing.lg), decoration: BoxDecoration( gradient: AppColorScheme.assetCardGradient, borderRadius: BorderRadius.circular(AppRadius.xl), ), child: Column( children: [ Text( '总资产估值(USDT)', style: theme.textTheme.small.copyWith(color: Colors.white70), ), SizedBox(height: AppSpacing.sm), Text( overview?.totalAsset ?? '0.00', style: theme.textTheme.h1.copyWith( fontWeight: FontWeight.bold, color: Colors.white, ), ), SizedBox(height: AppSpacing.md), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(LucideIcons.trendingUp, color: Colors.white70, size: 16), SizedBox(width: AppSpacing.xs), Text( '总盈亏: ${overview?.totalProfit ?? '0.00'} USDT', style: theme.textTheme.small.copyWith(color: Colors.white70), ), ], ), ], ), ); } } /// Tab 选择器 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 theme = ShadTheme.of(context); return Container( padding: EdgeInsets.all(AppSpacing.xs), decoration: BoxDecoration( color: theme.colorScheme.card, 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: Container( padding: EdgeInsets.symmetric(vertical: AppSpacing.sm + AppSpacing.xs), decoration: BoxDecoration( color: isSelected ? theme.colorScheme.primary : Colors.transparent, borderRadius: BorderRadius.circular(AppRadius.md), ), child: Center( child: Text( label, style: TextStyle( color: isSelected ? Colors.white : theme.colorScheme.mutedForeground, fontWeight: isSelected ? FontWeight.w600 : FontWeight.normal, ), ), ), ), ), ); }).toList(), ), ); } } /// 资金账户卡片 class _FundAccountCard extends StatelessWidget { final AssetProvider provider; const _FundAccountCard({required this.provider}); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); final fund = provider.fundAccount; return ShadCard( padding: EdgeInsets.all(AppSpacing.lg + AppSpacing.xs), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text('USDT余额', style: theme.textTheme.muted), GestureDetector( onTap: () => Navigator.push( context, MaterialPageRoute(builder: (_) => const FundOrdersPage()), ), child: Row( children: [ Text('充提记录', style: TextStyle(color: theme.colorScheme.primary, fontSize: 12)), Icon(LucideIcons.chevronRight, size: 14, color: theme.colorScheme.primary), ], ), ), ], ), SizedBox(height: AppSpacing.sm), Text(fund?.balance ?? '0.00', style: theme.textTheme.h2.copyWith(fontWeight: FontWeight.bold)), SizedBox(height: AppSpacing.lg), Row( children: [ Expanded(child: _ActionButton(label: '充值', icon: LucideIcons.plus, color: AppColorScheme.success, onTap: () => _showDepositDialog(context))), SizedBox(width: AppSpacing.sm + AppSpacing.xs), Expanded(child: _ActionButton(label: '提现', icon: LucideIcons.minus, color: AppColorScheme.warning, onTap: () => _showWithdrawDialog(context, fund?.balance))), SizedBox(width: AppSpacing.sm + AppSpacing.xs), Expanded(child: _ActionButton.outline(label: '划转', icon: LucideIcons.arrowRightLeft, onTap: () => _showTransferDialog(context))), ], ), ], ), ); } } /// 操作按钮 class _ActionButton extends StatelessWidget { final String label; final IconData icon; final Color? color; final bool isOutline; final VoidCallback onTap; const _ActionButton({ required this.label, required this.icon, required this.color, required this.onTap, }) : isOutline = false; const _ActionButton.outline({ required this.label, required this.icon, required this.onTap, }) : color = null, isOutline = true; @override Widget build(BuildContext context) { final child = Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(icon, size: 18, color: isOutline ? null : Colors.white), SizedBox(width: AppSpacing.xs), Text(label), ], ); return isOutline ? ShadButton.outline(onPressed: onTap, child: child) : ShadButton(backgroundColor: color, onPressed: onTap, child: child); } } /// 交易账户卡片 class _TradeAccountCard extends StatelessWidget { final List holdings; const _TradeAccountCard({required this.holdings}); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return ShadCard( padding: AppSpacing.cardPadding, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('持仓列表', style: theme.textTheme.large.copyWith(fontWeight: FontWeight.bold)), 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: (_, __) => Divider(color: theme.colorScheme.border, height: 1), itemBuilder: (context, index) => _HoldingItem(holding: holdings[index]), ), ], ), ); } } /// 空状态 class _EmptyState extends StatelessWidget { final IconData icon; final String message; const _EmptyState({required this.icon, required this.message}); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return Center( child: Padding( padding: EdgeInsets.all(AppSpacing.xl), child: Column( children: [ Icon(icon, size: 48, color: theme.colorScheme.mutedForeground), SizedBox(height: AppSpacing.sm + AppSpacing.xs), Text(message, style: theme.textTheme.muted), ], ), ), ); } } /// 持仓项 class _HoldingItem extends StatelessWidget { final dynamic holding; const _HoldingItem({required this.holding}); @override Widget build(BuildContext context) { final theme = ShadTheme.of(context); return Padding( padding: EdgeInsets.symmetric(vertical: AppSpacing.sm), child: Row( children: [ CircleAvatar( radius: 20, backgroundColor: theme.colorScheme.primary.withValues(alpha: 0.1), child: Text( holding.coinCode.substring(0, 1), style: TextStyle(color: theme.colorScheme.primary, fontWeight: FontWeight.bold), ), ), SizedBox(width: AppSpacing.sm + AppSpacing.xs), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(holding.coinCode, style: theme.textTheme.large.copyWith(fontWeight: FontWeight.w600)), Text('数量: ${holding.quantity}', style: theme.textTheme.muted.copyWith(fontSize: 12)), ], ), ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text('${holding.currentValue} USDT', style: theme.textTheme.small), Text( holding.formattedProfitRate, style: TextStyle( color: holding.isProfit ? AppColorScheme.up : AppColorScheme.down, fontSize: 12, ), ), ], ), ], ), ); } } // Dialogs void _showDepositDialog(BuildContext context) { final amountController = TextEditingController(); final formKey = GlobalKey(); showShadDialog( context: context, builder: (ctx) => ShadDialog( title: const Text('充值'), child: ShadForm( key: formKey, child: ShadInputFormField( id: 'amount', controller: amountController, placeholder: const Text('请输入充值金额(USDT)'), keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: Validators.amount, ), ), actions: [ ShadButton.outline(child: const Text('取消'), onPressed: () => Navigator.of(ctx).pop()), ShadButton( child: const Text('下一步'), onPressed: () async { if (formKey.currentState!.saveAndValidate()) { Navigator.of(ctx).pop(); final response = await context.read().deposit(amount: amountController.text); if (context.mounted) { if (response.success && response.data != null) { _showDepositResultDialog(context, response.data!); } else { _showResultDialog(context, '申请失败', response.message); } } } }, ), ], ), ); } void _showDepositResultDialog(BuildContext context, Map data) { final orderNo = data['orderNo'] as String? ?? ''; final amount = data['amount']?.toString() ?? '0.00'; final walletAddress = data['walletAddress'] as String? ?? ''; final walletNetwork = data['walletNetwork'] as String? ?? 'TRC20'; showShadDialog( context: context, builder: (ctx) => ShadDialog( title: const Text('充值申请成功'), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('订单号: $orderNo', style: const TextStyle(fontSize: 12)), SizedBox(height: AppSpacing.sm), Text('充值金额: $amount USDT', style: const TextStyle(fontWeight: FontWeight.bold)), SizedBox(height: AppSpacing.md), const Text('请向以下地址转账:', style: TextStyle(fontSize: 12)), SizedBox(height: AppSpacing.sm), _WalletAddressCard(address: walletAddress, network: walletNetwork), SizedBox(height: AppSpacing.sm + AppSpacing.xs), const Text('转账完成后请点击"已打款"按钮确认', style: TextStyle(fontSize: 12, color: Colors.orange)), ], ), actions: [ ShadButton.outline(child: const Text('稍后确认'), onPressed: () => Navigator.of(ctx).pop()), ShadButton( child: const Text('已打款'), onPressed: () async { Navigator.of(ctx).pop(); final response = await context.read().confirmPay(orderNo); if (context.mounted) { _showResultDialog( context, response.success ? '确认成功' : '确认失败', response.success ? '请等待管理员审核' : response.message, ); } }, ), ], ), ); } class _WalletAddressCard extends StatelessWidget { final String address; final String network; const _WalletAddressCard({required this.address, required this.network}); @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.all(AppSpacing.sm + AppSpacing.xs), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(AppRadius.md), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Expanded( child: Text(address, style: const TextStyle(fontFamily: 'monospace', fontSize: 12)), ), IconButton( icon: const Icon(LucideIcons.copy, size: 18), onPressed: () { Clipboard.setData(ClipboardData(text: address)); ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('地址已复制到剪贴板')), ); }, tooltip: '复制地址', ), ], ), SizedBox(height: AppSpacing.xs), Text('网络: $network', style: const TextStyle(fontSize: 12, color: Colors.grey)), ], ), ); } } void _showWithdrawDialog(BuildContext context, String? balance) { final amountController = TextEditingController(); final addressController = TextEditingController(); final contactController = TextEditingController(); final formKey = GlobalKey(); showShadDialog( context: context, builder: (ctx) => ShadDialog( title: const Text('提现'), child: SingleChildScrollView( child: ShadForm( key: formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ if (balance != null) Padding( padding: EdgeInsets.only(bottom: AppSpacing.sm + AppSpacing.xs), child: Text('可用余额: $balance USDT', style: const TextStyle(color: Colors.grey)), ), ShadInputFormField( id: 'amount', controller: amountController, placeholder: const Text('请输入提现金额(USDT)'), keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: Validators.amount, ), SizedBox(height: AppSpacing.sm + AppSpacing.xs), ShadInputFormField( id: 'address', controller: addressController, placeholder: const Text('请输入提现地址'), validator: (v) => Validators.required(v, '提现地址'), ), SizedBox(height: AppSpacing.sm + AppSpacing.xs), ShadInputFormField( id: 'contact', controller: contactController, placeholder: const Text('联系方式(可选)'), ), ], ), ), ), actions: [ ShadButton.outline(child: const Text('取消'), onPressed: () => Navigator.of(ctx).pop()), ShadButton( child: const Text('提交'), onPressed: () async { if (formKey.currentState!.saveAndValidate()) { Navigator.of(ctx).pop(); final response = await context.read().withdraw( amount: amountController.text, withdrawAddress: addressController.text, withdrawContact: contactController.text.isNotEmpty ? contactController.text : null, ); if (context.mounted) { _showResultDialog( context, response.success ? '申请成功' : '申请失败', response.success ? '请等待管理员审批' : response.message, ); } } }, ), ], ), ); } void _showTransferDialog(BuildContext context) { final controller = TextEditingController(); final formKey = GlobalKey(); int direction = 1; showShadDialog( context: context, builder: (ctx) => StatefulBuilder( builder: (ctx, setState) => ShadDialog( title: const Text('划转'), child: Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox(height: AppSpacing.sm), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ _DirectionButton( label: '资金→交易', isSelected: direction == 1, onTap: () => setState(() => direction = 1), ), SizedBox(width: AppSpacing.sm), _DirectionButton( label: '交易→资金', isSelected: direction == 2, onTap: () => setState(() => direction = 2), ), ], ), SizedBox(height: AppSpacing.md), ShadForm( key: formKey, child: ShadInputFormField( id: 'amount', controller: controller, placeholder: const Text('请输入划转金额(USDT)'), keyboardType: const TextInputType.numberWithOptions(decimal: true), validator: Validators.amount, ), ), ], ), actions: [ ShadButton.outline(child: const Text('取消'), onPressed: () => Navigator.of(ctx).pop()), ShadButton( child: const Text('确认'), onPressed: () async { if (formKey.currentState!.saveAndValidate()) { Navigator.of(ctx).pop(); final response = await context.read().transfer( direction: direction, amount: controller.text, ); if (context.mounted) { _showResultDialog(context, response.success ? '划转成功' : '划转失败', response.message); } } }, ), ], ), ), ); } class _DirectionButton extends StatelessWidget { final String label; final bool isSelected; final VoidCallback onTap; const _DirectionButton({required this.label, required this.isSelected, required this.onTap}); @override Widget build(BuildContext context) { return ShadButton.outline( size: ShadButtonSize.sm, onPressed: onTap, child: Row( children: [ if (isSelected) const Icon(LucideIcons.check, size: 14) else const SizedBox(width: 14), SizedBox(width: AppSpacing.xs), Text(label), ], ), ); } } void _showResultDialog(BuildContext context, String title, String? message) { showShadDialog( context: context, builder: (ctx) => ShadDialog.alert( title: Text(title), description: message != null ? Text(message) : null, actions: [ ShadButton(child: const Text('确定'), onPressed: () => Navigator.of(ctx).pop()), ], ), ); }