feat: 重构充值提现功能,添加冷钱包管理
后端改动: - 新增冷钱包管理模块(ColdWallet实体、Mapper、Service、Controller) - 充值流程:创建订单→显示钱包地址→用户确认打款→管理员审核 - 提现流程:用户输入地址和联系方式→冻结余额→管理员审核 - OrderFund新增字段:walletId, walletAddress, withdrawContact, payTime, confirmTime 前端改动(monisuo-admin): - 新增冷钱包管理页面(wallets.vue) - 优化订单管理页面,支持新的状态流转 - 添加调试日志帮助排查登录问题 前端改动(flutter_monisuo): - 更新OrderFund模型支持新字段 - 充值成功后显示钱包地址弹窗 - 提现时收集提现地址和联系方式 - 新增资金订单页面 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -107,20 +107,68 @@ function formatAmount(amount: number): string {
|
||||
return amount.toLocaleString(undefined, { minimumFractionDigits: 2, maximumFractionDigits: 2 })
|
||||
}
|
||||
|
||||
function getStatusVariant(status: number): 'default' | 'secondary' | 'destructive' {
|
||||
if (status === 1)
|
||||
return 'secondary'
|
||||
if (status === 2)
|
||||
return 'default'
|
||||
return 'destructive'
|
||||
// 根据订单类型和状态获取状态样式
|
||||
function getStatusVariant(order: OrderFund): 'default' | 'secondary' | 'destructive' | 'outline' {
|
||||
const { type, status } = order
|
||||
// 充值状态: 1=待付款, 2=待确认, 3=已完成, 4=已驳回, 5=已取消
|
||||
// 提现状态: 1=待审批, 2=已完成, 3=已驳回, 4=已取消
|
||||
if (type === 1) {
|
||||
// 充值
|
||||
if (status === 1) return 'secondary' // 待付款
|
||||
if (status === 2) return 'default' // 待确认
|
||||
if (status === 3) return 'default' // 已完成
|
||||
return 'destructive' // 已驳回/已取消
|
||||
}
|
||||
else {
|
||||
// 提现
|
||||
if (status === 1) return 'default' // 待审批
|
||||
if (status === 2) return 'default' // 已完成
|
||||
return 'destructive' // 已驳回/已取消
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusText(status: number): string {
|
||||
if (status === 1)
|
||||
return '待审批'
|
||||
if (status === 2)
|
||||
return '已通过'
|
||||
return '已驳回'
|
||||
// 根据订单类型和状态获取状态文本
|
||||
function getStatusText(order: OrderFund): string {
|
||||
const { type, status } = order
|
||||
if (type === 1) {
|
||||
// 充值状态
|
||||
switch (status) {
|
||||
case 1: return '待付款'
|
||||
case 2: return '待确认'
|
||||
case 3: return '已完成'
|
||||
case 4: return '已驳回'
|
||||
case 5: return '已取消'
|
||||
default: return '未知'
|
||||
}
|
||||
}
|
||||
else {
|
||||
// 提现状态
|
||||
switch (status) {
|
||||
case 1: return '待审批'
|
||||
case 2: return '已完成'
|
||||
case 3: return '已驳回'
|
||||
case 4: return '已取消'
|
||||
default: return '未知'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 判断订单是否可审批
|
||||
// 充值: 仅待确认(status=2)可审批
|
||||
// 提现: 仅待审批(status=1)可审批
|
||||
function canApprove(order: OrderFund): boolean {
|
||||
if (order.type === 1) {
|
||||
return order.status === 2 // 充值待确认
|
||||
}
|
||||
else {
|
||||
return order.status === 1 // 提现待审批
|
||||
}
|
||||
}
|
||||
|
||||
// 复制到剪贴板
|
||||
function copyToClipboard(text: string) {
|
||||
navigator.clipboard.writeText(text)
|
||||
toast.success('已复制到剪贴板')
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -153,7 +201,11 @@ function getStatusText(status: number): string {
|
||||
<UiTableHead class="text-right">
|
||||
金额
|
||||
</UiTableHead>
|
||||
<UiTableHead>状态</UiTableHead>
|
||||
<UiTableHead class="hidden lg:table-cell">
|
||||
地址/联系方式
|
||||
</UiTableHead>
|
||||
<UiTableHead class="hidden xl:table-cell">
|
||||
时间
|
||||
</UiTableHead>
|
||||
<UiTableHead class="text-right">
|
||||
@@ -163,12 +215,12 @@ function getStatusText(status: number): string {
|
||||
</UiTableHeader>
|
||||
<UiTableBody>
|
||||
<UiTableRow v-if="pendingLoading">
|
||||
<UiTableCell :col-span="6" class="text-center py-8">
|
||||
<UiTableCell :col-span="8" class="text-center py-8">
|
||||
<UiSpinner class="mx-auto" />
|
||||
</UiTableCell>
|
||||
</UiTableRow>
|
||||
<UiTableRow v-else-if="pendingOrders.length === 0">
|
||||
<UiTableCell :col-span="6" class="text-center py-8 text-muted-foreground">
|
||||
<UiTableCell :col-span="8" class="text-center py-8 text-muted-foreground">
|
||||
<Icon icon="lucide:inbox" class="size-8 mx-auto mb-2 opacity-50" />
|
||||
<p>暂无待审批订单</p>
|
||||
</UiTableCell>
|
||||
@@ -187,8 +239,27 @@ function getStatusText(status: number): string {
|
||||
<UiTableCell class="text-right font-mono font-medium">
|
||||
¥{{ formatAmount(order.amount) }}
|
||||
</UiTableCell>
|
||||
<UiTableCell class="hidden lg:table-cell text-muted-foreground text-sm">
|
||||
{{ order.createTime }}
|
||||
<UiTableCell>
|
||||
<UiBadge :variant="getStatusVariant(order)">
|
||||
{{ getStatusText(order) }}
|
||||
</UiBadge>
|
||||
</UiTableCell>
|
||||
<UiTableCell class="hidden lg:table-cell">
|
||||
<div v-if="order.walletAddress" class="max-w-[150px]">
|
||||
<div class="font-mono text-xs truncate" :title="order.walletAddress">
|
||||
{{ order.walletAddress }}
|
||||
</div>
|
||||
<div v-if="order.withdrawContact" class="text-xs text-muted-foreground">
|
||||
{{ order.withdrawContact }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="text-muted-foreground">-</span>
|
||||
</UiTableCell>
|
||||
<UiTableCell class="hidden xl:table-cell text-muted-foreground text-sm">
|
||||
<div>{{ order.createTime }}</div>
|
||||
<div v-if="order.payTime" class="text-xs">
|
||||
打款: {{ order.payTime }}
|
||||
</div>
|
||||
</UiTableCell>
|
||||
<UiTableCell class="text-right">
|
||||
<div class="flex justify-end gap-1">
|
||||
@@ -196,6 +267,7 @@ function getStatusText(status: number): string {
|
||||
<Icon icon="lucide:eye" class="size-4" />
|
||||
</UiButton>
|
||||
<UiButton
|
||||
v-if="canApprove(order)"
|
||||
size="sm"
|
||||
:disabled="approveMutation.isPending.value"
|
||||
@click="openApproveDialog(order, 2)"
|
||||
@@ -203,6 +275,7 @@ function getStatusText(status: number): string {
|
||||
通过
|
||||
</UiButton>
|
||||
<UiButton
|
||||
v-if="canApprove(order)"
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
:disabled="approveMutation.isPending.value"
|
||||
@@ -233,19 +306,38 @@ function getStatusText(status: number): string {
|
||||
{{ order.username }}
|
||||
</div>
|
||||
</div>
|
||||
<UiBadge :variant="order.type === 1 ? 'default' : 'destructive'">
|
||||
{{ order.type === 1 ? '充值' : '提现' }}
|
||||
</UiBadge>
|
||||
<div class="text-right">
|
||||
<UiBadge :variant="order.type === 1 ? 'default' : 'destructive'" class="mb-1">
|
||||
{{ order.type === 1 ? '充值' : '提现' }}
|
||||
</UiBadge>
|
||||
<UiBadge :variant="getStatusVariant(order)" class="block">
|
||||
{{ getStatusText(order) }}
|
||||
</UiBadge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 pt-3 border-t">
|
||||
<div class="text-xl font-mono font-bold" :class="order.type === 1 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'">
|
||||
{{ order.type === 1 ? '+' : '-' }}¥{{ formatAmount(order.amount) }}
|
||||
</div>
|
||||
<!-- 显示地址信息 -->
|
||||
<div v-if="order.walletAddress" class="mt-2 text-sm">
|
||||
<span class="text-muted-foreground">{{ order.type === 1 ? '充值地址' : '提现地址' }}:</span>
|
||||
<div class="font-mono text-xs break-all mt-1 flex items-center gap-1">
|
||||
{{ order.walletAddress }}
|
||||
<Icon icon="lucide:copy" class="size-3 cursor-pointer" @click="copyToClipboard(order.walletAddress!)" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="order.withdrawContact" class="text-sm text-muted-foreground mt-1">
|
||||
联系方式: {{ order.withdrawContact }}
|
||||
</div>
|
||||
<div class="text-sm text-muted-foreground mt-1">
|
||||
{{ order.createTime }}
|
||||
</div>
|
||||
<div v-if="order.payTime" class="text-xs text-muted-foreground">
|
||||
确认打款: {{ order.payTime }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex gap-2">
|
||||
<div v-if="canApprove(order)" class="mt-3 flex gap-2">
|
||||
<UiButton size="sm" class="flex-1" @click="openApproveDialog(order, 2)">
|
||||
通过
|
||||
</UiButton>
|
||||
@@ -253,6 +345,11 @@ function getStatusText(status: number): string {
|
||||
驳回
|
||||
</UiButton>
|
||||
</div>
|
||||
<div v-else class="mt-3">
|
||||
<UiButton size="sm" variant="outline" class="w-full" @click="viewOrderDetail(order)">
|
||||
查看详情
|
||||
</UiButton>
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
<div v-else class="text-center py-8 text-muted-foreground">
|
||||
@@ -297,14 +394,20 @@ function getStatusText(status: number): string {
|
||||
全部
|
||||
</UiSelectItem>
|
||||
<UiSelectItem :value="1">
|
||||
待审批
|
||||
待付款/待审批
|
||||
</UiSelectItem>
|
||||
<UiSelectItem :value="2">
|
||||
已通过
|
||||
待确认/已完成
|
||||
</UiSelectItem>
|
||||
<UiSelectItem :value="3">
|
||||
已完成
|
||||
</UiSelectItem>
|
||||
<UiSelectItem :value="4">
|
||||
已驳回
|
||||
</UiSelectItem>
|
||||
<UiSelectItem :value="5">
|
||||
已取消
|
||||
</UiSelectItem>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
</div>
|
||||
@@ -328,6 +431,9 @@ function getStatusText(status: number): string {
|
||||
金额
|
||||
</UiTableHead>
|
||||
<UiTableHead>状态</UiTableHead>
|
||||
<UiTableHead class="hidden lg:table-cell">
|
||||
地址
|
||||
</UiTableHead>
|
||||
<UiTableHead class="hidden xl:table-cell">
|
||||
时间
|
||||
</UiTableHead>
|
||||
@@ -341,12 +447,12 @@ function getStatusText(status: number): string {
|
||||
</UiTableHeader>
|
||||
<UiTableBody>
|
||||
<UiTableRow v-if="allLoading">
|
||||
<UiTableCell :col-span="8" class="text-center py-8">
|
||||
<UiTableCell :col-span="9" class="text-center py-8">
|
||||
<UiSpinner class="mx-auto" />
|
||||
</UiTableCell>
|
||||
</UiTableRow>
|
||||
<UiTableRow v-else-if="allOrders.length === 0">
|
||||
<UiTableCell :col-span="8" class="text-center py-8 text-muted-foreground">
|
||||
<UiTableCell :col-span="9" class="text-center py-8 text-muted-foreground">
|
||||
暂无数据
|
||||
</UiTableCell>
|
||||
</UiTableRow>
|
||||
@@ -364,14 +470,25 @@ function getStatusText(status: number): string {
|
||||
¥{{ formatAmount(order.amount) }}
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<UiBadge :variant="getStatusVariant(order.status)">
|
||||
{{ getStatusText(order.status) }}
|
||||
<UiBadge :variant="getStatusVariant(order)">
|
||||
{{ getStatusText(order) }}
|
||||
</UiBadge>
|
||||
</UiTableCell>
|
||||
<UiTableCell class="hidden xl:table-cell text-muted-foreground text-sm">
|
||||
{{ order.createTime }}
|
||||
<UiTableCell class="hidden lg:table-cell">
|
||||
<div v-if="order.walletAddress" class="max-w-[120px]">
|
||||
<div class="font-mono text-xs truncate" :title="order.walletAddress">
|
||||
{{ order.walletAddress }}
|
||||
</div>
|
||||
</div>
|
||||
<span v-else class="text-muted-foreground">-</span>
|
||||
</UiTableCell>
|
||||
<UiTableCell class="hidden lg:table-cell text-sm text-muted-foreground max-w-[150px] truncate">
|
||||
<UiTableCell class="hidden xl:table-cell text-muted-foreground text-sm">
|
||||
<div>{{ order.createTime }}</div>
|
||||
<div v-if="order.confirmTime" class="text-xs">
|
||||
完成: {{ order.confirmTime }}
|
||||
</div>
|
||||
</UiTableCell>
|
||||
<UiTableCell class="hidden lg:table-cell text-sm text-muted-foreground max-w-[120px] truncate">
|
||||
{{ order.rejectReason || order.adminRemark || '-' }}
|
||||
</UiTableCell>
|
||||
<UiTableCell class="text-right">
|
||||
@@ -404,8 +521,8 @@ function getStatusText(status: number): string {
|
||||
<UiBadge :variant="order.type === 1 ? 'default' : 'destructive'" class="mb-1">
|
||||
{{ order.type === 1 ? '充值' : '提现' }}
|
||||
</UiBadge>
|
||||
<UiBadge :variant="getStatusVariant(order.status)" class="block">
|
||||
{{ getStatusText(order.status) }}
|
||||
<UiBadge :variant="getStatusVariant(order)" class="block">
|
||||
{{ getStatusText(order) }}
|
||||
</UiBadge>
|
||||
</div>
|
||||
</div>
|
||||
@@ -413,9 +530,23 @@ function getStatusText(status: number): string {
|
||||
<div class="text-xl font-mono font-bold">
|
||||
¥{{ formatAmount(order.amount) }}
|
||||
</div>
|
||||
<!-- 显示地址信息 -->
|
||||
<div v-if="order.walletAddress" class="mt-2 text-sm">
|
||||
<span class="text-muted-foreground">{{ order.type === 1 ? '充值地址' : '提现地址' }}:</span>
|
||||
<div class="font-mono text-xs break-all mt-1 flex items-center gap-1">
|
||||
{{ order.walletAddress }}
|
||||
<Icon icon="lucide:copy" class="size-3 cursor-pointer" @click="copyToClipboard(order.walletAddress!)" />
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="order.withdrawContact" class="text-sm text-muted-foreground mt-1">
|
||||
联系方式: {{ order.withdrawContact }}
|
||||
</div>
|
||||
<div class="text-sm text-muted-foreground mt-1">
|
||||
{{ order.createTime }}
|
||||
</div>
|
||||
<div v-if="order.confirmTime" class="text-xs text-muted-foreground">
|
||||
完成: {{ order.confirmTime }}
|
||||
</div>
|
||||
<div v-if="order.rejectReason || order.adminRemark" class="text-sm text-muted-foreground mt-1">
|
||||
备注: {{ order.rejectReason || order.adminRemark }}
|
||||
</div>
|
||||
@@ -489,7 +620,7 @@ function getStatusText(status: number): string {
|
||||
|
||||
<!-- 订单详情弹窗 -->
|
||||
<UiDialog v-model:open="showDetailDialog">
|
||||
<UiDialogContent class="max-w-md">
|
||||
<UiDialogContent class="max-w-md max-h-[90vh] overflow-y-auto">
|
||||
<UiDialogHeader>
|
||||
<UiDialogTitle>订单详情</UiDialogTitle>
|
||||
</UiDialogHeader>
|
||||
@@ -529,11 +660,33 @@ function getStatusText(status: number): string {
|
||||
状态
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<UiBadge :variant="getStatusVariant(currentOrder.status)">
|
||||
{{ getStatusText(currentOrder.status) }}
|
||||
<UiBadge :variant="getStatusVariant(currentOrder)">
|
||||
{{ getStatusText(currentOrder) }}
|
||||
</UiBadge>
|
||||
</div>
|
||||
|
||||
<!-- 充值/提现地址 -->
|
||||
<div class="text-muted-foreground">
|
||||
{{ currentOrder.type === 1 ? '充值地址' : '提现地址' }}
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
<div v-if="currentOrder.walletAddress" class="flex items-start gap-1">
|
||||
<span class="font-mono text-xs break-all">{{ currentOrder.walletAddress }}</span>
|
||||
<Icon icon="lucide:copy" class="size-4 cursor-pointer flex-shrink-0" @click="copyToClipboard(currentOrder.walletAddress!)" />
|
||||
</div>
|
||||
<span v-else class="text-muted-foreground">-</span>
|
||||
</div>
|
||||
|
||||
<!-- 提现联系方式 -->
|
||||
<template v-if="currentOrder.type === 2 && currentOrder.withdrawContact">
|
||||
<div class="text-muted-foreground">
|
||||
联系方式
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
{{ currentOrder.withdrawContact }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="text-muted-foreground">
|
||||
创建时间
|
||||
</div>
|
||||
@@ -541,6 +694,26 @@ function getStatusText(status: number): string {
|
||||
{{ currentOrder.createTime }}
|
||||
</div>
|
||||
|
||||
<!-- 用户确认打款时间(充值) -->
|
||||
<template v-if="currentOrder.type === 1 && currentOrder.payTime">
|
||||
<div class="text-muted-foreground">
|
||||
确认打款
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
{{ currentOrder.payTime }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 管理员确认时间 -->
|
||||
<template v-if="currentOrder.confirmTime">
|
||||
<div class="text-muted-foreground">
|
||||
完成时间
|
||||
</div>
|
||||
<div class="col-span-2">
|
||||
{{ currentOrder.confirmTime }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="currentOrder.rejectReason">
|
||||
<div class="text-muted-foreground text-red-500">
|
||||
驳回原因
|
||||
@@ -561,14 +734,14 @@ function getStatusText(status: number): string {
|
||||
</div>
|
||||
</div>
|
||||
<UiDialogFooter>
|
||||
<template v-if="currentOrder?.status === 1">
|
||||
<template v-if="currentOrder && canApprove(currentOrder)">
|
||||
<UiButton variant="outline" @click="showDetailDialog = false">
|
||||
关闭
|
||||
</UiButton>
|
||||
<UiButton @click="openApproveDialog(currentOrder!, 2); showDetailDialog = false">
|
||||
<UiButton @click="openApproveDialog(currentOrder, 2); showDetailDialog = false">
|
||||
通过
|
||||
</UiButton>
|
||||
<UiButton variant="destructive" @click="openApproveDialog(currentOrder!, 3); showDetailDialog = false">
|
||||
<UiButton variant="destructive" @click="openApproveDialog(currentOrder, 3); showDetailDialog = false">
|
||||
驳回
|
||||
</UiButton>
|
||||
</template>
|
||||
@@ -581,23 +754,69 @@ function getStatusText(status: number): string {
|
||||
|
||||
<!-- 审批弹窗 -->
|
||||
<UiDialog v-model:open="showApproveDialog">
|
||||
<UiDialogContent class="max-w-md">
|
||||
<UiDialogContent class="max-w-md max-h-[90vh] overflow-y-auto">
|
||||
<UiDialogHeader>
|
||||
<UiDialogTitle>{{ approveStatus === 2 ? '通过订单' : '驳回订单' }}</UiDialogTitle>
|
||||
</UiDialogHeader>
|
||||
<div v-if="currentOrder" class="grid gap-4 py-4">
|
||||
<div class="p-3 rounded-lg bg-muted/50 text-sm">
|
||||
<div class="text-muted-foreground">
|
||||
订单号
|
||||
<div class="p-3 rounded-lg bg-muted/50 text-sm space-y-2">
|
||||
<div>
|
||||
<div class="text-muted-foreground">
|
||||
订单号
|
||||
</div>
|
||||
<div class="font-mono">
|
||||
{{ currentOrder.orderNo }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="font-mono">
|
||||
{{ currentOrder.orderNo }}
|
||||
<div>
|
||||
<div class="text-muted-foreground">
|
||||
用户
|
||||
</div>
|
||||
<div class="font-medium">
|
||||
{{ currentOrder.username }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-muted-foreground mt-2">
|
||||
金额
|
||||
<div>
|
||||
<div class="text-muted-foreground">
|
||||
类型
|
||||
</div>
|
||||
<div>
|
||||
<UiBadge :variant="currentOrder.type === 1 ? 'default' : 'destructive'">
|
||||
{{ currentOrder.type === 1 ? '充值' : '提现' }}
|
||||
</UiBadge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="font-mono font-bold text-lg">
|
||||
¥{{ formatAmount(currentOrder.amount) }}
|
||||
<div>
|
||||
<div class="text-muted-foreground">
|
||||
金额
|
||||
</div>
|
||||
<div class="font-mono font-bold text-lg">
|
||||
¥{{ formatAmount(currentOrder.amount) }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- 显示地址信息 -->
|
||||
<div v-if="currentOrder.walletAddress">
|
||||
<div class="text-muted-foreground">
|
||||
{{ currentOrder.type === 1 ? '充值地址' : '提现地址' }}
|
||||
</div>
|
||||
<div class="flex items-center gap-1">
|
||||
<span class="font-mono text-xs break-all">{{ currentOrder.walletAddress }}</span>
|
||||
<Icon icon="lucide:copy" class="size-4 cursor-pointer flex-shrink-0" @click="copyToClipboard(currentOrder.walletAddress!)" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 提现联系方式 -->
|
||||
<div v-if="currentOrder.type === 2 && currentOrder.withdrawContact">
|
||||
<div class="text-muted-foreground">
|
||||
联系方式
|
||||
</div>
|
||||
<div>{{ currentOrder.withdrawContact }}</div>
|
||||
</div>
|
||||
<!-- 充值确认打款时间 -->
|
||||
<div v-if="currentOrder.type === 1 && currentOrder.payTime">
|
||||
<div class="text-muted-foreground">
|
||||
确认打款时间
|
||||
</div>
|
||||
<div>{{ currentOrder.payTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="approveStatus === 3" class="grid gap-2">
|
||||
@@ -615,7 +834,7 @@ function getStatusText(status: number): string {
|
||||
</UiButton>
|
||||
<UiButton
|
||||
:variant="approveStatus === 3 ? 'destructive' : 'default'"
|
||||
:disabled="approveMutation.isPending.value"
|
||||
:disabled="approveMutation.isPending.value || (approveStatus === 3 && !rejectReason.trim())"
|
||||
@click="handleApprove"
|
||||
>
|
||||
<UiSpinner v-if="approveMutation.isPending.value" class="mr-2" />
|
||||
|
||||
320
monisuo-admin/src/pages/monisuo/wallets.vue
Normal file
320
monisuo-admin/src/pages/monisuo/wallets.vue
Normal file
@@ -0,0 +1,320 @@
|
||||
<script lang="ts" setup>
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { toast } from 'vue-sonner'
|
||||
|
||||
import type { ColdWallet } from '@/services/api/monisuo-admin.api'
|
||||
|
||||
import { BasicPage } from '@/components/global-layout'
|
||||
import {
|
||||
useGetWalletListQuery,
|
||||
useCreateWalletMutation,
|
||||
useUpdateWalletMutation,
|
||||
useDeleteWalletMutation,
|
||||
useSetDefaultWalletMutation,
|
||||
useToggleWalletStatusMutation,
|
||||
} from '@/services/api/monisuo-admin.api'
|
||||
|
||||
const { data, isLoading, refetch } = useGetWalletListQuery()
|
||||
const createMutation = useCreateWalletMutation()
|
||||
const updateMutation = useUpdateWalletMutation()
|
||||
const deleteMutation = useDeleteWalletMutation()
|
||||
const setDefaultMutation = useSetDefaultWalletMutation()
|
||||
const toggleStatusMutation = useToggleWalletStatusMutation()
|
||||
|
||||
const wallets = computed(() => data.value?.data || [])
|
||||
|
||||
const editingWallet = ref<Partial<ColdWallet>>({})
|
||||
const showEditDialog = ref(false)
|
||||
const isEditing = ref(false)
|
||||
|
||||
// 表单验证
|
||||
const formErrors = ref<{ name?: string, address?: string }>({})
|
||||
|
||||
function validateForm(): boolean {
|
||||
formErrors.value = {}
|
||||
|
||||
if (!editingWallet.value.name?.trim()) {
|
||||
formErrors.value.name = '请输入钱包名称'
|
||||
return false
|
||||
}
|
||||
if (!editingWallet.value.address?.trim()) {
|
||||
formErrors.value.address = '请输入钱包地址'
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function openCreateDialog() {
|
||||
editingWallet.value = { network: 'TRC20', status: 1, isDefault: false }
|
||||
isEditing.value = false
|
||||
formErrors.value = {}
|
||||
showEditDialog.value = true
|
||||
}
|
||||
|
||||
function openEditDialog(wallet: ColdWallet) {
|
||||
editingWallet.value = { ...wallet }
|
||||
isEditing.value = true
|
||||
formErrors.value = {}
|
||||
showEditDialog.value = true
|
||||
}
|
||||
|
||||
async function saveWallet() {
|
||||
if (!validateForm())
|
||||
return
|
||||
|
||||
try {
|
||||
if (isEditing.value) {
|
||||
await updateMutation.mutateAsync(editingWallet.value)
|
||||
toast.success('钱包已更新')
|
||||
}
|
||||
else {
|
||||
await createMutation.mutateAsync(editingWallet.value)
|
||||
toast.success('钱包已创建')
|
||||
}
|
||||
showEditDialog.value = false
|
||||
refetch()
|
||||
}
|
||||
catch (e: any) {
|
||||
toast.error(e.response?.data?.msg || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function setDefault(wallet: ColdWallet) {
|
||||
if (wallet.isDefault)
|
||||
return
|
||||
|
||||
try {
|
||||
await setDefaultMutation.mutateAsync({ id: wallet.id })
|
||||
toast.success(`已将 ${wallet.name} 设为默认`)
|
||||
}
|
||||
catch (e: any) {
|
||||
toast.error(e.response?.data?.msg || '设置失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function toggleStatus(wallet: ColdWallet) {
|
||||
try {
|
||||
await toggleStatusMutation.mutateAsync({ id: wallet.id })
|
||||
toast.success(wallet.status === 1 ? '已禁用' : '已启用')
|
||||
}
|
||||
catch (e: any) {
|
||||
toast.error(e.response?.data?.msg || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteWallet(wallet: ColdWallet) {
|
||||
if (!confirm(`确定删除钱包 ${wallet.name} 吗?`))
|
||||
return
|
||||
|
||||
try {
|
||||
await deleteMutation.mutateAsync({ id: wallet.id })
|
||||
toast.success('钱包已删除')
|
||||
}
|
||||
catch (e: any) {
|
||||
toast.error(e.response?.data?.msg || '删除失败')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<BasicPage title="冷钱包管理" description="配置充值收款地址">
|
||||
<template #actions>
|
||||
<UiButton @click="openCreateDialog">
|
||||
<Icon icon="lucide:plus" class="mr-2 h-4 w-4" />
|
||||
新增钱包
|
||||
</UiButton>
|
||||
</template>
|
||||
|
||||
<!-- PC端表格 -->
|
||||
<UiCard class="hidden md:block overflow-x-auto p-4">
|
||||
<UiTable v-if="!isLoading">
|
||||
<UiTableHeader>
|
||||
<UiTableRow>
|
||||
<UiTableHead>名称</UiTableHead>
|
||||
<UiTableHead>地址</UiTableHead>
|
||||
<UiTableHead>网络</UiTableHead>
|
||||
<UiTableHead>默认</UiTableHead>
|
||||
<UiTableHead>状态</UiTableHead>
|
||||
<UiTableHead>操作</UiTableHead>
|
||||
</UiTableRow>
|
||||
</UiTableHeader>
|
||||
<UiTableBody>
|
||||
<UiTableRow v-for="wallet in wallets" :key="wallet.id">
|
||||
<UiTableCell class="font-medium">{{ wallet.name }}</UiTableCell>
|
||||
<UiTableCell class="font-mono text-xs max-w-[200px] truncate">
|
||||
{{ wallet.address }}
|
||||
</UiTableCell>
|
||||
<UiTableCell>{{ wallet.network }}</UiTableCell>
|
||||
<UiTableCell>
|
||||
<UiBadge v-if="wallet.isDefault" variant="default">
|
||||
默认
|
||||
</UiBadge>
|
||||
<span v-else class="text-muted-foreground">-</span>
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<UiBadge :variant="wallet.status === 1 ? 'default' : 'destructive'">
|
||||
{{ wallet.status === 1 ? '启用' : '禁用' }}
|
||||
</UiBadge>
|
||||
</UiTableCell>
|
||||
<UiTableCell>
|
||||
<div class="flex gap-2">
|
||||
<UiButton size="sm" variant="ghost" @click="openEditDialog(wallet)">
|
||||
编辑
|
||||
</UiButton>
|
||||
<UiButton
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
:disabled="wallet.isDefault"
|
||||
@click="setDefault(wallet)"
|
||||
>
|
||||
设为默认
|
||||
</UiButton>
|
||||
<UiButton
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
@click="toggleStatus(wallet)"
|
||||
>
|
||||
{{ wallet.status === 1 ? '禁用' : '启用' }}
|
||||
</UiButton>
|
||||
<UiButton
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
@click="deleteWallet(wallet)"
|
||||
>
|
||||
删除
|
||||
</UiButton>
|
||||
</div>
|
||||
</UiTableCell>
|
||||
</UiTableRow>
|
||||
<UiTableRow v-if="wallets.length === 0">
|
||||
<UiTableCell colspan="6" class="text-center text-muted-foreground py-8">
|
||||
暂无钱包数据
|
||||
</UiTableCell>
|
||||
</UiTableRow>
|
||||
</UiTableBody>
|
||||
</UiTable>
|
||||
<div v-else class="flex justify-center py-8">
|
||||
<Icon icon="lucide:loader-2" class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
</UiCard>
|
||||
|
||||
<!-- 移动端卡片 -->
|
||||
<div class="md:hidden space-y-4">
|
||||
<div v-if="isLoading" class="flex justify-center py-8">
|
||||
<Icon icon="lucide:loader-2" class="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
<template v-else-if="wallets.length > 0">
|
||||
<UiCard v-for="wallet in wallets" :key="wallet.id" class="p-4">
|
||||
<div class="flex justify-between items-start mb-2">
|
||||
<div>
|
||||
<div class="font-medium">{{ wallet.name }}</div>
|
||||
<div class="text-xs text-muted-foreground">{{ wallet.network }}</div>
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<UiBadge v-if="wallet.isDefault" variant="default" class="text-xs">
|
||||
默认
|
||||
</UiBadge>
|
||||
<UiBadge :variant="wallet.status === 1 ? 'default' : 'destructive'" class="text-xs">
|
||||
{{ wallet.status === 1 ? '启用' : '禁用' }}
|
||||
</UiBadge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="font-mono text-xs break-all mb-3 text-muted-foreground">
|
||||
{{ wallet.address }}
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
<UiButton size="sm" variant="outline" @click="openEditDialog(wallet)">
|
||||
编辑
|
||||
</UiButton>
|
||||
<UiButton
|
||||
size="sm"
|
||||
variant="outline"
|
||||
:disabled="wallet.isDefault"
|
||||
@click="setDefault(wallet)"
|
||||
>
|
||||
设为默认
|
||||
</UiButton>
|
||||
<UiButton
|
||||
size="sm"
|
||||
variant="outline"
|
||||
@click="toggleStatus(wallet)"
|
||||
>
|
||||
{{ wallet.status === 1 ? '禁用' : '启用' }}
|
||||
</UiButton>
|
||||
<UiButton
|
||||
size="sm"
|
||||
variant="destructive"
|
||||
@click="deleteWallet(wallet)"
|
||||
>
|
||||
删除
|
||||
</UiButton>
|
||||
</div>
|
||||
</UiCard>
|
||||
</template>
|
||||
<div v-else class="text-center text-muted-foreground py-8">
|
||||
暂无钱包数据
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 新增/编辑弹窗 -->
|
||||
<UiDialog v-model:open="showEditDialog">
|
||||
<UiDialogContent>
|
||||
<UiDialogHeader>
|
||||
<UiDialogTitle>{{ isEditing ? '编辑钱包' : '新增钱包' }}</UiDialogTitle>
|
||||
</UiDialogHeader>
|
||||
<div class="grid gap-4 py-4">
|
||||
<div class="grid gap-2">
|
||||
<UiLabel>钱包名称 <span class="text-red-500">*</span></UiLabel>
|
||||
<UiInput v-model="editingWallet.name" placeholder="如:主钱包" />
|
||||
<span v-if="formErrors.name" class="text-xs text-red-500">{{ formErrors.name }}</span>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<UiLabel>钱包地址 <span class="text-red-500">*</span></UiLabel>
|
||||
<UiInput v-model="editingWallet.address" placeholder="TRC20/ERC20 地址" />
|
||||
<span v-if="formErrors.address" class="text-xs text-red-500">{{ formErrors.address }}</span>
|
||||
</div>
|
||||
<div class="grid gap-2">
|
||||
<UiLabel>网络类型</UiLabel>
|
||||
<UiSelect v-model="editingWallet.network">
|
||||
<UiSelectTrigger>
|
||||
<UiSelectValue placeholder="选择网络" />
|
||||
</UiSelectTrigger>
|
||||
<UiSelectContent>
|
||||
<UiSelectItem value="TRC20">
|
||||
TRC20 (波场)
|
||||
</UiSelectItem>
|
||||
<UiSelectItem value="ERC20">
|
||||
ERC20 (以太坊)
|
||||
</UiSelectItem>
|
||||
<UiSelectItem value="BEP20">
|
||||
BEP20 (币安智能链)
|
||||
</UiSelectItem>
|
||||
</UiSelectContent>
|
||||
</UiSelect>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<UiCheckbox
|
||||
:checked="editingWallet.isDefault"
|
||||
@update:checked="editingWallet.isDefault = $event"
|
||||
/>
|
||||
<UiLabel class="cursor-pointer" @click="editingWallet.isDefault = !editingWallet.isDefault">
|
||||
设为默认钱包
|
||||
</UiLabel>
|
||||
</div>
|
||||
</div>
|
||||
<UiDialogFooter>
|
||||
<UiButton variant="outline" @click="showEditDialog = false">
|
||||
取消
|
||||
</UiButton>
|
||||
<UiButton
|
||||
:disabled="createMutation.isPending.value || updateMutation.isPending.value"
|
||||
@click="saveWallet"
|
||||
>
|
||||
{{ isEditing ? '保存' : '创建' }}
|
||||
</UiButton>
|
||||
</UiDialogFooter>
|
||||
</UiDialogContent>
|
||||
</UiDialog>
|
||||
</BasicPage>
|
||||
</template>
|
||||
Reference in New Issue
Block a user