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/constants/app_colors.dart'; import '../../../core/router/routes.dart'; import '../../../core/utils/format_utils.dart'; import '../../../data/models/trade_order_model.dart'; import '../../../domain/entities/price_info.dart'; import '../../../domain/entities/market_overview.dart'; import '../../../domain/entities/trade_order.dart'; import '../../../domain/entities/kline.dart'; import '../../providers/user_providers.dart'; import '../../providers/trading_providers.dart'; import '../../providers/asset_providers.dart'; import '../../widgets/shimmer_loading.dart'; import '../../widgets/kline_chart/kline_chart_widget.dart'; class TradingPage extends ConsumerStatefulWidget { const TradingPage({super.key}); @override ConsumerState createState() => _TradingPageState(); } class _TradingPageState extends ConsumerState { // 设计色彩 static const Color _orange = Color(0xFFFF6B00); static const Color _green = Color(0xFF10B981); static const Color _red = Color(0xFFEF4444); static const Color _grayText = Color(0xFF6B7280); static const Color _darkText = Color(0xFF1F2937); static const Color _bgGray = Color(0xFFF3F4F6); static const Color _lightGray = Color(0xFFF9FAFB); static const Color _borderGray = Color(0xFFE5E7EB); // 状态 int _selectedTab = 1; // 0: 买入, 1: 卖出 int _selectedTimeRange = 4; // 时间周期选择,默认1时 final _quantityController = TextEditingController(); final _priceController = TextEditingController(); bool _isFullScreen = false; // K线图全屏状态 final List _timeRanges = ['1分', '5分', '15分', '30分', '1时', '4时', '日']; @override void dispose() { _quantityController.dispose(); _priceController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final priceAsync = ref.watch(currentPriceProvider); final marketAsync = ref.watch(marketOverviewProvider); final ordersAsync = ref.watch(ordersProvider); final klinesAsync = ref.watch(klinesProvider); final user = ref.watch(userNotifierProvider); final accountSequence = user.accountSequence ?? ''; // 全屏K线图模式 if (_isFullScreen) { return KlineChartWidget( klines: klinesAsync.valueOrNull ?? [], currentPrice: priceAsync.valueOrNull?.price ?? '0', isFullScreen: true, onFullScreenToggle: () => setState(() => _isFullScreen = false), timeRanges: _timeRanges, selectedTimeIndex: _selectedTimeRange, onTimeRangeChanged: (index) { setState(() => _selectedTimeRange = index); ref.read(selectedKlinePeriodProvider.notifier).state = _timeRanges[index]; }, ); } return Scaffold( backgroundColor: const Color(0xFFF5F5F5), body: SafeArea( bottom: false, child: RefreshIndicator( onRefresh: () async { ref.invalidate(currentPriceProvider); ref.invalidate(marketOverviewProvider); ref.invalidate(ordersProvider); ref.invalidate(klinesProvider); }, child: Column( children: [ _buildAppBar(), Expanded( child: SingleChildScrollView( child: Column( children: [ _buildPriceCard(priceAsync), _buildChartSection(priceAsync, klinesAsync), _buildMarketDataCard(marketAsync), _buildTradingPanel(priceAsync), _buildMyOrdersCard(ordersAsync), const SizedBox(height: 24), ], ), ), ), ], ), ), ), ); } Widget _buildAppBar() { return Container( color: _bgGray.withValues(alpha: 0.9), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12), child: const Center( child: Text( '积分股交易', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Color(0xFF111827), ), ), ), ); } Widget _buildPriceCard(AsyncValue priceAsync) { final isLoading = priceAsync.isLoading; final priceInfo = priceAsync.valueOrNull; final hasError = priceAsync.hasError; if (hasError && priceInfo == null) { return _buildErrorCard('价格加载失败'); } final price = priceInfo?.price ?? '0'; return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '当前积分股价格', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _grayText, ), ), const SizedBox(height: 8), Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ AmountText( amount: priceInfo != null ? formatPrice(price) : null, isLoading: isLoading, prefix: '\u00A5 ', style: const TextStyle( fontSize: 30, fontWeight: FontWeight.bold, color: _orange, letterSpacing: -0.75, ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: _green.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(16), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.trending_up, size: 16, color: _green), DataText( data: isLoading ? null : '+0.00%', isLoading: isLoading, placeholder: '+--.--%', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _green, ), ), ], ), ), ], ), ], ), ); } Widget _buildChartSection(AsyncValue priceAsync, AsyncValue> klinesAsync) { final priceInfo = priceAsync.valueOrNull; final currentPrice = priceInfo?.price ?? '0.000000'; final klines = klinesAsync.valueOrNull ?? []; return Container( margin: const EdgeInsets.symmetric(horizontal: 16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: klinesAsync.isLoading ? const SizedBox( height: 280, child: Center(child: CircularProgressIndicator(strokeWidth: 2)), ) : KlineChartWidget( klines: klines, currentPrice: currentPrice, isFullScreen: false, onFullScreenToggle: () => setState(() => _isFullScreen = true), timeRanges: _timeRanges, selectedTimeIndex: _selectedTimeRange, onTimeRangeChanged: (index) { setState(() => _selectedTimeRange = index); ref.read(selectedKlinePeriodProvider.notifier).state = _timeRanges[index]; }, ), ); } Widget _buildMarketDataCard(AsyncValue marketAsync) { final isLoading = marketAsync.isLoading; final market = marketAsync.valueOrNull; final hasError = marketAsync.hasError; if (hasError && market == null) { return _buildErrorCard('市场数据加载失败'); } return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( width: 4, height: 16, decoration: BoxDecoration( color: _orange, borderRadius: BorderRadius.circular(2), ), ), const SizedBox(width: 8), const Text( '市场数据', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _darkText, ), ), ], ), const SizedBox(height: 24), Row( children: [ _buildMarketDataItem( '总积分股', market != null ? formatCompact(market.totalShares) : null, _orange, isLoading, ), Container(width: 1, height: 24, color: _bgGray), const SizedBox(width: 16), _buildMarketDataItem( '流通池', market != null ? formatCompact(market.circulationPool) : null, _orange, isLoading, ), ], ), const SizedBox(height: 24), Container(height: 1, color: _bgGray), const SizedBox(height: 24), Row( children: [ _buildMarketDataItem( '积分值池', market != null ? formatCompact(market.greenPoints) : null, _orange, isLoading, ), Container(width: 1, height: 24, color: _bgGray), const SizedBox(width: 16), _buildMarketDataItem( '黑洞销毁量', market != null ? formatCompact(market.blackHoleAmount) : null, _red, isLoading, ), ], ), ], ), ); } Widget _buildMarketDataItem(String label, String? value, Color valueColor, bool isLoading) { return Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(label, style: const TextStyle(fontSize: 12, color: _grayText)), const SizedBox(height: 4), DataText( data: value, isLoading: isLoading, placeholder: '--,---,---', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: valueColor, ), ), ], ), ); } Widget _buildTradingPanel(AsyncValue priceAsync) { final priceInfo = priceAsync.valueOrNull; final currentPrice = priceInfo?.price ?? '0'; // 获取用户资产信息 final user = ref.watch(userNotifierProvider); final accountSequence = user.accountSequence ?? ''; final assetAsync = ref.watch(accountAssetProvider(accountSequence)); final asset = assetAsync.valueOrNull; // 获取买入功能开关状态 final buyEnabledAsync = ref.watch(buyEnabledProvider); final buyEnabled = buyEnabledAsync.valueOrNull ?? false; // 挖矿账户积分股(可划转卖出) final miningShareBalance = asset?.miningShareBalance ?? '0'; // 交易账户积分股(可直接卖出) final tradingShareBalance = asset?.tradingShareBalance ?? '0'; // 可用积分股(总计:挖矿 + 交易) final availableShares = asset?.availableShares ?? '0'; // 可用积分值(现金) final availableCash = asset?.availableCash ?? '0'; // 设置默认价格 if (_priceController.text.isEmpty && priceInfo != null) { _priceController.text = currentPrice; } // 如果选中买入但买入功能未开启,强制切换到卖出 if (_selectedTab == 0 && !buyEnabled) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted && _selectedTab == 0) { setState(() => _selectedTab = 1); } }); } return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( children: [ Container( decoration: const BoxDecoration( border: Border(bottom: BorderSide(color: _bgGray)), ), child: Row( children: [ Expanded( child: GestureDetector( onTap: buyEnabled ? () => setState(() => _selectedTab = 0) : null, child: Container( padding: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: _selectedTab == 0 && buyEnabled ? _orange : Colors.transparent, width: 2, ), ), ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( '买入', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: buyEnabled ? (_selectedTab == 0 ? _orange : _grayText) : _grayText.withValues(alpha: 0.5), ), ), if (!buyEnabled) ...[ const SizedBox(width: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1), decoration: BoxDecoration( color: _grayText.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: Text( '待开启', style: TextStyle( fontSize: 10, color: _grayText.withValues(alpha: 0.7), ), ), ), ], ], ), ), ), ), Expanded( child: GestureDetector( onTap: () => setState(() => _selectedTab = 1), child: Container( padding: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: _selectedTab == 1 ? _orange : Colors.transparent, width: 2, ), ), ), child: Text( '卖出', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _selectedTab == 1 ? _orange : _grayText, ), ), ), ), ), ], ), ), const SizedBox(height: 24), // 买入功能未开启时显示提示 if (_selectedTab == 0 && !buyEnabled) ...[ Container( padding: const EdgeInsets.all(24), child: Column( children: [ Icon( Icons.lock_outline, size: 48, color: _grayText.withValues(alpha: 0.5), ), const SizedBox(height: 16), const Text( '买入功能待开启', style: TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: _grayText, ), ), const SizedBox(height: 8), Text( '买入功能暂未开放,请耐心等待', style: TextStyle( fontSize: 14, color: _grayText.withValues(alpha: 0.7), ), ), ], ), ), ] else ...[ // 可用余额提示 if (_selectedTab == 0) ...[ // 买入时显示可用积分值 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: _orange.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '可用积分值', style: TextStyle(fontSize: 12, color: _grayText), ), Text( formatAmount(availableCash), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _orange, ), ), ], ), ), ] else ...[ // 卖出时显示可用积分股 Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), decoration: BoxDecoration( color: _orange.withValues(alpha: 0.05), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '可用积分股', style: TextStyle(fontSize: 12, color: _grayText), ), Text( formatAmount(tradingShareBalance), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _orange, ), ), ], ), ), const SizedBox(height: 4), // 划转入口 Align( alignment: Alignment.centerRight, child: GestureDetector( onTap: () => _showTransferDialog(miningShareBalance, tradingShareBalance), child: const Text( '划转', style: TextStyle( fontSize: 12, color: _orange, fontWeight: FontWeight.w500, ), ), ), ), ], const SizedBox(height: 16), // 价格输入 _buildInputField('价格', _priceController, '请输入价格', '积分值'), const SizedBox(height: 16), // 数量输入 - 带"全部"按钮 _buildQuantityInputField( '数量', _quantityController, '请输入数量', '积分股', _selectedTab == 1 ? availableShares : null, _selectedTab == 0 ? availableCash : null, currentPrice, ), const SizedBox(height: 16), // 预计获得/支出 Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( _selectedTab == 0 ? '预计支出' : '预计获得', style: const TextStyle(fontSize: 12, color: _grayText), ), Text( _calculateEstimate(), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _orange, ), ), ], ), ), const SizedBox(height: 16), // 交易手续费说明 (卖出时显示) if (_selectedTab == 1) Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: const [ Text( '交易手续费', style: TextStyle(fontSize: 12, color: _grayText), ), Text( '10% 进入积分股池', style: TextStyle( fontSize: 12, color: _green, fontFamily: 'monospace', ), ), ], ), ), const SizedBox(height: 24), // 提交按钮 SizedBox( width: double.infinity, height: 48, child: ElevatedButton( onPressed: _handleTrade, style: ElevatedButton.styleFrom( backgroundColor: _orange, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: Text( _selectedTab == 0 ? '买入积分股' : '卖出积分股', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ], ], ), ); } Widget _buildQuantityInputField( String label, TextEditingController controller, String hint, String suffix, String? availableSharesForSell, String? availableCashForBuy, String currentPrice, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _grayText, ), ), const SizedBox(height: 8), Container( height: 44, decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Expanded( child: TextField( controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: InputDecoration( hintText: hint, hintStyle: const TextStyle( fontSize: 14, color: Color(0xFF9CA3AF), ), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 16), ), onChanged: (_) => setState(() {}), ), ), // 全部按钮 GestureDetector( onTap: () { if (availableSharesForSell != null) { // 卖出时填入全部可用积分股 controller.text = availableSharesForSell; } else if (availableCashForBuy != null) { // 买入时根据可用积分值计算可买数量 final price = double.tryParse(currentPrice) ?? 0; final cash = double.tryParse(availableCashForBuy) ?? 0; if (price > 0) { final maxQuantity = cash / price; controller.text = maxQuantity.toStringAsFixed(4); } } setState(() {}); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), margin: const EdgeInsets.only(right: 4), decoration: BoxDecoration( color: _orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), ), child: const Text( '全部', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _orange, ), ), ), ), Text(suffix, style: const TextStyle(fontSize: 12, color: _grayText)), const SizedBox(width: 12), ], ), ), ], ); } Widget _buildInputField( String label, TextEditingController controller, String hint, String suffix, ) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _grayText, ), ), const SizedBox(height: 8), Container( height: 44, decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Expanded( child: TextField( controller: controller, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: InputDecoration( hintText: hint, hintStyle: const TextStyle( fontSize: 14, color: Color(0xFF9CA3AF), ), border: InputBorder.none, contentPadding: const EdgeInsets.symmetric(horizontal: 16), ), ), ), Text(suffix, style: const TextStyle(fontSize: 12, color: _grayText)), const SizedBox(width: 12), ], ), ), ], ); } String _calculateEstimate() { final price = double.tryParse(_priceController.text) ?? 0; final quantity = double.tryParse(_quantityController.text) ?? 0; final total = price * quantity; if (total == 0) { return '0.00 积分值'; } if (_selectedTab == 1) { // 卖出时扣除10%销毁 final afterBurn = total * 0.9; return '${formatAmount(afterBurn.toString())} 积分值'; } return '${formatAmount(total.toString())} 积分值'; } Widget _buildMyOrdersCard(AsyncValue ordersAsync) { final isLoading = ordersAsync.isLoading; final ordersPage = ordersAsync.valueOrNull; final orders = ordersPage?.data ?? []; return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(20), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '我的挂单', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _darkText, ), ), GestureDetector( onTap: () { // TODO: 查看全部挂单 }, child: const Text( '全部 >', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _orange, ), ), ), ], ), const SizedBox(height: 16), if (isLoading) const Center( child: Padding( padding: EdgeInsets.all(20), child: CircularProgressIndicator(color: _orange), ), ) else if (orders.isEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 20), child: Text( '暂无挂单', style: TextStyle(fontSize: 14, color: _grayText), ), ) else Column( children: orders.take(3).map((order) => Padding( padding: const EdgeInsets.only(bottom: 8), child: _buildOrderItemFromEntity(order), )).toList(), ), ], ), ); } Widget _buildOrderItemFromEntity(TradeOrder order) { final isSell = order.isSell; final dateFormat = DateFormat('MM/dd HH:mm'); final formattedDate = dateFormat.format(order.createdAt); String statusText; switch (order.status) { case OrderStatus.pending: statusText = '待成交'; break; case OrderStatus.partial: statusText = '部分成交'; break; case OrderStatus.filled: statusText = '已成交'; break; case OrderStatus.cancelled: statusText = '已取消'; break; } return Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _lightGray, borderRadius: BorderRadius.circular(8), border: Border.all(color: _bgGray), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Container( padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), decoration: BoxDecoration( color: (isSell ? _red : _green).withValues(alpha: 0.1), borderRadius: BorderRadius.circular(6), ), child: Text( isSell ? '卖出' : '买入', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: isSell ? _red : _green, ), ), ), const SizedBox(width: 8), Text( formatPrice(order.price), style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _darkText, ), ), ], ), const SizedBox(height: 4), Text( formattedDate, style: const TextStyle(fontSize: 12, color: _grayText), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( '${formatCompact(order.quantity)} 股', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.w500, color: _darkText, ), ), const SizedBox(height: 4), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: _orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(9999), ), child: Text( statusText, style: const TextStyle(fontSize: 12, color: _orange), ), ), ], ), ], ), ); } Widget _buildErrorCard(String message) { return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(32), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Center( child: Column( children: [ const Icon(Icons.error_outline, size: 48, color: AppColors.error), const SizedBox(height: 8), Text(message), ], ), ), ); } void _handleTrade() async { if (_priceController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请输入价格')), ); return; } if (_quantityController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请输入数量')), ); return; } final isBuy = _selectedTab == 0; final price = double.tryParse(_priceController.text) ?? 0; final quantity = double.tryParse(_quantityController.text) ?? 0; // 卖出时显示确认弹窗 if (!isBuy) { final total = price * quantity; final burned = total * 0.1; final received = total * 0.9; final confirmed = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text('确认卖出'), content: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('卖出数量: ${formatAmount(quantity.toString())} 积分股'), const SizedBox(height: 8), Text('卖出价格: ${formatPrice(price.toString())} 积分值'), const SizedBox(height: 8), Text('交易总额: ${formatAmount(total.toString())} 积分值'), const SizedBox(height: 8), Text( '进入积分股池: ${formatAmount(burned.toString())} 积分值 (10%)', style: const TextStyle(color: _green), ), const SizedBox(height: 8), Text( '实际获得: ${formatAmount(received.toString())} 积分值', style: const TextStyle( fontWeight: FontWeight.bold, color: _green, ), ), const SizedBox(height: 16), const Text( '注意: 卖出积分股将扣除10%进入积分股池,此操作不可撤销。', style: TextStyle( fontSize: 12, color: _grayText, ), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('取消'), ), TextButton( onPressed: () => Navigator.pop(context, true), child: const Text( '确认卖出', style: TextStyle(color: _orange), ), ), ], ), ); if (confirmed != true) return; } bool success; if (isBuy) { success = await ref .read(tradingNotifierProvider.notifier) .buyShares(_priceController.text, _quantityController.text); } else { success = await ref .read(tradingNotifierProvider.notifier) .sellShares(_priceController.text, _quantityController.text); } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(success ? (isBuy ? '买入订单已提交' : '卖出订单已提交') : (isBuy ? '买入失败' : '卖出失败')), backgroundColor: success ? _green : AppColors.error, ), ); if (success) { _quantityController.clear(); // 交易成功后立即刷新 _refreshAfterTrade(); } } } /// 交易成功后刷新数据 /// 立即刷新一次,然后在2秒和5秒后再各刷新一次 /// 确保能看到做市商处理后的最新状态 void _refreshAfterTrade() { final user = ref.read(userNotifierProvider); final accountSeq = user.accountSequence ?? ''; // 立即刷新 _doRefresh(accountSeq); // 2秒后再刷新(做市商可能在1-4秒内吃单) Future.delayed(const Duration(seconds: 2), () { if (mounted) _doRefresh(accountSeq); }); // 5秒后最终刷新(确保看到最终状态) Future.delayed(const Duration(seconds: 5), () { if (mounted) _doRefresh(accountSeq); }); } void _doRefresh(String accountSeq) { ref.invalidate(ordersProvider); ref.invalidate(currentPriceProvider); ref.invalidate(marketOverviewProvider); ref.invalidate(accountAssetProvider(accountSeq)); } /// 显示划转弹窗 void _showTransferDialog(String miningBalance, String tradingBalance) { showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: Colors.transparent, builder: (context) => _TransferBottomSheet( miningBalance: miningBalance, tradingBalance: tradingBalance, onTransferComplete: () { final user = ref.read(userNotifierProvider); final accountSeq = user.accountSequence ?? ''; _doRefresh(accountSeq); }, ), ); } } /// 划转底部弹窗 class _TransferBottomSheet extends ConsumerStatefulWidget { final String miningBalance; final String tradingBalance; final VoidCallback onTransferComplete; const _TransferBottomSheet({ required this.miningBalance, required this.tradingBalance, required this.onTransferComplete, }); @override ConsumerState<_TransferBottomSheet> createState() => _TransferBottomSheetState(); } class _TransferBottomSheetState extends ConsumerState<_TransferBottomSheet> { static const Color _orange = Color(0xFFFF6B00); static const Color _grayText = Color(0xFF6B7280); static const Color _darkText = Color(0xFF1F2937); static const Color _bgGray = Color(0xFFF3F4F6); static const Color _green = Color(0xFF10B981); // 0: 从挖矿划入交易, 1: 从交易划出到挖矿 int _direction = 0; final _amountController = TextEditingController(); bool _isLoading = false; @override void dispose() { _amountController.dispose(); super.dispose(); } String get _availableBalance { return _direction == 0 ? widget.miningBalance : widget.tradingBalance; } @override Widget build(BuildContext context) { return Container( padding: EdgeInsets.only( bottom: MediaQuery.of(context).viewInsets.bottom, ), decoration: const BoxDecoration( color: Colors.white, borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), child: SafeArea( child: Padding( padding: const EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ // 标题栏 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '积分股划转', style: TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: _darkText, ), ), GestureDetector( onTap: () => Navigator.pop(context), child: const Icon(Icons.close, color: _grayText), ), ], ), const SizedBox(height: 20), // 划转方向切换 Container( padding: const EdgeInsets.all(4), decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded( child: GestureDetector( onTap: () => setState(() { _direction = 0; _amountController.clear(); }), child: Container( padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( color: _direction == 0 ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(6), boxShadow: _direction == 0 ? [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 4)] : null, ), child: Text( '划入交易账户', textAlign: TextAlign.center, style: TextStyle( fontSize: 13, fontWeight: _direction == 0 ? FontWeight.bold : FontWeight.normal, color: _direction == 0 ? _orange : _grayText, ), ), ), ), ), Expanded( child: GestureDetector( onTap: () => setState(() { _direction = 1; _amountController.clear(); }), child: Container( padding: const EdgeInsets.symmetric(vertical: 10), decoration: BoxDecoration( color: _direction == 1 ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(6), boxShadow: _direction == 1 ? [BoxShadow(color: Colors.black.withValues(alpha: 0.05), blurRadius: 4)] : null, ), child: Text( '划出到挖矿账户', textAlign: TextAlign.center, style: TextStyle( fontSize: 13, fontWeight: _direction == 1 ? FontWeight.bold : FontWeight.normal, color: _direction == 1 ? _orange : _grayText, ), ), ), ), ), ], ), ), const SizedBox(height: 20), // 划转说明 Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( _direction == 0 ? '挖矿账户' : '交易账户', style: const TextStyle(fontSize: 12, color: _grayText), ), const SizedBox(height: 4), Text( formatAmount(_direction == 0 ? widget.miningBalance : widget.tradingBalance), style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: _darkText, ), ), ], ), ), const Icon(Icons.arrow_forward, color: _orange), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( _direction == 0 ? '交易账户' : '挖矿账户', style: const TextStyle(fontSize: 12, color: _grayText), ), const SizedBox(height: 4), Text( formatAmount(_direction == 0 ? widget.tradingBalance : widget.miningBalance), style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: _darkText, ), ), ], ), ), ], ), ), const SizedBox(height: 20), // 数量输入 const Text( '划转数量', style: TextStyle(fontSize: 14, color: _darkText), ), const SizedBox(height: 8), Container( decoration: BoxDecoration( border: Border.all(color: _bgGray), borderRadius: BorderRadius.circular(8), ), child: Row( children: [ Expanded( child: TextField( controller: _amountController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: '请输入划转数量', hintStyle: TextStyle(color: _grayText, fontSize: 14), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 12, vertical: 14), ), ), ), GestureDetector( onTap: () { _amountController.text = _availableBalance; }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), margin: const EdgeInsets.only(right: 8), decoration: BoxDecoration( color: _orange.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(4), ), child: const Text( '全部', style: TextStyle( fontSize: 12, color: _orange, fontWeight: FontWeight.w500, ), ), ), ), ], ), ), const SizedBox(height: 8), Text( '可用: ${formatAmount(_availableBalance)} 积分股', style: const TextStyle(fontSize: 12, color: _grayText), ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '提示: 最低划转数量为 5 积分股', style: TextStyle(fontSize: 12, color: _grayText), ), GestureDetector( onTap: () { Navigator.pop(context); context.push(Routes.transferRecords); }, child: const Text( '划转记录', style: TextStyle( fontSize: 12, color: _orange, fontWeight: FontWeight.w500, ), ), ), ], ), const SizedBox(height: 24), // 提交按钮 SizedBox( width: double.infinity, height: 48, child: ElevatedButton( onPressed: _isLoading ? null : _handleTransfer, style: ElevatedButton.styleFrom( backgroundColor: _orange, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12), ), ), child: _isLoading ? const SizedBox( width: 20, height: 20, child: CircularProgressIndicator( strokeWidth: 2, valueColor: AlwaysStoppedAnimation(Colors.white), ), ) : Text( _direction == 0 ? '划入交易账户' : '划出到挖矿账户', style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), ), ], ), ), ), ); } Future _handleTransfer() async { final amount = _amountController.text.trim(); if (amount.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请输入划转数量')), ); return; } final amountValue = double.tryParse(amount) ?? 0; if (amountValue < 5) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('最低划转数量为 5 积分股')), ); return; } final available = double.tryParse(_availableBalance) ?? 0; if (amountValue > available) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('划转数量超过可用余额')), ); return; } setState(() => _isLoading = true); try { bool success; if (_direction == 0) { // 从挖矿划入交易 success = await ref.read(tradingNotifierProvider.notifier).transferIn(amount); } else { // 从交易划出到挖矿 success = await ref.read(tradingNotifierProvider.notifier).transferOut(amount); } if (mounted) { if (success) { Navigator.pop(context); widget.onTransferComplete(); ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(_direction == 0 ? '划入成功' : '划出成功'), backgroundColor: _green, ), ); } else { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( content: Text('划转失败,请稍后重试'), backgroundColor: Colors.red, ), ); } } } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text('划转失败: $e'), backgroundColor: Colors.red, ), ); } } finally { if (mounted) { setState(() => _isLoading = false); } } } }