111
This commit is contained in:
Binary file not shown.
@@ -89,6 +89,9 @@ class ApiEndpoints {
|
|||||||
/// 取消订单
|
/// 取消订单
|
||||||
static const String cancelOrder = '/api/fund/cancel';
|
static const String cancelOrder = '/api/fund/cancel';
|
||||||
|
|
||||||
|
/// 获取可用提现网络列表
|
||||||
|
static const String walletNetworks = '/api/wallet/networks';
|
||||||
|
|
||||||
/// 获取充提记录
|
/// 获取充提记录
|
||||||
static const String fundOrders = '/api/fund/orders';
|
static const String fundOrders = '/api/fund/orders';
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ class OrderFund {
|
|||||||
// 提现状态: 1=待审批, 2=已完成, 3=已驳回, 4=已取消, 5=待财务审核
|
// 提现状态: 1=待审批, 2=已完成, 3=已驳回, 4=已取消, 5=待财务审核
|
||||||
final int? walletId; // 冷钱包ID(充值)
|
final int? walletId; // 冷钱包ID(充值)
|
||||||
final String? walletAddress; // 钱包地址(充值/提现)
|
final String? walletAddress; // 钱包地址(充值/提现)
|
||||||
|
final String? network; // 提现网络类型
|
||||||
final String? withdrawContact; // 提现联系方式
|
final String? withdrawContact; // 提现联系方式
|
||||||
final String remark;
|
final String remark;
|
||||||
final String? rejectReason;
|
final String? rejectReason;
|
||||||
@@ -102,6 +103,7 @@ class OrderFund {
|
|||||||
required this.status,
|
required this.status,
|
||||||
this.walletId,
|
this.walletId,
|
||||||
this.walletAddress,
|
this.walletAddress,
|
||||||
|
this.network,
|
||||||
this.withdrawContact,
|
this.withdrawContact,
|
||||||
required this.remark,
|
required this.remark,
|
||||||
this.rejectReason,
|
this.rejectReason,
|
||||||
@@ -124,6 +126,7 @@ class OrderFund {
|
|||||||
status: json['status'] as int? ?? 1,
|
status: json['status'] as int? ?? 1,
|
||||||
walletId: json['walletId'] as int?,
|
walletId: json['walletId'] as int?,
|
||||||
walletAddress: json['walletAddress'] as String?,
|
walletAddress: json['walletAddress'] as String?,
|
||||||
|
network: json['network'] as String?,
|
||||||
withdrawContact: json['withdrawContact'] as String?,
|
withdrawContact: json['withdrawContact'] as String?,
|
||||||
remark: json['remark']?.toString() ?? '',
|
remark: json['remark']?.toString() ?? '',
|
||||||
rejectReason: json['rejectReason'] as String?,
|
rejectReason: json['rejectReason'] as String?,
|
||||||
@@ -167,7 +170,7 @@ class OrderFund {
|
|||||||
case 1:
|
case 1:
|
||||||
return '待审批';
|
return '待审批';
|
||||||
case 2:
|
case 2:
|
||||||
return '已完成';
|
return '已出款';
|
||||||
case 3:
|
case 3:
|
||||||
return '已驳回';
|
return '已驳回';
|
||||||
case 4:
|
case 4:
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class FundService {
|
|||||||
Future<ApiResponse<Map<String, dynamic>>> withdraw({
|
Future<ApiResponse<Map<String, dynamic>>> withdraw({
|
||||||
required String amount,
|
required String amount,
|
||||||
required String withdrawAddress,
|
required String withdrawAddress,
|
||||||
|
String? network,
|
||||||
String? withdrawContact,
|
String? withdrawContact,
|
||||||
String? remark,
|
String? remark,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -59,12 +60,27 @@ class FundService {
|
|||||||
data: {
|
data: {
|
||||||
'amount': amount,
|
'amount': amount,
|
||||||
'withdrawAddress': withdrawAddress,
|
'withdrawAddress': withdrawAddress,
|
||||||
|
if (network != null) 'network': network,
|
||||||
if (withdrawContact != null) 'withdrawContact': withdrawContact,
|
if (withdrawContact != null) 'withdrawContact': withdrawContact,
|
||||||
if (remark != null) 'remark': remark,
|
if (remark != null) 'remark': remark,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取可用的提现网络列表
|
||||||
|
Future<ApiResponse<List<String>>> getWalletNetworks() async {
|
||||||
|
final response = await _client.get<List<dynamic>>(
|
||||||
|
ApiEndpoints.walletNetworks,
|
||||||
|
);
|
||||||
|
if (response.success && response.data != null) {
|
||||||
|
return ApiResponse.success(
|
||||||
|
response.data!.cast<String>(),
|
||||||
|
response.message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return ApiResponse.fail(response.message ?? '获取网络列表失败');
|
||||||
|
}
|
||||||
|
|
||||||
/// 取消订单
|
/// 取消订单
|
||||||
Future<ApiResponse<void>> cancelOrder(String orderNo) async {
|
Future<ApiResponse<void>> cancelOrder(String orderNo) async {
|
||||||
return _client.post<void>(
|
return _client.post<void>(
|
||||||
|
|||||||
@@ -185,6 +185,7 @@ class AssetProvider extends ChangeNotifier {
|
|||||||
Future<ApiResponse<Map<String, dynamic>>> withdraw({
|
Future<ApiResponse<Map<String, dynamic>>> withdraw({
|
||||||
required String amount,
|
required String amount,
|
||||||
required String withdrawAddress,
|
required String withdrawAddress,
|
||||||
|
String? network,
|
||||||
String? withdrawContact,
|
String? withdrawContact,
|
||||||
String? remark,
|
String? remark,
|
||||||
}) async {
|
}) async {
|
||||||
@@ -192,6 +193,7 @@ class AssetProvider extends ChangeNotifier {
|
|||||||
final response = await _fundService.withdraw(
|
final response = await _fundService.withdraw(
|
||||||
amount: amount,
|
amount: amount,
|
||||||
withdrawAddress: withdrawAddress,
|
withdrawAddress: withdrawAddress,
|
||||||
|
network: network,
|
||||||
withdrawContact: withdrawContact,
|
withdrawContact: withdrawContact,
|
||||||
remark: remark,
|
remark: remark,
|
||||||
);
|
);
|
||||||
@@ -244,6 +246,19 @@ class AssetProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 获取可用提现网络列表
|
||||||
|
Future<List<String>> getWalletNetworks() async {
|
||||||
|
try {
|
||||||
|
final response = await _fundService.getWalletNetworks();
|
||||||
|
if (response.success) {
|
||||||
|
return response.data ?? [];
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (_) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 刷新所有资产数据
|
/// 刷新所有资产数据
|
||||||
Future<void> refreshAll({bool force = false}) async {
|
Future<void> refreshAll({bool force = false}) async {
|
||||||
await Future.wait([
|
await Future.wait([
|
||||||
|
|||||||
@@ -926,6 +926,8 @@ void _showWithdrawDialog(BuildContext context, String? balance) {
|
|||||||
final formKey = GlobalKey<ShadFormState>();
|
final formKey = GlobalKey<ShadFormState>();
|
||||||
final feeNotifier = ValueNotifier<String>('提现将扣除10%手续费');
|
final feeNotifier = ValueNotifier<String>('提现将扣除10%手续费');
|
||||||
final colorScheme = Theme.of(context).colorScheme;
|
final colorScheme = Theme.of(context).colorScheme;
|
||||||
|
final networksNotifier = ValueNotifier<List<String>>([]);
|
||||||
|
final selectedNetworkNotifier = ValueNotifier<String?>(null);
|
||||||
|
|
||||||
amountController.addListener(() {
|
amountController.addListener(() {
|
||||||
final amount = double.tryParse(amountController.text) ?? 0;
|
final amount = double.tryParse(amountController.text) ?? 0;
|
||||||
@@ -938,211 +940,261 @@ void _showWithdrawDialog(BuildContext context, String? balance) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 获取网络列表
|
||||||
|
context.read<AssetProvider>().getWalletNetworks().then((list) {
|
||||||
|
networksNotifier.value = list;
|
||||||
|
if (list.isNotEmpty) {
|
||||||
|
selectedNetworkNotifier.value = list.first;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
showShadDialog(
|
showShadDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (ctx) => Dialog(
|
builder: (ctx) => StatefulBuilder(
|
||||||
backgroundColor: Colors.transparent,
|
builder: (ctx, setState) => Dialog(
|
||||||
child: GlassPanel(
|
backgroundColor: Colors.transparent,
|
||||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
child: GlassPanel(
|
||||||
padding: EdgeInsets.all(AppSpacing.lg),
|
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||||
child: SingleChildScrollView(
|
padding: EdgeInsets.all(AppSpacing.lg),
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
mainAxisSize: MainAxisSize.min,
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
Row(
|
children: [
|
||||||
children: [
|
Row(
|
||||||
Container(
|
children: [
|
||||||
padding: EdgeInsets.all(AppSpacing.sm),
|
Container(
|
||||||
decoration: BoxDecoration(
|
padding: EdgeInsets.all(AppSpacing.sm),
|
||||||
color: colorScheme.primary.withOpacity(0.1),
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
color: colorScheme.primary.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||||
|
),
|
||||||
|
child: Icon(
|
||||||
|
LucideIcons.wallet,
|
||||||
|
color: colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Icon(
|
SizedBox(width: AppSpacing.sm),
|
||||||
LucideIcons.wallet,
|
Text(
|
||||||
color: colorScheme.primary,
|
'提现 (Withdraw)',
|
||||||
|
style: GoogleFonts.spaceGrotesk(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: colorScheme.onSurface,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
SizedBox(height: AppSpacing.xs),
|
||||||
|
Text(
|
||||||
|
'Securely transfer your assets to an external wallet address.',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
),
|
),
|
||||||
SizedBox(width: AppSpacing.sm),
|
),
|
||||||
Text(
|
if (balance != null) ...[
|
||||||
'提现 (Withdraw)',
|
SizedBox(height: AppSpacing.md),
|
||||||
style: GoogleFonts.spaceGrotesk(
|
Container(
|
||||||
fontSize: 16,
|
padding: EdgeInsets.symmetric(
|
||||||
fontWeight: FontWeight.bold,
|
horizontal: AppSpacing.md,
|
||||||
color: colorScheme.onSurface,
|
vertical: AppSpacing.sm,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: AppColorScheme.up.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||||
|
border: Border.all(
|
||||||
|
color: AppColorScheme.up.withOpacity(0.2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'可用余额: ',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
letterSpacing: 0.1,
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'$balance USDT',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: AppColorScheme.up,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
SizedBox(height: AppSpacing.lg),
|
||||||
SizedBox(height: AppSpacing.xs),
|
ShadForm(
|
||||||
Text(
|
key: formKey,
|
||||||
'Securely transfer your assets to an external wallet address.',
|
child: Column(
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 12,
|
|
||||||
color: colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (balance != null) ...[
|
|
||||||
SizedBox(height: AppSpacing.md),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: AppSpacing.md,
|
|
||||||
vertical: AppSpacing.sm,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: AppColorScheme.up.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
|
||||||
border: Border.all(
|
|
||||||
color: AppColorScheme.up.withOpacity(0.2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
Text(
|
ShadInputFormField(
|
||||||
'可用余额: ',
|
id: 'amount',
|
||||||
style: TextStyle(
|
controller: amountController,
|
||||||
fontSize: 10,
|
label: const Text('提现金额'),
|
||||||
letterSpacing: 0.1,
|
placeholder: const Text('请输入提现金额(USDT)'),
|
||||||
color: colorScheme.onSurfaceVariant,
|
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
validator: Validators.amount,
|
||||||
|
),
|
||||||
|
SizedBox(height: AppSpacing.xs),
|
||||||
|
Container(
|
||||||
|
padding: EdgeInsets.symmetric(
|
||||||
|
horizontal: AppSpacing.md,
|
||||||
|
vertical: AppSpacing.sm,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orange.withOpacity(0.1),
|
||||||
|
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||||
|
border: Border.all(color: Colors.orange.withOpacity(0.3)),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Icon(Icons.info_outline, size: 14, color: Colors.orange),
|
||||||
|
SizedBox(width: AppSpacing.xs),
|
||||||
|
Expanded(
|
||||||
|
child: ValueListenableBuilder<String>(
|
||||||
|
valueListenable: feeNotifier,
|
||||||
|
builder: (_, text, __) => Text(
|
||||||
|
text,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 11,
|
||||||
|
color: Colors.orange.shade800,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
SizedBox(height: AppSpacing.md),
|
||||||
'$balance USDT',
|
// 提现网络选择
|
||||||
style: TextStyle(
|
ValueListenableBuilder<List<String>>(
|
||||||
fontSize: 12,
|
valueListenable: networksNotifier,
|
||||||
fontWeight: FontWeight.bold,
|
builder: (_, networks, __) {
|
||||||
color: AppColorScheme.up,
|
if (networks.isEmpty) return const SizedBox.shrink();
|
||||||
),
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'提现网络',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
child: ValueListenableBuilder<String?>(
|
||||||
|
valueListenable: selectedNetworkNotifier,
|
||||||
|
builder: (_, selected, __) => ShadSelect<String>(
|
||||||
|
initialValue: selected ?? networks.first,
|
||||||
|
placeholder: const Text('选择提现网络'),
|
||||||
|
onChanged: (value) {
|
||||||
|
selectedNetworkNotifier.value = value;
|
||||||
|
},
|
||||||
|
selectedOptionBuilder: (context, value) => Text(value),
|
||||||
|
options: networks.map((network) => ShadOption<String>(
|
||||||
|
value: network,
|
||||||
|
child: Text(network),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
SizedBox(height: AppSpacing.md),
|
||||||
|
ShadInputFormField(
|
||||||
|
id: 'address',
|
||||||
|
controller: addressController,
|
||||||
|
label: const Text('目标地址'),
|
||||||
|
placeholder: const Text('请输入提现地址'),
|
||||||
|
validator: (v) => Validators.required(v, '提现地址'),
|
||||||
|
),
|
||||||
|
SizedBox(height: AppSpacing.md),
|
||||||
|
ShadInputFormField(
|
||||||
|
id: 'contact',
|
||||||
|
controller: contactController,
|
||||||
|
label: const Text('联系方式(可选)'),
|
||||||
|
placeholder: const Text('联系方式'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
SizedBox(height: AppSpacing.lg),
|
||||||
SizedBox(height: AppSpacing.lg),
|
Row(
|
||||||
ShadForm(
|
|
||||||
key: formKey,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
children: [
|
||||||
ShadInputFormField(
|
Expanded(
|
||||||
id: 'amount',
|
child: NeonButton(
|
||||||
controller: amountController,
|
text: '取消',
|
||||||
label: const Text('提现金额'),
|
type: NeonButtonType.outline,
|
||||||
placeholder: const Text('请输入提现金额(USDT)'),
|
onPressed: () => Navigator.of(ctx).pop(),
|
||||||
keyboardType: const TextInputType.numberWithOptions(decimal: true),
|
height: 44,
|
||||||
validator: Validators.amount,
|
showGlow: false,
|
||||||
),
|
|
||||||
SizedBox(height: AppSpacing.xs),
|
|
||||||
Container(
|
|
||||||
padding: EdgeInsets.symmetric(
|
|
||||||
horizontal: AppSpacing.md,
|
|
||||||
vertical: AppSpacing.sm,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Colors.orange.withOpacity(0.1),
|
|
||||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
|
||||||
border: Border.all(color: Colors.orange.withOpacity(0.3)),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.info_outline, size: 14, color: Colors.orange),
|
|
||||||
SizedBox(width: AppSpacing.xs),
|
|
||||||
Expanded(
|
|
||||||
child: ValueListenableBuilder<String>(
|
|
||||||
valueListenable: feeNotifier,
|
|
||||||
builder: (_, text, __) => Text(
|
|
||||||
text,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 11,
|
|
||||||
color: Colors.orange.shade800,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
SizedBox(height: AppSpacing.md),
|
SizedBox(width: AppSpacing.sm),
|
||||||
ShadInputFormField(
|
Expanded(
|
||||||
id: 'address',
|
child: NeonButton(
|
||||||
controller: addressController,
|
text: '提交',
|
||||||
label: const Text('目标地址'),
|
type: NeonButtonType.primary,
|
||||||
placeholder: const Text('请输入提现地址'),
|
onPressed: () async {
|
||||||
validator: (v) => Validators.required(v, '提现地址'),
|
if (formKey.currentState!.saveAndValidate()) {
|
||||||
),
|
Navigator.of(ctx).pop();
|
||||||
SizedBox(height: AppSpacing.md),
|
final response = await context.read<AssetProvider>().withdraw(
|
||||||
ShadInputFormField(
|
amount: amountController.text,
|
||||||
id: 'contact',
|
withdrawAddress: addressController.text,
|
||||||
controller: contactController,
|
network: selectedNetworkNotifier.value,
|
||||||
label: const Text('联系方式(可选)'),
|
withdrawContact: contactController.text.isNotEmpty
|
||||||
placeholder: const Text('联系方式'),
|
? contactController.text
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
_showResultDialog(
|
||||||
|
context,
|
||||||
|
response.success ? '申请成功' : '申请失败',
|
||||||
|
response.success ? '请等待管理员审批' : response.message,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
height: 44,
|
||||||
|
showGlow: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: AppSpacing.md),
|
||||||
SizedBox(height: AppSpacing.lg),
|
Row(
|
||||||
Row(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Icon(
|
||||||
child: NeonButton(
|
Icons.verified_user,
|
||||||
text: '取消',
|
size: 12,
|
||||||
type: NeonButtonType.outline,
|
|
||||||
onPressed: () => Navigator.of(ctx).pop(),
|
|
||||||
height: 44,
|
|
||||||
showGlow: false,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(width: AppSpacing.sm),
|
|
||||||
Expanded(
|
|
||||||
child: NeonButton(
|
|
||||||
text: '提交',
|
|
||||||
type: NeonButtonType.primary,
|
|
||||||
onPressed: () async {
|
|
||||||
if (formKey.currentState!.saveAndValidate()) {
|
|
||||||
Navigator.of(ctx).pop();
|
|
||||||
final response = await context.read<AssetProvider>().withdraw(
|
|
||||||
amount: amountController.text,
|
|
||||||
withdrawAddress: addressController.text,
|
|
||||||
withdrawContact: contactController.text.isNotEmpty
|
|
||||||
? contactController.text
|
|
||||||
: null,
|
|
||||||
);
|
|
||||||
if (context.mounted) {
|
|
||||||
_showResultDialog(
|
|
||||||
context,
|
|
||||||
response.success ? '申请成功' : '申请失败',
|
|
||||||
response.success ? '请等待管理员审批' : response.message,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
height: 44,
|
|
||||||
showGlow: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
SizedBox(height: AppSpacing.md),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.verified_user,
|
|
||||||
size: 12,
|
|
||||||
color: colorScheme.onSurfaceVariant.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
SizedBox(width: AppSpacing.xs),
|
|
||||||
Text(
|
|
||||||
'End-to-End Encrypted Transaction',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 10,
|
|
||||||
letterSpacing: 0.1,
|
|
||||||
color: colorScheme.onSurfaceVariant.withOpacity(0.5),
|
color: colorScheme.onSurfaceVariant.withOpacity(0.5),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(width: AppSpacing.xs),
|
||||||
],
|
Text(
|
||||||
),
|
'End-to-End Encrypted Transaction',
|
||||||
],
|
style: TextStyle(
|
||||||
|
fontSize: 10,
|
||||||
|
letterSpacing: 0.1,
|
||||||
|
color: colorScheme.onSurfaceVariant.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ class _FundOrderCard extends StatelessWidget {
|
|||||||
case 1:
|
case 1:
|
||||||
return '待审批';
|
return '待审批';
|
||||||
case 2:
|
case 2:
|
||||||
return '已完成';
|
return '已出款';
|
||||||
case 3:
|
case 3:
|
||||||
return '已驳回';
|
return '已驳回';
|
||||||
case 4:
|
case 4:
|
||||||
|
|||||||
@@ -278,6 +278,34 @@ class _FundOrdersPageState extends State<FundOrdersPage> {
|
|||||||
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
// 提现详情
|
||||||
|
if (!isDeposit) ...[
|
||||||
|
if (order.fee != null) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text('手续费(10%): ', style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||||
|
Text('-${order.fee} USDT', style: const TextStyle(fontSize: 12)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (order.receivableAmount != null) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Text('到账金额: ', style: TextStyle(fontSize: 12, color: Colors.grey)),
|
||||||
|
Text('${order.receivableAmount} USDT', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.bold)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
if (order.network != null) ...[
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
'提现网络: ${order.network}',
|
||||||
|
style: const TextStyle(fontSize: 12, color: Colors.grey),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
|||||||
@@ -16,151 +16,191 @@ const isLoading = computed(() => overviewLoading.value || cashFlowLoading.value)
|
|||||||
const overview = computed(() => overviewData.value?.data)
|
const overview = computed(() => overviewData.value?.data)
|
||||||
const cashFlow = computed(() => cashFlowData.value?.data || [])
|
const cashFlow = computed(() => cashFlowData.value?.data || [])
|
||||||
|
|
||||||
// ========== 模块1: 资金概览 ==========
|
// ========== 工具函数 ==========
|
||||||
const fundMetrics = computed(() => [
|
|
||||||
{
|
|
||||||
label: '在管资金',
|
|
||||||
value: overview.value?.fundBalance || 0,
|
|
||||||
icon: 'lucide:wallet',
|
|
||||||
color: 'text-blue-600',
|
|
||||||
bgColor: 'bg-blue-50 dark:bg-blue-950',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '交易账户',
|
|
||||||
value: overview.value?.tradeValue || 0,
|
|
||||||
icon: 'lucide:bar-chart-3',
|
|
||||||
color: 'text-purple-600',
|
|
||||||
bgColor: 'bg-purple-50 dark:bg-purple-950',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '总资产',
|
|
||||||
value: (overview.value?.fundBalance || 0) + (overview.value?.tradeValue || 0),
|
|
||||||
icon: 'lucide:landmark',
|
|
||||||
color: 'text-orange-600',
|
|
||||||
bgColor: 'bg-orange-50 dark:bg-orange-950',
|
|
||||||
},
|
|
||||||
])
|
|
||||||
|
|
||||||
// ========== 模块2: 资金流动 ==========
|
function formatCurrency(value: number | undefined): string {
|
||||||
function calcGrowthRate(current: number, previous: number): string {
|
const v = value || 0
|
||||||
if (previous === 0)
|
if (v >= 100_000_000)
|
||||||
return current > 0 ? '+100%' : '0%'
|
return `${(v / 100_000_000).toFixed(2)}亿`
|
||||||
const rate = new Decimal(current).minus(previous).div(previous).mul(100).toDecimalPlaces(1)
|
if (v >= 10_000)
|
||||||
const sign = rate.gte(0) ? '+' : ''
|
return `${(v / 10_000).toFixed(1)}万`
|
||||||
return `{sign}{rate}%`
|
return v.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const flowMetrics = computed(() => {
|
function calcGrowthRate(current: number, previous: number): { text: string, up: boolean } {
|
||||||
const flow = cashFlow.value
|
if (previous === 0) {
|
||||||
const len = flow.length
|
return current > 0 ? { text: '+100%', up: true } : { text: '0%', up: true }
|
||||||
const thisMonth = len >= 1 ? flow[len - 1] : null
|
}
|
||||||
const lastMonth = len >= 2 ? flow[len - 2] : null
|
const rate = new Decimal(current).minus(previous).div(previous).mul(100).toDecimalPlaces(1)
|
||||||
|
const sign = rate.gte(0) ? '+' : ''
|
||||||
|
return { text: `${sign}${rate}%`, up: rate.gte(0) }
|
||||||
|
}
|
||||||
|
|
||||||
const depositTrend = thisMonth && lastMonth
|
// ========== 第一层:核心 KPI ==========
|
||||||
? calcGrowthRate(thisMonth.deposit as number, lastMonth.deposit as number)
|
|
||||||
: '+0%'
|
const kpiItems = computed(() => {
|
||||||
const withdrawTrend = thisMonth && lastMonth
|
const o = overview.value
|
||||||
? calcGrowthRate(thisMonth.withdraw as number, lastMonth.withdraw as number)
|
if (!o) return []
|
||||||
: '+0%'
|
|
||||||
const netInflowTrend = thisMonth && lastMonth
|
const netInflow = (o.totalDeposit || 0) - (o.totalWithdraw || 0)
|
||||||
? calcGrowthRate(thisMonth.netInflow as number, lastMonth.netInflow as number)
|
const depositGrowth = calcGrowthRate(o.monthlyDeposit, o.lastMonthDeposit)
|
||||||
: '+0%'
|
const withdrawGrowth = calcGrowthRate(o.monthlyWithdraw, o.lastMonthWithdraw)
|
||||||
|
|
||||||
|
// 净流入环比用充值环比近似(实际出款与提现趋势一致)
|
||||||
|
const netMonthGrowth = calcGrowthRate(
|
||||||
|
o.monthlyDeposit - o.monthlyWithdraw,
|
||||||
|
o.lastMonthDeposit - o.lastMonthWithdraw,
|
||||||
|
)
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
label: '累计充值',
|
label: '累计充值',
|
||||||
value: overview.value?.totalDeposit || 0,
|
value: o.totalDeposit,
|
||||||
icon: 'lucide:arrow-down-circle',
|
icon: 'lucide:arrow-down-to-line',
|
||||||
color: 'text-green-600',
|
color: 'text-emerald-600',
|
||||||
bgColor: 'bg-green-50 dark:bg-green-950',
|
bgColor: 'bg-emerald-50 dark:bg-emerald-950/40',
|
||||||
trend: depositTrend,
|
growth: depositGrowth,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '累计提现',
|
label: '累计提现',
|
||||||
value: overview.value?.totalWithdraw || 0,
|
value: o.totalWithdraw,
|
||||||
icon: 'lucide:arrow-up-circle',
|
icon: 'lucide:arrow-up-from-line',
|
||||||
color: 'text-red-600',
|
color: 'text-red-500',
|
||||||
bgColor: 'bg-red-50 dark:bg-red-950',
|
bgColor: 'bg-red-50 dark:bg-red-950/40',
|
||||||
trend: withdrawTrend,
|
growth: withdrawGrowth,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '实际出款',
|
||||||
|
value: o.totalActualPayout,
|
||||||
|
icon: 'lucide:banknote',
|
||||||
|
color: 'text-amber-600',
|
||||||
|
bgColor: 'bg-amber-50 dark:bg-amber-950/40',
|
||||||
|
growth: withdrawGrowth,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '净流入',
|
label: '净流入',
|
||||||
value: (overview.value?.totalDeposit || 0) - (overview.value?.totalWithdraw || 0),
|
value: netInflow,
|
||||||
icon: 'lucide:trending-up',
|
icon: 'lucide:trending-up',
|
||||||
color: 'text-emerald-600',
|
color: 'text-sky-600',
|
||||||
bgColor: 'bg-emerald-50 dark:bg-emerald-950',
|
bgColor: 'bg-sky-50 dark:bg-sky-950/40',
|
||||||
trend: netInflowTrend,
|
growth: netMonthGrowth,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
// ========== 模块3: 资金趋势图 ==========
|
// ========== 第二层:资金趋势图 ==========
|
||||||
|
|
||||||
const trendChartOption = computed(() => ({
|
const trendChartOption = computed(() => ({
|
||||||
tooltip: { trigger: 'axis' },
|
tooltip: {
|
||||||
legend: { data: ['充值', '提现'], bottom: 0, top: 'auto' },
|
trigger: 'axis',
|
||||||
grid: { left: '3%', right: '4%', bottom: '15%', top: '5%', containLabel: true },
|
formatter(params: any) {
|
||||||
|
const items = Array.isArray(params) ? params : [params]
|
||||||
|
const lines = items.map((p: any) =>
|
||||||
|
`${p.marker} ${p.seriesName}: USDT ${Number(p.value).toLocaleString()}`,
|
||||||
|
)
|
||||||
|
return `<div style="font-size:12px"><b>${items[0].axisValue}</b><br/>${lines.join('<br/>')}</div>`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: { data: ['充值', '提现'], bottom: 0 },
|
||||||
|
grid: { left: '3%', right: '4%', bottom: '14%', top: '8%', containLabel: true },
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: 'category',
|
type: 'category',
|
||||||
data: cashFlow.value.map((t: any) => t.month),
|
data: cashFlow.value.map((t: any) => t.month),
|
||||||
|
axisTick: { show: false },
|
||||||
|
axisLine: { lineStyle: { color: '#e5e7eb' } },
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: 'value',
|
type: 'value',
|
||||||
axisLabel: { formatter: 'USDT{value}K' },
|
axisLabel: {
|
||||||
|
formatter(value: number) {
|
||||||
|
if (value >= 10_000) return `${(value / 10_000).toFixed(0)}万`
|
||||||
|
return `${value}`
|
||||||
|
},
|
||||||
|
},
|
||||||
|
splitLine: { lineStyle: { color: '#f3f4f6', type: 'dashed' } },
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '充值',
|
name: '充值',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
data: cashFlow.value.map((t: any) => t.deposit),
|
data: cashFlow.value.map((t: any) => t.deposit),
|
||||||
itemStyle: { color: '#10b981' },
|
itemStyle: { color: '#10b981' },
|
||||||
areaStyle: { color: 'rgba(16, 185, 129, 0.1)' },
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(16, 185, 129, 0.25)' },
|
||||||
|
{ offset: 1, color: 'rgba(16, 185, 129, 0.02)' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '提现',
|
name: '提现',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
smooth: true,
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
data: cashFlow.value.map((t: any) => t.withdraw),
|
data: cashFlow.value.map((t: any) => t.withdraw),
|
||||||
itemStyle: { color: '#ef4444' },
|
itemStyle: { color: '#ef4444' },
|
||||||
areaStyle: { color: 'rgba(239, 68, 68, 0.1)' },
|
areaStyle: {
|
||||||
|
color: {
|
||||||
|
type: 'linear', x: 0, y: 0, x2: 0, y2: 1,
|
||||||
|
colorStops: [
|
||||||
|
{ offset: 0, color: 'rgba(239, 68, 68, 0.25)' },
|
||||||
|
{ offset: 1, color: 'rgba(239, 68, 68, 0.02)' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ========== 模块4: 资金分布 ==========
|
// ========== 第二层:资产状态 ==========
|
||||||
const distributionOption = computed(() => {
|
|
||||||
const fundBalance = overview.value?.fundBalance || 0
|
|
||||||
const tradeValue = overview.value?.tradeValue || 0
|
|
||||||
|
|
||||||
return {
|
const assetMetrics = computed(() => {
|
||||||
tooltip: { trigger: 'item', formatter: '{b}: {d}%' },
|
const o = overview.value
|
||||||
legend: { orient: 'vertical', right: '5%', top: 'center' },
|
if (!o) return []
|
||||||
series: [{
|
const totalAssets = (o.fundBalance || 0) + (o.tradeValue || 0)
|
||||||
type: 'pie',
|
|
||||||
radius: ['50%', '75%'],
|
return [
|
||||||
center: ['35%', '50%'],
|
{
|
||||||
avoidLabelOverlap: false,
|
label: '平台总资产',
|
||||||
itemStyle: { borderRadius: 8, borderColor: '#fff', borderWidth: 2 },
|
value: totalAssets,
|
||||||
label: { show: true, position: 'inside', formatter: '{d}%', fontSize: 12 },
|
icon: 'lucide:landmark',
|
||||||
data: [
|
color: 'text-violet-600',
|
||||||
{ value: fundBalance, name: '在管资金', itemStyle: { color: '#3b82f6' } },
|
},
|
||||||
{ value: tradeValue, name: '交易账户', itemStyle: { color: '#8b5cf6' } },
|
{
|
||||||
],
|
label: '在管资金',
|
||||||
}],
|
value: o.fundBalance,
|
||||||
}
|
icon: 'lucide:wallet',
|
||||||
|
color: 'text-blue-600',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '冻结中',
|
||||||
|
value: o.totalFrozen,
|
||||||
|
icon: 'lucide:lock',
|
||||||
|
color: 'text-slate-500',
|
||||||
|
},
|
||||||
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
// ========== 模块5: 运营指标 ==========
|
// ========== 第三层:运营快报 ==========
|
||||||
const operationMetrics = computed(() => [
|
|
||||||
{ label: '用户总数', value: overview.value?.userCount || 0, icon: 'lucide:users' },
|
|
||||||
{ label: '待审批', value: overview.value?.pendingCount || 0, icon: 'lucide:clock' },
|
|
||||||
])
|
|
||||||
|
|
||||||
function formatCurrency(value: number): string {
|
const operationMetrics = computed(() => {
|
||||||
if (value >= 10000)
|
const o = overview.value
|
||||||
return `USDT{(value / 10000).toFixed(1)}万`
|
if (!o) return []
|
||||||
return `USDT{value.toLocaleString()}`
|
|
||||||
}
|
return [
|
||||||
|
{ label: '用户总数', value: o.userCount, icon: 'lucide:users', color: 'text-blue-600', bgColor: 'bg-blue-50 dark:bg-blue-950/40' },
|
||||||
|
{ label: '今日活跃', value: o.todayActiveUsers, icon: 'lucide:activity', color: 'text-green-600', bgColor: 'bg-green-50 dark:bg-green-950/40' },
|
||||||
|
{ label: '本月新增', value: o.monthNewUsers, icon: 'lucide:user-plus', color: 'text-purple-600', bgColor: 'bg-purple-50 dark:bg-purple-950/40' },
|
||||||
|
{ label: '待审批', value: o.pendingCount, icon: 'lucide:clock', color: o.pendingCount > 0 ? 'text-amber-600' : 'text-slate-500', bgColor: o.pendingCount > 0 ? 'bg-amber-50 dark:bg-amber-950/40' : 'bg-slate-50 dark:bg-slate-950/40' },
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
function navigateTo(path: string) {
|
function navigateTo(path: string) {
|
||||||
router.push(path)
|
router.push(path)
|
||||||
@@ -168,126 +208,114 @@ function navigateTo(path: string) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<BasicPage title="数据看板" description="核心业务数据一目了然">
|
<BasicPage title="数据看板" description="核心业务数据一览">
|
||||||
<div v-if="isLoading" class="flex items-center justify-center py-20">
|
<div v-if="isLoading" class="flex items-center justify-center py-20">
|
||||||
<UiSpinner class="w-8 h-8" />
|
<UiSpinner class="w-8 h-8" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-else class="grid gap-6">
|
<div v-else class="grid gap-6">
|
||||||
<!-- 模块1: 资金概览 -->
|
<!-- 第一层:核心 KPI 横幅 -->
|
||||||
<section class="space-y-3">
|
<section>
|
||||||
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-4">
|
||||||
<Icon icon="lucide:wallet" class="size-4" />
|
<UiCard v-for="item in kpiItems" :key="item.label" class="hover:shadow-sm transition-shadow">
|
||||||
资金概览
|
<UiCardContent class="p-5">
|
||||||
</h2>
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div class="grid gap-3 grid-cols-1 sm:grid-cols-3">
|
|
||||||
<UiCard v-for="item in fundMetrics" :key="item.label" class="hover:shadow-sm transition-shadow">
|
|
||||||
<UiCardContent class="p-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="p-2 rounded-lg" :class="[item.bgColor]">
|
<div class="p-2 rounded-lg" :class="[item.bgColor]">
|
||||||
<Icon :icon="item.icon" class="size-4" :class="item.color" />
|
<Icon :icon="item.icon" class="size-4" :class="item.color" />
|
||||||
</div>
|
</div>
|
||||||
|
<UiBadge
|
||||||
|
:variant="item.growth.up ? 'default' : 'destructive'"
|
||||||
|
class="text-xs font-mono"
|
||||||
|
>
|
||||||
|
{{ item.growth.text }}
|
||||||
|
</UiBadge>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 space-y-1">
|
<p class="text-xs text-muted-foreground mb-1">
|
||||||
<p class="text-xs text-muted-foreground">
|
{{ item.label }}
|
||||||
{{ item.label }}
|
</p>
|
||||||
</p>
|
<p class="text-xl font-bold font-mono truncate" :class="item.color">
|
||||||
<p class="text-lg sm:text-xl font-bold font-mono truncate" :class="item.color">
|
{{ formatCurrency(item.value) }}
|
||||||
{{ formatCurrency(item.value) }}
|
</p>
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</UiCardContent>
|
</UiCardContent>
|
||||||
</UiCard>
|
</UiCard>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 模块2: 资金流动 -->
|
<!-- 第二层:资金趋势 + 资产状态 -->
|
||||||
<section class="space-y-3">
|
<div class="grid gap-6 lg:grid-cols-5">
|
||||||
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
<!-- 资金趋势图 -->
|
||||||
<Icon icon="lucide:git-compare" class="size-4" />
|
<section class="lg:col-span-3">
|
||||||
资金流动
|
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2 mb-3">
|
||||||
</h2>
|
|
||||||
<div class="grid gap-3 grid-cols-1 sm:grid-cols-3">
|
|
||||||
<UiCard v-for="item in flowMetrics" :key="item.label" class="hover:shadow-sm transition-shadow">
|
|
||||||
<UiCardContent class="p-4">
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div class="p-2 rounded-lg" :class="[item.bgColor]">
|
|
||||||
<Icon :icon="item.icon" class="size-4" :class="item.color" />
|
|
||||||
</div>
|
|
||||||
<span class="text-xs font-medium text-green-600">{{ item.trend }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mt-3 space-y-1">
|
|
||||||
<p class="text-xs text-muted-foreground">
|
|
||||||
{{ item.label }}
|
|
||||||
</p>
|
|
||||||
<p class="text-lg sm:text-xl font-bold font-mono truncate" :class="item.color">
|
|
||||||
{{ formatCurrency(item.value) }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</UiCardContent>
|
|
||||||
</UiCard>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<!-- 模块3+4: 图表区域 -->
|
|
||||||
<div class="grid gap-6 lg:grid-cols-2">
|
|
||||||
<!-- 资金趋势 -->
|
|
||||||
<section class="space-y-3">
|
|
||||||
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
|
||||||
<Icon icon="lucide:trending-up" class="size-4" />
|
<Icon icon="lucide:trending-up" class="size-4" />
|
||||||
资金趋势
|
资金流动趋势(近6月)
|
||||||
</h2>
|
</h2>
|
||||||
<UiCard>
|
<UiCard>
|
||||||
<UiCardContent class="p-4">
|
<UiCardContent class="p-4">
|
||||||
<VChart :option="trendChartOption" autoresize style="height: 240px" />
|
<VChart :option="trendChartOption" autoresize style="height: 280px" />
|
||||||
</UiCardContent>
|
</UiCardContent>
|
||||||
</UiCard>
|
</UiCard>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 资金分布 -->
|
<!-- 资产状态面板 -->
|
||||||
<section class="space-y-3">
|
<section class="lg:col-span-2">
|
||||||
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2 mb-3">
|
||||||
<Icon icon="lucide:pie-chart" class="size-4" />
|
<Icon icon="lucide:wallet" class="size-4" />
|
||||||
资金分布
|
资产状态
|
||||||
</h2>
|
</h2>
|
||||||
<UiCard>
|
<div class="grid gap-3">
|
||||||
<UiCardContent class="p-4">
|
<UiCard v-for="item in assetMetrics" :key="item.label" class="hover:shadow-sm transition-shadow">
|
||||||
<VChart :option="distributionOption" autoresize style="height: 240px" />
|
<UiCardContent class="p-4">
|
||||||
</UiCardContent>
|
<div class="flex items-center justify-between">
|
||||||
</UiCard>
|
<div class="flex items-center gap-3">
|
||||||
|
<Icon :icon="item.icon" class="size-4 text-muted-foreground" />
|
||||||
|
<span class="text-sm text-muted-foreground">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
<span class="text-lg font-bold font-mono" :class="item.color">
|
||||||
|
{{ formatCurrency(item.value) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</UiCardContent>
|
||||||
|
</UiCard>
|
||||||
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 模块5: 运营指标 + 快捷入口 -->
|
<!-- 第三层:运营快报 + 快捷入口 -->
|
||||||
<div class="grid gap-6 lg:grid-cols-5">
|
<div class="grid gap-6 lg:grid-cols-5">
|
||||||
<!-- 运营指标 -->
|
<!-- 运营快报 -->
|
||||||
<section class="space-y-3 lg:col-span-2">
|
<section class="lg:col-span-2">
|
||||||
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2 mb-3">
|
||||||
<Icon icon="lucide:activity" class="size-4" />
|
<Icon icon="lucide:activity" class="size-4" />
|
||||||
运营指标
|
运营快报
|
||||||
</h2>
|
</h2>
|
||||||
<div class="grid gap-3 grid-cols-2">
|
<div class="grid gap-3 grid-cols-2">
|
||||||
<UiCard v-for="item in operationMetrics" :key="item.label" class="hover:shadow-sm transition-shadow">
|
<UiCard
|
||||||
|
v-for="item in operationMetrics"
|
||||||
|
:key="item.label"
|
||||||
|
class="hover:shadow-sm transition-shadow"
|
||||||
|
:class="{ 'cursor-pointer': item.label === '待审批' }"
|
||||||
|
@click="item.label === '待审批' && navigateTo('/monisuo/orders')"
|
||||||
|
>
|
||||||
<UiCardContent class="p-4">
|
<UiCardContent class="p-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-center mb-2">
|
||||||
<div class="min-w-0">
|
<div class="p-2 rounded-lg" :class="[item.bgColor]">
|
||||||
<p class="text-xs text-muted-foreground truncate">
|
<Icon :icon="item.icon" class="size-4" :class="item.color" />
|
||||||
{{ item.label }}
|
|
||||||
</p>
|
|
||||||
<p class="text-lg sm:text-xl font-bold mt-1 truncate">
|
|
||||||
{{ item.value }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<Icon :icon="item.icon" class="size-6 text-muted-foreground/30 shrink-0" />
|
|
||||||
</div>
|
</div>
|
||||||
|
<p class="text-xs text-muted-foreground text-center mb-1">
|
||||||
|
{{ item.label }}
|
||||||
|
</p>
|
||||||
|
<p class="text-xl font-bold text-center">
|
||||||
|
{{ item.value }}
|
||||||
|
</p>
|
||||||
</UiCardContent>
|
</UiCardContent>
|
||||||
</UiCard>
|
</UiCard>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<!-- 快捷入口 -->
|
<!-- 快捷入口 -->
|
||||||
<section class="space-y-3 lg:col-span-3">
|
<section class="lg:col-span-3">
|
||||||
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2">
|
<h2 class="text-sm font-medium text-muted-foreground flex items-center gap-2 mb-3">
|
||||||
<Icon icon="lucide:zap" class="size-4" />
|
<Icon icon="lucide:zap" class="size-4" />
|
||||||
快捷入口
|
快捷入口
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ function copyToClipboard(text: string) {
|
|||||||
手续费
|
手续费
|
||||||
</UiTableHead>
|
</UiTableHead>
|
||||||
<UiTableHead class="text-right">
|
<UiTableHead class="text-right">
|
||||||
应付款
|
到账金额
|
||||||
</UiTableHead>
|
</UiTableHead>
|
||||||
<UiTableHead>审批人</UiTableHead>
|
<UiTableHead>审批人</UiTableHead>
|
||||||
<UiTableHead class="hidden xl:table-cell">
|
<UiTableHead class="hidden xl:table-cell">
|
||||||
@@ -282,7 +282,7 @@ function copyToClipboard(text: string) {
|
|||||||
<div class="text-muted-foreground">手续费(10%)</div>
|
<div class="text-muted-foreground">手续费(10%)</div>
|
||||||
<div class="col-span-2 font-mono">-{{ formatAmount(currentOrder.fee || 0) }}</div>
|
<div class="col-span-2 font-mono">-{{ formatAmount(currentOrder.fee || 0) }}</div>
|
||||||
|
|
||||||
<div class="text-muted-foreground">应付款</div>
|
<div class="text-muted-foreground">到账金额</div>
|
||||||
<div class="col-span-2 font-mono font-bold text-green-600">{{ formatAmount(currentOrder.receivableAmount || 0) }}</div>
|
<div class="col-span-2 font-mono font-bold text-green-600">{{ formatAmount(currentOrder.receivableAmount || 0) }}</div>
|
||||||
|
|
||||||
<div class="text-muted-foreground">提现地址</div>
|
<div class="text-muted-foreground">提现地址</div>
|
||||||
@@ -294,13 +294,21 @@ function copyToClipboard(text: string) {
|
|||||||
<span v-else class="text-muted-foreground">-</span>
|
<span v-else class="text-muted-foreground">-</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<template v-if="currentOrder.network">
|
||||||
|
<div class="text-muted-foreground">提现网络</div>
|
||||||
|
<div class="col-span-2">{{ currentOrder.network }}</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="text-muted-foreground">发起时间</div>
|
||||||
|
<div class="col-span-2">{{ currentOrder.createTime }}</div>
|
||||||
|
|
||||||
|
<div v-if="currentOrder.financeApproveTime" class="text-muted-foreground">到账时间</div>
|
||||||
|
<div v-if="currentOrder.financeApproveTime" class="col-span-2">{{ currentOrder.financeApproveTime }}</div>
|
||||||
|
|
||||||
<div v-if="currentOrder.approveAdminName" class="text-muted-foreground">审批人</div>
|
<div v-if="currentOrder.approveAdminName" class="text-muted-foreground">审批人</div>
|
||||||
<div v-if="currentOrder.approveAdminName" class="col-span-2">
|
<div v-if="currentOrder.approveAdminName" class="col-span-2">
|
||||||
{{ currentOrder.approveAdminName }}
|
{{ currentOrder.approveAdminName }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="text-muted-foreground">创建时间</div>
|
|
||||||
<div class="col-span-2">{{ currentOrder.createTime }}</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UiDialogFooter>
|
<UiDialogFooter>
|
||||||
@@ -340,7 +348,7 @@ function copyToClipboard(text: string) {
|
|||||||
<div class="font-mono font-bold text-lg">{{ formatAmount(currentOrder.amount) }}</div>
|
<div class="font-mono font-bold text-lg">{{ formatAmount(currentOrder.amount) }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-muted-foreground">应付款</div>
|
<div class="text-muted-foreground">到账金额</div>
|
||||||
<div class="font-mono text-green-600">{{ formatAmount(currentOrder.receivableAmount || 0) }}</div>
|
<div class="font-mono text-green-600">{{ formatAmount(currentOrder.receivableAmount || 0) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ function formatAmount(amount: number): string {
|
|||||||
function getStatusVariant(order: OrderFund): 'default' | 'secondary' | 'destructive' | 'outline' {
|
function getStatusVariant(order: OrderFund): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||||
const { type, status } = order
|
const { type, status } = order
|
||||||
// 充值状态: 1=待付款, 2=待确认, 3=已完成, 4=已驳回, 5=已取消
|
// 充值状态: 1=待付款, 2=待确认, 3=已完成, 4=已驳回, 5=已取消
|
||||||
// 提现状态: 1=待审批, 2=已完成, 3=已驳回, 4=已取消
|
// 提现状态: 1=待审批, 2=已出款, 3=已驳回, 4=已取消
|
||||||
if (type === 1) {
|
if (type === 1) {
|
||||||
// 充值
|
// 充值
|
||||||
if (status === 1) return 'secondary' // 待付款
|
if (status === 1) return 'secondary' // 待付款
|
||||||
@@ -168,7 +168,7 @@ function getStatusText(order: OrderFund): string {
|
|||||||
// 提现状态
|
// 提现状态
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case 1: return '待审批'
|
case 1: return '待审批'
|
||||||
case 2: return '已完成'
|
case 2: return '已出款'
|
||||||
case 3: return '已驳回'
|
case 3: return '已驳回'
|
||||||
case 4: return '已取消'
|
case 4: return '已取消'
|
||||||
default: return '未知'
|
default: return '未知'
|
||||||
@@ -711,7 +711,7 @@ function copyToClipboard(text: string) {
|
|||||||
</template>
|
</template>
|
||||||
<template v-if="currentOrder.type === 2 && currentOrder.receivableAmount">
|
<template v-if="currentOrder.type === 2 && currentOrder.receivableAmount">
|
||||||
<div class="text-muted-foreground">
|
<div class="text-muted-foreground">
|
||||||
应收款项
|
到账金额
|
||||||
</div>
|
</div>
|
||||||
<div class="col-span-2 font-mono font-bold text-green-600">
|
<div class="col-span-2 font-mono font-bold text-green-600">
|
||||||
{{ currentOrder.receivableAmount }}
|
{{ currentOrder.receivableAmount }}
|
||||||
@@ -740,6 +740,16 @@ function copyToClipboard(text: string) {
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<!-- 提现网络 -->
|
||||||
|
<template v-if="currentOrder.type === 2 && currentOrder.network">
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
提现网络
|
||||||
|
</div>
|
||||||
|
<div class="col-span-2">
|
||||||
|
{{ currentOrder.network }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div class="text-muted-foreground">
|
<div class="text-muted-foreground">
|
||||||
创建时间
|
创建时间
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -60,9 +60,10 @@ export interface OrderFund {
|
|||||||
amount: number
|
amount: number
|
||||||
fee?: number // 手续费
|
fee?: number // 手续费
|
||||||
receivableAmount?: number // 应收款项
|
receivableAmount?: number // 应收款项
|
||||||
status: number // 充值: 1待付款 2待确认 3已完成 4已驳回 5已取消; 提现: 1待审批 2已完成 3已驳回 4已取消 5待财务审核
|
status: number // 充值: 1待付款 2待确认 3已完成 4已驳回 5已取消; 提现: 1待审批 2已出款 3已驳回 4已取消 5待财务审核
|
||||||
walletId?: number
|
walletId?: number
|
||||||
walletAddress?: string
|
walletAddress?: string
|
||||||
|
network?: string // 提现网络类型
|
||||||
withdrawContact?: string
|
withdrawContact?: string
|
||||||
payTime?: string
|
payTime?: string
|
||||||
confirmTime?: string
|
confirmTime?: string
|
||||||
@@ -88,12 +89,24 @@ export interface ColdWallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface FinanceOverview {
|
export interface FinanceOverview {
|
||||||
|
// 核心 KPI
|
||||||
totalDeposit: number
|
totalDeposit: number
|
||||||
totalWithdraw: number
|
totalWithdraw: number
|
||||||
|
totalActualPayout: number // 实际出款金额
|
||||||
|
// 资金状态
|
||||||
fundBalance: number
|
fundBalance: number
|
||||||
|
totalFrozen: number // 冻结中金额
|
||||||
tradeValue: number
|
tradeValue: number
|
||||||
|
// 运营数据
|
||||||
pendingCount: number
|
pendingCount: number
|
||||||
userCount: number
|
userCount: number
|
||||||
|
monthNewUsers: number // 本月新增用户
|
||||||
|
todayActiveUsers: number // 今日活跃用户
|
||||||
|
// 环比数据
|
||||||
|
monthlyDeposit: number
|
||||||
|
monthlyWithdraw: number
|
||||||
|
lastMonthDeposit: number
|
||||||
|
lastMonthWithdraw: number
|
||||||
}
|
}
|
||||||
|
|
||||||
// Auth API
|
// Auth API
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ import com.it.rattan.monisuo.context.UserContext;
|
|||||||
import com.it.rattan.monisuo.entity.*;
|
import com.it.rattan.monisuo.entity.*;
|
||||||
import com.it.rattan.monisuo.mapper.AccountFundMapper;
|
import com.it.rattan.monisuo.mapper.AccountFundMapper;
|
||||||
import com.it.rattan.monisuo.mapper.OrderFundMapper;
|
import com.it.rattan.monisuo.mapper.OrderFundMapper;
|
||||||
|
import com.it.rattan.monisuo.mapper.OrderTradeMapper;
|
||||||
import com.it.rattan.monisuo.service.*;
|
import com.it.rattan.monisuo.service.*;
|
||||||
import com.it.rattan.monisuo.util.JwtUtil;
|
import com.it.rattan.monisuo.util.JwtUtil;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -46,6 +48,9 @@ public class AdminController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private OrderFundMapper orderFundMapper;
|
private OrderFundMapper orderFundMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderTradeMapper orderTradeMapper;
|
||||||
|
|
||||||
// ==================== 公开接口 ====================
|
// ==================== 公开接口 ====================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -451,7 +456,7 @@ public class AdminController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资金总览
|
* 资金总览(看板专用,一次 API 返回所有数据)
|
||||||
*/
|
*/
|
||||||
@GetMapping("/finance/overview")
|
@GetMapping("/finance/overview")
|
||||||
public Result<Map<String, Object>> getFinanceOverview() {
|
public Result<Map<String, Object>> getFinanceOverview() {
|
||||||
@@ -461,21 +466,47 @@ public class AdminController {
|
|||||||
|
|
||||||
Map<String, Object> data = new HashMap<>();
|
Map<String, Object> data = new HashMap<>();
|
||||||
|
|
||||||
// 合并为一次查询获取充值总额、提现总额、待审批数
|
// 本月和上月时间范围(用于环比)
|
||||||
Map<String, Object> fundStats = orderFundMapper.sumFinanceOverview();
|
LocalDate today = LocalDate.now();
|
||||||
|
LocalDateTime monthStart = today.withDayOfMonth(1).atStartOfDay();
|
||||||
|
LocalDateTime monthEnd = today.plusMonths(1).withDayOfMonth(1).atStartOfDay();
|
||||||
|
LocalDateTime lastMonthStart = today.minusMonths(1).withDayOfMonth(1).atStartOfDay();
|
||||||
|
LocalDateTime lastMonthEnd = monthStart;
|
||||||
|
|
||||||
|
// 查询1:order_fund 一次聚合(充值总额、提现总额、实际出款、待审批、环比)
|
||||||
|
Map<String, Object> fundStats = orderFundMapper.sumDashboardStats(
|
||||||
|
monthStart, monthEnd, lastMonthStart, lastMonthEnd);
|
||||||
data.put("totalDeposit", fundStats.get("totalDeposit"));
|
data.put("totalDeposit", fundStats.get("totalDeposit"));
|
||||||
data.put("totalWithdraw", fundStats.get("totalWithdraw"));
|
data.put("totalWithdraw", fundStats.get("totalWithdraw"));
|
||||||
|
data.put("totalActualPayout", fundStats.get("totalActualPayout"));
|
||||||
data.put("pendingCount", ((Number) fundStats.get("pendingCount")).intValue());
|
data.put("pendingCount", ((Number) fundStats.get("pendingCount")).intValue());
|
||||||
|
data.put("monthlyDeposit", fundStats.get("monthlyDeposit"));
|
||||||
|
data.put("monthlyWithdraw", fundStats.get("monthlyWithdraw"));
|
||||||
|
data.put("lastMonthDeposit", fundStats.get("lastMonthDeposit"));
|
||||||
|
data.put("lastMonthWithdraw", fundStats.get("lastMonthWithdraw"));
|
||||||
|
|
||||||
BigDecimal fundBalance = accountFundMapper.sumAllBalance();
|
// 查询2:account_fund 余额和冻结
|
||||||
data.put("fundBalance", fundBalance);
|
Map<String, Object> balanceStats = accountFundMapper.sumBalanceAndFrozen();
|
||||||
|
data.put("fundBalance", balanceStats.get("totalBalance"));
|
||||||
|
data.put("totalFrozen", balanceStats.get("totalFrozen"));
|
||||||
|
|
||||||
|
// 查询3:交易账户市值
|
||||||
BigDecimal tradeValue = accountFundMapper.sumAllTradeValue();
|
BigDecimal tradeValue = accountFundMapper.sumAllTradeValue();
|
||||||
data.put("tradeValue", tradeValue != null ? tradeValue : BigDecimal.ZERO);
|
data.put("tradeValue", tradeValue != null ? tradeValue : BigDecimal.ZERO);
|
||||||
|
|
||||||
|
// 查询4:用户统计
|
||||||
long userCount = userService.count();
|
long userCount = userService.count();
|
||||||
data.put("userCount", userCount);
|
data.put("userCount", userCount);
|
||||||
|
|
||||||
|
int monthNewUsers = userService.count(new LambdaQueryWrapper<User>()
|
||||||
|
.ge(User::getCreateTime, monthStart));
|
||||||
|
data.put("monthNewUsers", monthNewUsers);
|
||||||
|
|
||||||
|
// 查询5:今日活跃用户(今日有过交易的独立用户数)
|
||||||
|
int todayActiveUsers = orderTradeMapper.countDistinctUserByTime(
|
||||||
|
today.atStartOfDay(), LocalDateTime.now());
|
||||||
|
data.put("todayActiveUsers", todayActiveUsers);
|
||||||
|
|
||||||
return Result.success(data);
|
return Result.success(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,4 +135,13 @@ public class ColdWalletController {
|
|||||||
result.put("network", wallet.getNetwork());
|
result.put("network", wallet.getNetwork());
|
||||||
return Result.success(result);
|
return Result.success(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户端 - 获取可用的提现网络列表
|
||||||
|
*/
|
||||||
|
@GetMapping("/api/wallet/networks")
|
||||||
|
public Result<List<String>> getNetworks() {
|
||||||
|
List<String> networks = coldWalletService.getEnabledNetworks();
|
||||||
|
return Result.success(networks);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -87,6 +87,7 @@ public class FundController {
|
|||||||
|
|
||||||
BigDecimal amount = request.getAmount();
|
BigDecimal amount = request.getAmount();
|
||||||
String withdrawAddress = request.getWithdrawAddress();
|
String withdrawAddress = request.getWithdrawAddress();
|
||||||
|
String network = request.getNetwork();
|
||||||
String withdrawContact = request.getWithdrawContact();
|
String withdrawContact = request.getWithdrawContact();
|
||||||
String remark = request.getRemark();
|
String remark = request.getRemark();
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ public class FundController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Map<String, Object> result = fundService.withdraw(userId, amount, withdrawAddress, withdrawContact, remark);
|
Map<String, Object> result = fundService.withdraw(userId, amount, withdrawAddress, network, withdrawContact, remark);
|
||||||
return Result.success("申请成功,等待审批", result);
|
return Result.success("申请成功,等待审批", result);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return Result.fail(e.getMessage());
|
return Result.fail(e.getMessage());
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import java.math.BigDecimal;
|
|||||||
public class WithdrawRequest {
|
public class WithdrawRequest {
|
||||||
private BigDecimal amount;
|
private BigDecimal amount;
|
||||||
private String withdrawAddress;
|
private String withdrawAddress;
|
||||||
|
private String network;
|
||||||
private String withdrawContact;
|
private String withdrawContact;
|
||||||
private String remark;
|
private String remark;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ public class OrderFund implements Serializable {
|
|||||||
/** 冷钱包地址(充值)/提现地址 */
|
/** 冷钱包地址(充值)/提现地址 */
|
||||||
private String walletAddress;
|
private String walletAddress;
|
||||||
|
|
||||||
|
/** 提现网络类型(TRC20/ERC20等) */
|
||||||
|
private String network;
|
||||||
|
|
||||||
/** 提现联系方式 */
|
/** 提现联系方式 */
|
||||||
private String withdrawContact;
|
private String withdrawContact;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.it.rattan.monisuo.entity.AccountFund;
|
|||||||
import org.apache.ibatis.annotations.Mapper;
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
import org.apache.ibatis.annotations.Select;
|
import org.apache.ibatis.annotations.Select;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 资金账户Mapper
|
* 资金账户Mapper
|
||||||
@@ -15,6 +16,12 @@ public interface AccountFundMapper extends BaseMapper<AccountFund> {
|
|||||||
@Select("SELECT IFNULL(SUM(balance), 0) FROM account_fund")
|
@Select("SELECT IFNULL(SUM(balance), 0) FROM account_fund")
|
||||||
BigDecimal sumAllBalance();
|
BigDecimal sumAllBalance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 一次查询获取总余额和总冻结金额
|
||||||
|
*/
|
||||||
|
@Select("SELECT IFNULL(SUM(balance), 0) as totalBalance, IFNULL(SUM(frozen), 0) as totalFrozen FROM account_fund")
|
||||||
|
Map<String, Object> sumBalanceAndFrozen();
|
||||||
|
|
||||||
@Select("SELECT IFNULL(SUM(total_deposit), 0) FROM account_fund")
|
@Select("SELECT IFNULL(SUM(total_deposit), 0) FROM account_fund")
|
||||||
BigDecimal sumTotalDeposit();
|
BigDecimal sumTotalDeposit();
|
||||||
|
|
||||||
|
|||||||
@@ -16,31 +16,34 @@ import java.util.Map;
|
|||||||
@Mapper
|
@Mapper
|
||||||
public interface OrderFundMapper extends BaseMapper<OrderFund> {
|
public interface OrderFundMapper extends BaseMapper<OrderFund> {
|
||||||
|
|
||||||
@Select("SELECT IFNULL(SUM(amount), 0) FROM order_fund WHERE type = 1 AND status = 2")
|
|
||||||
BigDecimal sumCompletedDeposit();
|
|
||||||
|
|
||||||
@Select("SELECT IFNULL(SUM(amount), 0) FROM order_fund WHERE type = 2 AND status = 2")
|
|
||||||
BigDecimal sumCompletedWithdraw();
|
|
||||||
|
|
||||||
@Select("SELECT COUNT(*) FROM order_fund WHERE status = 1")
|
|
||||||
int countPending();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 一次性聚合查询:充值总额、提现总额、待审批数
|
* 一次性聚合查询:充值总额、提现总额、实际出款、待审批数、本月/上月环比数据
|
||||||
|
* 充值已完成 status=3,提现已完成 status=2
|
||||||
*/
|
*/
|
||||||
@Select("SELECT " +
|
@Select("SELECT " +
|
||||||
"IFNULL(SUM(CASE WHEN type = 1 AND status = 2 THEN amount ELSE 0 END), 0) as totalDeposit, " +
|
"IFNULL(SUM(CASE WHEN type = 1 AND status = 3 THEN amount ELSE 0 END), 0) as totalDeposit, " +
|
||||||
"IFNULL(SUM(CASE WHEN type = 2 AND status = 2 THEN amount ELSE 0 END), 0) as totalWithdraw, " +
|
"IFNULL(SUM(CASE WHEN type = 2 AND status = 2 THEN amount ELSE 0 END), 0) as totalWithdraw, " +
|
||||||
"SUM(CASE WHEN status IN (1, 5) THEN 1 ELSE 0 END) as pendingCount " +
|
"IFNULL(SUM(CASE WHEN type = 2 AND status = 2 THEN IFNULL(receivable_amount, amount * 0.9) ELSE 0 END), 0) as totalActualPayout, " +
|
||||||
|
"SUM(CASE WHEN (type = 1 AND status = 2) OR (type = 2 AND status IN (1, 5)) THEN 1 ELSE 0 END) as pendingCount, " +
|
||||||
|
"IFNULL(SUM(CASE WHEN type = 1 AND status = 3 AND create_time >= #{monthStart} AND create_time < #{monthEnd} THEN amount ELSE 0 END), 0) as monthlyDeposit, " +
|
||||||
|
"IFNULL(SUM(CASE WHEN type = 2 AND status = 2 AND create_time >= #{monthStart} AND create_time < #{monthEnd} THEN amount ELSE 0 END), 0) as monthlyWithdraw, " +
|
||||||
|
"IFNULL(SUM(CASE WHEN type = 1 AND status = 3 AND create_time >= #{lastMonthStart} AND create_time < #{lastMonthEnd} THEN amount ELSE 0 END), 0) as lastMonthDeposit, " +
|
||||||
|
"IFNULL(SUM(CASE WHEN type = 2 AND status = 2 AND create_time >= #{lastMonthStart} AND create_time < #{lastMonthEnd} THEN amount ELSE 0 END), 0) as lastMonthWithdraw " +
|
||||||
"FROM order_fund")
|
"FROM order_fund")
|
||||||
Map<String, Object> sumFinanceOverview();
|
Map<String, Object> sumDashboardStats(
|
||||||
|
@Param("monthStart") LocalDateTime monthStart,
|
||||||
|
@Param("monthEnd") LocalDateTime monthEnd,
|
||||||
|
@Param("lastMonthStart") LocalDateTime lastMonthStart,
|
||||||
|
@Param("lastMonthEnd") LocalDateTime lastMonthEnd);
|
||||||
|
|
||||||
// ========== 分析相关查询 ==========
|
// ========== 分析相关查询 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 指定时间段内的手续费总额(0.5%)
|
* 指定时间段内的手续费总额(0.5%)
|
||||||
|
* 充值已完成 status=3,提现已完成 status=2
|
||||||
*/
|
*/
|
||||||
@Select("SELECT IFNULL(SUM(amount * 0.005), 0) FROM order_fund WHERE status = 2 AND create_time >= #{startTime}")
|
@Select("SELECT IFNULL(SUM(amount * 0.005), 0) FROM order_fund WHERE " +
|
||||||
|
"((type = 1 AND status = 3) OR (type = 2 AND status = 2)) AND create_time >= #{startTime}")
|
||||||
BigDecimal sumFeeByTime(@Param("startTime") LocalDateTime startTime);
|
BigDecimal sumFeeByTime(@Param("startTime") LocalDateTime startTime);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,9 +61,10 @@ public interface OrderFundMapper extends BaseMapper<OrderFund> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 按月分组统计充值/提现金额(替代循环 N 次查询)
|
* 按月分组统计充值/提现金额(替代循环 N 次查询)
|
||||||
|
* 充值已完成 status=3,提现已完成 status=2
|
||||||
*/
|
*/
|
||||||
@Select("SELECT DATE_FORMAT(create_time, '%Y-%m') as month, " +
|
@Select("SELECT DATE_FORMAT(create_time, '%Y-%m') as month, " +
|
||||||
"IFNULL(SUM(CASE WHEN type = 1 AND status = 2 THEN amount ELSE 0 END), 0) as deposit, " +
|
"IFNULL(SUM(CASE WHEN type = 1 AND status = 3 THEN amount ELSE 0 END), 0) as deposit, " +
|
||||||
"IFNULL(SUM(CASE WHEN type = 2 AND status = 2 THEN amount ELSE 0 END), 0) as withdraw " +
|
"IFNULL(SUM(CASE WHEN type = 2 AND status = 2 THEN amount ELSE 0 END), 0) as withdraw " +
|
||||||
"FROM order_fund WHERE create_time >= #{startTime} AND create_time < #{endTime} " +
|
"FROM order_fund WHERE create_time >= #{startTime} AND create_time < #{endTime} " +
|
||||||
"GROUP BY DATE_FORMAT(create_time, '%Y-%m') ORDER BY month")
|
"GROUP BY DATE_FORMAT(create_time, '%Y-%m') ORDER BY month")
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 冷钱包地址服务
|
* 冷钱包地址服务
|
||||||
@@ -28,6 +29,18 @@ public class ColdWalletService {
|
|||||||
return coldWalletMapper.selectList(wrapper);
|
return coldWalletMapper.selectList(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取所有启用的网络类型(去重)
|
||||||
|
*/
|
||||||
|
public List<String> getEnabledNetworks() {
|
||||||
|
List<ColdWallet> wallets = getEnabledList();
|
||||||
|
return wallets.stream()
|
||||||
|
.map(ColdWallet::getNetwork)
|
||||||
|
.filter(n -> n != null && !n.isEmpty())
|
||||||
|
.distinct()
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取启用的钱包列表
|
* 获取启用的钱包列表
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import java.util.*;
|
|||||||
*
|
*
|
||||||
* 状态定义:
|
* 状态定义:
|
||||||
* 充值: 1=待付款, 2=待确认, 3=已完成, 4=已驳回, 5=已取消
|
* 充值: 1=待付款, 2=待确认, 3=已完成, 4=已驳回, 5=已取消
|
||||||
* 提现: 1=待审批, 2=已完成, 3=已驳回, 4=已取消
|
* 提现: 1=待审批, 2=已出款, 3=已驳回, 4=已取消
|
||||||
*
|
*
|
||||||
* 审批参数说明:
|
* 审批参数说明:
|
||||||
* status=2 表示审批通过(充值订单最终状态为3,提现订单最终状态为2)
|
* status=2 表示审批通过(充值订单最终状态为3,提现订单最终状态为2)
|
||||||
@@ -128,7 +128,7 @@ public class FundService {
|
|||||||
*/
|
*/
|
||||||
@Transactional
|
@Transactional
|
||||||
public Map<String, Object> withdraw(Long userId, BigDecimal amount, String withdrawAddress,
|
public Map<String, Object> withdraw(Long userId, BigDecimal amount, String withdrawAddress,
|
||||||
String withdrawContact, String remark) {
|
String network, String withdrawContact, String remark) {
|
||||||
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
|
||||||
throw new RuntimeException("提现金额必须大于0");
|
throw new RuntimeException("提现金额必须大于0");
|
||||||
}
|
}
|
||||||
@@ -200,6 +200,7 @@ public class FundService {
|
|||||||
order.setReceivableAmount(receivableAmount);
|
order.setReceivableAmount(receivableAmount);
|
||||||
order.setStatus(1); // 待审批
|
order.setStatus(1); // 待审批
|
||||||
order.setWalletAddress(withdrawAddress);
|
order.setWalletAddress(withdrawAddress);
|
||||||
|
order.setNetwork(network);
|
||||||
order.setWithdrawContact(withdrawContact);
|
order.setWithdrawContact(withdrawContact);
|
||||||
order.setRemark(remark);
|
order.setRemark(remark);
|
||||||
order.setCreateTime(LocalDateTime.now());
|
order.setCreateTime(LocalDateTime.now());
|
||||||
|
|||||||
Reference in New Issue
Block a user