diff --git a/flutter_monisuo/lib/core/constants/api_endpoints.dart b/flutter_monisuo/lib/core/constants/api_endpoints.dart index 88d8cc4..c81dccc 100644 --- a/flutter_monisuo/lib/core/constants/api_endpoints.dart +++ b/flutter_monisuo/lib/core/constants/api_endpoints.dart @@ -69,6 +69,9 @@ class ApiEndpoints { /// 申请充值 static const String deposit = '/api/fund/deposit'; + /// 确认已打款 + static const String confirmPay = '/api/fund/confirmPay'; + /// 申请提现 static const String withdraw = '/api/fund/withdraw'; @@ -77,4 +80,7 @@ class ApiEndpoints { /// 获取充提记录 static const String fundOrders = '/api/fund/orders'; + + /// 获取默认钱包地址 + static const String defaultWallet = '/api/wallet/default'; } diff --git a/flutter_monisuo/lib/data/models/order_models.dart b/flutter_monisuo/lib/data/models/order_models.dart index d9b64e1..4cc6aee 100644 --- a/flutter_monisuo/lib/data/models/order_models.dart +++ b/flutter_monisuo/lib/data/models/order_models.dart @@ -72,25 +72,39 @@ class OrderFund { final int id; final String orderNo; final int userId; - final int orderType; // 1=充值, 2=提现 + final String username; + final int type; // 1=充值, 2=提现 final String amount; - final int status; // 1=待审核, 2=已通过, 3=已拒绝, 4=已取消 + final int status; + // 充值状态: 1=待付款, 2=待确认, 3=已完成, 4=已驳回, 5=已取消 + // 提现状态: 1=待审批, 2=已完成, 3=已驳回, 4=已取消 + final int? walletId; // 冷钱包ID(充值) + final String? walletAddress; // 钱包地址(充值/提现) + final String? withdrawContact; // 提现联系方式 final String remark; - final String? auditRemark; + final String? rejectReason; + final String? adminRemark; final DateTime? createTime; - final DateTime? auditTime; + final DateTime? payTime; // 用户确认打款时间 + final DateTime? confirmTime; // 管理员确认时间 OrderFund({ required this.id, required this.orderNo, required this.userId, - required this.orderType, + required this.username, + required this.type, required this.amount, required this.status, + this.walletId, + this.walletAddress, + this.withdrawContact, required this.remark, - this.auditRemark, + this.rejectReason, + this.adminRemark, this.createTime, - this.auditTime, + this.payTime, + this.confirmTime, }); factory OrderFund.fromJson(Map json) { @@ -98,42 +112,117 @@ class OrderFund { id: json['id'] as int? ?? 0, orderNo: json['orderNo'] as String? ?? '', userId: json['userId'] as int? ?? 0, - orderType: json['orderType'] as int? ?? 1, + username: json['username'] as String? ?? '', + type: json['type'] as int? ?? 1, amount: json['amount']?.toString() ?? '0.00', status: json['status'] as int? ?? 1, + walletId: json['walletId'] as int?, + walletAddress: json['walletAddress'] as String?, + withdrawContact: json['withdrawContact'] as String?, remark: json['remark']?.toString() ?? '', - auditRemark: json['auditRemark']?.toString(), + rejectReason: json['rejectReason'] as String?, + adminRemark: json['adminRemark'] as String?, createTime: json['createTime'] != null ? DateTime.tryParse(json['createTime']) : null, - auditTime: json['auditTime'] != null - ? DateTime.tryParse(json['auditTime']) + payTime: json['payTime'] != null + ? DateTime.tryParse(json['payTime']) + : null, + confirmTime: json['confirmTime'] != null + ? DateTime.tryParse(json['confirmTime']) : null, ); } /// 订单类型文字 - String get orderTypeText => orderType == 1 ? '充值' : '提现'; + String get typeText => type == 1 ? '充值' : '提现'; - /// 状态文字 + /// 状态文字 (根据类型区分) String get statusText { - switch (status) { - case 1: - return '待审核'; - case 2: - return '已通过'; - case 3: - return '已拒绝'; - case 4: - return '已取消'; - default: - return '未知'; + if (type == 1) { + // 充值状态 + switch (status) { + case 1: + return '待付款'; + case 2: + return '待确认'; + case 3: + return '已完成'; + case 4: + return '已驳回'; + case 5: + return '已取消'; + default: + return '未知'; + } + } else { + // 提现状态 + switch (status) { + case 1: + return '待审批'; + case 2: + return '已完成'; + case 3: + return '已驳回'; + case 4: + return '已取消'; + default: + return '未知'; + } } } /// 是否为充值 - bool get isDeposit => orderType == 1; + bool get isDeposit => type == 1; /// 是否可取消 - bool get canCancel => status == 1; + /// 充值: 仅待付款可取消 + /// 提现: 仅待审批可取消 + bool get canCancel { + if (type == 1) { + return status == 1; // 充值待付款 + } else { + return status == 1; // 提现待审批 + } + } + + /// 是否可确认打款 (仅充值待付款) + bool get canConfirmPay => type == 1 && status == 1; +} + +/// 冷钱包模型 +class ColdWallet { + final int id; + final String name; + final String address; + final String network; + final bool isDefault; + final int status; // 0=禁用, 1=启用 + final DateTime? createTime; + + ColdWallet({ + required this.id, + required this.name, + required this.address, + required this.network, + required this.isDefault, + required this.status, + this.createTime, + }); + + factory ColdWallet.fromJson(Map json) { + return ColdWallet( + id: json['id'] as int? ?? 0, + name: json['name'] as String? ?? '', + address: json['address'] as String? ?? '', + network: json['network'] as String? ?? 'TRC20', + isDefault: json['isDefault'] as bool? ?? false, + status: json['status'] as int? ?? 1, + createTime: json['createTime'] != null + ? DateTime.tryParse(json['createTime']) + : null, + ); + } + + bool get isEnabled => status == 1; } diff --git a/flutter_monisuo/lib/data/services/fund_service.dart b/flutter_monisuo/lib/data/services/fund_service.dart index a3aa15a..10632ae 100644 --- a/flutter_monisuo/lib/data/services/fund_service.dart +++ b/flutter_monisuo/lib/data/services/fund_service.dart @@ -8,7 +8,23 @@ class FundService { FundService(this._client); + /// 获取默认钱包地址 + Future> getDefaultWallet() async { + final response = await _client.get>( + ApiEndpoints.defaultWallet, + ); + + if (response.success && response.data != null) { + return ApiResponse.success( + ColdWallet.fromJson(response.data!), + response.message, + ); + } + return ApiResponse.fail(response.message ?? '获取钱包地址失败'); + } + /// 申请充值 + /// 返回包含 orderNo, amount, status, walletAddress, walletNetwork 的信息 Future>> deposit({ required String amount, String? remark, @@ -22,15 +38,27 @@ class FundService { ); } + /// 用户确认已打款 + Future> confirmPay(String orderNo) async { + return _client.post( + ApiEndpoints.confirmPay, + data: {'orderNo': orderNo}, + ); + } + /// 申请提现 Future>> withdraw({ required String amount, + required String withdrawAddress, + String? withdrawContact, String? remark, }) async { return _client.post>( ApiEndpoints.withdraw, data: { 'amount': amount, + 'withdrawAddress': withdrawAddress, + if (withdrawContact != null) 'withdrawContact': withdrawContact, if (remark != null) 'remark': remark, }, ); @@ -62,19 +90,9 @@ class FundService { ); } - /// 获取充提订单详情 - Future> getOrderDetail(String orderNo) async { - final response = await _client.get>( - ApiEndpoints.fundOrders, - queryParameters: {'orderNo': orderNo}, - ); - - if (response.success && response.data != null) { - return ApiResponse.success( - OrderFund.fromJson(response.data!), - response.message, - ); - } - return ApiResponse.fail(response.message ?? '获取订单详情失败'); + /// 解析充提记录列表 + List parseOrderList(List? list) { + if (list == null) return []; + return list.map((e) => OrderFund.fromJson(e as Map)).toList(); } } diff --git a/flutter_monisuo/lib/providers/asset_provider.dart b/flutter_monisuo/lib/providers/asset_provider.dart index bf1969e..8ee7ee5 100644 --- a/flutter_monisuo/lib/providers/asset_provider.dart +++ b/flutter_monisuo/lib/providers/asset_provider.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import '../data/models/account_models.dart'; +import '../data/models/order_models.dart'; import '../data/services/asset_service.dart'; import '../data/services/fund_service.dart'; import '../core/network/dio_client.dart'; @@ -13,8 +14,10 @@ class AssetProvider extends ChangeNotifier { AccountFund? _fundAccount; List _tradeAccounts = []; List _flows = []; + List _fundOrders = []; bool _isLoading = false; bool _isLoadingFlows = false; + bool _isLoadingOrders = false; String? _error; AssetProvider(this._assetService, this._fundService); @@ -25,8 +28,10 @@ class AssetProvider extends ChangeNotifier { List get tradeAccounts => _tradeAccounts; List get holdings => _tradeAccounts; List get flows => _flows; + List get fundOrders => _fundOrders; bool get isLoading => _isLoading; bool get isLoadingFlows => _isLoadingFlows; + bool get isLoadingOrders => _isLoadingOrders; String? get error => _error; /// 加载资产总览 @@ -120,8 +125,8 @@ class AssetProvider extends ChangeNotifier { } } - /// 充值 - Future> deposit({required String amount, String? remark}) async { + /// 充值 - 返回订单详情包含钱包地址 + Future>> deposit({required String amount, String? remark}) async { try { final response = await _fundService.deposit(amount: amount, remark: remark); if (response.success) { @@ -134,10 +139,33 @@ class AssetProvider extends ChangeNotifier { } } - /// 提现 - Future> withdraw({required String amount, String? remark}) async { + /// 确认已打款 + Future> confirmPay(String orderNo) async { try { - final response = await _fundService.withdraw(amount: amount, remark: remark); + final response = await _fundService.confirmPay(orderNo); + if (response.success) { + await loadFundOrders(); + } + return response; + } catch (e) { + return ApiResponse.fail('确认打款失败: $e'); + } + } + + /// 提现 + Future>> withdraw({ + required String amount, + required String withdrawAddress, + String? withdrawContact, + String? remark, + }) async { + try { + final response = await _fundService.withdraw( + amount: amount, + withdrawAddress: withdrawAddress, + withdrawContact: withdrawContact, + remark: remark, + ); if (response.success) { await loadOverview(); await loadFundAccount(); @@ -148,6 +176,43 @@ class AssetProvider extends ChangeNotifier { } } + /// 加载充提订单 + Future loadFundOrders({int? type, int pageNum = 1, int pageSize = 20}) async { + _isLoadingOrders = true; + notifyListeners(); + + try { + final response = await _fundService.getOrders( + type: type, + pageNum: pageNum, + pageSize: pageSize, + ); + if (response.success && response.data != null) { + final list = response.data!['list'] as List?; + _fundOrders = _fundService.parseOrderList(list); + } + } catch (_) { + // 忽略错误 + } + + _isLoadingOrders = false; + notifyListeners(); + } + + /// 取消订单 + Future> cancelOrder(String orderNo) async { + try { + final response = await _fundService.cancelOrder(orderNo); + if (response.success) { + await loadFundOrders(); + await loadFundAccount(); + } + return response; + } catch (e) { + return ApiResponse.fail('取消订单失败: $e'); + } + } + /// 刷新所有资产数据 Future refreshAll() async { await Future.wait([ diff --git a/flutter_monisuo/lib/ui/pages/asset/asset_page.dart b/flutter_monisuo/lib/ui/pages/asset/asset_page.dart index 1963ba8..3289f80 100644 --- a/flutter_monisuo/lib/ui/pages/asset/asset_page.dart +++ b/flutter_monisuo/lib/ui/pages/asset/asset_page.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:provider/provider.dart'; import '../../../providers/asset_provider.dart'; +import '../orders/fund_orders_page.dart'; /// 资产页面 - 使用 shadcn_ui 现代化设计 class AssetPage extends StatefulWidget { @@ -201,9 +203,38 @@ class _AssetPageState extends State with AutomaticKeepAliveClientMixi child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - 'USDT余额', - style: theme.textTheme.muted, + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + 'USDT余额', + style: theme.textTheme.muted, + ), + GestureDetector( + onTap: () { + Navigator.push( + context, + MaterialPageRoute(builder: (_) => const FundOrdersPage()), + ); + }, + child: Row( + children: [ + Text( + '充提记录', + style: TextStyle( + color: theme.colorScheme.primary, + fontSize: 12, + ), + ), + Icon( + LucideIcons.chevronRight, + size: 14, + color: theme.colorScheme.primary, + ), + ], + ), + ), + ], ), const SizedBox(height: 8), Text( @@ -378,28 +409,241 @@ class _AssetPageState extends State with AutomaticKeepAliveClientMixi } void _showDepositDialog(AssetProvider provider) { - _showActionDialog( - title: '充值', - hint: '请输入充值金额(USDT)', - onSubmit: (amount) async { - final response = await provider.deposit(amount: amount); - if (mounted) { - _showResult(response.success ? '申请成功' : '申请失败', response.message); - } - }, + final amountController = TextEditingController(); + final formKey = GlobalKey(); + + showShadDialog( + context: context, + builder: (context) => ShadDialog( + title: const Text('充值'), + child: ShadForm( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ShadInputFormField( + id: 'amount', + controller: amountController, + placeholder: const Text('请输入充值金额(USDT)'), + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (value) { + if (value == null || value.isEmpty) { + return '请输入金额'; + } + final amount = double.tryParse(value); + if (amount == null || amount <= 0) { + return '请输入有效金额'; + } + return null; + }, + ), + ], + ), + ), + actions: [ + ShadButton.outline( + child: const Text('取消'), + onPressed: () => Navigator.of(context).pop(), + ), + ShadButton( + child: const Text('下一步'), + onPressed: () async { + if (formKey.currentState!.saveAndValidate()) { + Navigator.of(context).pop(); + // 提交充值申请 + final response = await provider.deposit(amount: amountController.text); + if (mounted) { + if (response.success && response.data != null) { + // 显示钱包地址 + _showDepositResultDialog(response.data!); + } else { + _showResult('申请失败', response.message); + } + } + } + }, + ), + ], + ), + ); + } + + /// 显示充值结果 - 包含钱包地址 + void _showDepositResultDialog(Map data) { + final orderNo = data['orderNo'] as String? ?? ''; + final amount = data['amount']?.toString() ?? '0.00'; + final walletAddress = data['walletAddress'] as String? ?? ''; + final walletNetwork = data['walletNetwork'] as String? ?? 'TRC20'; + final assetProvider = context.read(); + + showShadDialog( + context: context, + builder: (context) => ShadDialog( + title: const Text('充值申请成功'), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('订单号: $orderNo', style: const TextStyle(fontSize: 12)), + const SizedBox(height: 8), + Text('充值金额: $amount USDT', style: const TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 16), + const Text('请向以下地址转账:', style: TextStyle(fontSize: 12)), + const SizedBox(height: 8), + Container( + padding: const EdgeInsets.all(12), + decoration: BoxDecoration( + color: Colors.grey[100], + borderRadius: BorderRadius.circular(8), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Text( + walletAddress, + style: const TextStyle(fontFamily: 'monospace', fontSize: 12), + ), + ), + IconButton( + icon: const Icon(LucideIcons.copy, size: 18), + onPressed: () { + Clipboard.setData(ClipboardData(text: walletAddress)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('地址已复制到剪贴板')), + ); + }, + tooltip: '复制地址', + ), + ], + ), + const SizedBox(height: 4), + Text('网络: $walletNetwork', style: const TextStyle(fontSize: 12, color: Colors.grey)), + ], + ), + ), + const SizedBox(height: 12), + const Text( + '转账完成后请点击"已打款"按钮确认', + style: TextStyle(fontSize: 12, color: Colors.orange), + ), + ], + ), + actions: [ + ShadButton.outline( + child: const Text('稍后确认'), + onPressed: () => Navigator.of(context).pop(), + ), + ShadButton( + child: const Text('已打款'), + onPressed: () async { + Navigator.of(context).pop(); + final response = await assetProvider.confirmPay(orderNo); + if (mounted) { + _showResult( + response.success ? '确认成功' : '确认失败', + response.success ? '请等待管理员审核' : response.message, + ); + } + }, + ), + ], + ), ); } void _showWithdrawDialog(AssetProvider provider) { - _showActionDialog( - title: '提现', - hint: '请输入提现金额(USDT)', - onSubmit: (amount) async { - final response = await provider.withdraw(amount: amount); - if (mounted) { - _showResult(response.success ? '申请成功' : '申请失败', response.message); - } - }, + final amountController = TextEditingController(); + final addressController = TextEditingController(); + final contactController = TextEditingController(); + final formKey = GlobalKey(); + final fund = provider.fundAccount; + + showShadDialog( + context: context, + builder: (context) => ShadDialog( + title: const Text('提现'), + child: SingleChildScrollView( + child: ShadForm( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (fund != null) + Padding( + padding: const EdgeInsets.only(bottom: 12), + child: Text( + '可用余额: ${fund.balance} USDT', + style: const TextStyle(color: Colors.grey), + ), + ), + ShadInputFormField( + id: 'amount', + controller: amountController, + placeholder: const Text('请输入提现金额(USDT)'), + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (value) { + if (value == null || value.isEmpty) { + return '请输入金额'; + } + final amount = double.tryParse(value); + if (amount == null || amount <= 0) { + return '请输入有效金额'; + } + return null; + }, + ), + const SizedBox(height: 12), + ShadInputFormField( + id: 'address', + controller: addressController, + placeholder: const Text('请输入提现地址'), + validator: (value) { + if (value == null || value.isEmpty) { + return '请输入提现地址'; + } + return null; + }, + ), + const SizedBox(height: 12), + ShadInputFormField( + id: 'contact', + controller: contactController, + placeholder: const Text('联系方式(可选)'), + ), + ], + ), + ), + ), + actions: [ + ShadButton.outline( + child: const Text('取消'), + onPressed: () => Navigator.of(context).pop(), + ), + ShadButton( + child: const Text('提交'), + onPressed: () async { + if (formKey.currentState!.saveAndValidate()) { + Navigator.of(context).pop(); + final response = await provider.withdraw( + amount: amountController.text, + withdrawAddress: addressController.text, + withdrawContact: contactController.text.isNotEmpty ? contactController.text : null, + ); + if (mounted) { + _showResult( + response.success ? '申请成功' : '申请失败', + response.success ? '请等待管理员审批' : response.message, + ); + } + } + }, + ), + ], + ), ); } @@ -502,59 +746,7 @@ class _AssetPageState extends State with AutomaticKeepAliveClientMixi ); } - void _showActionDialog({ - required String title, - required String hint, - required Function(String) onSubmit, - }) { - final controller = TextEditingController(); - final formKey = GlobalKey(); - - showShadDialog( - context: context, - builder: (context) => ShadDialog( - title: Text(title), - child: ShadForm( - key: formKey, - child: ShadInputFormField( - id: 'amount', - controller: controller, - placeholder: Text(hint), - keyboardType: const TextInputType.numberWithOptions(decimal: true), - validator: (value) { - if (value == null || value.isEmpty) { - return '请输入金额'; - } - final amount = double.tryParse(value); - if (amount == null || amount <= 0) { - return '请输入有效金额'; - } - return null; - }, - ), - ), - actions: [ - ShadButton.outline( - child: const Text('取消'), - onPressed: () => Navigator.of(context).pop(), - ), - ShadButton( - child: const Text('确认'), - onPressed: () async { - if (formKey.currentState!.saveAndValidate()) { - Navigator.of(context).pop(); - onSubmit(controller.text); - } - }, - ), - ], - ), - ); - } - void _showResult(String title, String? message) { - final theme = ShadTheme.of(context); - showShadDialog( context: context, builder: (context) => ShadDialog.alert( diff --git a/flutter_monisuo/lib/ui/pages/home/home_page.dart b/flutter_monisuo/lib/ui/pages/home/home_page.dart index 359e426..de09048 100644 --- a/flutter_monisuo/lib/ui/pages/home/home_page.dart +++ b/flutter_monisuo/lib/ui/pages/home/home_page.dart @@ -391,9 +391,81 @@ class _HomePageState extends State with AutomaticKeepAliveClientMixin } void _showWithdraw() { - _showActionDialog('提现', '请输入提现金额(USDT)', (amount) { - context.read().withdraw(amount: amount); - }); + final amountController = TextEditingController(); + final addressController = TextEditingController(); + final contactController = TextEditingController(); + final formKey = GlobalKey(); + + showShadDialog( + context: context, + builder: (context) => ShadDialog( + title: const Text('提现'), + child: ShadForm( + key: formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ShadInputFormField( + id: 'amount', + placeholder: const Text('请输入提现金额(USDT)'), + controller: amountController, + keyboardType: const TextInputType.numberWithOptions(decimal: true), + validator: (value) { + if (value == null || value.isEmpty) { + return '请输入金额'; + } + final amount = double.tryParse(value); + if (amount == null || amount <= 0) { + return '请输入有效金额'; + } + return null; + }, + ), + const SizedBox(height: 12), + ShadInputFormField( + id: 'address', + placeholder: const Text('请输入提现地址'), + controller: addressController, + validator: (value) { + if (value == null || value.isEmpty) { + return '请输入提现地址'; + } + return null; + }, + ), + const SizedBox(height: 12), + ShadInputFormField( + id: 'contact', + placeholder: const Text('联系方式(可选)'), + controller: contactController, + ), + ], + ), + ), + actions: [ + ShadButton.outline( + child: const Text('取消'), + onPressed: () => Navigator.of(context).pop(), + ), + ShadButton( + child: const Text('确认'), + onPressed: () { + if (formKey.currentState!.saveAndValidate()) { + final amount = amountController.text.trim(); + final address = addressController.text.trim(); + final contact = contactController.text.trim(); + Navigator.of(context).pop(); + context.read().withdraw( + amount: amount, + withdrawAddress: address, + withdrawContact: contact.isEmpty ? null : contact, + ); + } + }, + ), + ], + ), + ); } void _showTransfer() { diff --git a/flutter_monisuo/lib/ui/pages/orders/fund_orders_page.dart b/flutter_monisuo/lib/ui/pages/orders/fund_orders_page.dart new file mode 100644 index 0000000..b51de29 --- /dev/null +++ b/flutter_monisuo/lib/ui/pages/orders/fund_orders_page.dart @@ -0,0 +1,403 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:shadcn_ui/shadcn_ui.dart'; +import 'package:provider/provider.dart'; +import '../../../providers/asset_provider.dart'; +import '../../../data/models/order_models.dart'; + +/// 充提订单页面 +class FundOrdersPage extends StatefulWidget { + const FundOrdersPage({super.key}); + + @override + State createState() => _FundOrdersPageState(); +} + +class _FundOrdersPageState extends State { + int _activeTab = 0; // 0=全部, 1=充值, 2=提现 + + // 颜色常量 + static const upColor = Color(0xFF00C853); + static const downColor = Color(0xFFFF5252); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addPostFrameCallback((_) { + _loadData(); + }); + } + + void _loadData() { + final type = _activeTab == 0 ? null : _activeTab; + context.read().loadFundOrders(type: type); + } + + @override + Widget build(BuildContext context) { + final theme = ShadTheme.of(context); + + return Scaffold( + backgroundColor: theme.colorScheme.background, + appBar: AppBar( + title: const Text('充提记录'), + backgroundColor: theme.colorScheme.background, + elevation: 0, + ), + body: Column( + children: [ + _buildTabs(), + Expanded(child: _buildOrderList()), + ], + ), + ); + } + + Widget _buildTabs() { + final theme = ShadTheme.of(context); + + return Container( + padding: const EdgeInsets.all(16), + child: Row( + children: [ + _buildTab('全部', 0), + const SizedBox(width: 12), + _buildTab('充值', 1), + const SizedBox(width: 12), + _buildTab('提现', 2), + ], + ), + ); + } + + Widget _buildTab(String label, int index) { + final theme = ShadTheme.of(context); + final isActive = _activeTab == index; + + return Expanded( + child: GestureDetector( + onTap: () { + if (_activeTab != index) { + setState(() => _activeTab = index); + _loadData(); + } + }, + child: Container( + padding: const EdgeInsets.symmetric(vertical: 12), + decoration: BoxDecoration( + color: isActive ? theme.colorScheme.primary : theme.colorScheme.card, + borderRadius: BorderRadius.circular(8), + border: Border.all( + color: isActive ? theme.colorScheme.primary : theme.colorScheme.border, + ), + ), + child: Center( + child: Text( + label, + style: TextStyle( + color: isActive ? Colors.white : theme.colorScheme.mutedForeground, + fontWeight: isActive ? FontWeight.w600 : FontWeight.normal, + ), + ), + ), + ), + ), + ); + } + + Widget _buildOrderList() { + return Consumer( + builder: (context, provider, _) { + final orders = provider.fundOrders; + final isLoading = provider.isLoadingOrders; + + if (isLoading) { + return const Center(child: CircularProgressIndicator()); + } + + if (orders.isEmpty) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + LucideIcons.inbox, + size: 64, + color: Colors.grey[400], + ), + const SizedBox(height: 16), + Text( + '暂无订单记录', + style: TextStyle(color: Colors.grey[600]), + ), + ], + ), + ); + } + + return RefreshIndicator( + onRefresh: () async => _loadData(), + child: ListView.separated( + padding: const EdgeInsets.all(16), + itemCount: orders.length, + separatorBuilder: (_, __) => const SizedBox(height: 12), + itemBuilder: (context, index) { + return _buildOrderCard(orders[index]); + }, + ), + ); + }, + ); + } + + Widget _buildOrderCard(OrderFund order) { + final theme = ShadTheme.of(context); + final isDeposit = order.isDeposit; + + return ShadCard( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), + decoration: BoxDecoration( + color: isDeposit + ? upColor.withValues(alpha: 0.1) + : downColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(4), + ), + child: Text( + order.typeText, + style: TextStyle( + color: isDeposit ? upColor : downColor, + fontSize: 12, + fontWeight: FontWeight.w600, + ), + ), + ), + const SizedBox(width: 8), + _buildStatusBadge(order), + ], + ), + Text( + order.orderNo, + style: const TextStyle(fontSize: 11, color: Colors.grey), + ), + ], + ), + const SizedBox(height: 12), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '${isDeposit ? '+' : '-'}${order.amount} USDT', + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.bold, + color: isDeposit ? upColor : downColor, + ), + ), + if (order.canCancel || order.canConfirmPay) + Row( + children: [ + if (order.canConfirmPay) + ShadButton.outline( + size: ShadButtonSize.sm, + onPressed: () => _confirmPay(order), + child: const Text('已打款'), + ), + if (order.canCancel) ...[ + const SizedBox(width: 8), + ShadButton.destructive( + size: ShadButtonSize.sm, + onPressed: () => _cancelOrder(order), + child: const Text('取消'), + ), + ], + ], + ), + ], + ), + const SizedBox(height: 12), + // 显示地址信息 + if (order.walletAddress != null) ...[ + const Divider(), + const SizedBox(height: 8), + Row( + children: [ + Text( + '${isDeposit ? '充值地址' : '提现地址'}: ', + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + Expanded( + child: Text( + order.walletAddress!, + style: const TextStyle(fontSize: 11, fontFamily: 'monospace'), + overflow: TextOverflow.ellipsis, + ), + ), + GestureDetector( + onTap: () { + Clipboard.setData(ClipboardData(text: order.walletAddress!)); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('地址已复制')), + ); + }, + child: Icon(LucideIcons.copy, size: 14, color: Colors.grey[600]), + ), + ], + ), + ], + if (order.withdrawContact != null) ...[ + const SizedBox(height: 4), + Text( + '联系方式: ${order.withdrawContact}', + style: const TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + const SizedBox(height: 8), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '创建: ${_formatTime(order.createTime)}', + style: const TextStyle(fontSize: 11, color: Colors.grey), + ), + if (order.rejectReason != null) + Expanded( + child: Text( + '驳回: ${order.rejectReason}', + style: const TextStyle(fontSize: 11, color: downColor), + textAlign: TextAlign.right, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + ], + ), + ); + } + + Widget _buildStatusBadge(OrderFund order) { + Color bgColor; + Color textColor; + + // 根据类型和状态设置颜色 + if (order.type == 1) { + // 充值状态 + switch (order.status) { + case 1: // 待付款 + case 2: // 待确认 + bgColor = Colors.orange.withValues(alpha: 0.1); + textColor = Colors.orange; + break; + case 3: // 已完成 + bgColor = upColor.withValues(alpha: 0.1); + textColor = upColor; + break; + default: // 已驳回/已取消 + bgColor = downColor.withValues(alpha: 0.1); + textColor = downColor; + } + } else { + // 提现状态 + switch (order.status) { + case 1: // 待审批 + bgColor = Colors.orange.withValues(alpha: 0.1); + textColor = Colors.orange; + break; + case 2: // 已完成 + bgColor = upColor.withValues(alpha: 0.1); + textColor = upColor; + break; + default: // 已驳回/已取消 + bgColor = downColor.withValues(alpha: 0.1); + textColor = downColor; + } + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2), + decoration: BoxDecoration( + color: bgColor, + borderRadius: BorderRadius.circular(4), + ), + child: Text( + order.statusText, + style: TextStyle(fontSize: 11, color: textColor), + ), + ); + } + + String _formatTime(DateTime? time) { + if (time == null) return '-'; + return '${time.year}-${time.month.toString().padLeft(2, '0')}-${time.day.toString().padLeft(2, '0')} ' + '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; + } + + void _confirmPay(OrderFund order) async { + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('确认已打款'), + content: const Text('确认您已完成向指定地址的转账?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('取消'), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('确认'), + ), + ], + ), + ); + + if (confirmed == true && mounted) { + final response = await context.read().confirmPay(order.orderNo); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(response.success ? '确认成功,请等待审核' : response.message ?? '确认失败')), + ); + } + } + } + + void _cancelOrder(OrderFund order) async { + final confirmed = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text('取消订单'), + content: Text('确定要取消订单 ${order.orderNo} 吗?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('返回'), + ), + TextButton( + onPressed: () => Navigator.pop(context, true), + style: TextButton.styleFrom(foregroundColor: downColor), + child: const Text('确定取消'), + ), + ], + ), + ); + + if (confirmed == true && mounted) { + final response = await context.read().cancelOrder(order.orderNo); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(response.success ? '订单已取消' : response.message ?? '取消失败')), + ); + } + } + } +} diff --git a/monisuo-admin/src/composables/use-auth.ts b/monisuo-admin/src/composables/use-auth.ts index 1002d24..18e0b8e 100644 --- a/monisuo-admin/src/composables/use-auth.ts +++ b/monisuo-admin/src/composables/use-auth.ts @@ -28,12 +28,16 @@ export function useAuth() { try { const result = await loginMutation.mutateAsync({ username, password }) + console.log('Login result:', result) if (result.code === '0000' && result.data) { + console.log('Setting token and adminInfo...') authStore.setToken(result.data.token) authStore.setAdminInfo(result.data.adminInfo) + console.log('isLogin after setToken:', authStore.isLogin) const redirect = router.currentRoute.value.query.redirect as string + console.log('Redirecting to:', redirect || '/monisuo/dashboard') if (!redirect || redirect.startsWith('//')) { toHome() } @@ -42,10 +46,12 @@ export function useAuth() { } } else { + console.log('Login failed:', result.code, result.msg) error.value = result.msg || '登录失败' } } catch (e: any) { + console.error('Login error:', e) error.value = e.response?.data?.msg || '网络错误,请稍后重试' } finally { diff --git a/monisuo-admin/src/pages/monisuo/orders.vue b/monisuo-admin/src/pages/monisuo/orders.vue index 59dd995..c355e41 100644 --- a/monisuo-admin/src/pages/monisuo/orders.vue +++ b/monisuo-admin/src/pages/monisuo/orders.vue @@ -107,20 +107,68 @@ function formatAmount(amount: number): string { return amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 }) } -function getStatusVariant(status: number): 'default' | 'secondary' | 'destructive' { - if (status === 1) - return 'secondary' - if (status === 2) - return 'default' - return 'destructive' +// 根据订单类型和状态获取状态样式 +function getStatusVariant(order: OrderFund): 'default' | 'secondary' | 'destructive' | 'outline' { + const { type, status } = order + // 充值状态: 1=待付款, 2=待确认, 3=已完成, 4=已驳回, 5=已取消 + // 提现状态: 1=待审批, 2=已完成, 3=已驳回, 4=已取消 + if (type === 1) { + // 充值 + if (status === 1) return 'secondary' // 待付款 + if (status === 2) return 'default' // 待确认 + if (status === 3) return 'default' // 已完成 + return 'destructive' // 已驳回/已取消 + } + else { + // 提现 + if (status === 1) return 'default' // 待审批 + if (status === 2) return 'default' // 已完成 + return 'destructive' // 已驳回/已取消 + } } -function getStatusText(status: number): string { - if (status === 1) - return '待审批' - if (status === 2) - return '已通过' - return '已驳回' +// 根据订单类型和状态获取状态文本 +function getStatusText(order: OrderFund): string { + const { type, status } = order + if (type === 1) { + // 充值状态 + switch (status) { + case 1: return '待付款' + case 2: return '待确认' + case 3: return '已完成' + case 4: return '已驳回' + case 5: return '已取消' + default: return '未知' + } + } + else { + // 提现状态 + switch (status) { + case 1: return '待审批' + case 2: return '已完成' + case 3: return '已驳回' + case 4: return '已取消' + default: return '未知' + } + } +} + +// 判断订单是否可审批 +// 充值: 仅待确认(status=2)可审批 +// 提现: 仅待审批(status=1)可审批 +function canApprove(order: OrderFund): boolean { + if (order.type === 1) { + return order.status === 2 // 充值待确认 + } + else { + return order.status === 1 // 提现待审批 + } +} + +// 复制到剪贴板 +function copyToClipboard(text: string) { + navigator.clipboard.writeText(text) + toast.success('已复制到剪贴板') } @@ -153,7 +201,11 @@ function getStatusText(status: number): string { 金额 + 状态 + @@ -163,12 +215,12 @@ function getStatusText(status: number): string { - + - +

暂无待审批订单

@@ -187,8 +239,27 @@ function getStatusText(status: number): string { ¥{{ formatAmount(order.amount) }} -