348 lines
9.0 KiB
Vue
348 lines
9.0 KiB
Vue
<script setup lang="ts">
|
|
import { onMounted, ref } from 'vue'
|
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
|
|
import {
|
|
fetchAdminWithdrawals,
|
|
reviewWithdrawal,
|
|
confirmPayment,
|
|
type WithdrawalDetail,
|
|
} from '@/features/admin/api/adminWithdrawal'
|
|
|
|
import WithdrawalDetailDialog from '../components/WithdrawalDetailDialog.vue'
|
|
|
|
const loading = ref(false)
|
|
const withdrawals = ref<WithdrawalDetail[]>([])
|
|
const total = ref(0)
|
|
const currentPage = ref(1)
|
|
const pageSize = ref(20)
|
|
|
|
const filters = ref({
|
|
status: '',
|
|
user_id: undefined as number | undefined,
|
|
})
|
|
|
|
const showDetailDialog = ref(false)
|
|
const selectedWithdrawal = ref<WithdrawalDetail | null>(null)
|
|
|
|
const statusOptions = [
|
|
{ label: '全部', value: '' },
|
|
{ label: '待审核', value: 'pending' },
|
|
{ label: '处理中', value: 'processing' },
|
|
{ label: '已完成', value: 'completed' },
|
|
{ label: '已拒绝', value: 'rejected' },
|
|
{ label: '已取消', value: 'cancelled' },
|
|
]
|
|
|
|
onMounted(() => {
|
|
loadWithdrawals()
|
|
})
|
|
|
|
async function loadWithdrawals() {
|
|
loading.value = true
|
|
try {
|
|
const result = await fetchAdminWithdrawals({
|
|
status: filters.value.status || undefined,
|
|
user_id: filters.value.user_id,
|
|
page: currentPage.value,
|
|
page_size: pageSize.value,
|
|
})
|
|
withdrawals.value = result.items
|
|
total.value = result.total
|
|
} catch (error: any) {
|
|
ElMessage.error(error.response?.data?.message || '加载失败')
|
|
} finally {
|
|
loading.value = false
|
|
}
|
|
}
|
|
|
|
function handleFilter() {
|
|
currentPage.value = 1
|
|
loadWithdrawals()
|
|
}
|
|
|
|
function openDetail(withdrawal: WithdrawalDetail) {
|
|
selectedWithdrawal.value = withdrawal
|
|
showDetailDialog.value = true
|
|
}
|
|
|
|
async function handleReview(withdrawal: WithdrawalDetail, approved: boolean) {
|
|
const action = approved ? '通过' : '拒绝'
|
|
try {
|
|
const { value: remark } = await ElMessageBox.prompt(
|
|
`请输入${action}原因(可选)`,
|
|
`${action}审核`,
|
|
{
|
|
confirmButtonText: `确认${action}`,
|
|
cancelButtonText: '取消',
|
|
inputType: 'textarea',
|
|
inputPlaceholder: '输入备注信息...',
|
|
}
|
|
)
|
|
|
|
await reviewWithdrawal(withdrawal.id, {
|
|
approved,
|
|
remark: remark || '',
|
|
})
|
|
|
|
ElMessage.success(`审核${action}`)
|
|
await loadWithdrawals()
|
|
} catch (error: any) {
|
|
if (error !== 'cancel') {
|
|
ElMessage.error(error.response?.data?.message || '操作失败')
|
|
}
|
|
}
|
|
}
|
|
|
|
async function handleConfirmPayment(withdrawal: WithdrawalDetail) {
|
|
try {
|
|
const { value: remark } = await ElMessageBox.prompt(
|
|
'请输入打款备注(如:已通过支付宝转账)',
|
|
'确认打款',
|
|
{
|
|
confirmButtonText: '确认完成',
|
|
cancelButtonText: '取消',
|
|
inputType: 'textarea',
|
|
inputPlaceholder: '输入打款备注...',
|
|
inputValidator: value => {
|
|
return value && value.trim().length > 0
|
|
},
|
|
inputErrorMessage: '请输入打款备注',
|
|
}
|
|
)
|
|
|
|
await confirmPayment(withdrawal.id, {
|
|
remark: remark || '',
|
|
})
|
|
|
|
ElMessage.success('打款完成')
|
|
await loadWithdrawals()
|
|
} catch (error: any) {
|
|
if (error !== 'cancel') {
|
|
ElMessage.error(error.response?.data?.message || '操作失败')
|
|
}
|
|
}
|
|
}
|
|
|
|
function statusLabel(status: string) {
|
|
const labels: Record<string, string> = {
|
|
pending: '待审核',
|
|
processing: '处理中',
|
|
completed: '已完成',
|
|
rejected: '已拒绝',
|
|
cancelled: '已取消',
|
|
}
|
|
return labels[status] || status
|
|
}
|
|
|
|
function statusType(status: string) {
|
|
const types: Record<string, 'success' | 'warning' | 'danger' | 'info' | 'primary'> = {
|
|
pending: 'warning',
|
|
processing: 'primary',
|
|
completed: 'success',
|
|
rejected: 'danger',
|
|
cancelled: 'info',
|
|
}
|
|
return types[status] || 'info'
|
|
}
|
|
|
|
function accountTypeLabel(type: string) {
|
|
const labels: Record<string, string> = {
|
|
alipay: '支付宝',
|
|
wechat: '微信',
|
|
bank: '银行卡',
|
|
}
|
|
return labels[type] || type
|
|
}
|
|
|
|
function onDetailDialogSaved() {
|
|
showDetailDialog.value = false
|
|
loadWithdrawals()
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<section class="page">
|
|
<div class="page-header-row">
|
|
<div class="page-header">
|
|
<p class="eyebrow">Withdrawals</p>
|
|
<h1>提现管理</h1>
|
|
<p>审核用户提现申请并确认打款</p>
|
|
</div>
|
|
<div class="toolbar-actions">
|
|
<el-button @click="loadWithdrawals">刷新</el-button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 筛选器 -->
|
|
<el-card shadow="never" style="margin-bottom: 16px">
|
|
<el-form :model="filters" inline>
|
|
<el-form-item label="状态">
|
|
<el-select
|
|
v-model="filters.status"
|
|
placeholder="全部"
|
|
style="width: 150px"
|
|
@change="handleFilter"
|
|
>
|
|
<el-option
|
|
v-for="opt in statusOptions"
|
|
:key="opt.value"
|
|
:label="opt.label"
|
|
:value="opt.value"
|
|
/>
|
|
</el-select>
|
|
</el-form-item>
|
|
<el-form-item label="用户ID">
|
|
<el-input
|
|
v-model.number="filters.user_id"
|
|
placeholder="输入用户ID"
|
|
style="width: 150px"
|
|
clearable
|
|
@clear="handleFilter"
|
|
/>
|
|
</el-form-item>
|
|
<el-form-item>
|
|
<el-button type="primary" @click="handleFilter"> 查询 </el-button>
|
|
</el-form-item>
|
|
</el-form>
|
|
</el-card>
|
|
|
|
<!-- 提现列表 -->
|
|
<el-table v-loading="loading" class="table-panel" :data="withdrawals">
|
|
<el-table-column prop="id" label="ID" width="70" />
|
|
<el-table-column prop="withdraw_no" label="提现单号" min-width="180" />
|
|
<el-table-column label="用户" min-width="140">
|
|
<template #default="{ row }">
|
|
<div>{{ row.user_nickname }}</div>
|
|
<div style="font-size: 12px; color: #999">{{ row.user_phone }}</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="提现金额" width="120" align="right">
|
|
<template #default="{ row }">
|
|
<span style="color: #f56c6c; font-weight: 600"> ¥{{ row.amount.toFixed(2) }} </span>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="收款方式" min-width="180">
|
|
<template #default="{ row }">
|
|
<div>
|
|
<el-tag size="small" style="margin-right: 4px">
|
|
{{ accountTypeLabel(row.account_type) }}
|
|
</el-tag>
|
|
{{ row.account_name }}
|
|
</div>
|
|
<div style="font-size: 12px; color: #666; margin-top: 4px">
|
|
{{ row.account_no }}
|
|
</div>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="状态" width="100">
|
|
<template #default="{ row }">
|
|
<el-tag :type="statusType(row.status)">
|
|
{{ statusLabel(row.status) }}
|
|
</el-tag>
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="申请时间" width="160">
|
|
<template #default="{ row }">
|
|
{{ new Date(row.created_at).toLocaleString('zh-CN') }}
|
|
</template>
|
|
</el-table-column>
|
|
<el-table-column label="操作" width="240" fixed="right">
|
|
<template #default="{ row }">
|
|
<el-button size="small" @click="openDetail(row)"> 详情 </el-button>
|
|
<el-button
|
|
v-if="row.status === 'pending'"
|
|
size="small"
|
|
type="success"
|
|
@click="handleReview(row, true)"
|
|
>
|
|
通过
|
|
</el-button>
|
|
<el-button
|
|
v-if="row.status === 'pending'"
|
|
size="small"
|
|
type="danger"
|
|
@click="handleReview(row, false)"
|
|
>
|
|
拒绝
|
|
</el-button>
|
|
<el-button
|
|
v-if="row.status === 'processing'"
|
|
size="small"
|
|
type="primary"
|
|
@click="handleConfirmPayment(row)"
|
|
>
|
|
确认打款
|
|
</el-button>
|
|
</template>
|
|
</el-table-column>
|
|
</el-table>
|
|
|
|
<el-pagination
|
|
v-if="total > pageSize"
|
|
v-model:current-page="currentPage"
|
|
v-model:page-size="pageSize"
|
|
:total="total"
|
|
:page-sizes="[10, 20, 50, 100]"
|
|
layout="total, sizes, prev, pager, next, jumper"
|
|
style="margin-top: 16px; justify-content: center"
|
|
@size-change="loadWithdrawals"
|
|
@current-change="loadWithdrawals"
|
|
/>
|
|
|
|
<!-- 详情对话框 -->
|
|
<WithdrawalDetailDialog
|
|
v-model="showDetailDialog"
|
|
:withdrawal="selectedWithdrawal"
|
|
@saved="onDetailDialogSaved"
|
|
/>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.page {
|
|
padding: 24px;
|
|
}
|
|
|
|
.page-header-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: flex-start;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
.page-header {
|
|
flex: 1;
|
|
}
|
|
|
|
.eyebrow {
|
|
margin: 0 0 8px;
|
|
color: #909399;
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
|
|
.page-header h1 {
|
|
margin: 0 0 8px;
|
|
font-size: 28px;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.page-header p {
|
|
margin: 0;
|
|
color: #666;
|
|
}
|
|
|
|
.toolbar-actions {
|
|
display: flex;
|
|
gap: 8px;
|
|
}
|
|
|
|
.table-panel {
|
|
background: #fff;
|
|
border-radius: 8px;
|
|
}
|
|
</style>
|