feat(mining-app): add trading records page and remove withdrawal records

- 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 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-01-16 09:22:15 -08:00
parent 4ec6c9f48b
commit 1f0bd15946
4 changed files with 424 additions and 6 deletions

View File

@ -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<GoRouter>((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: [

View File

@ -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';
}

View File

@ -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),
),
],
),

View File

@ -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<TradingRecordsPage> createState() => _TradingRecordsPageState();
}
class _TradingRecordsPageState extends ConsumerState<TradingRecordsPage> {
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),
),
),
);
}
}