This commit is contained in:
sion
2026-04-10 00:27:28 +08:00
parent 2c8bcc2acc
commit a61c7ed40e
12 changed files with 437 additions and 28 deletions

View File

@@ -5,7 +5,7 @@ import { toast } from 'vue-sonner'
import type { OrderFund } from '@/services/api/monisuo-admin.api'
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 pageSize = ref(10)
@@ -28,6 +28,25 @@ const approveStatus = ref(2)
const rejectReason = 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) {
currentOrder.value = order
showDetailDialog.value = true
@@ -55,12 +74,19 @@ async function handleApprove() {
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
refetchPending()
}
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="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="col-span-2 font-mono font-bold text-lg">{{ formatAmount(currentOrder.amount) }}</div>
@@ -346,7 +490,45 @@ function copyToClipboard(text: string) {
</div>
<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 class="text-muted-foreground">提现金额</div>

View File

@@ -6,7 +6,7 @@ import type { OrderFund } from '@/services/api/monisuo-admin.api'
import { BasicPage } from '@/components/global-layout'
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)
@@ -61,6 +61,11 @@ const approveStatus = ref(2)
const rejectReason = 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) {
currentOrder.value = order
showDetailDialog.value = true
@@ -88,13 +93,27 @@ async function handleApprove() {
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
refetchPending()
if (allLoaded.value) refetchAll()
}
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 })
}
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' {
const { type, status } = order
@@ -673,8 +706,126 @@ function copyToClipboard(text: string) {
</div>
<div class="col-span-2 font-medium">
{{ currentOrder.username }}
<UiBadge v-if="currentOrder.type === 2" variant="outline" class="ml-2 text-xs">
ID: {{ currentOrder.userId }}
</UiBadge>
</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>
@@ -863,8 +1014,46 @@ function copyToClipboard(text: string) {
</div>
<div class="font-medium">
{{ currentOrder.username }}
<span v-if="currentOrder.type === 2" class="text-xs text-muted-foreground">(ID: {{ currentOrder.userId }})</span>
</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 class="text-muted-foreground">
类型

View File

@@ -340,6 +340,8 @@ export function useApproveOrderMutation() {
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['useGetPendingOrdersQuery'] })
queryClient.invalidateQueries({ queryKey: ['useGetAllOrdersQuery'] })
queryClient.invalidateQueries({ queryKey: ['useGetFinanceOverviewQuery'] })
queryClient.invalidateQueries({ queryKey: ['useGetUserStatsQuery'] })
},
})
}