feat: 添加提现管理功能,整合提现页面及相关路由,支持提现请求的审核与管理

This commit is contained in:
2026-01-19 23:05:50 +07:00
parent a0efa2c3c3
commit 657853c8b7
8 changed files with 208 additions and 143 deletions

View File

@@ -231,7 +231,8 @@ const local: App.I18n.Schema = {
home: 'Home',
product: 'Product',
user: ' User',
news: 'News'
news: 'News',
withdraw: 'Withdraw'
},
page: {
login: {

View File

@@ -227,7 +227,8 @@ const local: App.I18n.Schema = {
home: '首页',
product: '产品管理',
user: '用户管理',
news: '新闻管理'
news: '新闻管理',
withdraw: '提现管理'
},
page: {
login: {

View File

@@ -24,4 +24,5 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
news: () => import("@/views/news/index.vue"),
product: () => import("@/views/product/index.vue"),
user: () => import("@/views/user/index.vue"),
withdraw: () => import("@/views/withdraw/index.vue"),
};

View File

@@ -99,5 +99,14 @@ export const generatedRoutes: GeneratedRoute[] = [
title: 'user',
i18nKey: 'route.user'
}
},
{
name: 'withdraw',
path: '/withdraw',
component: 'layout.base$view.withdraw',
meta: {
title: 'withdraw',
i18nKey: 'route.withdraw'
}
}
];

View File

@@ -171,7 +171,8 @@ const routeMap: RouteMap = {
"login": "/login/:module(pwd-login|code-login|register|reset-pwd|bind-wechat)?",
"news": "/news",
"product": "/product",
"user": "/user"
"user": "/user",
"withdraw": "/withdraw"
};
/**

View File

@@ -26,6 +26,7 @@ declare module "@elegant-router/types" {
"news": "/news";
"product": "/product";
"user": "/user";
"withdraw": "/withdraw";
};
/**
@@ -66,6 +67,7 @@ declare module "@elegant-router/types" {
| "news"
| "product"
| "user"
| "withdraw"
>;
/**
@@ -91,6 +93,7 @@ declare module "@elegant-router/types" {
| "news"
| "product"
| "user"
| "withdraw"
>;
/**

View File

@@ -1,154 +1,17 @@
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import dayjs from 'dayjs';
import { client, safeClient } from '@/service/api';
import type { TableBaseColumns, TableFetchData, TableInst } from '@/components/table';
import WithdrawPage from '@/views/withdraw/index.vue';
defineOptions({
name: 'WithdrawDialog'
});
const props = defineProps<{
userId: string;
userId?: string;
}>();
enum WithdrawStatus {
pending_review = '审核中',
pending_payout = '待打款',
completed = '已完成',
rejected = '已拒绝'
}
const tableInst = useTemplateRef<TableInst>('tableInst');
const fetchData: TableFetchData = ({ pagination, filter }) => {
return safeClient(() => client.api.admin.withdraw.get({ query: { userId: props.userId, ...pagination, ...filter } }));
};
const columns: TableBaseColumns = [
{
key: 'walletTypeId',
title: '收款方式类型'
},
{
key: 'orderNo',
title: '订单号'
},
{
key: 'amount',
title: '金额(元)',
render: (row: any) => {
return Number(row.amount);
}
},
{
key: 'receiptMethodId',
title: '收款方式ID'
},
{
key: 'createdAt',
title: '创建时间',
render: (row: any) => {
return dayjs(row.createdAt).format('YYYY-MM-DD HH:mm');
}
},
{
key: 'status',
title: '状态',
render: (row: any) => {
return WithdrawStatus[row.status];
}
},
{
key: 'operations',
title: '操作',
width: 150,
fixed: 'right',
operations: (row: any) => [
{
contentText: '通过',
size: 'small',
ghost: true,
type: 'primary',
visible: row.status === 'pending_review',
onClick: async () => {
window.$dialog?.create({
title: '确认通过该提现请求吗?',
content: '通过后将无法撤销',
type: 'warning',
positiveText: '确认通过',
negativeText: '取消',
onPositiveClick: async () => {
await safeClient(() =>
client.api.admin.withdraw({ orderId: row.orderNo }).approve.post({
reviewNote: `管理员通过于 ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`
})
);
window.$message?.success('操作成功');
tableInst.value?.reload();
}
});
}
},
{
contentText: '拒绝',
size: 'small',
ghost: true,
type: 'error',
visible: row.status === 'pending_review',
onClick: async () => {
window.$dialog?.create({
title: '确认拒绝该提现请求吗?',
content: '拒绝后将无法撤销',
type: 'warning',
positiveText: '确认拒绝',
negativeText: '取消',
onPositiveClick: async () => {
await safeClient(() =>
client.api.admin.withdraw({ orderId: row.orderNo }).reject.post({
rejectReason: `管理员拒绝于 ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`
})
);
window.$message?.success('操作成功');
tableInst.value?.reload();
}
});
}
},
{
contentText: '已打款',
size: 'small',
ghost: true,
type: 'success',
visible: row.status === 'pending_payout',
onClick: async () => {
window.$dialog?.create({
title: '确认已打款该提现请求吗?',
content: '确认后将无法撤销',
type: 'warning',
positiveText: '确认已打款',
negativeText: '取消',
onPositiveClick: async () => {
await safeClient(() => client.api.admin.withdraw({ orderId: row.orderNo }).paid.post());
window.$message?.success('操作成功');
tableInst.value?.reload();
}
});
}
}
]
}
];
</script>
<template>
<TableBase
ref="tableInst"
:fetch-data="fetchData"
:columns="columns"
:scroll-x="800"
:show-header-operation="false"
/>
<WithdrawPage :user-id="userId" :scroll-x="800" :show-header-operation="false" />
</template>
<style lang="css" scoped></style>

View File

@@ -0,0 +1,186 @@
<script lang="ts" setup>
import { useTemplateRef } from 'vue';
import { NSelect } from 'naive-ui';
import dayjs from 'dayjs';
import { client, safeClient } from '@/service/api';
import type { TableBaseColumns, TableFetchData, TableFilterColumns, TableInst } from '@/components/table';
defineOptions({
name: 'WithdrawPage'
});
const props = defineProps<{
userId?: string;
}>();
enum WithdrawStatus {
pending_review = '审核中',
pending_payout = '待打款',
completed = '已完成',
rejected = '已拒绝'
}
const tableInst = useTemplateRef<TableInst>('tableInst');
const fetchData: TableFetchData = ({ pagination, filter }) => {
return safeClient(() => client.api.admin.withdraw.get({ query: { userId: props.userId, ...pagination, ...filter } }));
};
const columns: TableBaseColumns = [
{
key: 'walletTypeId',
title: '收款方式类型'
},
{
key: 'orderNo',
title: '订单号'
},
{
key: 'amount',
title: '金额(元)',
render: (row: any) => {
return Number(row.amount);
}
},
{
key: 'receiptMethodId',
title: '收款方式ID'
},
{
key: 'createdAt',
title: '创建时间',
render: (row: any) => {
return dayjs(row.createdAt).format('YYYY-MM-DD HH:mm');
}
},
{
key: 'status',
title: '状态',
render: (row: any) => {
return WithdrawStatus[row.status];
}
},
{
key: 'operations',
title: '操作',
width: 150,
fixed: 'right',
operations: (row: any) => [
{
contentText: '通过',
size: 'small',
ghost: true,
type: 'primary',
visible: row.status === 'pending_review',
onClick: async () => {
window.$dialog?.create({
title: '确认通过该提现请求吗?',
content: '通过后将无法撤销',
type: 'warning',
positiveText: '确认通过',
negativeText: '取消',
onPositiveClick: async () => {
await safeClient(() =>
client.api.admin.withdraw({ orderId: row.orderNo }).approve.post({
reviewNote: `管理员通过于 ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`
})
);
window.$message?.success('操作成功');
tableInst.value?.reload();
}
});
}
},
{
contentText: '拒绝',
size: 'small',
ghost: true,
type: 'error',
visible: row.status === 'pending_review',
onClick: async () => {
window.$dialog?.create({
title: '确认拒绝该提现请求吗?',
content: '拒绝后将无法撤销',
type: 'warning',
positiveText: '确认拒绝',
negativeText: '取消',
onPositiveClick: async () => {
await safeClient(() =>
client.api.admin.withdraw({ orderId: row.orderNo }).reject.post({
rejectReason: `管理员拒绝于 ${dayjs().format('YYYY-MM-DD HH:mm:ss')}`
})
);
window.$message?.success('操作成功');
tableInst.value?.reload();
}
});
}
},
{
contentText: '已打款',
size: 'small',
ghost: true,
type: 'success',
visible: row.status === 'pending_payout',
onClick: async () => {
window.$dialog?.create({
title: '确认已打款该提现请求吗?',
content: '确认后将无法撤销',
type: 'warning',
positiveText: '确认已打款',
negativeText: '取消',
onPositiveClick: async () => {
await safeClient(() => client.api.admin.withdraw({ orderId: row.orderNo }).paid.post());
window.$message?.success('操作成功');
tableInst.value?.reload();
}
});
}
}
]
}
];
const filterColumns: TableFilterColumns = [
{
key: 'uid',
title: '用户UID'
},
{
key: 'orderNo',
title: '订单号'
},
{
key: 'status',
title: '状态',
component: NSelect,
componentProps: {
options: [
{ label: '审核中', value: 'pending_review' },
{ label: '待打款', value: 'pending_payout' },
{ label: '已完成', value: 'completed' },
{ label: '已拒绝', value: 'rejected' }
]
}
}
];
</script>
<template>
<TableBase
ref="tableInst"
:fetch-data="fetchData"
:columns="columns"
:filter-columns="filterColumns"
:scroll-x="1000"
:show-header-operation="true"
:header-operations="{
add: false,
refresh: true,
columns: true
}"
v-bind="{ ...$attrs }"
/>
</template>
<style lang="css" scoped></style>