diff --git a/frontend/mining-app/lib/core/router/app_router.dart b/frontend/mining-app/lib/core/router/app_router.dart index f095b2ad..f28d60e6 100644 --- a/frontend/mining-app/lib/core/router/app_router.dart +++ b/frontend/mining-app/lib/core/router/app_router.dart @@ -21,6 +21,7 @@ import '../../presentation/pages/c2c/c2c_publish_page.dart'; import '../../presentation/pages/c2c/c2c_order_detail_page.dart'; import '../../presentation/pages/profile/team_page.dart'; import '../../presentation/pages/profile/trading_records_page.dart'; +import '../../presentation/pages/trading/transfer_records_page.dart'; import '../../presentation/pages/profile/help_center_page.dart'; import '../../presentation/pages/profile/about_page.dart'; import '../../presentation/widgets/main_shell.dart'; @@ -157,6 +158,10 @@ final appRouterProvider = Provider((ref) { path: Routes.tradingRecords, builder: (context, state) => const TradingRecordsPage(), ), + GoRoute( + path: Routes.transferRecords, + builder: (context, state) => const TransferRecordsPage(), + ), GoRoute( path: Routes.helpCenter, builder: (context, state) => const HelpCenterPage(), diff --git a/frontend/mining-app/lib/core/router/routes.dart b/frontend/mining-app/lib/core/router/routes.dart index 66f5f027..e1e81113 100644 --- a/frontend/mining-app/lib/core/router/routes.dart +++ b/frontend/mining-app/lib/core/router/routes.dart @@ -23,6 +23,8 @@ class Routes { static const String myTeam = '/my-team'; // 交易记录 static const String tradingRecords = '/trading-records'; + // 划转记录 + static const String transferRecords = '/transfer-records'; // 其他设置 static const String helpCenter = '/help-center'; static const String about = '/about'; diff --git a/frontend/mining-app/lib/data/datasources/remote/trading_remote_datasource.dart b/frontend/mining-app/lib/data/datasources/remote/trading_remote_datasource.dart index cad763c5..f9b4db2f 100644 --- a/frontend/mining-app/lib/data/datasources/remote/trading_remote_datasource.dart +++ b/frontend/mining-app/lib/data/datasources/remote/trading_remote_datasource.dart @@ -48,6 +48,9 @@ abstract class TradingRemoteDataSource { /// 划出积分股 (从交易账户到挖矿账户) Future> transferOut(String amount); + /// 获取划转历史记录 + Future> getTransferHistory({int page = 1, int pageSize = 50}); + /// 获取我的资产显示信息 Future getMyAsset({String? dailyAllocation}); @@ -243,6 +246,19 @@ class TradingRemoteDataSourceImpl implements TradingRemoteDataSource { } } + @override + Future> getTransferHistory({int page = 1, int pageSize = 50}) async { + try { + final response = await client.get( + ApiEndpoints.transferHistory, + queryParameters: {'page': page, 'pageSize': pageSize}, + ); + return response.data['data'] ?? []; + } catch (e) { + throw ServerException(e.toString()); + } + } + @override Future getMyAsset({String? dailyAllocation}) async { try { diff --git a/frontend/mining-app/lib/data/repositories/trading_repository_impl.dart b/frontend/mining-app/lib/data/repositories/trading_repository_impl.dart index 8ac9acb1..121ad49c 100644 --- a/frontend/mining-app/lib/data/repositories/trading_repository_impl.dart +++ b/frontend/mining-app/lib/data/repositories/trading_repository_impl.dart @@ -151,6 +151,18 @@ class TradingRepositoryImpl implements TradingRepository { } } + @override + Future>> getTransferHistory({int page = 1, int pageSize = 50}) async { + try { + final result = await remoteDataSource.getTransferHistory(page: page, pageSize: pageSize); + return Right(result); + } on ServerException catch (e) { + return Left(ServerFailure(e.message)); + } on NetworkException { + return Left(const NetworkFailure()); + } + } + @override Future> getMyAsset({String? dailyAllocation}) async { try { diff --git a/frontend/mining-app/lib/domain/repositories/trading_repository.dart b/frontend/mining-app/lib/domain/repositories/trading_repository.dart index 817b160e..faf464bd 100644 --- a/frontend/mining-app/lib/domain/repositories/trading_repository.dart +++ b/frontend/mining-app/lib/domain/repositories/trading_repository.dart @@ -45,6 +45,9 @@ abstract class TradingRepository { /// 划出积分股 (从交易账户到挖矿账户) Future>> transferOut(String amount); + /// 获取划转历史记录 + Future>> getTransferHistory({int page = 1, int pageSize = 50}); + /// 获取我的资产显示信息 Future> getMyAsset({String? dailyAllocation}); diff --git a/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart b/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart index 364ae3a1..577065ff 100644 --- a/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart +++ b/frontend/mining-app/lib/presentation/pages/trading/trading_page.dart @@ -1,7 +1,9 @@ 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'; @@ -352,7 +354,11 @@ class _TradingPageState extends ConsumerState { 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'; @@ -497,32 +503,75 @@ class _TradingPageState extends ConsumerState { ), ] else ...[ // 可用余额提示 - Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), - decoration: BoxDecoration( - color: _orange.withValues(alpha: 0.05), - borderRadius: BorderRadius.circular(8), + 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, + ), + ), + ], + ), ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - _selectedTab == 0 ? '可用积分值' : '可用积分股', - style: const TextStyle(fontSize: 12, color: _grayText), - ), - Text( - _selectedTab == 0 - ? formatAmount(availableCash) - : formatAmount(availableShares), - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, + ] 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, '请输入价格', '积分值'), @@ -1094,4 +1143,411 @@ class _TradingPageState extends ConsumerState { 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); + } + } + } } diff --git a/frontend/mining-app/lib/presentation/pages/trading/transfer_records_page.dart b/frontend/mining-app/lib/presentation/pages/trading/transfer_records_page.dart new file mode 100644 index 00000000..c3137710 --- /dev/null +++ b/frontend/mining-app/lib/presentation/pages/trading/transfer_records_page.dart @@ -0,0 +1,318 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import '../../../core/constants/app_colors.dart'; +import '../../../core/utils/format_utils.dart'; +import '../../../domain/repositories/trading_repository.dart'; +import '../../providers/trading_providers.dart'; + +/// 划转记录数据模型 +class TransferRecord { + final String transferNo; + final String direction; // IN: 划入交易账户, OUT: 划出到挖矿账户 + final String amount; + final String status; // PENDING, COMPLETED, FAILED + final DateTime createdAt; + final DateTime? completedAt; + final String? errorMessage; + + TransferRecord({ + required this.transferNo, + required this.direction, + required this.amount, + required this.status, + required this.createdAt, + this.completedAt, + this.errorMessage, + }); + + factory TransferRecord.fromJson(Map json) { + return TransferRecord( + transferNo: json['transferNo'] ?? '', + direction: json['direction'] ?? '', + amount: json['amount']?.toString() ?? '0', + status: json['status'] ?? '', + createdAt: DateTime.tryParse(json['createdAt'] ?? '') ?? DateTime.now(), + completedAt: json['completedAt'] != null + ? DateTime.tryParse(json['completedAt']) + : null, + errorMessage: json['errorMessage'], + ); + } +} + +/// 划转记录页面 Provider +final transferRecordsProvider = FutureProvider>((ref) async { + final repository = ref.watch(tradingRepositoryProvider); + final result = await repository.getTransferHistory(); + + return result.fold( + (failure) => throw Exception(failure.message), + (records) => records.map((json) => TransferRecord.fromJson(json as Map)).toList(), + ); +}); + +class TransferRecordsPage extends ConsumerWidget { + const TransferRecordsPage({super.key}); + + 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); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final recordsAsync = ref.watch(transferRecordsProvider); + + return Scaffold( + backgroundColor: _bgGray, + appBar: AppBar( + title: const Text( + '划转记录', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: _darkText, + ), + ), + centerTitle: true, + backgroundColor: Colors.white, + elevation: 0, + iconTheme: const IconThemeData(color: _darkText), + ), + body: RefreshIndicator( + onRefresh: () async { + ref.invalidate(transferRecordsProvider); + }, + child: recordsAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (error, stack) => Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.error_outline, size: 48, color: _grayText), + const SizedBox(height: 16), + Text( + '加载失败', + style: TextStyle(color: _grayText), + ), + const SizedBox(height: 8), + TextButton( + onPressed: () => ref.invalidate(transferRecordsProvider), + child: const Text('点击重试'), + ), + ], + ), + ), + data: (records) { + if (records.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.swap_horiz, + size: 64, + color: _grayText.withValues(alpha: 0.5), + ), + const SizedBox(height: 16), + const Text( + '暂无划转记录', + style: TextStyle( + fontSize: 16, + color: _grayText, + ), + ), + ], + ), + ); + } + + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: records.length, + itemBuilder: (context, index) { + return _buildRecordCard(records[index]); + }, + ); + }, + ), + ), + ); + } + + Widget _buildRecordCard(TransferRecord record) { + final isIn = record.direction == 'IN'; + final statusColor = _getStatusColor(record.status); + final statusText = _getStatusText(record.status); + final dateFormat = DateFormat('yyyy-MM-dd HH:mm:ss'); + + return Container( + margin: const EdgeInsets.only(bottom: 12), + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(12), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题行 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + width: 32, + height: 32, + decoration: BoxDecoration( + color: (isIn ? _green : _orange).withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: Icon( + isIn ? Icons.arrow_downward : Icons.arrow_upward, + size: 18, + color: isIn ? _green : _orange, + ), + ), + const SizedBox(width: 12), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isIn ? '划入交易账户' : '划出到挖矿账户', + style: const TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: _darkText, + ), + ), + const SizedBox(height: 2), + Text( + dateFormat.format(record.createdAt), + style: const TextStyle( + fontSize: 12, + color: _grayText, + ), + ), + ], + ), + ], + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + statusText, + style: TextStyle( + fontSize: 12, + color: statusColor, + fontWeight: FontWeight.w500, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + const Divider(height: 1), + const SizedBox(height: 12), + // 数量 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + '划转数量', + style: TextStyle(fontSize: 13, color: _grayText), + ), + Text( + '${isIn ? '+' : '-'}${formatAmount(record.amount)} 积分股', + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.bold, + color: isIn ? _green : _orange, + ), + ), + ], + ), + const SizedBox(height: 8), + // 单号 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + '划转单号', + style: TextStyle(fontSize: 12, color: _grayText), + ), + Text( + record.transferNo, + style: const TextStyle( + fontSize: 12, + color: _grayText, + fontFamily: 'monospace', + ), + ), + ], + ), + // 错误信息 + if (record.status == 'FAILED' && record.errorMessage != null) ...[ + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: _red.withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(4), + ), + child: Row( + children: [ + const Icon(Icons.info_outline, size: 14, color: _red), + const SizedBox(width: 4), + Expanded( + child: Text( + record.errorMessage!, + style: const TextStyle( + fontSize: 12, + color: _red, + ), + ), + ), + ], + ), + ), + ], + ], + ), + ); + } + + Color _getStatusColor(String status) { + switch (status) { + case 'COMPLETED': + return _green; + case 'PENDING': + return _orange; + case 'FAILED': + return _red; + default: + return _grayText; + } + } + + String _getStatusText(String status) { + switch (status) { + case 'COMPLETED': + return '已完成'; + case 'PENDING': + return '处理中'; + case 'FAILED': + return '失败'; + default: + return status; + } + } +}