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:
parent
4ec6c9f48b
commit
1f0bd15946
|
|
@ -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: [
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue