111
This commit is contained in:
Binary file not shown.
@@ -238,7 +238,9 @@ class AssetProvider extends ChangeNotifier {
|
|||||||
final response = await _fundService.cancelOrder(orderNo);
|
final response = await _fundService.cancelOrder(orderNo);
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
await loadFundOrders();
|
await loadFundOrders();
|
||||||
await loadFundAccount();
|
await loadFundAccount(force: true);
|
||||||
|
_eventBus.fire(AppEventType.assetChanged);
|
||||||
|
_eventBus.fire(AppEventType.orderChanged);
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:shadcn_ui/shadcn_ui.dart';
|
|||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../../../core/theme/app_spacing.dart';
|
import '../../../core/theme/app_spacing.dart';
|
||||||
import '../../../core/theme/app_theme_extension.dart';
|
import '../../../core/theme/app_theme_extension.dart';
|
||||||
|
import '../../../core/event/app_event_bus.dart';
|
||||||
import '../../../data/models/coin.dart';
|
import '../../../data/models/coin.dart';
|
||||||
import '../../../providers/market_provider.dart';
|
import '../../../providers/market_provider.dart';
|
||||||
import '../../../providers/asset_provider.dart';
|
import '../../../providers/asset_provider.dart';
|
||||||
@@ -265,6 +266,8 @@ class _TradePageState extends State<TradePage>
|
|||||||
_amountController.clear();
|
_amountController.clear();
|
||||||
// 刷新資產數據
|
// 刷新資產數據
|
||||||
context.read<AssetProvider>().refreshAll(force: true);
|
context.read<AssetProvider>().refreshAll(force: true);
|
||||||
|
// 通知其他頁面刷新
|
||||||
|
context.read<AppEventBus>().fire(AppEventType.assetChanged);
|
||||||
_showResultDialog(true, '${isBuy ? '買入' : '賣出'}成功',
|
_showResultDialog(true, '${isBuy ? '買入' : '賣出'}成功',
|
||||||
'$quantity $coinCode @ $price USDT');
|
'$quantity $coinCode @ $price USDT');
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { toast } from 'vue-sonner'
|
|||||||
import type { OrderFund } from '@/services/api/monisuo-admin.api'
|
import type { OrderFund } from '@/services/api/monisuo-admin.api'
|
||||||
|
|
||||||
import { BasicPage } from '@/components/global-layout'
|
import { BasicPage } from '@/components/global-layout'
|
||||||
import { useApproveOrderMutation, useGetPendingOrdersQuery } from '@/services/api/monisuo-admin.api'
|
import { useApproveOrderMutation, useGetPendingOrdersQuery, useGetUserStatsQuery } from '@/services/api/monisuo-admin.api'
|
||||||
|
|
||||||
const pageNum = ref(1)
|
const pageNum = ref(1)
|
||||||
const pageSize = ref(10)
|
const pageSize = ref(10)
|
||||||
@@ -28,6 +28,25 @@ const approveStatus = ref(2)
|
|||||||
const rejectReason = ref('')
|
const rejectReason = ref('')
|
||||||
const adminRemark = ref('')
|
const adminRemark = ref('')
|
||||||
|
|
||||||
|
// 查询订单关联用户的统计数据
|
||||||
|
const orderUserId = computed(() => currentOrder.value?.userId ?? 0)
|
||||||
|
const { data: userStatsData, isLoading: userStatsLoading } = useGetUserStatsQuery(orderUserId)
|
||||||
|
const userStats = computed(() => userStatsData.value?.data)
|
||||||
|
|
||||||
|
function formatVal(val: number | string | undefined | null): string {
|
||||||
|
if (val == null) return '0.00'
|
||||||
|
const n = typeof val === 'string' ? Number.parseFloat(val) : val
|
||||||
|
if (Number.isNaN(n)) return '0.00'
|
||||||
|
return n.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function daysAgo(time: string | undefined | null): number {
|
||||||
|
if (!time) return 0
|
||||||
|
const d = new Date(time.replace('T', ' '))
|
||||||
|
if (Number.isNaN(d.getTime())) return 0
|
||||||
|
return Math.floor((Date.now() - d.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
|
}
|
||||||
|
|
||||||
function viewOrderDetail(order: OrderFund) {
|
function viewOrderDetail(order: OrderFund) {
|
||||||
currentOrder.value = order
|
currentOrder.value = order
|
||||||
showDetailDialog.value = true
|
showDetailDialog.value = true
|
||||||
@@ -55,12 +74,19 @@ async function handleApprove() {
|
|||||||
adminRemark: adminRemark.value || undefined,
|
adminRemark: adminRemark.value || undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success(`订单已{action}`)
|
// 立即更新本地订单状态,防止旧状态仍显示操作按钮
|
||||||
|
if (approveStatus.value === 2) {
|
||||||
|
currentOrder.value.status = 2 // 财务通过→已出款
|
||||||
|
} else {
|
||||||
|
currentOrder.value.status = 3 // 驳回
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`订单已${action}`)
|
||||||
showApproveDialog.value = false
|
showApproveDialog.value = false
|
||||||
refetchPending()
|
refetchPending()
|
||||||
}
|
}
|
||||||
catch (e: any) {
|
catch (e: any) {
|
||||||
toast.error(e.message || e.response?.data?.msg || `{action}失败`)
|
toast.error(e.message || e.response?.data?.msg || `${action}失败`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -274,7 +300,125 @@ function copyToClipboard(text: string) {
|
|||||||
<div class="col-span-2 font-mono">{{ currentOrder.orderNo }}</div>
|
<div class="col-span-2 font-mono">{{ currentOrder.orderNo }}</div>
|
||||||
|
|
||||||
<div class="text-muted-foreground">用户</div>
|
<div class="text-muted-foreground">用户</div>
|
||||||
<div class="col-span-2 font-medium">{{ currentOrder.username }}</div>
|
<div class="col-span-2 font-medium">
|
||||||
|
{{ currentOrder.username }}
|
||||||
|
<UiBadge variant="outline" class="ml-2 text-xs">
|
||||||
|
ID: {{ currentOrder.userId }}
|
||||||
|
</UiBadge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 用户统计信息 -->
|
||||||
|
<div class="col-span-3">
|
||||||
|
<div v-if="userStatsLoading" class="py-2 text-center">
|
||||||
|
<UiSpinner class="size-4 mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="userStats" class="p-3 rounded-lg bg-muted/50 text-sm space-y-2">
|
||||||
|
<div class="font-medium text-muted-foreground mb-2">
|
||||||
|
用户概况
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-3 gap-2 text-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
资金余额
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold">
|
||||||
|
{{ formatVal(userStats.fundAccount?.balance) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
累计充值
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold text-green-600 dark:text-green-400">
|
||||||
|
{{ formatVal(userStats.fundAccount?.totalDeposit) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
累计提现
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold text-red-600 dark:text-red-400">
|
||||||
|
{{ formatVal(userStats.fundAccount?.totalWithdraw) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-3 gap-2 text-center pt-2 border-t">
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
冻结金额
|
||||||
|
</div>
|
||||||
|
<div class="font-mono">
|
||||||
|
{{ formatVal(userStats.fundAccount?.frozen) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
推广人数
|
||||||
|
</div>
|
||||||
|
<div class="font-mono">
|
||||||
|
{{ userStats.referralStats?.directCount || 0 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
累计福利
|
||||||
|
</div>
|
||||||
|
<div class="font-mono text-amber-600 dark:text-amber-400">
|
||||||
|
{{ formatVal(userStats.bonusStats?.totalBonusClaimed) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
注册时间: {{ userStats.user?.createTime || '-' }}
|
||||||
|
</div>
|
||||||
|
<!-- 成功充值记录 -->
|
||||||
|
<template v-if="userStats.recentFundOrders?.filter(o => o.type === 1 && o.status === 3).length">
|
||||||
|
<div class="font-medium text-muted-foreground pt-2 border-t">
|
||||||
|
成功充值记录
|
||||||
|
</div>
|
||||||
|
<div class="max-h-[200px] overflow-y-auto">
|
||||||
|
<div class="rounded border">
|
||||||
|
<UiTable>
|
||||||
|
<UiTableHeader>
|
||||||
|
<UiTableRow>
|
||||||
|
<UiTableHead class="text-xs h-8">
|
||||||
|
时间
|
||||||
|
</UiTableHead>
|
||||||
|
<UiTableHead class="text-xs h-8 text-right">
|
||||||
|
金额
|
||||||
|
</UiTableHead>
|
||||||
|
<UiTableHead class="text-xs h-8 text-right">
|
||||||
|
距今
|
||||||
|
</UiTableHead>
|
||||||
|
<UiTableHead class="text-xs h-8">
|
||||||
|
状态
|
||||||
|
</UiTableHead>
|
||||||
|
</UiTableRow>
|
||||||
|
</UiTableHeader>
|
||||||
|
<UiTableBody>
|
||||||
|
<UiTableRow v-for="dep in userStats.recentFundOrders.filter(o => o.type === 1 && o.status === 3)" :key="dep.orderNo">
|
||||||
|
<UiTableCell class="text-xs py-1">
|
||||||
|
{{ dep.createTime?.replace('T', ' ').substring(0, 16) }}
|
||||||
|
</UiTableCell>
|
||||||
|
<UiTableCell class="text-xs py-1 text-right font-mono text-green-600 dark:text-green-400">
|
||||||
|
+{{ formatVal(dep.amount) }}
|
||||||
|
</UiTableCell>
|
||||||
|
<UiTableCell class="text-xs py-1 text-right text-muted-foreground">
|
||||||
|
{{ daysAgo(dep.createTime) }}天
|
||||||
|
</UiTableCell>
|
||||||
|
<UiTableCell class="text-xs py-1">
|
||||||
|
<UiBadge variant="default" class="text-xs">
|
||||||
|
已完成
|
||||||
|
</UiBadge>
|
||||||
|
</UiTableCell>
|
||||||
|
</UiTableRow>
|
||||||
|
</UiTableBody>
|
||||||
|
</UiTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-muted-foreground">提现金额</div>
|
<div class="text-muted-foreground">提现金额</div>
|
||||||
<div class="col-span-2 font-mono font-bold text-lg">{{ formatAmount(currentOrder.amount) }}</div>
|
<div class="col-span-2 font-mono font-bold text-lg">{{ formatAmount(currentOrder.amount) }}</div>
|
||||||
@@ -346,7 +490,45 @@ function copyToClipboard(text: string) {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-muted-foreground">用户</div>
|
<div class="text-muted-foreground">用户</div>
|
||||||
<div class="font-medium">{{ currentOrder.username }}</div>
|
<div class="font-medium">
|
||||||
|
{{ currentOrder.username }}
|
||||||
|
<span class="text-xs text-muted-foreground">(ID: {{ currentOrder.userId }})</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- 用户统计摘要 -->
|
||||||
|
<div v-if="userStatsLoading" class="py-1">
|
||||||
|
<UiSpinner class="size-4 mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="userStats" class="p-2 rounded bg-background text-xs space-y-1">
|
||||||
|
<div class="grid grid-cols-3 gap-1 text-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
资金余额
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold">
|
||||||
|
{{ formatVal(userStats.fundAccount?.balance) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
累计充值
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold text-green-600 dark:text-green-400">
|
||||||
|
{{ formatVal(userStats.fundAccount?.totalDeposit) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
累计提现
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold text-red-600 dark:text-red-400">
|
||||||
|
{{ formatVal(userStats.fundAccount?.totalWithdraw) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted-foreground text-center">
|
||||||
|
冻结: {{ formatVal(userStats.fundAccount?.frozen) }} | 推广: {{ userStats.referralStats?.directCount || 0 }}人 | 注册: {{ userStats.user?.createTime?.substring(0, 10) || '-' }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-muted-foreground">提现金额</div>
|
<div class="text-muted-foreground">提现金额</div>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import type { OrderFund } from '@/services/api/monisuo-admin.api'
|
|||||||
|
|
||||||
import { BasicPage } from '@/components/global-layout'
|
import { BasicPage } from '@/components/global-layout'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
import { useAuthStore } from '@/stores/auth'
|
||||||
import { useApproveOrderMutation, useGetAllOrdersQuery, useGetPendingOrdersQuery } from '@/services/api/monisuo-admin.api'
|
import { useApproveOrderMutation, useGetAllOrdersQuery, useGetPendingOrdersQuery, useGetUserStatsQuery } from '@/services/api/monisuo-admin.api'
|
||||||
|
|
||||||
// 分离分页状态
|
// 分离分页状态
|
||||||
const pendingPage = ref(1)
|
const pendingPage = ref(1)
|
||||||
@@ -61,6 +61,11 @@ const approveStatus = ref(2)
|
|||||||
const rejectReason = ref('')
|
const rejectReason = ref('')
|
||||||
const adminRemark = ref('')
|
const adminRemark = ref('')
|
||||||
|
|
||||||
|
// 查询订单关联用户的统计数据
|
||||||
|
const orderUserId = computed(() => currentOrder.value?.userId ?? 0)
|
||||||
|
const { data: userStatsData, isLoading: userStatsLoading } = useGetUserStatsQuery(orderUserId)
|
||||||
|
const userStats = computed(() => userStatsData.value?.data)
|
||||||
|
|
||||||
function viewOrderDetail(order: OrderFund) {
|
function viewOrderDetail(order: OrderFund) {
|
||||||
currentOrder.value = order
|
currentOrder.value = order
|
||||||
showDetailDialog.value = true
|
showDetailDialog.value = true
|
||||||
@@ -88,13 +93,27 @@ async function handleApprove() {
|
|||||||
adminRemark: adminRemark.value || undefined,
|
adminRemark: adminRemark.value || undefined,
|
||||||
})
|
})
|
||||||
|
|
||||||
toast.success(`订单已{action}`)
|
// 立即更新本地订单状态,防止旧状态仍显示操作按钮
|
||||||
|
const role = adminRole.value
|
||||||
|
if (approveStatus.value === 2) {
|
||||||
|
if (currentOrder.value.type === 1) {
|
||||||
|
currentOrder.value.status = 3 // 充值完成
|
||||||
|
} else if (role === 2 || (role === 1 && currentOrder.value.status === 1)) {
|
||||||
|
currentOrder.value.status = 5 // 提现管理员通过→待财务审核
|
||||||
|
} else {
|
||||||
|
currentOrder.value.status = 2 // 提现财务通过→已出款
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
currentOrder.value.status = currentOrder.value.type === 1 ? 4 : 3 // 驳回
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.success(`订单已${action}`)
|
||||||
showApproveDialog.value = false
|
showApproveDialog.value = false
|
||||||
refetchPending()
|
refetchPending()
|
||||||
if (allLoaded.value) refetchAll()
|
if (allLoaded.value) refetchAll()
|
||||||
}
|
}
|
||||||
catch (e: any) {
|
catch (e: any) {
|
||||||
toast.error(e.message || e.response?.data?.msg || `{action}失败`)
|
toast.error(e.message || e.response?.data?.msg || `${action}失败`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,6 +148,20 @@ function formatAmount(amount: number): string {
|
|||||||
return amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
return amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatVal(val: number | string | undefined | null): string {
|
||||||
|
if (val == null) return '0.00'
|
||||||
|
const n = typeof val === 'string' ? Number.parseFloat(val) : val
|
||||||
|
if (Number.isNaN(n)) return '0.00'
|
||||||
|
return n.toFixed(2)
|
||||||
|
}
|
||||||
|
|
||||||
|
function daysAgo(time: string | undefined | null): number {
|
||||||
|
if (!time) return 0
|
||||||
|
const d = new Date(time.replace('T', ' '))
|
||||||
|
if (Number.isNaN(d.getTime())) return 0
|
||||||
|
return Math.floor((Date.now() - d.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
|
}
|
||||||
|
|
||||||
// 根据订单类型和状态获取状态样式
|
// 根据订单类型和状态获取状态样式
|
||||||
function getStatusVariant(order: OrderFund): 'default' | 'secondary' | 'destructive' | 'outline' {
|
function getStatusVariant(order: OrderFund): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||||
const { type, status } = order
|
const { type, status } = order
|
||||||
@@ -673,8 +706,126 @@ function copyToClipboard(text: string) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-span-2 font-medium">
|
<div class="col-span-2 font-medium">
|
||||||
{{ currentOrder.username }}
|
{{ currentOrder.username }}
|
||||||
|
<UiBadge v-if="currentOrder.type === 2" variant="outline" class="ml-2 text-xs">
|
||||||
|
ID: {{ currentOrder.userId }}
|
||||||
|
</UiBadge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 提现用户统计信息 -->
|
||||||
|
<template v-if="currentOrder.type === 2">
|
||||||
|
<div class="col-span-3">
|
||||||
|
<div v-if="userStatsLoading" class="py-2 text-center">
|
||||||
|
<UiSpinner class="size-4 mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="userStats" class="p-3 rounded-lg bg-muted/50 text-sm space-y-2">
|
||||||
|
<div class="font-medium text-muted-foreground mb-2">
|
||||||
|
用户概况
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-3 gap-2 text-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
资金余额
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold">
|
||||||
|
{{ formatVal(userStats.fundAccount?.balance) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
累计充值
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold text-green-600 dark:text-green-400">
|
||||||
|
{{ formatVal(userStats.fundAccount?.totalDeposit) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
累计提现
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold text-red-600 dark:text-red-400">
|
||||||
|
{{ formatVal(userStats.fundAccount?.totalWithdraw) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-3 gap-2 text-center pt-2 border-t">
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
冻结金额
|
||||||
|
</div>
|
||||||
|
<div class="font-mono">
|
||||||
|
{{ formatVal(userStats.fundAccount?.frozen) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
推广人数
|
||||||
|
</div>
|
||||||
|
<div class="font-mono">
|
||||||
|
{{ userStats.referralStats?.directCount || 0 }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
累计福利
|
||||||
|
</div>
|
||||||
|
<div class="font-mono text-amber-600 dark:text-amber-400">
|
||||||
|
{{ formatVal(userStats.bonusStats?.totalBonusClaimed) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-xs text-muted-foreground">
|
||||||
|
注册时间: {{ userStats.user?.createTime || '-' }}
|
||||||
|
</div>
|
||||||
|
<!-- 成功充值记录 -->
|
||||||
|
<template v-if="userStats.recentFundOrders?.filter(o => o.type === 1 && o.status === 3).length">
|
||||||
|
<div class="font-medium text-muted-foreground pt-2 border-t">
|
||||||
|
成功充值记录
|
||||||
|
</div>
|
||||||
|
<div class="max-h-[200px] overflow-y-auto">
|
||||||
|
<div class="rounded border">
|
||||||
|
<UiTable>
|
||||||
|
<UiTableHeader>
|
||||||
|
<UiTableRow>
|
||||||
|
<UiTableHead class="text-xs h-8">
|
||||||
|
时间
|
||||||
|
</UiTableHead>
|
||||||
|
<UiTableHead class="text-xs h-8 text-right">
|
||||||
|
金额
|
||||||
|
</UiTableHead>
|
||||||
|
<UiTableHead class="text-xs h-8 text-right">
|
||||||
|
距今
|
||||||
|
</UiTableHead>
|
||||||
|
<UiTableHead class="text-xs h-8">
|
||||||
|
状态
|
||||||
|
</UiTableHead>
|
||||||
|
</UiTableRow>
|
||||||
|
</UiTableHeader>
|
||||||
|
<UiTableBody>
|
||||||
|
<UiTableRow v-for="dep in userStats.recentFundOrders.filter(o => o.type === 1 && o.status === 3)" :key="dep.orderNo">
|
||||||
|
<UiTableCell class="text-xs py-1">
|
||||||
|
{{ dep.createTime?.replace('T', ' ').substring(0, 16) }}
|
||||||
|
</UiTableCell>
|
||||||
|
<UiTableCell class="text-xs py-1 text-right font-mono text-green-600 dark:text-green-400">
|
||||||
|
+{{ formatVal(dep.amount) }}
|
||||||
|
</UiTableCell>
|
||||||
|
<UiTableCell class="text-xs py-1 text-right text-muted-foreground">
|
||||||
|
{{ daysAgo(dep.createTime) }}天
|
||||||
|
</UiTableCell>
|
||||||
|
<UiTableCell class="text-xs py-1">
|
||||||
|
<UiBadge variant="default" class="text-xs">
|
||||||
|
已完成
|
||||||
|
</UiBadge>
|
||||||
|
</UiTableCell>
|
||||||
|
</UiTableRow>
|
||||||
|
</UiTableBody>
|
||||||
|
</UiTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<div class="text-muted-foreground">
|
<div class="text-muted-foreground">
|
||||||
类型
|
类型
|
||||||
</div>
|
</div>
|
||||||
@@ -863,8 +1014,46 @@ function copyToClipboard(text: string) {
|
|||||||
</div>
|
</div>
|
||||||
<div class="font-medium">
|
<div class="font-medium">
|
||||||
{{ currentOrder.username }}
|
{{ currentOrder.username }}
|
||||||
|
<span v-if="currentOrder.type === 2" class="text-xs text-muted-foreground">(ID: {{ currentOrder.userId }})</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 提现用户统计摘要 -->
|
||||||
|
<template v-if="currentOrder.type === 2">
|
||||||
|
<div v-if="userStatsLoading" class="py-1">
|
||||||
|
<UiSpinner class="size-4 mx-auto" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="userStats" class="p-2 rounded bg-background text-xs space-y-1">
|
||||||
|
<div class="grid grid-cols-3 gap-1 text-center">
|
||||||
|
<div>
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
资金余额
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold">
|
||||||
|
{{ formatVal(userStats.fundAccount?.balance) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
累计充值
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold text-green-600 dark:text-green-400">
|
||||||
|
{{ formatVal(userStats.fundAccount?.totalDeposit) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="text-muted-foreground">
|
||||||
|
累计提现
|
||||||
|
</div>
|
||||||
|
<div class="font-mono font-bold text-red-600 dark:text-red-400">
|
||||||
|
{{ formatVal(userStats.fundAccount?.totalWithdraw) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-muted-foreground text-center">
|
||||||
|
冻结: {{ formatVal(userStats.fundAccount?.frozen) }} | 推广: {{ userStats.referralStats?.directCount || 0 }}人 | 注册: {{ userStats.user?.createTime?.substring(0, 10) || '-' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<div>
|
<div>
|
||||||
<div class="text-muted-foreground">
|
<div class="text-muted-foreground">
|
||||||
类型
|
类型
|
||||||
|
|||||||
@@ -340,6 +340,8 @@ export function useApproveOrderMutation() {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
queryClient.invalidateQueries({ queryKey: ['useGetPendingOrdersQuery'] })
|
queryClient.invalidateQueries({ queryKey: ['useGetPendingOrdersQuery'] })
|
||||||
queryClient.invalidateQueries({ queryKey: ['useGetAllOrdersQuery'] })
|
queryClient.invalidateQueries({ queryKey: ['useGetAllOrdersQuery'] })
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['useGetFinanceOverviewQuery'] })
|
||||||
|
queryClient.invalidateQueries({ queryKey: ['useGetUserStatsQuery'] })
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import org.springframework.boot.web.servlet.ServletComponentScan;
|
|||||||
import org.springframework.context.annotation.ComponentScan;
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
|
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@ServletComponentScan(basePackages ={"com.it.rattan"})
|
@ServletComponentScan(basePackages ={"com.it.rattan"})
|
||||||
@@ -19,6 +21,8 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
|||||||
public class SpcCloudApplication {
|
public class SpcCloudApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
|
// 统一设置JVM默认时区为北京时间,确保无论服务器部署在哪里时间都一致
|
||||||
|
TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||||
SpringApplication.run(SpcCloudApplication.class, args);
|
SpringApplication.run(SpcCloudApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,17 +7,27 @@ import com.fasterxml.jackson.databind.DeserializationFeature;
|
|||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Primary;
|
import org.springframework.context.annotation.Primary;
|
||||||
|
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Jackson 配置类
|
* Jackson 配置类
|
||||||
* 彻底解决 StackOverflowError 问题
|
* 统一时间格式为 yyyy-MM-dd HH:mm:ss,时区为 Asia/Shanghai
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
public class JacksonConfig {
|
public class JacksonConfig {
|
||||||
|
|
||||||
|
private static final String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";
|
||||||
|
private static final String DATE_PATTERN = "yyyy-MM-dd";
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Primary
|
@Primary
|
||||||
public ObjectMapper objectMapper() {
|
public ObjectMapper objectMapper() {
|
||||||
@@ -35,12 +45,22 @@ public class JacksonConfig {
|
|||||||
// 不序列化 null 值
|
// 不序列化 null 值
|
||||||
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||||
|
|
||||||
// 注册 Java 8 时间模块
|
// 注册 Java 8 时间模块,自定义日期时间格式
|
||||||
mapper.registerModule(new JavaTimeModule());
|
JavaTimeModule module = new JavaTimeModule();
|
||||||
|
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern(DATE_TIME_PATTERN);
|
||||||
|
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(DATE_PATTERN);
|
||||||
|
module.addSerializer(java.time.LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter));
|
||||||
|
module.addDeserializer(java.time.LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));
|
||||||
|
module.addSerializer(java.time.LocalDate.class, new LocalDateSerializer(dateFormatter));
|
||||||
|
module.addDeserializer(java.time.LocalDate.class, new LocalDateDeserializer(dateFormatter));
|
||||||
|
mapper.registerModule(module);
|
||||||
|
|
||||||
// 禁用日期作为时间戳
|
// 禁用日期作为时间戳
|
||||||
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||||
|
|
||||||
|
// 统一时区为北京时间
|
||||||
|
mapper.setTimeZone(TimeZone.getTimeZone("Asia/Shanghai"));
|
||||||
|
|
||||||
return mapper;
|
return mapper;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -445,6 +445,7 @@ public class AdminController {
|
|||||||
coin.setStatus(status);
|
coin.setStatus(status);
|
||||||
coin.setUpdateTime(LocalDateTime.now());
|
coin.setUpdateTime(LocalDateTime.now());
|
||||||
coinService.updateById(coin);
|
coinService.updateById(coin);
|
||||||
|
coinService.clearCache(coin.getCode());
|
||||||
|
|
||||||
return Result.success(status == 1 ? "已上架" : "已下架", null);
|
return Result.success(status == 1 ? "已上架" : "已下架", null);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -540,7 +540,7 @@ public class FundService {
|
|||||||
LambdaQueryWrapper<OrderFund> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<OrderFund> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
|
||||||
if (adminRole != null && adminRole == 3) {
|
if (adminRole != null && adminRole == 3) {
|
||||||
// 财务只看提现订单
|
// 财务:看所有提现订单
|
||||||
wrapper.eq(OrderFund::getType, 2);
|
wrapper.eq(OrderFund::getType, 2);
|
||||||
} else if (type != null && type > 0) {
|
} else if (type != null && type > 0) {
|
||||||
wrapper.eq(OrderFund::getType, type);
|
wrapper.eq(OrderFund::getType, type);
|
||||||
@@ -550,14 +550,9 @@ public class FundService {
|
|||||||
wrapper.eq(OrderFund::getStatus, status);
|
wrapper.eq(OrderFund::getStatus, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (adminId != null) {
|
if (adminId != null && adminRole != null && adminRole == 2) {
|
||||||
if (adminRole != null && adminRole == 3) {
|
// 普通管理员:看自己审批过的
|
||||||
// 财务:看自己审批过的
|
wrapper.eq(OrderFund::getApproveAdminId, adminId);
|
||||||
wrapper.eq(OrderFund::getFinanceAdminId, adminId);
|
|
||||||
} else {
|
|
||||||
// 管理员:看自己审批过的
|
|
||||||
wrapper.eq(OrderFund::getApproveAdminId, adminId);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapper.orderByDesc(OrderFund::getCreateTime);
|
wrapper.orderByDesc(OrderFund::getCreateTime);
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ spring:
|
|||||||
connection-timeout: 30000
|
connection-timeout: 30000
|
||||||
connection-test-query: SELECT 1
|
connection-test-query: SELECT 1
|
||||||
jackson:
|
jackson:
|
||||||
|
time-zone: Asia/Shanghai
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
serialization:
|
serialization:
|
||||||
write-dates-as-timestamps: false
|
write-dates-as-timestamps: false
|
||||||
fail-on-empty-beans: false
|
fail-on-empty-beans: false
|
||||||
|
|||||||
@@ -31,6 +31,15 @@ spring:
|
|||||||
maxPoolPreparedStatementPerConnectionSize: 20
|
maxPoolPreparedStatementPerConnectionSize: 20
|
||||||
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
|
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
|
||||||
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
|
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
|
||||||
|
jackson:
|
||||||
|
time-zone: Asia/Shanghai
|
||||||
|
date-format: yyyy-MM-dd HH:mm:ss
|
||||||
|
serialization:
|
||||||
|
write-dates-as-timestamps: false
|
||||||
|
fail-on-empty-beans: false
|
||||||
|
deserialization:
|
||||||
|
fail-on-unknown-properties: false
|
||||||
|
default-property-inclusion: non_null
|
||||||
|
|
||||||
|
|
||||||
#mybatis-plus
|
#mybatis-plus
|
||||||
|
|||||||
Reference in New Issue
Block a user