From 7da98c248bdda2a586eb0ba72bfe7110cdd7e3ce Mon Sep 17 00:00:00 2001 From: hailin Date: Tue, 20 Jan 2026 03:15:25 -0800 Subject: [PATCH] =?UTF-8?q?feat(trading):=20=E4=BA=A4=E6=98=93=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E9=A1=B5=E9=9D=A2=E6=B7=BB=E5=8A=A0=E6=88=90=E4=BA=A4?= =?UTF-8?q?=E6=98=8E=E7=BB=86Tab=EF=BC=8C=E6=98=BE=E7=A4=BA=E6=89=8B?= =?UTF-8?q?=E7=BB=AD=E8=B4=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端: - trading-service 添加 GET /trading/trades API 获取成交记录 - 成交记录包含: 交易总额、手续费(10%)、实际收到金额 前端: - 新增 TradeRecord 实体和 TradesPageModel - 交易记录页面添加 Tab: "订单记录" 和 "成交明细" - 成交明细显示: 价格、数量、交易总额、手续费、实际收到 Co-Authored-By: Claude Opus 4.5 --- .../src/api/controllers/trading.controller.ts | 20 ++ .../repositories/order.repository.ts | 78 +++++ .../lib/core/network/api_endpoints.dart | 1 + .../remote/trading_remote_datasource.dart | 22 ++ .../lib/data/models/trade_order_model.dart | 53 ++++ .../repositories/trading_repository_impl.dart | 18 ++ .../lib/domain/entities/trade_record.dart | 52 ++++ .../repositories/trading_repository.dart | 6 + .../pages/profile/trading_records_page.dart | 289 ++++++++++++------ .../providers/trading_providers.dart | 17 ++ 10 files changed, 465 insertions(+), 91 deletions(-) create mode 100644 frontend/mining-app/lib/domain/entities/trade_record.dart diff --git a/backend/services/trading-service/src/api/controllers/trading.controller.ts b/backend/services/trading-service/src/api/controllers/trading.controller.ts index dc3b88aa..130c0dbf 100644 --- a/backend/services/trading-service/src/api/controllers/trading.controller.ts +++ b/backend/services/trading-service/src/api/controllers/trading.controller.ts @@ -122,4 +122,24 @@ export class TradingController { total: result.total, }; } + + @Get('trades') + @ApiOperation({ summary: '获取用户成交记录(含手续费明细)' }) + @ApiQuery({ name: 'page', required: false, type: Number }) + @ApiQuery({ name: 'pageSize', required: false, type: Number }) + async getTrades( + @Req() req: any, + @Query('page') page?: number, + @Query('pageSize') pageSize?: number, + ) { + const accountSequence = req.user?.accountSequence; + if (!accountSequence) { + throw new Error('Unauthorized'); + } + + return this.orderRepository.findTradesByAccountSequence(accountSequence, { + page: page ?? 1, + pageSize: pageSize ?? 50, + }); + } } diff --git a/backend/services/trading-service/src/infrastructure/persistence/repositories/order.repository.ts b/backend/services/trading-service/src/infrastructure/persistence/repositories/order.repository.ts index ebcfa275..8ffcdaff 100644 --- a/backend/services/trading-service/src/infrastructure/persistence/repositories/order.repository.ts +++ b/backend/services/trading-service/src/infrastructure/persistence/repositories/order.repository.ts @@ -213,6 +213,84 @@ export class OrderRepository { }; } + /** + * 查询用户的成交记录(作为买方或卖方) + */ + async findTradesByAccountSequence( + accountSequence: string, + options?: { page?: number; pageSize?: number }, + ): Promise<{ + data: Array<{ + id: string; + tradeNo: string; + type: 'BUY' | 'SELL'; + price: string; + quantity: string; + amount: string; + fee: string; + netAmount: string; + counterparty: string; + createdAt: Date; + }>; + total: number; + }> { + const page = options?.page ?? 1; + const pageSize = options?.pageSize ?? 50; + + // 查询作为买方和卖方的成交记录 + const [buyerTrades, sellerTrades, buyerCount, sellerCount] = await Promise.all([ + this.prisma.trade.findMany({ + where: { buyerSequence: accountSequence }, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.trade.findMany({ + where: { sellerSequence: accountSequence }, + orderBy: { createdAt: 'desc' }, + }), + this.prisma.trade.count({ where: { buyerSequence: accountSequence } }), + this.prisma.trade.count({ where: { sellerSequence: accountSequence } }), + ]); + + // 合并并转换格式 + const allTrades = [ + ...buyerTrades.map((t) => ({ + id: t.id, + tradeNo: t.tradeNo, + type: 'BUY' as const, + price: t.price.toString(), + quantity: t.quantity.toString(), + amount: t.amount.toString(), + fee: '0', // 买方不支付手续费 + netAmount: t.amount.toString(), + counterparty: t.sellerSequence, + createdAt: t.createdAt, + })), + ...sellerTrades.map((t) => ({ + id: t.id, + tradeNo: t.tradeNo, + type: 'SELL' as const, + price: t.price.toString(), + quantity: t.quantity.toString(), + amount: t.amount.toString(), // 这是扣除手续费后的金额 + fee: t.fee.toString(), + netAmount: t.amount.toString(), // amount 已经是 net(扣除手续费后) + counterparty: t.buyerSequence, + createdAt: t.createdAt, + })), + ]; + + // 按时间排序 + allTrades.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + + // 分页 + const paged = allTrades.slice((page - 1) * pageSize, page * pageSize); + + return { + data: paged, + total: buyerCount + sellerCount, + }; + } + private toDomain(record: any): OrderAggregate { return OrderAggregate.reconstitute({ id: record.id, diff --git a/frontend/mining-app/lib/core/network/api_endpoints.dart b/frontend/mining-app/lib/core/network/api_endpoints.dart index c14d2175..598c84d7 100644 --- a/frontend/mining-app/lib/core/network/api_endpoints.dart +++ b/frontend/mining-app/lib/core/network/api_endpoints.dart @@ -38,6 +38,7 @@ class ApiEndpoints { static const String orderBook = '/api/v2/trading/trading/orderbook'; static const String createOrder = '/api/v2/trading/trading/orders'; static const String orders = '/api/v2/trading/trading/orders'; + static const String trades = '/api/v2/trading/trading/trades'; static String cancelOrder(String orderNo) => '/api/v2/trading/trading/orders/$orderNo/cancel'; 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 478a6b79..c04e23e2 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 @@ -39,6 +39,12 @@ abstract class TradingRemoteDataSource { int pageSize = 50, }); + /// 获取用户成交记录(含手续费明细) + Future getTrades({ + int page = 1, + int pageSize = 50, + }); + /// 预估卖出收益 Future> estimateSell(String quantity); @@ -222,6 +228,22 @@ class TradingRemoteDataSourceImpl implements TradingRemoteDataSource { } } + @override + Future getTrades({ + int page = 1, + int pageSize = 50, + }) async { + try { + final response = await client.get( + ApiEndpoints.trades, + queryParameters: {'page': page, 'pageSize': pageSize}, + ); + return TradesPageModel.fromJson(response.data); + } catch (e) { + throw ServerException(e.toString()); + } + } + @override Future> estimateSell(String quantity) async { try { diff --git a/frontend/mining-app/lib/data/models/trade_order_model.dart b/frontend/mining-app/lib/data/models/trade_order_model.dart index 21ec87b9..6800e240 100644 --- a/frontend/mining-app/lib/data/models/trade_order_model.dart +++ b/frontend/mining-app/lib/data/models/trade_order_model.dart @@ -1,4 +1,5 @@ import '../../domain/entities/trade_order.dart'; +import '../../domain/entities/trade_record.dart'; class TradeOrderModel extends TradeOrder { const TradeOrderModel({ @@ -86,3 +87,55 @@ class OrdersPageModel { ); } } + +/// 成交记录 Model +class TradeRecordModel extends TradeRecord { + const TradeRecordModel({ + required super.id, + required super.tradeNo, + required super.type, + required super.price, + required super.quantity, + required super.amount, + required super.fee, + required super.netAmount, + required super.counterparty, + required super.createdAt, + }); + + factory TradeRecordModel.fromJson(Map json) { + return TradeRecordModel( + id: json['id']?.toString() ?? '', + tradeNo: json['tradeNo']?.toString() ?? '', + type: json['type'] == 'BUY' ? TradeType.buy : TradeType.sell, + price: json['price']?.toString() ?? '0', + quantity: json['quantity']?.toString() ?? '0', + amount: json['amount']?.toString() ?? '0', + fee: json['fee']?.toString() ?? '0', + netAmount: json['netAmount']?.toString() ?? '0', + counterparty: json['counterparty']?.toString() ?? '', + createdAt: json['createdAt'] != null + ? DateTime.parse(json['createdAt'].toString()) + : DateTime.now(), + ); + } +} + +/// 成交记录列表响应 +class TradesPageModel { + final List data; + final int total; + + const TradesPageModel({ + required this.data, + required this.total, + }); + + factory TradesPageModel.fromJson(Map json) { + final dataList = (json['data'] as List?) ?? []; + return TradesPageModel( + data: dataList.map((e) => TradeRecordModel.fromJson(e)).toList(), + total: json['total'] ?? 0, + ); + } +} 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 2669cebb..af8a06f2 100644 --- a/frontend/mining-app/lib/data/repositories/trading_repository_impl.dart +++ b/frontend/mining-app/lib/data/repositories/trading_repository_impl.dart @@ -115,6 +115,24 @@ class TradingRepositoryImpl implements TradingRepository { } } + @override + Future> getTrades({ + int page = 1, + int pageSize = 50, + }) async { + try { + final result = await remoteDataSource.getTrades( + page: page, + pageSize: pageSize, + ); + return Right(result); + } on ServerException catch (e) { + return Left(ServerFailure(e.message)); + } on NetworkException { + return Left(const NetworkFailure()); + } + } + @override Future>> estimateSell(String quantity) async { try { diff --git a/frontend/mining-app/lib/domain/entities/trade_record.dart b/frontend/mining-app/lib/domain/entities/trade_record.dart new file mode 100644 index 00000000..3c9dda06 --- /dev/null +++ b/frontend/mining-app/lib/domain/entities/trade_record.dart @@ -0,0 +1,52 @@ +import 'package:equatable/equatable.dart'; + +enum TradeType { buy, sell } + +/// 成交记录实体(包含手续费明细) +class TradeRecord extends Equatable { + /// 成交ID + final String id; + /// 成交编号 + final String tradeNo; + /// 交易类型 + final TradeType type; + /// 成交价格 + final String price; + /// 成交数量 + final String quantity; + /// 成交金额(卖方扣除手续费后的金额) + final String amount; + /// 手续费(卖方支付,买方为0) + final String fee; + /// 净额(实际收到/支付金额) + final String netAmount; + /// 对手方账号 + final String counterparty; + /// 成交时间 + final DateTime createdAt; + + const TradeRecord({ + required this.id, + required this.tradeNo, + required this.type, + required this.price, + required this.quantity, + required this.amount, + required this.fee, + required this.netAmount, + required this.counterparty, + required this.createdAt, + }); + + bool get isBuy => type == TradeType.buy; + bool get isSell => type == TradeType.sell; + + /// 是否有手续费 + bool get hasFee { + final feeValue = double.tryParse(fee) ?? 0; + return feeValue > 0; + } + + @override + List get props => [id, tradeNo, type, price, quantity, fee]; +} diff --git a/frontend/mining-app/lib/domain/repositories/trading_repository.dart b/frontend/mining-app/lib/domain/repositories/trading_repository.dart index c4352ca8..649d3a2d 100644 --- a/frontend/mining-app/lib/domain/repositories/trading_repository.dart +++ b/frontend/mining-app/lib/domain/repositories/trading_repository.dart @@ -36,6 +36,12 @@ abstract class TradingRepository { int pageSize = 50, }); + /// 获取用户成交记录(含手续费明细) + Future> getTrades({ + int page = 1, + int pageSize = 50, + }); + /// 预估卖出收益 Future>> estimateSell(String quantity); 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 index 61e7b13b..941898eb 100644 --- a/frontend/mining-app/lib/presentation/pages/profile/trading_records_page.dart +++ b/frontend/mining-app/lib/presentation/pages/profile/trading_records_page.dart @@ -2,7 +2,9 @@ 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/entities/trade_order.dart'; +import '../../../domain/entities/trade_record.dart'; import '../../../data/models/trade_order_model.dart'; import '../../providers/trading_providers.dart'; @@ -14,7 +16,7 @@ class TradingRecordsPage extends ConsumerStatefulWidget { ConsumerState createState() => _TradingRecordsPageState(); } -class _TradingRecordsPageState extends ConsumerState { +class _TradingRecordsPageState extends ConsumerState with SingleTickerProviderStateMixin { static const Color _orange = Color(0xFFFF6B00); static const Color _green = Color(0xFF22C55E); static const Color _red = Color(0xFFEF4444); @@ -22,10 +24,22 @@ class _TradingRecordsPageState extends ConsumerState { static const Color _darkText = Color(0xFF1F2937); static const Color _lightGray = Color(0xFF9CA3AF); + late TabController _tabController; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - final ordersAsync = ref.watch(ordersProvider); - return Scaffold( backgroundColor: const Color(0xFFF5F5F5), appBar: AppBar( @@ -44,22 +58,66 @@ class _TradingRecordsPageState extends ConsumerState { ), ), 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); - }, + bottom: TabBar( + controller: _tabController, + labelColor: _orange, + unselectedLabelColor: _grayText, + indicatorColor: _orange, + tabs: const [ + Tab(text: '订单记录'), + Tab(text: '成交明细'), + ], ), ), + body: TabBarView( + controller: _tabController, + children: [ + _buildOrdersTab(), + _buildTradesTab(), + ], + ), + ); + } + + // ==================== 订单记录 Tab ==================== + Widget _buildOrdersTab() { + final ordersAsync = ref.watch(ordersProvider); + + return RefreshIndicator( + onRefresh: () async { + ref.invalidate(ordersProvider); + }, + child: ordersAsync.when( + loading: () => _buildLoadingList(), + error: (error, stack) => _buildErrorView(error.toString(), () => ref.invalidate(ordersProvider)), + data: (ordersPage) { + if (ordersPage == null || ordersPage.data.isEmpty) { + return _buildEmptyView('暂无订单记录', '前往交易页面进行买卖操作'); + } + return _buildOrdersList(ordersPage); + }, + ), + ); + } + + // ==================== 成交明细 Tab ==================== + Widget _buildTradesTab() { + final tradesAsync = ref.watch(tradesProvider); + + return RefreshIndicator( + onRefresh: () async { + ref.invalidate(tradesProvider); + }, + child: tradesAsync.when( + loading: () => _buildLoadingList(), + error: (error, stack) => _buildErrorView(error.toString(), () => ref.invalidate(tradesProvider)), + data: (tradesPage) { + if (tradesPage == null || tradesPage.data.isEmpty) { + return _buildEmptyView('暂无成交记录', '成交后将在此显示明细'); + } + return _buildTradesList(tradesPage); + }, + ), ); } @@ -125,34 +183,12 @@ class _TradingRecordsPageState extends ConsumerState { ), ], ), - 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) { + Widget _buildErrorView(String error, VoidCallback onRetry) { return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -171,9 +207,7 @@ class _TradingRecordsPageState extends ConsumerState { ), const SizedBox(height: 16), ElevatedButton( - onPressed: () { - ref.invalidate(ordersProvider); - }, + onPressed: onRetry, style: ElevatedButton.styleFrom(backgroundColor: _orange), child: const Text('重试', style: TextStyle(color: Colors.white)), ), @@ -182,22 +216,16 @@ class _TradingRecordsPageState extends ConsumerState { ); } - Widget _buildEmptyView() { + Widget _buildEmptyView(String title, String subtitle) { 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), - ), + Text(title, style: TextStyle(fontSize: 16, color: _grayText)), const SizedBox(height: 8), - Text( - '前往交易页面进行买卖操作', - style: TextStyle(fontSize: 14, color: _grayText.withValues(alpha: 0.7)), - ), + Text(subtitle, style: TextStyle(fontSize: 14, color: _grayText.withValues(alpha: 0.7))), ], ), ); @@ -209,13 +237,26 @@ class _TradingRecordsPageState extends ConsumerState { itemCount: ordersPage.data.length + 1, itemBuilder: (context, index) { if (index == ordersPage.data.length) { - return _buildBottomInfo(ordersPage.total); + return _buildBottomInfo('共 ${ordersPage.total} 条订单记录'); } return _buildOrderCard(ordersPage.data[index]); }, ); } + Widget _buildTradesList(TradesPageModel tradesPage) { + return ListView.builder( + padding: const EdgeInsets.all(16), + itemCount: tradesPage.data.length + 1, + itemBuilder: (context, index) { + if (index == tradesPage.data.length) { + return _buildBottomInfo('共 ${tradesPage.total} 条成交记录'); + } + return _buildTradeCard(tradesPage.data[index]); + }, + ); + } + Widget _buildOrderCard(TradeOrder order) { final isBuy = order.isBuy; final typeColor = isBuy ? _green : _red; @@ -233,7 +274,6 @@ class _TradingRecordsPageState extends ConsumerState { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // 第一行:类型标签 + 状态 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -245,11 +285,7 @@ class _TradingRecordsPageState extends ConsumerState { ), child: Text( typeText, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: typeColor, - ), + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: typeColor), ), ), Container( @@ -260,18 +296,12 @@ class _TradingRecordsPageState extends ConsumerState { ), child: Text( statusText, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: statusColor, - ), + style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500, color: statusColor), ), ), ], ), const SizedBox(height: 12), - - // 第二行:价格和数量 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -280,8 +310,6 @@ class _TradingRecordsPageState extends ConsumerState { ], ), const SizedBox(height: 8), - - // 第三行:成交数量和金额 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -290,26 +318,17 @@ class _TradingRecordsPageState extends ConsumerState { ], ), const SizedBox(height: 8), - - // 第四行:订单号和时间 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: Row( children: [ - Text( - '订单号: ', - style: TextStyle(fontSize: 11, color: _lightGray), - ), + Text('订单号: ', style: TextStyle(fontSize: 11, color: _lightGray)), Expanded( child: Text( order.orderNo, - style: TextStyle( - fontSize: 11, - color: _grayText, - fontFamily: 'monospace', - ), + style: TextStyle(fontSize: 11, color: _grayText, fontFamily: 'monospace'), overflow: TextOverflow.ellipsis, ), ), @@ -334,19 +353,110 @@ class _TradingRecordsPageState extends ConsumerState { ); } - Widget _buildInfoItem(String label, String value) { + Widget _buildTradeCard(TradeRecord trade) { + final isBuy = trade.isBuy; + final typeColor = isBuy ? _green : _red; + final typeText = isBuy ? '买入' : '卖出'; + + // 计算交易总额(卖出时:netAmount + fee) + final grossAmount = isBuy + ? double.tryParse(trade.amount) ?? 0 + : (double.tryParse(trade.netAmount) ?? 0) + (double.tryParse(trade.fee) ?? 0); + + 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), + ), + ), + Text( + DateFormat('MM-dd HH:mm').format(trade.createdAt), + style: TextStyle(fontSize: 12, color: _lightGray), + ), + ], + ), + const SizedBox(height: 12), + + // 第二行:价格和数量 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildInfoItem('价格', _formatPrice(trade.price)), + _buildInfoItem('数量', _formatQuantity(trade.quantity)), + ], + ), + const SizedBox(height: 8), + + // 第三行:交易总额 + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildInfoItem('交易总额', '${formatAmount(grossAmount.toString())} 积分值'), + if (trade.hasFee) + _buildInfoItem('手续费(10%)', '${formatAmount(trade.fee)} 积分值', valueColor: _red), + ], + ), + + // 第四行:实际收到(仅卖出时显示) + if (trade.isSell && trade.hasFee) ...[ + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildInfoItem('实际收到', '${formatAmount(trade.netAmount)} 积分值', valueColor: _green), + const SizedBox(), + ], + ), + ], + + const SizedBox(height: 8), + // 成交编号 + Row( + children: [ + Text('成交编号: ', style: TextStyle(fontSize: 11, color: _lightGray)), + Expanded( + child: Text( + trade.tradeNo, + style: TextStyle(fontSize: 11, color: _grayText, fontFamily: 'monospace'), + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildInfoItem(String label, String value, {Color? valueColor}) { return Row( mainAxisSize: MainAxisSize.min, children: [ - Text( - '$label: ', - style: TextStyle(fontSize: 12, color: _grayText), - ), + Text('$label: ', style: TextStyle(fontSize: 12, color: _grayText)), Text( value, - style: const TextStyle( + style: TextStyle( fontSize: 12, - color: _darkText, + color: valueColor ?? _darkText, fontWeight: FontWeight.w500, fontFamily: 'monospace', ), @@ -402,14 +512,11 @@ class _TradingRecordsPageState extends ConsumerState { } } - Widget _buildBottomInfo(int total) { + Widget _buildBottomInfo(String text) { return Container( padding: const EdgeInsets.symmetric(vertical: 16), child: Center( - child: Text( - '共 $total 条交易记录', - style: TextStyle(fontSize: 12, color: _grayText), - ), + child: Text(text, style: TextStyle(fontSize: 12, color: _grayText)), ), ); } diff --git a/frontend/mining-app/lib/presentation/providers/trading_providers.dart b/frontend/mining-app/lib/presentation/providers/trading_providers.dart index 95ec782b..e9298ec0 100644 --- a/frontend/mining-app/lib/presentation/providers/trading_providers.dart +++ b/frontend/mining-app/lib/presentation/providers/trading_providers.dart @@ -329,6 +329,23 @@ final ordersProvider = FutureProvider((ref) async { ); }); +// 成交记录列表 Provider(含手续费明细) +final tradesProvider = FutureProvider((ref) async { + final repository = ref.watch(tradingRepositoryProvider); + final result = await repository.getTrades(page: 1, pageSize: 50); + + ref.keepAlive(); + final timer = Timer(const Duration(seconds: 30), () { + ref.invalidateSelf(); + }); + ref.onDispose(() => timer.cancel()); + + return result.fold( + (failure) => throw Exception(failure.message), + (trades) => trades, + ); +}); + // 交易状态 class TradingState { final bool isLoading;