From 1f0bd15946247d633efc1cfdb3f7d177cd38cb2c Mon Sep 17 00:00:00 2001 From: hailin Date: Fri, 16 Jan 2026 09:22:15 -0800 Subject: [PATCH] feat(mining-app): add trading records page and remove withdrawal records MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add TradingRecordsPage to display trade order history with status - Connect trading records to profile page "交易记录" button - Remove unused "提现记录" button from profile page - Add route and navigation for trading records Co-Authored-By: Claude Opus 4.5 --- .../lib/core/router/app_router.dart | 5 + .../mining-app/lib/core/router/routes.dart | 2 + .../pages/profile/profile_page.dart | 7 +- .../pages/profile/trading_records_page.dart | 416 ++++++++++++++++++ 4 files changed, 424 insertions(+), 6 deletions(-) create mode 100644 frontend/mining-app/lib/presentation/pages/profile/trading_records_page.dart diff --git a/frontend/mining-app/lib/core/router/app_router.dart b/frontend/mining-app/lib/core/router/app_router.dart index 586b0435..d92b976d 100644 --- a/frontend/mining-app/lib/core/router/app_router.dart +++ b/frontend/mining-app/lib/core/router/app_router.dart @@ -20,6 +20,7 @@ import '../../presentation/pages/c2c/c2c_market_page.dart'; 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/widgets/main_shell.dart'; import '../../presentation/providers/user_providers.dart'; import 'routes.dart'; @@ -150,6 +151,10 @@ final appRouterProvider = Provider((ref) { path: Routes.myTeam, builder: (context, state) => const TeamPage(), ), + GoRoute( + path: Routes.tradingRecords, + builder: (context, state) => const TradingRecordsPage(), + ), ShellRoute( builder: (context, state, child) => MainShell(child: child), routes: [ diff --git a/frontend/mining-app/lib/core/router/routes.dart b/frontend/mining-app/lib/core/router/routes.dart index 6968521a..86611159 100644 --- a/frontend/mining-app/lib/core/router/routes.dart +++ b/frontend/mining-app/lib/core/router/routes.dart @@ -21,4 +21,6 @@ class Routes { static const String c2cOrderDetail = '/c2c-order-detail'; // 团队路由 static const String myTeam = '/my-team'; + // 交易记录 + static const String tradingRecords = '/trading-records'; } diff --git a/frontend/mining-app/lib/presentation/pages/profile/profile_page.dart b/frontend/mining-app/lib/presentation/pages/profile/profile_page.dart index e16fc772..08be54b3 100644 --- a/frontend/mining-app/lib/presentation/pages/profile/profile_page.dart +++ b/frontend/mining-app/lib/presentation/pages/profile/profile_page.dart @@ -402,12 +402,7 @@ class ProfilePage extends ConsumerWidget { _buildRecordIcon( icon: Icons.receipt_long, label: '交易记录', - onTap: () {}, - ), - _buildRecordIcon( - icon: Icons.account_balance_wallet, - label: '提现记录', - onTap: () {}, + onTap: () => context.push(Routes.tradingRecords), ), ], ), diff --git a/frontend/mining-app/lib/presentation/pages/profile/trading_records_page.dart b/frontend/mining-app/lib/presentation/pages/profile/trading_records_page.dart new file mode 100644 index 00000000..ebfede36 --- /dev/null +++ b/frontend/mining-app/lib/presentation/pages/profile/trading_records_page.dart @@ -0,0 +1,416 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; +import '../../../core/constants/app_colors.dart'; +import '../../../domain/entities/trade_order.dart'; +import '../../../data/models/trade_order_model.dart'; +import '../../providers/trading_providers.dart'; + +/// 交易记录页面 +class TradingRecordsPage extends ConsumerStatefulWidget { + const TradingRecordsPage({super.key}); + + @override + ConsumerState createState() => _TradingRecordsPageState(); +} + +class _TradingRecordsPageState extends ConsumerState { + static const Color _orange = Color(0xFFFF6B00); + static const Color _green = Color(0xFF22C55E); + static const Color _red = Color(0xFFEF4444); + static const Color _grayText = Color(0xFF6B7280); + static const Color _darkText = Color(0xFF1F2937); + static const Color _lightGray = Color(0xFF9CA3AF); + + @override + Widget build(BuildContext context) { + final ordersAsync = ref.watch(ordersProvider); + + return Scaffold( + backgroundColor: const Color(0xFFF5F5F5), + appBar: AppBar( + backgroundColor: Colors.white, + elevation: 0, + leading: IconButton( + icon: const Icon(Icons.arrow_back_ios, color: _darkText, size: 20), + onPressed: () => Navigator.of(context).pop(), + ), + title: const Text( + '交易记录', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w600, + color: _darkText, + ), + ), + centerTitle: true, + ), + body: RefreshIndicator( + onRefresh: () async { + ref.invalidate(ordersProvider); + }, + child: ordersAsync.when( + loading: () => _buildLoadingList(), + error: (error, stack) => _buildErrorView(error.toString()), + data: (ordersPage) { + if (ordersPage == null || ordersPage.data.isEmpty) { + return _buildEmptyView(); + } + return _buildOrdersList(ordersPage); + }, + ), + ), + ); + } + + Widget _buildLoadingList() { + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: 10, + itemBuilder: (context, index) => _buildShimmerCard(), + ); + } + + Widget _buildShimmerCard() { + 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: [ + Container( + width: 80, + height: 24, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), + ), + ), + Container( + width: 60, + height: 20, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 100, + height: 14, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), + ), + ), + Container( + width: 80, + height: 14, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ), + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 120, + height: 12, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), + ), + ), + Container( + width: 100, + height: 12, + decoration: BoxDecoration( + color: Colors.grey[300], + borderRadius: BorderRadius.circular(4), + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildErrorView(String error) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Icon(Icons.error_outline, size: 48, color: AppColors.error), + const SizedBox(height: 16), + Text('加载失败', style: TextStyle(fontSize: 16, color: _grayText)), + const SizedBox(height: 8), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 32), + child: Text( + error, + style: TextStyle(fontSize: 12, color: _grayText), + textAlign: TextAlign.center, + ), + ), + const SizedBox(height: 16), + ElevatedButton( + onPressed: () { + ref.invalidate(ordersProvider); + }, + style: ElevatedButton.styleFrom(backgroundColor: _orange), + child: const Text('重试', style: TextStyle(color: Colors.white)), + ), + ], + ), + ); + } + + Widget _buildEmptyView() { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.receipt_long_outlined, size: 64, color: _grayText.withValues(alpha: 0.5)), + const SizedBox(height: 16), + Text( + '暂无交易记录', + style: TextStyle(fontSize: 16, color: _grayText), + ), + const SizedBox(height: 8), + Text( + '前往交易页面进行买卖操作', + style: TextStyle(fontSize: 14, color: _grayText.withValues(alpha: 0.7)), + ), + ], + ), + ); + } + + Widget _buildOrdersList(OrdersPageModel ordersPage) { + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: ordersPage.data.length + 1, + itemBuilder: (context, index) { + if (index == ordersPage.data.length) { + return _buildBottomInfo(ordersPage.total); + } + return _buildOrderCard(ordersPage.data[index]); + }, + ); + } + + Widget _buildOrderCard(TradeOrder order) { + final isBuy = order.isBuy; + final typeColor = isBuy ? _green : _red; + final typeText = isBuy ? '买入' : '卖出'; + final statusText = _getStatusText(order.status); + final statusColor = _getStatusColor(order.status); + + 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: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: typeColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + typeText, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w600, + color: typeColor, + ), + ), + ), + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + statusText, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: statusColor, + ), + ), + ), + ], + ), + const SizedBox(height: 12), + + // 第二行:价格和数量 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildInfoItem('价格', _formatPrice(order.price)), + _buildInfoItem('数量', _formatQuantity(order.quantity)), + ], + ), + const SizedBox(height: 8), + + // 第三行:成交数量和金额 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildInfoItem('已成交', '${_formatQuantity(order.filledQuantity)} / ${_formatQuantity(order.quantity)}'), + _buildInfoItem('金额', '${_formatPrice(order.totalAmount)} USDT'), + ], + ), + const SizedBox(height: 8), + + // 第四行:订单号和时间 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Text( + '订单号: ', + style: TextStyle(fontSize: 11, color: _lightGray), + ), + Expanded( + child: Text( + order.orderNo, + style: TextStyle( + fontSize: 11, + color: _grayText, + fontFamily: 'monospace', + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ), + const SizedBox(width: 8), + Row( + children: [ + Icon(Icons.access_time, size: 11, color: _lightGray), + const SizedBox(width: 4), + Text( + DateFormat('MM-dd HH:mm').format(order.createdAt), + style: TextStyle(fontSize: 11, color: _lightGray), + ), + ], + ), + ], + ), + ], + ), + ); + } + + Widget _buildInfoItem(String label, String value) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$label: ', + style: TextStyle(fontSize: 12, color: _grayText), + ), + Text( + value, + style: const TextStyle( + fontSize: 12, + color: _darkText, + fontWeight: FontWeight.w500, + fontFamily: 'monospace', + ), + ), + ], + ); + } + + String _getStatusText(OrderStatus status) { + switch (status) { + case OrderStatus.pending: + return '委托中'; + case OrderStatus.partial: + return '部分成交'; + case OrderStatus.filled: + return '已成交'; + case OrderStatus.cancelled: + return '已取消'; + } + } + + Color _getStatusColor(OrderStatus status) { + switch (status) { + case OrderStatus.pending: + return _orange; + case OrderStatus.partial: + return Colors.blue; + case OrderStatus.filled: + return _green; + case OrderStatus.cancelled: + return _grayText; + } + } + + String _formatPrice(String price) { + try { + final value = double.parse(price); + return value.toStringAsFixed(4); + } catch (e) { + return price; + } + } + + String _formatQuantity(String quantity) { + try { + final value = double.parse(quantity); + if (value == value.truncate()) { + return value.truncate().toString(); + } + return value.toStringAsFixed(2); + } catch (e) { + return quantity; + } + } + + Widget _buildBottomInfo(int total) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 16), + child: Center( + child: Text( + '共 $total 条交易记录', + style: TextStyle(fontSize: 12, color: _grayText), + ), + ), + ); + } +}