增加提现相关的与打款相关逻辑
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
|
||||
import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
|
||||
|
||||
// 管理员端提现详情接口
|
||||
export interface WithdrawalDetail {
|
||||
id: number
|
||||
withdraw_no: string
|
||||
user_id: number
|
||||
user_nickname: string
|
||||
user_phone: string
|
||||
amount: number
|
||||
fee: number
|
||||
actual_amount: number
|
||||
payment_account_id: number | null
|
||||
account_type: string
|
||||
account_name: string
|
||||
account_no: string // 管理员可见完整账号
|
||||
bank_name: string
|
||||
bank_branch: string
|
||||
certificate_urls: string[] // 收款二维码图片
|
||||
status: string
|
||||
reviewed_by: number | null
|
||||
reviewed_by_name: string
|
||||
reviewed_at: string | null
|
||||
review_remark: string
|
||||
paid_by: number | null
|
||||
paid_by_name: string
|
||||
paid_at: string | null
|
||||
payment_proof_url: string
|
||||
payment_remark: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
// 审核请求
|
||||
export interface ReviewWithdrawalRequest {
|
||||
approved: boolean
|
||||
remark: string
|
||||
}
|
||||
|
||||
// 确认打款请求
|
||||
export interface ConfirmPaymentRequest {
|
||||
payment_proof_url?: string
|
||||
remark: string
|
||||
}
|
||||
|
||||
// ========== 管理员端提现管理 API ==========
|
||||
|
||||
export async function fetchAdminWithdrawals(params: {
|
||||
status?: string
|
||||
user_id?: number
|
||||
page?: number
|
||||
page_size?: number
|
||||
}) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<WithdrawalDetail>>>('/admin/withdrawals', {
|
||||
params,
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchAdminWithdrawal(id: number) {
|
||||
const { data } = await apiClient.get<ApiResponse<WithdrawalDetail>>(`/admin/withdrawals/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function reviewWithdrawal(id: number, req: ReviewWithdrawalRequest) {
|
||||
const { data } = await apiClient.post<ApiResponse<WithdrawalDetail>>(`/admin/withdrawals/${id}/review`, req)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function confirmPayment(id: number, req: ConfirmPaymentRequest) {
|
||||
const { data } = await apiClient.post<ApiResponse<WithdrawalDetail>>(`/admin/withdrawals/${id}/confirm-payment`, req)
|
||||
return data.data
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import type { WithdrawalDetail } from '@/features/admin/api/adminWithdrawal'
|
||||
import { reviewWithdrawal, confirmPayment } from '@/features/admin/api/adminWithdrawal'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
withdrawal: WithdrawalDetail | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', val: boolean): void
|
||||
(e: 'saved'): void
|
||||
}>()
|
||||
|
||||
const submitting = ref(false)
|
||||
|
||||
async function handleReview(approved: boolean) {
|
||||
if (!props.withdrawal) return
|
||||
|
||||
const action = approved ? '通过' : '拒绝'
|
||||
try {
|
||||
const { value: remark } = await ElMessageBox.prompt(
|
||||
`请输入${action}原因(可选)`,
|
||||
`${action}审核`,
|
||||
{
|
||||
confirmButtonText: `确认${action}`,
|
||||
cancelButtonText: '取消',
|
||||
inputType: 'textarea',
|
||||
inputPlaceholder: '输入备注信息...',
|
||||
}
|
||||
)
|
||||
|
||||
submitting.value = true
|
||||
await reviewWithdrawal(props.withdrawal.id, {
|
||||
approved,
|
||||
remark: remark || '',
|
||||
})
|
||||
|
||||
ElMessage.success(`审核${action}`)
|
||||
emit('saved')
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||
}
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleConfirmPayment() {
|
||||
if (!props.withdrawal) return
|
||||
|
||||
try {
|
||||
const { value: remark } = await ElMessageBox.prompt(
|
||||
'请输入打款备注(如:已通过支付宝转账)',
|
||||
'确认打款',
|
||||
{
|
||||
confirmButtonText: '确认完成',
|
||||
cancelButtonText: '取消',
|
||||
inputType: 'textarea',
|
||||
inputPlaceholder: '输入打款备注...',
|
||||
inputValidator: (value) => {
|
||||
return value && value.trim().length > 0
|
||||
},
|
||||
inputErrorMessage: '请输入打款备注',
|
||||
}
|
||||
)
|
||||
|
||||
submitting.value = true
|
||||
await confirmPayment(props.withdrawal.id, {
|
||||
remark: remark || '',
|
||||
})
|
||||
|
||||
ElMessage.success('打款完成')
|
||||
emit('saved')
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||
}
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function statusLabel(status: string) {
|
||||
const labels: Record<string, string> = {
|
||||
pending: '待审核',
|
||||
processing: '处理中',
|
||||
completed: '已完成',
|
||||
rejected: '已拒绝',
|
||||
cancelled: '已取消',
|
||||
}
|
||||
return labels[status] || status
|
||||
}
|
||||
|
||||
function statusType(status: string) {
|
||||
return status === 'completed' ? 'success' :
|
||||
status === 'pending' ? 'warning' :
|
||||
status === 'processing' ? 'primary' :
|
||||
status === 'rejected' ? 'danger' : 'info'
|
||||
}
|
||||
|
||||
function accountTypeLabel(type: string) {
|
||||
const labels: Record<string, string> = {
|
||||
alipay: '支付宝',
|
||||
wechat: '微信',
|
||||
bank: '银行卡',
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
title="提现详情"
|
||||
width="700px"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
>
|
||||
<div v-if="withdrawal" class="withdrawal-detail">
|
||||
<!-- 基本信息 -->
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="提现单号" :span="2">
|
||||
{{ withdrawal.withdraw_no }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="状态">
|
||||
<el-tag :type="statusType(withdrawal.status)">
|
||||
{{ statusLabel(withdrawal.status) }}
|
||||
</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">
|
||||
{{ new Date(withdrawal.created_at).toLocaleString('zh-CN') }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 用户信息 -->
|
||||
<h3 style="margin-top: 20px">用户信息</h3>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="用户ID">
|
||||
{{ withdrawal.user_id }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="昵称">
|
||||
{{ withdrawal.user_nickname }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手机号" :span="2">
|
||||
{{ withdrawal.user_phone }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 金额信息 -->
|
||||
<h3 style="margin-top: 20px">金额信息</h3>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="提现金额">
|
||||
<span style="color: #f56c6c; font-weight: 600; font-size: 16px">
|
||||
¥{{ withdrawal.amount.toFixed(2) }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="手续费">
|
||||
¥{{ withdrawal.fee.toFixed(2) }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="实际到账" :span="2">
|
||||
<span style="color: #67c23a; font-weight: 600; font-size: 16px">
|
||||
¥{{ withdrawal.actual_amount.toFixed(2) }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 收款账号信息 -->
|
||||
<h3 style="margin-top: 20px">收款账号信息</h3>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="账号类型">
|
||||
<el-tag>{{ accountTypeLabel(withdrawal.account_type) }}</el-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="账户名">
|
||||
{{ withdrawal.account_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="账号" :span="2">
|
||||
<span style="font-family: monospace; font-weight: 600">
|
||||
{{ withdrawal.account_no }}
|
||||
</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="withdrawal.bank_name" label="银行名称">
|
||||
{{ withdrawal.bank_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="withdrawal.bank_branch" label="开户支行">
|
||||
{{ withdrawal.bank_branch }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="withdrawal.certificate_urls && withdrawal.certificate_urls.length > 0" label="收款二维码" :span="2">
|
||||
<div style="display: flex; gap: 8px; flex-wrap: wrap">
|
||||
<el-image
|
||||
v-for="(url, idx) in withdrawal.certificate_urls"
|
||||
:key="idx"
|
||||
:src="url"
|
||||
:preview-src-list="withdrawal.certificate_urls"
|
||||
:initial-index="idx"
|
||||
fit="cover"
|
||||
style="width: 100px; height: 100px; border-radius: 4px; cursor: pointer"
|
||||
/>
|
||||
</div>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<!-- 审核信息 -->
|
||||
<div v-if="withdrawal.reviewed_at">
|
||||
<h3 style="margin-top: 20px">审核信息</h3>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="审核人">
|
||||
{{ withdrawal.reviewed_by_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="审核时间">
|
||||
{{ new Date(withdrawal.reviewed_at).toLocaleString('zh-CN') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="withdrawal.review_remark" label="审核备注" :span="2">
|
||||
{{ withdrawal.review_remark }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 打款信息 -->
|
||||
<div v-if="withdrawal.paid_at">
|
||||
<h3 style="margin-top: 20px">打款信息</h3>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="打款人">
|
||||
{{ withdrawal.paid_by_name }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="打款时间">
|
||||
{{ new Date(withdrawal.paid_at).toLocaleString('zh-CN') }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="withdrawal.payment_remark" label="打款备注" :span="2">
|
||||
{{ withdrawal.payment_remark }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="withdrawal.payment_proof_url" label="打款凭证" :span="2">
|
||||
<el-link :href="withdrawal.payment_proof_url" target="_blank" type="primary">
|
||||
查看凭证
|
||||
</el-link>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<!-- 操作提示 -->
|
||||
<el-alert
|
||||
v-if="withdrawal.status === 'pending'"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<template #title>
|
||||
<div style="font-size: 13px">
|
||||
请仔细核对账号信息后进行审核。审核通过后,需要手动转账到用户账号,并确认打款完成。
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<el-alert
|
||||
v-if="withdrawal.status === 'processing'"
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-top: 20px"
|
||||
>
|
||||
<template #title>
|
||||
<div style="font-size: 13px">
|
||||
请手动转账 <strong>¥{{ withdrawal.actual_amount.toFixed(2) }}</strong> 到用户收款账号,
|
||||
完成后点击"确认打款"按钮。
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div style="display: flex; justify-content: space-between; width: 100%">
|
||||
<div>
|
||||
<el-button
|
||||
v-if="withdrawal?.status === 'pending'"
|
||||
type="success"
|
||||
:loading="submitting"
|
||||
@click="handleReview(true)"
|
||||
>
|
||||
通过审核
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="withdrawal?.status === 'pending'"
|
||||
type="danger"
|
||||
:loading="submitting"
|
||||
@click="handleReview(false)"
|
||||
>
|
||||
拒绝
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="withdrawal?.status === 'processing'"
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
@click="handleConfirmPayment"
|
||||
>
|
||||
确认打款
|
||||
</el-button>
|
||||
</div>
|
||||
<el-button @click="emit('update:modelValue', false)">
|
||||
关闭
|
||||
</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.withdrawal-detail h3 {
|
||||
margin: 20px 0 12px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
:deep(.el-descriptions__label) {
|
||||
width: 120px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,357 @@
|
||||
<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>
|
||||
@@ -0,0 +1,126 @@
|
||||
import { apiClient } from '@/shared/api/client'
|
||||
|
||||
import type { ApiResponse, PaginatedResult } from '@/shared/types/types'
|
||||
|
||||
// 收款账号类型
|
||||
export type AccountType = 'alipay' | 'wechat' | 'bank'
|
||||
|
||||
// 收款账号接口
|
||||
export interface PaymentAccount {
|
||||
id: number
|
||||
user_id: number
|
||||
account_type: AccountType
|
||||
account_name: string
|
||||
account_no: string // 脱敏显示
|
||||
bank_name: string
|
||||
bank_branch: string
|
||||
certificate_urls: string[]
|
||||
is_default: boolean
|
||||
status: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
// 创建收款账号请求
|
||||
export interface CreatePaymentAccountRequest {
|
||||
account_type: AccountType
|
||||
account_name: string
|
||||
account_no: string
|
||||
bank_name?: string
|
||||
bank_branch?: string
|
||||
certificate_urls?: string[]
|
||||
}
|
||||
|
||||
// 更新收款账号请求
|
||||
export interface UpdatePaymentAccountRequest {
|
||||
bank_branch?: string
|
||||
certificate_urls?: string[]
|
||||
is_default?: boolean
|
||||
}
|
||||
|
||||
// 提现状态
|
||||
export type WithdrawalStatus = 'pending' | 'processing' | 'completed' | 'rejected' | 'cancelled'
|
||||
|
||||
// 提现申请接口
|
||||
export interface WithdrawalRequest {
|
||||
id: number
|
||||
withdraw_no: string
|
||||
user_id: number
|
||||
amount: number
|
||||
fee: number
|
||||
actual_amount: number
|
||||
account_type: AccountType
|
||||
account_name: string
|
||||
account_no: string
|
||||
bank_name: string
|
||||
status: WithdrawalStatus
|
||||
review_remark: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
reviewed_at: string | null
|
||||
paid_at: string | null
|
||||
}
|
||||
|
||||
// 创建提现申请请求
|
||||
export interface CreateWithdrawalRequest {
|
||||
payment_account_id: number
|
||||
amount: number
|
||||
}
|
||||
|
||||
// ========== 收款账号管理 API ==========
|
||||
|
||||
export async function fetchPaymentAccounts(page = 1, pageSize = 20) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<PaymentAccount>>>('/payment-accounts', {
|
||||
params: { page, page_size: pageSize },
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchPaymentAccount(id: number) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaymentAccount>>(`/payment-accounts/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function createPaymentAccount(req: CreatePaymentAccountRequest) {
|
||||
const { data } = await apiClient.post<ApiResponse<PaymentAccount>>('/payment-accounts', req)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function updatePaymentAccount(id: number, req: UpdatePaymentAccountRequest) {
|
||||
const { data } = await apiClient.put<ApiResponse<PaymentAccount>>(`/payment-accounts/${id}`, req)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function deletePaymentAccount(id: number) {
|
||||
const { data } = await apiClient.delete<ApiResponse<{ deleted: boolean }>>(`/payment-accounts/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function setDefaultPaymentAccount(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<{ updated: boolean }>>(`/payment-accounts/${id}/set-default`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
// ========== 提现申请 API ==========
|
||||
|
||||
export async function createWithdrawal(req: CreateWithdrawalRequest) {
|
||||
const { data } = await apiClient.post<ApiResponse<WithdrawalRequest>>('/withdrawals', req)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchWithdrawals(page = 1, pageSize = 20) {
|
||||
const { data } = await apiClient.get<ApiResponse<PaginatedResult<WithdrawalRequest>>>('/withdrawals', {
|
||||
params: { page, page_size: pageSize },
|
||||
})
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function fetchWithdrawal(id: number) {
|
||||
const { data } = await apiClient.get<ApiResponse<WithdrawalRequest>>(`/withdrawals/${id}`)
|
||||
return data.data
|
||||
}
|
||||
|
||||
export async function cancelWithdrawal(id: number) {
|
||||
const { data } = await apiClient.post<ApiResponse<{ cancelled: boolean }>>(`/withdrawals/${id}/cancel`)
|
||||
return data.data
|
||||
}
|
||||
@@ -0,0 +1,362 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { Upload, Delete, Loading } from '@element-plus/icons-vue'
|
||||
|
||||
import {
|
||||
createPaymentAccount,
|
||||
updatePaymentAccount,
|
||||
type PaymentAccount,
|
||||
type CreatePaymentAccountRequest,
|
||||
type UpdatePaymentAccountRequest,
|
||||
} from '../api/withdrawal'
|
||||
import { uploadFile } from '@/shared/api/files'
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
account?: PaymentAccount | null
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:modelValue', val: boolean): void
|
||||
(e: 'saved'): void
|
||||
}>()
|
||||
|
||||
const submitting = ref(false)
|
||||
const uploading = ref(false)
|
||||
const form = ref<CreatePaymentAccountRequest>({
|
||||
account_type: 'alipay',
|
||||
account_name: '',
|
||||
account_no: '',
|
||||
bank_name: '',
|
||||
bank_branch: '',
|
||||
certificate_urls: [],
|
||||
})
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
(val) => {
|
||||
if (val) {
|
||||
if (props.account) {
|
||||
// 编辑模式:只能修改部分字段
|
||||
form.value = {
|
||||
account_type: props.account.account_type,
|
||||
account_name: props.account.account_name,
|
||||
account_no: '', // 不显示原账号
|
||||
bank_name: props.account.bank_name,
|
||||
bank_branch: props.account.bank_branch,
|
||||
certificate_urls: props.account.certificate_urls || [],
|
||||
}
|
||||
} else {
|
||||
// 新建模式
|
||||
form.value = {
|
||||
account_type: 'alipay',
|
||||
account_name: '',
|
||||
account_no: '',
|
||||
bank_name: '',
|
||||
bank_branch: '',
|
||||
certificate_urls: [],
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!form.value.account_name) {
|
||||
ElMessage.warning('请输入账户名')
|
||||
return
|
||||
}
|
||||
if (!props.account && !form.value.account_no) {
|
||||
ElMessage.warning('请输入账号')
|
||||
return
|
||||
}
|
||||
if (form.value.account_type === 'bank' && !form.value.bank_name) {
|
||||
ElMessage.warning('请输入银行名称')
|
||||
return
|
||||
}
|
||||
|
||||
submitting.value = true
|
||||
try {
|
||||
if (props.account) {
|
||||
// 编辑
|
||||
const updateReq: UpdatePaymentAccountRequest = {
|
||||
bank_branch: form.value.bank_branch,
|
||||
certificate_urls: form.value.certificate_urls,
|
||||
}
|
||||
await updatePaymentAccount(props.account.id, updateReq)
|
||||
ElMessage.success('更新成功')
|
||||
} else {
|
||||
// 新建
|
||||
await createPaymentAccount(form.value)
|
||||
ElMessage.success('添加成功')
|
||||
}
|
||||
emit('saved')
|
||||
emit('update:modelValue', false)
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function accountTypeLabel(type: string) {
|
||||
const labels: Record<string, string> = {
|
||||
alipay: '支付宝',
|
||||
wechat: '微信',
|
||||
bank: '银行卡',
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
|
||||
async function handleUpload(event: Event) {
|
||||
const input = event.target as HTMLInputElement
|
||||
const file = input.files?.[0]
|
||||
if (!file) return
|
||||
|
||||
// 验证文件类型
|
||||
if (!file.type.startsWith('image/')) {
|
||||
ElMessage.error('只能上传图片文件')
|
||||
return
|
||||
}
|
||||
|
||||
// 验证文件大小 (最大5MB)
|
||||
if (file.size > 5 * 1024 * 1024) {
|
||||
ElMessage.error('图片大小不能超过5MB')
|
||||
return
|
||||
}
|
||||
|
||||
uploading.value = true
|
||||
try {
|
||||
const uploaded = await uploadFile(file, 'payment-cert')
|
||||
if (!form.value.certificate_urls) {
|
||||
form.value.certificate_urls = []
|
||||
}
|
||||
// 使用公开访问的URL,不需要认证
|
||||
const publicUrl = uploaded.url.replace('/api/files/object', '/api/public/files/object')
|
||||
form.value.certificate_urls.push(publicUrl)
|
||||
ElMessage.success('上传成功')
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.response?.data?.message || '上传失败')
|
||||
} finally {
|
||||
uploading.value = false
|
||||
// 清空 input 以便重复上传同一文件
|
||||
input.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
function removeImage(index: number) {
|
||||
form.value.certificate_urls?.splice(index, 1)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
:model-value="modelValue"
|
||||
:title="account ? '编辑收款账号' : '添加收款账号'"
|
||||
width="500px"
|
||||
@update:model-value="emit('update:modelValue', $event)"
|
||||
>
|
||||
<el-form
|
||||
:model="form"
|
||||
label-width="100px"
|
||||
label-position="left"
|
||||
>
|
||||
<el-form-item label="账号类型" required>
|
||||
<el-radio-group
|
||||
v-model="form.account_type"
|
||||
:disabled="!!account"
|
||||
>
|
||||
<el-radio-button value="alipay">支付宝</el-radio-button>
|
||||
<el-radio-button value="wechat">微信</el-radio-button>
|
||||
<el-radio-button value="bank">银行卡</el-radio-button>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="账户名" required>
|
||||
<el-input
|
||||
v-model="form.account_name"
|
||||
placeholder="必须与实名认证姓名一致"
|
||||
:disabled="!!account"
|
||||
/>
|
||||
<div style="font-size: 12px; color: #999; margin-top: 4px">
|
||||
账户名将用于验证实名信息,请确保准确无误
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item
|
||||
v-if="!account"
|
||||
:label="form.account_type === 'bank' ? '银行卡号' : '账号'"
|
||||
required
|
||||
>
|
||||
<el-input
|
||||
v-model="form.account_no"
|
||||
:placeholder="
|
||||
form.account_type === 'alipay'
|
||||
? '支付宝账号(手机号或邮箱)'
|
||||
: form.account_type === 'wechat'
|
||||
? '微信号'
|
||||
: '银行卡号'
|
||||
"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<template v-if="form.account_type === 'bank'">
|
||||
<el-form-item label="银行名称" required>
|
||||
<el-input
|
||||
v-model="form.bank_name"
|
||||
placeholder="如:中国工商银行"
|
||||
:disabled="!!account"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="开户支行">
|
||||
<el-input
|
||||
v-model="form.bank_branch"
|
||||
placeholder="如:北京朝阳支行(可选)"
|
||||
/>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<el-form-item label="凭证截图">
|
||||
<div style="font-size: 12px; color: #999; margin-bottom: 8px">
|
||||
可上传收款码截图等凭证(可选,最多3张)
|
||||
</div>
|
||||
<div class="certificate-upload">
|
||||
<div v-for="(url, index) in form.certificate_urls" :key="url" class="certificate-item">
|
||||
<img :src="url" alt="凭证">
|
||||
<el-button
|
||||
:icon="Delete"
|
||||
circle
|
||||
size="small"
|
||||
type="danger"
|
||||
class="delete-btn"
|
||||
@click="removeImage(index)"
|
||||
/>
|
||||
</div>
|
||||
<label v-if="!form.certificate_urls || form.certificate_urls.length < 3" class="upload-btn" :class="{ 'is-uploading': uploading }">
|
||||
<input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
style="display: none"
|
||||
:disabled="uploading"
|
||||
@change="handleUpload"
|
||||
>
|
||||
<el-icon v-if="uploading" class="is-loading"><Loading /></el-icon>
|
||||
<el-icon v-else><Upload /></el-icon>
|
||||
<span>{{ uploading ? '上传中...' : '上传凭证' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-alert
|
||||
v-if="!account"
|
||||
type="warning"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #title>
|
||||
<div style="font-size: 13px">
|
||||
<strong>重要提示</strong>
|
||||
<ul style="margin: 8px 0 0; padding-left: 20px">
|
||||
<li>账户名必须与您的实名认证姓名完全一致</li>
|
||||
<li>每个用户最多可添加 5 个收款账号</li>
|
||||
<li>提现时将转账到您指定的收款账号</li>
|
||||
<li>请确保账号信息准确,避免提现失败</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<template #footer>
|
||||
<el-button @click="emit('update:modelValue', false)">
|
||||
取消
|
||||
</el-button>
|
||||
<el-button
|
||||
type="primary"
|
||||
:loading="submitting"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
{{ account ? '更新' : '添加' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
:deep(.el-radio-button__inner) {
|
||||
padding: 10px 20px;
|
||||
}
|
||||
|
||||
.certificate-upload {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.certificate-item {
|
||||
position: relative;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.certificate-item img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.certificate-item .delete-btn {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 4px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s;
|
||||
}
|
||||
|
||||
.certificate-item:hover .delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.upload-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border: 1px dashed #dcdfe6;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.upload-btn:hover {
|
||||
border-color: #409eff;
|
||||
color: #409eff;
|
||||
}
|
||||
|
||||
.upload-btn.is-uploading {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.upload-btn .el-icon {
|
||||
font-size: 24px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.upload-btn span {
|
||||
font-size: 12px;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.upload-btn:hover span {
|
||||
color: #409eff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,4 +1,5 @@
|
||||
// Wallet 模块统一导出
|
||||
export * from './api/wallet'
|
||||
export * from './api/withdrawal'
|
||||
export * from './composables/useWallet'
|
||||
export type * from './types'
|
||||
|
||||
@@ -0,0 +1,307 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { Plus, Delete, Star, StarFilled, Edit } from '@element-plus/icons-vue'
|
||||
|
||||
import {
|
||||
fetchPaymentAccounts,
|
||||
deletePaymentAccount,
|
||||
setDefaultPaymentAccount,
|
||||
type PaymentAccount,
|
||||
} from '../api/withdrawal'
|
||||
|
||||
import PaymentAccountDialog from '../components/PaymentAccountDialog.vue'
|
||||
|
||||
const loading = ref(false)
|
||||
const accounts = ref<PaymentAccount[]>([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
|
||||
const showDialog = ref(false)
|
||||
const editingAccount = ref<PaymentAccount | null>(null)
|
||||
|
||||
onMounted(() => {
|
||||
loadAccounts()
|
||||
})
|
||||
|
||||
async function loadAccounts() {
|
||||
loading.value = true
|
||||
try {
|
||||
const result = await fetchPaymentAccounts(currentPage.value, pageSize.value)
|
||||
accounts.value = result.items
|
||||
total.value = result.total
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.response?.data?.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function openCreateDialog() {
|
||||
editingAccount.value = null
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
function openEditDialog(account: PaymentAccount) {
|
||||
editingAccount.value = account
|
||||
showDialog.value = true
|
||||
}
|
||||
|
||||
async function handleSetDefault(account: PaymentAccount) {
|
||||
if (account.is_default) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
await setDefaultPaymentAccount(account.id)
|
||||
ElMessage.success('已设为默认账号')
|
||||
await loadAccounts()
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.response?.data?.message || '操作失败')
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDelete(account: PaymentAccount) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确定要删除此收款账号吗?`, '删除确认', {
|
||||
confirmButtonText: '确认删除',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
})
|
||||
await deletePaymentAccount(account.id)
|
||||
ElMessage.success('删除成功')
|
||||
await loadAccounts()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.response?.data?.message || '删除失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function accountTypeLabel(type: string) {
|
||||
const labels: Record<string, string> = {
|
||||
alipay: '支付宝',
|
||||
wechat: '微信',
|
||||
bank: '银行卡',
|
||||
}
|
||||
return labels[type] || type
|
||||
}
|
||||
|
||||
function accountTypeColor(type: string) {
|
||||
const colors: Record<string, string> = {
|
||||
alipay: '#1677ff',
|
||||
wechat: '#07c160',
|
||||
bank: '#ff6a00',
|
||||
}
|
||||
return colors[type] || '#999'
|
||||
}
|
||||
|
||||
const canAddMore = computed(() => accounts.value.length < 5)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="payment-accounts-page">
|
||||
<div class="page-header">
|
||||
<div>
|
||||
<h2>收款账号管理</h2>
|
||||
<p class="page-desc">管理您的收款账号,用于提现时接收资金(最多5个)</p>
|
||||
</div>
|
||||
<el-button
|
||||
v-if="canAddMore"
|
||||
type="primary"
|
||||
:icon="Plus"
|
||||
@click="openCreateDialog"
|
||||
>
|
||||
添加收款账号
|
||||
</el-button>
|
||||
<el-tag v-else type="info">已达账号数量上限</el-tag>
|
||||
</div>
|
||||
|
||||
<el-alert
|
||||
v-if="accounts.length === 0 && !loading"
|
||||
title="还没有收款账号"
|
||||
type="info"
|
||||
description="请先添加收款账号,才能进行提现操作。账户名必须与您的实名认证姓名一致。"
|
||||
show-icon
|
||||
:closable="false"
|
||||
style="margin-bottom: 20px"
|
||||
/>
|
||||
|
||||
<div v-loading="loading" class="accounts-grid">
|
||||
<div
|
||||
v-for="account in accounts"
|
||||
:key="account.id"
|
||||
class="account-card"
|
||||
:class="{ 'is-default': account.is_default }"
|
||||
>
|
||||
<div class="account-header">
|
||||
<div class="account-type">
|
||||
<el-tag
|
||||
:color="accountTypeColor(account.account_type)"
|
||||
effect="dark"
|
||||
size="large"
|
||||
>
|
||||
{{ accountTypeLabel(account.account_type) }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="account-actions">
|
||||
<el-tooltip
|
||||
:content="account.is_default ? '默认账号' : '设为默认'"
|
||||
placement="top"
|
||||
>
|
||||
<el-button
|
||||
:icon="account.is_default ? StarFilled : Star"
|
||||
:type="account.is_default ? 'warning' : 'default'"
|
||||
circle
|
||||
size="small"
|
||||
@click="handleSetDefault(account)"
|
||||
/>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
:icon="Edit"
|
||||
circle
|
||||
size="small"
|
||||
@click="openEditDialog(account)"
|
||||
/>
|
||||
<el-button
|
||||
:icon="Delete"
|
||||
circle
|
||||
size="small"
|
||||
type="danger"
|
||||
@click="handleDelete(account)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="account-info">
|
||||
<div class="info-row">
|
||||
<span class="label">账户名:</span>
|
||||
<span class="value">{{ account.account_name }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="label">账号:</span>
|
||||
<span class="value monospace">{{ account.account_no }}</span>
|
||||
</div>
|
||||
<div v-if="account.account_type === 'bank'" class="info-row">
|
||||
<span class="label">银行:</span>
|
||||
<span class="value">{{ account.bank_name }}</span>
|
||||
</div>
|
||||
<div v-if="account.account_type === 'bank' && account.bank_branch" class="info-row">
|
||||
<span class="label">支行:</span>
|
||||
<span class="value">{{ account.bank_branch }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="account-footer">
|
||||
<span class="created-time">添加于 {{ new Date(account.created_at).toLocaleDateString() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PaymentAccountDialog
|
||||
v-model="showDialog"
|
||||
:account="editingAccount"
|
||||
@saved="loadAccounts"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.payment-accounts-page {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.accounts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.account-card {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.account-card:hover {
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.account-card.is-default {
|
||||
border-color: #f59e0b;
|
||||
background: linear-gradient(135deg, #fffbeb 0%, #ffffff 100%);
|
||||
}
|
||||
|
||||
.account-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.account-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.account-info {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.info-row .label {
|
||||
width: 70px;
|
||||
color: #666;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.info-row .value {
|
||||
flex: 1;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.info-row .value.monospace {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.account-footer {
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.created-time {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { CircleCheck, Lock, Money, Refresh, Tickets, Wallet as WalletIcon } from '@element-plus/icons-vue'
|
||||
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
import { balanceTypeLabel, ledgerDirectionLabel, walletStatusLabel } from '@/utils/statusLabels'
|
||||
import { formatDateTime } from '@/utils/time'
|
||||
|
||||
const router = useRouter()
|
||||
const loading = ref(false)
|
||||
const account = ref<WalletAccount | null>(null)
|
||||
const ledger = ref<WalletLedger[]>([])
|
||||
@@ -72,7 +74,7 @@ function loadLedgerPage() {
|
||||
}
|
||||
|
||||
function handleWithdraw() {
|
||||
ElMessage.info('提现功能待实现')
|
||||
router.push('/wallet/withdrawal')
|
||||
}
|
||||
|
||||
function formatMoney(value: number) {
|
||||
@@ -134,9 +136,8 @@ function amountPrefix(direction: string) {
|
||||
<div class="wallet-hero-action">
|
||||
<span>当前可用</span>
|
||||
<strong>{{ account ? formatMoney(account.available_balance) : '¥0.00' }}</strong>
|
||||
<el-button class="withdraw-button" :icon="Money" disabled @click="handleWithdraw">
|
||||
<el-button class="withdraw-button" :icon="Money" type="primary" @click="handleWithdraw">
|
||||
申请提现
|
||||
<el-tag size="small" type="info" effect="plain" class="withdraw-tag">待开发</el-tag>
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,454 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { Wallet, Money, DocumentChecked, Warning } from '@element-plus/icons-vue'
|
||||
|
||||
import {
|
||||
fetchPaymentAccounts,
|
||||
createWithdrawal,
|
||||
fetchWithdrawals,
|
||||
cancelWithdrawal,
|
||||
type PaymentAccount,
|
||||
type WithdrawalRequest,
|
||||
} from '../api/withdrawal'
|
||||
import { fetchWalletBalance, type WalletAccount } from '../api/wallet'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const loading = ref(false)
|
||||
const submitting = ref(false)
|
||||
const account = ref<WalletAccount | null>(null)
|
||||
const paymentAccounts = ref<PaymentAccount[]>([])
|
||||
const withdrawals = ref<WithdrawalRequest[]>([])
|
||||
|
||||
const withdrawForm = ref({
|
||||
payment_account_id: 0,
|
||||
amount: 0,
|
||||
})
|
||||
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
const total = ref(0)
|
||||
|
||||
const MIN_AMOUNT = 10
|
||||
const MAX_AMOUNT = 5000
|
||||
|
||||
onMounted(() => {
|
||||
loadData()
|
||||
})
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true
|
||||
try {
|
||||
const [balanceData, accountsData, withdrawalsData] = await Promise.all([
|
||||
fetchWalletBalance(),
|
||||
fetchPaymentAccounts(1, 100),
|
||||
fetchWithdrawals(currentPage.value, pageSize.value),
|
||||
])
|
||||
account.value = balanceData
|
||||
paymentAccounts.value = accountsData.items
|
||||
withdrawals.value = withdrawalsData.items
|
||||
total.value = withdrawalsData.total
|
||||
|
||||
// 自动选择默认账号
|
||||
const defaultAccount = paymentAccounts.value.find(a => a.is_default)
|
||||
if (defaultAccount) {
|
||||
withdrawForm.value.payment_account_id = defaultAccount.id
|
||||
}
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.response?.data?.message || '加载失败')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const selectedAccount = computed(() => {
|
||||
return paymentAccounts.value.find(a => a.id === withdrawForm.value.payment_account_id)
|
||||
})
|
||||
|
||||
const canWithdraw = computed(() => {
|
||||
return (
|
||||
withdrawForm.value.payment_account_id > 0 &&
|
||||
withdrawForm.value.amount >= MIN_AMOUNT &&
|
||||
withdrawForm.value.amount <= MAX_AMOUNT &&
|
||||
account.value &&
|
||||
withdrawForm.value.amount <= account.value.available_balance
|
||||
)
|
||||
})
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!canWithdraw.value) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
`确认提现 ¥${withdrawForm.value.amount.toFixed(2)} 到 ${selectedAccount.value?.account_name} (${selectedAccount.value?.account_no}) ?`,
|
||||
'确认提现',
|
||||
{
|
||||
confirmButtonText: '确认',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
|
||||
submitting.value = true
|
||||
await createWithdrawal(withdrawForm.value)
|
||||
ElMessage.success('提现申请已提交')
|
||||
withdrawForm.value.amount = 0
|
||||
await loadData()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.response?.data?.message || '提现申请失败')
|
||||
}
|
||||
} finally {
|
||||
submitting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCancel(withdrawal: WithdrawalRequest) {
|
||||
try {
|
||||
await ElMessageBox.confirm('确定要取消此提现申请吗?', '取消提现', {
|
||||
confirmButtonText: '确认取消',
|
||||
cancelButtonText: '返回',
|
||||
type: 'warning',
|
||||
})
|
||||
await cancelWithdrawal(withdrawal.id)
|
||||
ElMessage.success('已取消提现')
|
||||
await loadData()
|
||||
} catch (error: any) {
|
||||
if (error !== 'cancel') {
|
||||
ElMessage.error(error.response?.data?.message || '取消失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function goToPaymentAccounts() {
|
||||
router.push('/wallet/payment-accounts')
|
||||
}
|
||||
|
||||
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, string> = {
|
||||
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
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="withdrawal-page">
|
||||
<div class="page-header">
|
||||
<h2>提现</h2>
|
||||
<p class="page-desc">将钱包余额提现到您的收款账号</p>
|
||||
</div>
|
||||
|
||||
<!-- 余额卡片 -->
|
||||
<el-card class="balance-card" shadow="never">
|
||||
<div class="balance-info">
|
||||
<div class="balance-item">
|
||||
<div class="balance-label">
|
||||
<el-icon><Wallet /></el-icon>
|
||||
可用余额
|
||||
</div>
|
||||
<div class="balance-value">
|
||||
¥{{ account?.available_balance.toFixed(2) || '0.00' }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="balance-item">
|
||||
<div class="balance-label">
|
||||
<el-icon><Money /></el-icon>
|
||||
冻结余额
|
||||
</div>
|
||||
<div class="balance-value frozen">
|
||||
¥{{ account?.frozen_balance.toFixed(2) || '0.00' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 提现表单 -->
|
||||
<el-card class="withdraw-form-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>申请提现</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div v-if="paymentAccounts.length === 0" class="no-accounts">
|
||||
<el-empty description="还没有收款账号">
|
||||
<el-button type="primary" @click="goToPaymentAccounts">
|
||||
添加收款账号
|
||||
</el-button>
|
||||
</el-empty>
|
||||
</div>
|
||||
|
||||
<div v-else>
|
||||
<el-form :model="withdrawForm" label-width="100px" label-position="left">
|
||||
<el-form-item label="收款账号" required>
|
||||
<el-select
|
||||
v-model="withdrawForm.payment_account_id"
|
||||
placeholder="请选择收款账号"
|
||||
style="width: 100%"
|
||||
>
|
||||
<el-option
|
||||
v-for="acc in paymentAccounts"
|
||||
:key="acc.id"
|
||||
:label="`${accountTypeLabel(acc.account_type)} - ${acc.account_name} (${acc.account_no})`"
|
||||
:value="acc.id"
|
||||
>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||
<span>
|
||||
<el-tag size="small" style="margin-right: 8px">{{ accountTypeLabel(acc.account_type) }}</el-tag>
|
||||
{{ acc.account_name }} ({{ acc.account_no }})
|
||||
</span>
|
||||
<el-tag v-if="acc.is_default" type="warning" size="small">默认</el-tag>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<div style="margin-top: 8px; display: flex; gap: 8px">
|
||||
<el-button size="small" @click="goToPaymentAccounts">
|
||||
添加收款账号
|
||||
</el-button>
|
||||
<el-button size="small" text @click="goToPaymentAccounts">
|
||||
管理收款账号
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="提现金额" required>
|
||||
<el-input
|
||||
v-model.number="withdrawForm.amount"
|
||||
type="number"
|
||||
placeholder="请输入提现金额"
|
||||
:min="MIN_AMOUNT"
|
||||
:max="MAX_AMOUNT"
|
||||
>
|
||||
<template #prefix>¥</template>
|
||||
</el-input>
|
||||
<div style="margin-top: 8px; font-size: 13px; color: #999">
|
||||
单笔限额:¥{{ MIN_AMOUNT }} - ¥{{ MAX_AMOUNT }},手续费:0%
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="到账金额">
|
||||
<div class="actual-amount">
|
||||
¥{{ withdrawForm.amount > 0 ? withdrawForm.amount.toFixed(2) : '0.00' }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-alert
|
||||
v-if="withdrawForm.amount > 0 && account && withdrawForm.amount > account.available_balance"
|
||||
type="error"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
余额不足,可用余额:¥{{ account.available_balance.toFixed(2) }}
|
||||
</el-alert>
|
||||
|
||||
<el-alert
|
||||
type="info"
|
||||
:closable="false"
|
||||
show-icon
|
||||
style="margin-bottom: 16px"
|
||||
>
|
||||
<template #title>
|
||||
<div style="font-size: 13px">
|
||||
<strong>提现说明</strong>
|
||||
<ul style="margin: 8px 0 0; padding-left: 20px">
|
||||
<li>提现申请提交后,将冻结相应金额</li>
|
||||
<li>财务审核通过后,将手动转账到您的收款账号</li>
|
||||
<li>正常情况下,1-3个工作日内完成转账</li>
|
||||
<li>待审核状态下可取消提现申请</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
</el-alert>
|
||||
|
||||
<el-form-item>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="submitting"
|
||||
:disabled="!canWithdraw"
|
||||
@click="handleSubmit"
|
||||
>
|
||||
提交申请
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 提现记录 -->
|
||||
<el-card class="withdrawal-records-card" shadow="never">
|
||||
<template #header>
|
||||
<div class="card-header">
|
||||
<span>提现记录</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<el-table
|
||||
v-loading="loading"
|
||||
:data="withdrawals"
|
||||
stripe
|
||||
>
|
||||
<el-table-column prop="withdraw_no" label="提现单号" min-width="180" />
|
||||
<el-table-column label="提现金额" width="120">
|
||||
<template #default="{ row }">
|
||||
<span style="color: #f56c6c; font-weight: 600">
|
||||
¥{{ row.amount.toFixed(2) }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="收款方式" width="150">
|
||||
<template #default="{ row }">
|
||||
<div>{{ accountTypeLabel(row.account_type) }}</div>
|
||||
<div style="font-size: 12px; color: #999">{{ 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 prop="review_remark" label="备注" min-width="150" />
|
||||
<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="100" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="row.status === 'pending'"
|
||||
size="small"
|
||||
type="danger"
|
||||
text
|
||||
@click="handleCancel(row)"
|
||||
>
|
||||
取消
|
||||
</el-button>
|
||||
<span v-else style="color: #999">-</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-if="total > pageSize"
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:total="total"
|
||||
layout="total, prev, pager, next"
|
||||
style="margin-top: 16px; justify-content: center"
|
||||
@current-change="loadData"
|
||||
/>
|
||||
</el-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.withdrawal-page {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.page-header h2 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.page-desc {
|
||||
margin: 0;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.balance-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.balance-info {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.balance-item {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.balance-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.balance-value {
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
color: #67c23a;
|
||||
}
|
||||
|
||||
.balance-value.frozen {
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.withdraw-form-card,
|
||||
.withdrawal-records-card {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.no-accounts {
|
||||
padding: 40px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.actual-amount {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #67c23a;
|
||||
}
|
||||
</style>
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
Operation,
|
||||
Fold,
|
||||
Expand,
|
||||
Money,
|
||||
ScaleToOriginal,
|
||||
Shop,
|
||||
SwitchButton,
|
||||
@@ -46,6 +47,7 @@ const allNavItems: NavItem[] = [
|
||||
{ label: '仲裁中心', to: '/admin/disputes', icon: ScaleToOriginal, permission: 'dispute:view' },
|
||||
{ label: '客服群聊', to: '/admin/chats', icon: ChatDotRound, permission: 'chat:view' },
|
||||
{ label: '资金流水', to: '/admin/wallet-ledger', icon: Wallet, permission: 'wallet:view' },
|
||||
{ label: '提现审核', to: '/admin/withdrawals', icon: Money, permission: 'withdrawal:approve' },
|
||||
{ label: '公告管理', to: '/admin/announcements', icon: Bell, permission: 'announcement:view' },
|
||||
{ label: '系统配置', to: '/admin/system-configs', icon: Operation, permission: 'system_config:view' },
|
||||
{ label: '审计日志', to: '/admin/audit-logs', icon: Document, permission: 'audit_log:view' },
|
||||
|
||||
@@ -36,6 +36,18 @@ export const accountRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/features/wallet/views/WalletView.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/wallet/payment-accounts',
|
||||
name: 'payment-accounts',
|
||||
component: () => import('@/features/wallet/views/PaymentAccountsView.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/wallet/withdrawal',
|
||||
name: 'withdrawal',
|
||||
component: () => import('@/features/wallet/views/WithdrawalView.vue'),
|
||||
meta: { requiresAuth: true },
|
||||
},
|
||||
{
|
||||
path: '/notifications',
|
||||
name: 'notifications',
|
||||
|
||||
@@ -70,6 +70,12 @@ export const adminRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/features/admin/views/AdminWalletLedgerView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/withdrawals',
|
||||
name: 'admin-withdrawals',
|
||||
component: () => import('@/features/admin/views/AdminWithdrawalsView.vue'),
|
||||
meta: adminMeta,
|
||||
},
|
||||
{
|
||||
path: '/admin/system-configs',
|
||||
name: 'admin-system-configs',
|
||||
|
||||
Reference in New Issue
Block a user