增加提现相关的与打款相关逻辑

This commit is contained in:
yml
2026-06-06 13:16:44 +08:00
parent 8a2d2f2a18
commit 5c830fe1f4
32 changed files with 3835 additions and 72 deletions
@@ -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>