import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../../core/constants/app_colors.dart'; import '../../../core/utils/format_utils.dart'; import '../../providers/mining_providers.dart'; import '../../providers/user_providers.dart'; import '../../providers/trading_providers.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 = 1; // 时间周期选择 final _amountController = TextEditingController(); final List _timeRanges = ['1分', '5分', '15分', '30分', '1时', '4时', '日']; @override void dispose() { _amountController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final globalState = ref.watch(globalStateProvider); final user = ref.watch(userNotifierProvider); final accountSequence = user.accountSequence ?? ''; return Scaffold( backgroundColor: const Color(0xFFF5F5F5), body: SafeArea( child: Column( children: [ // 顶部导航栏 _buildAppBar(), // 可滚动内容 Expanded( child: SingleChildScrollView( child: Column( children: [ // 价格卡片 globalState.when( data: (state) => _buildPriceCard(state), loading: () => _buildLoadingCard(), error: (_, __) => _buildErrorCard('价格加载失败'), ), // K线图占位区域 _buildChartSection(), // 市场数据 globalState.when( data: (state) => _buildMarketDataCard(state), loading: () => _buildLoadingCard(), error: (_, __) => _buildErrorCard('市场数据加载失败'), ), // 买入/卖出交易面板 _buildTradingPanel(accountSequence), // 我的挂单 _buildMyOrdersCard(), const SizedBox(height: 24), ], ), ), ), ], ), ), ); } Widget _buildAppBar() { return Container( color: _bgGray.withOpacity(0.9), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ // 积分股交易按钮 Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8), decoration: BoxDecoration( color: _orange, borderRadius: BorderRadius.circular(9999), ), child: const Text( '积分股交易', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: Colors.white, ), ), ), // 通知图标 Container( width: 40, height: 40, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ), ], ), child: Stack( children: [ const Center( child: Icon(Icons.notifications_outlined, color: _grayText), ), Positioned( right: 10, top: 10, child: Container( width: 8, height: 8, decoration: const BoxDecoration( color: Colors.red, shape: BoxShape.circle, ), ), ), ], ), ), ], ), ); } Widget _buildPriceCard(state) { final isPriceUp = state?.isPriceUp ?? true; final currentPrice = state?.currentPrice ?? '0.000156'; final priceChange = state?.priceChange24h ?? '8.52'; 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( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( '当前积分股价格', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _grayText, ), ), Text( '= 156.00 绿积分', style: TextStyle( fontSize: 12, color: _grayText, ), ), ], ), const SizedBox(height: 8), // 价格和涨跌 Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( '¥ ${formatPrice(currentPrice)}', 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.withOpacity(0.1), borderRadius: BorderRadius.circular(16), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( isPriceUp ? Icons.trending_up : Icons.trending_down, size: 16, color: _green, ), Text( '+$priceChange%', style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _green, ), ), ], ), ), ], ), ], ), ); } Widget _buildChartSection() { return Container( margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: Column( children: [ // K线图占位 Container( height: 200, decoration: BoxDecoration( color: _lightGray, borderRadius: BorderRadius.circular(8), ), child: Stack( children: [ // 模拟K线图 CustomPaint( size: const Size(double.infinity, 200), painter: _CandlestickPainter(), ), // 当前价格标签 Positioned( right: 0, top: 60, child: Container( padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 2), decoration: BoxDecoration( color: _orange, borderRadius: BorderRadius.circular(4), boxShadow: [ BoxShadow( color: Colors.black.withOpacity(0.1), blurRadius: 2, offset: const Offset(0, 1), ), ], ), child: const Text( '0.000156', style: TextStyle( fontSize: 10, color: Colors.white, ), ), ), ), ], ), ), const SizedBox(height: 16), // 时间周期选择 SingleChildScrollView( scrollDirection: Axis.horizontal, child: Row( children: List.generate(_timeRanges.length, (index) { final isSelected = _selectedTimeRange == index; return Padding( padding: const EdgeInsets.only(right: 8), child: GestureDetector( onTap: () => setState(() => _selectedTimeRange = index), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6), decoration: BoxDecoration( color: isSelected ? _orange : Colors.white, borderRadius: BorderRadius.circular(9999), border: isSelected ? null : Border.all(color: _borderGray), ), child: Text( _timeRanges[index], style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: isSelected ? Colors.white : _grayText, ), ), ), ), ); }), ), ), ], ), ); } Widget _buildMarketDataCard(state) { 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('积分股池', '8,888,888,888', _orange), Container(width: 1, height: 24, color: _bgGray), const SizedBox(width: 16), _buildMarketDataItem('流通池', '1,234,567', _orange), ], ), const SizedBox(height: 24), Container(height: 1, color: _bgGray), const SizedBox(height: 24), // 第二行数据 Row( children: [ _buildMarketDataItem('绿积分池', '99,999,999', _orange), Container(width: 1, height: 24, color: _bgGray), const SizedBox(width: 16), _buildMarketDataItem('黑洞销毁量', '50,000,000', _red), ], ), ], ), ); } Widget _buildMarketDataItem(String label, String value, Color valueColor) { return Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( label, style: const TextStyle( fontSize: 12, color: _grayText, ), ), const SizedBox(height: 4), Text( value, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: valueColor, ), ), ], ), ); } Widget _buildTradingPanel(String accountSequence) { 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: () => setState(() => _selectedTab = 0), child: Container( padding: const EdgeInsets.only(bottom: 12), decoration: BoxDecoration( border: Border( bottom: BorderSide( color: _selectedTab == 0 ? _orange : Colors.transparent, width: 2, ), ), ), child: Text( '买入', textAlign: TextAlign.center, style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _selectedTab == 0 ? _orange : _grayText, ), ), ), ), ), 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), // 数量输入 Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text( '数量', style: TextStyle( fontSize: 12, fontWeight: FontWeight.w500, color: _grayText, ), ), const SizedBox(height: 8), Row( children: [ Expanded( child: Container( height: 44, decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Expanded( child: TextField( controller: _amountController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( hintText: '请输入数量', hintStyle: TextStyle( fontSize: 14, color: Color(0xFF9CA3AF), ), border: InputBorder.none, contentPadding: EdgeInsets.symmetric(horizontal: 16), ), ), ), const Icon(Icons.currency_yuan, size: 15, color: _grayText), const SizedBox(width: 12), ], ), ), ), const SizedBox(width: 12), GestureDetector( onTap: () { // TODO: 设置最大数量 }, child: const Text( 'MAX', style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: _orange, ), ), ), ], ), ], ), const SizedBox(height: 16), // 预计获得 Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: _bgGray, borderRadius: BorderRadius.circular(12), ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( '预计获得', style: TextStyle( fontSize: 12, color: _grayText, ), ), const Text( '0.00 绿积分', style: TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _orange, ), ), ], ), ), const SizedBox(height: 16), // 手续费 Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: const [ Text( '手续费 (10%)', style: TextStyle( fontSize: 12, color: _grayText, ), ), Text( '0.00', style: TextStyle( fontSize: 12, color: _grayText, fontFamily: 'monospace', ), ), ], ), ), const SizedBox(height: 24), // 提交按钮 SizedBox( width: double.infinity, height: 48, child: ElevatedButton( onPressed: () => _handleTrade(accountSequence), 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 _buildMyOrdersCard() { 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), // 挂单列表项 _buildOrderItem( type: '卖出', price: '0.000156', quantity: '1,000 股', time: '12/05 14:30', status: '待成交', ), ], ), ); } Widget _buildOrderItem({ required String type, required String price, required String quantity, required String time, required String status, }) { final isSell = type == '卖出'; 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).withOpacity(0.1), borderRadius: BorderRadius.circular(6), ), child: Text( type, style: TextStyle( fontSize: 12, fontWeight: FontWeight.bold, color: isSell ? _red : _green, ), ), ), const SizedBox(width: 8), Text( price, style: const TextStyle( fontSize: 14, fontWeight: FontWeight.bold, color: _darkText, ), ), ], ), const SizedBox(height: 4), Text( time, style: const TextStyle( fontSize: 12, color: _grayText, ), ), ], ), Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( 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.withOpacity(0.1), borderRadius: BorderRadius.circular(9999), ), child: Text( status, style: const TextStyle( fontSize: 12, color: _orange, ), ), ), ], ), ], ), ); } Widget _buildLoadingCard() { return Container( margin: const EdgeInsets.all(16), padding: const EdgeInsets.all(32), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), ), child: const Center(child: CircularProgressIndicator()), ); } 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(String accountSequence) async { if (_amountController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('请输入数量')), ); return; } final isBuy = _selectedTab == 0; bool success; if (isBuy) { success = await ref .read(tradingNotifierProvider.notifier) .buyShares(accountSequence, _amountController.text); } else { success = await ref .read(tradingNotifierProvider.notifier) .sellShares(accountSequence, _amountController.text); } if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( content: Text(success ? (isBuy ? '买入订单已提交' : '卖出订单已提交') : (isBuy ? '买入失败' : '卖出失败')), backgroundColor: success ? _green : AppColors.error, ), ); if (success) { _amountController.clear(); ref.invalidate(shareAccountProvider(accountSequence)); } } } } // K线图绘制器(简化版本,显示模拟数据) class _CandlestickPainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { final greenPaint = Paint()..color = const Color(0xFF10B981); final redPaint = Paint()..color = const Color(0xFFEF4444); final dashPaint = Paint() ..color = const Color(0xFFFF6B00) ..strokeWidth = 1 ..style = PaintingStyle.stroke; // 模拟K线数据 final candleData = [ {'open': 0.6, 'close': 0.5, 'high': 0.7, 'low': 0.45}, {'open': 0.5, 'close': 0.55, 'high': 0.6, 'low': 0.48}, {'open': 0.55, 'close': 0.52, 'high': 0.58, 'low': 0.5}, {'open': 0.52, 'close': 0.6, 'high': 0.65, 'low': 0.5}, {'open': 0.6, 'close': 0.58, 'high': 0.65, 'low': 0.55}, {'open': 0.58, 'close': 0.62, 'high': 0.68, 'low': 0.55}, {'open': 0.62, 'close': 0.55, 'high': 0.65, 'low': 0.52}, {'open': 0.55, 'close': 0.58, 'high': 0.62, 'low': 0.52}, {'open': 0.58, 'close': 0.52, 'high': 0.6, 'low': 0.5}, {'open': 0.52, 'close': 0.65, 'high': 0.7, 'low': 0.5}, {'open': 0.65, 'close': 0.7, 'high': 0.75, 'low': 0.62}, {'open': 0.7, 'close': 0.75, 'high': 0.8, 'low': 0.68}, ]; final candleWidth = (size.width - 40) / candleData.length; final padding = 20.0; for (int i = 0; i < candleData.length; i++) { final data = candleData[i]; final open = data['open']!; final close = data['close']!; final high = data['high']!; final low = data['low']!; final isGreen = close >= open; final paint = isGreen ? greenPaint : redPaint; final x = padding + i * candleWidth + candleWidth / 2; final yOpen = size.height - (open * size.height * 0.8 + size.height * 0.1); final yClose = size.height - (close * size.height * 0.8 + size.height * 0.1); final yHigh = size.height - (high * size.height * 0.8 + size.height * 0.1); final yLow = size.height - (low * size.height * 0.8 + size.height * 0.1); // 绘制影线 canvas.drawLine( Offset(x, yHigh), Offset(x, yLow), paint..strokeWidth = 1, ); // 绘制实体 final bodyTop = isGreen ? yClose : yOpen; final bodyBottom = isGreen ? yOpen : yClose; canvas.drawRect( Rect.fromLTRB(x - candleWidth * 0.3, bodyTop, x + candleWidth * 0.3, bodyBottom), paint..style = PaintingStyle.fill, ); } // 绘制虚线参考线 final dashY = size.height * 0.35; const dashWidth = 5.0; const dashSpace = 3.0; double startX = 0; while (startX < size.width - 60) { canvas.drawLine( Offset(startX, dashY), Offset(startX + dashWidth, dashY), dashPaint, ); startX += dashWidth + dashSpace; } } @override bool shouldRepaint(covariant CustomPainter oldDelegate) => false; }