qiuyuan a23bff7a3d 1
2025-12-31 11:27:23 +08:00

540 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="withdrawal-management">
<!-- 页面标题 -->
<a-page-header title="提现管理" />
<!-- 提现账户信息卡片 -->
<a-row :gutter="24" class="mb-6">
<a-col :span="24">
<a-card title="可提现金额">
<div class="balance-info">
<div class="balance-amount">
<span class="amount">{{ formatCurrency(accountInfo.availableBalance) }}</span>
<span class="currency"></span>
</div>
<div class="balance-desc">
当前可提现金额提现将在1-3个工作日内到账
</div>
<a-button
type="primary"
size="small"
class="w-full mt-4"
@click="showWithdrawalModal"
:disabled="accountInfo.availableBalance <= 0"
>
去提现
</a-button>
</div>
</a-card>
</a-col>
</a-row>
<!-- 提现记录查询 -->
<a-card title="提现记录" class="mb-6">
<template #extra>
<a-range-picker
v-model:value="searchParams.dateRange"
:format="dateFormat"
@change="handleDateChange"
/>
</template>
<!-- 查询条件 -->
<a-form layout="inline" :model="searchParams" class="mb-4">
<a-form-item label="提现单号">
<a-input
v-model:value="searchParams.orderNo"
placeholder="请输入提现单号"
@pressEnter="handleSearch"
/>
</a-form-item>
<a-form-item label="账户">
<a-select
v-model:value="searchParams.account"
placeholder="请选择账户"
style="width: 150px"
allowClear
>
<a-select-option v-for="account in accountOptions" :key="account.value">
{{ account.label }}
</a-select-option>
</a-select>
</a-form-item>
<a-form-item label="状态">
<a-select
v-model:value="searchParams.status"
placeholder="请选择状态"
style="width: 150px"
allowClear
>
<a-select-option value="pending">处理中</a-select-option>
<a-select-option value="success">成功</a-select-option>
<a-select-option value="failed">失败</a-select-option>
</a-select>
</a-form-item>
<a-form-item>
<a-button type="primary" @click="handleSearch">查询</a-button>
<a-button @click="handleReset" class="ml-2">重置</a-button>
</a-form-item>
</a-form>
<!-- 提现记录表格 -->
<a-table
:columns="columns"
:data-source="withdrawalRecords"
:pagination="pagination"
@change="handleTableChange"
:loading="loading"
rowKey="id"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'amount'">
<span class="amount-cell">{{ formatCurrency(record.amount) }}</span>
</template>
<template v-else-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)">
{{ getStatusText(record.status) }}
</a-tag>
</template>
<template v-else-if="column.key === 'createdAt'">
{{ formatDate(record.createdAt) }}
</template>
<template v-else-if="column.key === 'completedAt'">
{{ record.completedAt ? formatDate(record.completedAt) : '-' }}
</template>
<template v-else-if="column.key === 'actions'">
<a-button type="link" @click="viewDetail(record)">查看详情</a-button>
</template>
</template>
</a-table>
</a-card>
<!-- 提现弹窗 -->
<a-modal
v-model:visible="withdrawalModal.visible"
title="提现申请"
@ok="handleWithdrawalSubmit"
@cancel="handleWithdrawalCancel"
:confirm-loading="withdrawalModal.confirming"
>
<a-form
ref="withdrawalFormRef"
:model="withdrawalForm"
:rules="withdrawalRules"
layout="vertical"
>
<a-form-item label="提现金额" name="amount">
<a-input-number
v-model:value="withdrawalForm.amount"
placeholder="请输入提现金额"
:min="1"
:max="accountInfo.availableBalance"
style="width: 100%"
:formatter="value => `¥ ${value}`.replace(/\B(?=(\d{3})+(?!\d))/g, ',')"
:parser="value => value.replace(/¥\s?|(,*)/g, '')"
/>
<div class="amount-hint mt-2">
<div>可提现金额: {{ formatCurrency(accountInfo.availableBalance) }}</div>
<div>单笔最低提现: 1</div>
</div>
</a-form-item>
<a-form-item label="提现到账账户" name="accountId">
<a-select
v-model:value="withdrawalForm.accountId"
placeholder="请选择提现账户"
>
<a-select-option :value="accountInfo.id">
{{ accountInfo.bank }} ({{ accountInfo.number }})
</a-select-option>
</a-select>
</a-form-item>
<a-alert
v-if="withdrawalForm.amount"
:message="`预计到账金额: ${formatCurrency(calculateActualAmount(withdrawalForm.amount))}元`"
type="info"
show-icon
class="mb-4"
/>
</a-form>
</a-modal>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue'
import { message } from 'ant-design-vue'
import dayjs from 'dayjs'
// 账户信息
const accountInfo = reactive({
id: '1',
type: '银行账户',
number: '6228 **** **** 5678',
name: '张三',
bank: '中国工商银行',
availableBalance: 5000.00
})
// 提现记录查询参数
const searchParams = reactive({
orderNo: '',
account: undefined,
status: undefined,
dateRange: [dayjs().subtract(30, 'day'), dayjs()]
})
// 分页配置
const pagination = reactive({
current: 1,
pageSize: 10,
total: 0,
showSizeChanger: true,
showQuickJumper: true,
showTotal: total => `${total} 条记录`
})
// 表格列定义
const columns = [
{
title: '提现单号',
dataIndex: 'orderNo',
key: 'orderNo',
width: 200
},
{
title: '账户',
key: 'account',
width: 180,
render: (_, record) => `${record.bank} (${record.accountNumber})`
},
{
title: '提现金额',
key: 'amount',
width: 120,
align: 'right'
},
{
title: '手续费',
dataIndex: 'fee',
key: 'fee',
width: 100,
align: 'right',
render: fee => fee ? `${fee}` : '-'
},
{
title: '状态',
key: 'status',
width: 100
},
{
title: '申请时间',
key: 'createdAt',
width: 180
},
{
title: '完成时间',
key: 'completedAt',
width: 180
},
{
title: '操作',
key: 'actions',
width: 100,
fixed: 'right'
}
]
// 提现记录数据
const withdrawalRecords = ref([])
const loading = ref(false)
const accountOptions = ref([
{ value: '1', label: '工商银行(尾号5678)' },
{ value: '2', label: '建设银行(尾号1234)' }
])
// 提现弹窗
const withdrawalModal = reactive({
visible: false,
confirming: false
})
// 提现表单
const withdrawalFormRef = ref()
const withdrawalForm = reactive({
amount: undefined,
accountId: accountInfo.id
})
// 表单验证规则
const withdrawalRules = {
amount: [
{ required: true, message: '请输入提现金额' },
{ type: 'number', min: 1, max: accountInfo.availableBalance, message: `提现金额需在1-${accountInfo.availableBalance}元之间` }
],
accountId: [
{ required: true, message: '请选择提现账户' }
]
}
// 日期格式
const dateFormat = 'YYYY-MM-DD'
// 生命周期
onMounted(() => {
fetchWithdrawalRecords()
})
// 获取提现记录
const fetchWithdrawalRecords = async () => {
loading.value = true
try {
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 500))
// 模拟数据
const mockData = Array.from({ length: 35 }, (_, index) => ({
id: index + 1,
orderNo: `TX${Date.now()}${index}`,
bank: ['工商银行', '建设银行', '农业银行'][index % 3],
accountNumber: `6228 **** **** ${1000 + index}`,
amount: Math.floor(Math.random() * 10000) + 100,
fee: Math.floor(Math.random() * 10),
status: ['pending', 'success', 'failed'][index % 3],
createdAt: dayjs().subtract(index, 'day').format('YYYY-MM-DD HH:mm:ss'),
completedAt: index % 3 !== 0 ? dayjs().subtract(index, 'day').add(1, 'hour').format('YYYY-MM-DD HH:mm:ss') : null
}))
// 应用筛选
let filteredData = mockData
if (searchParams.orderNo) {
filteredData = filteredData.filter(item => item.orderNo.includes(searchParams.orderNo))
}
if (searchParams.account) {
filteredData = filteredData.filter(item => item.accountNumber.endsWith(searchParams.account))
}
if (searchParams.status) {
filteredData = filteredData.filter(item => item.status === searchParams.status)
}
// 应用日期范围筛选
if (searchParams.dateRange && searchParams.dateRange[0] && searchParams.dateRange[1]) {
const start = searchParams.dateRange[0]
const end = searchParams.dateRange[1]
filteredData = filteredData.filter(item => {
const date = dayjs(item.createdAt)
return date.isAfter(start) && date.isBefore(end)
})
}
// 分页
const start = (pagination.current - 1) * pagination.pageSize
const end = start + pagination.pageSize
withdrawalRecords.value = filteredData.slice(start, end)
pagination.total = filteredData.length
} catch (error) {
message.error('获取提现记录失败')
} finally {
loading.value = false
}
}
// 表格分页变化
const handleTableChange = (page) => {
pagination.current = page.current
pagination.pageSize = page.pageSize
fetchWithdrawalRecords()
}
// 查询
const handleSearch = () => {
pagination.current = 1
fetchWithdrawalRecords()
}
// 重置查询
const handleReset = () => {
searchParams.orderNo = ''
searchParams.account = undefined
searchParams.status = undefined
searchParams.dateRange = [dayjs().subtract(30, 'day'), dayjs()]
handleSearch()
}
// 日期变化
const handleDateChange = () => {
handleSearch()
}
// 显示提现弹窗
const showWithdrawalModal = () => {
withdrawalModal.visible = true
}
// 处理提现提交
const handleWithdrawalSubmit = async () => {
try {
await withdrawalFormRef.value.validate()
withdrawalModal.confirming = true
// 模拟API调用
await new Promise(resolve => setTimeout(resolve, 1000))
message.success('提现申请提交成功')
// 更新账户余额
accountInfo.availableBalance -= withdrawalForm.amount
// 重置表单并关闭弹窗
withdrawalFormRef.value.resetFields()
withdrawalModal.visible = false
// 刷新记录
fetchWithdrawalRecords()
} catch (error) {
console.error('提现提交失败:', error)
} finally {
withdrawalModal.confirming = false
}
}
// 处理提现取消
const handleWithdrawalCancel = () => {
withdrawalFormRef.value.resetFields()
withdrawalModal.visible = false
}
// 查看详情
const viewDetail = (record) => {
message.info(`查看提现记录详情: ${record.orderNo}`)
}
// 计算实际到账金额
const calculateActualAmount = (amount) => {
const fee = amount * 0.01 // 假设手续费1%
return amount - fee
}
// 格式化货币
const formatCurrency = (amount) => {
return amount.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,')
}
// 格式化日期
const formatDate = (dateString) => {
return dayjs(dateString).format('YYYY-MM-DD HH:mm:ss')
}
// 获取状态颜色
const getStatusColor = (status) => {
const colors = {
pending: 'orange',
success: 'green',
failed: 'red'
}
return colors[status] || 'default'
}
// 获取状态文本
const getStatusText = (status) => {
const texts = {
pending: '处理中',
success: '成功',
failed: '失败'
}
return texts[status] || status
}
</script>
<style scoped>
.withdrawal-management {
padding: 20px;
}
.account-info .info-item {
margin-bottom: 12px;
font-size: 14px;
}
.account-info .label {
color: #666;
margin-right: 8px;
}
.account-info .value {
color: #333;
font-weight: 500;
}
.balance-info .balance-amount {
text-align: center;
margin-bottom: 8px;
}
.balance-info .amount {
font-size: 32px;
font-weight: bold;
color: #1890ff;
}
.balance-info .currency {
font-size: 18px;
color: #666;
margin-left: 4px;
}
.balance-info .balance-desc {
color: #666;
font-size: 12px;
text-align: center;
margin-bottom: 16px;
}
.amount-hint {
color: #666;
font-size: 12px;
}
.amount-hint div {
margin-bottom: 4px;
}
.amount-cell {
color: #1890ff;
font-weight: 500;
}
.mb-4 {
margin-bottom: 16px;
}
.mb-6 {
margin-bottom: 24px;
}
.mt-2 {
margin-top: 8px;
}
.mt-4 {
margin-top: 16px;
}
.ml-2 {
margin-left: 8px;
}
.w-full {
float: inline-end;
width: 10%;
}
</style>