Files
hfb_sys/frontend/src/features/wallet/components/PaymentAccountDialog.vue
T
2026-06-21 12:39:55 +08:00

386 lines
10 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.
<script setup lang="ts">
import { ref, watch } from 'vue'
import { ElMessage } from 'element-plus'
import { Upload, Delete, Loading } from '@element-plus/icons-vue'
import wechatReceiveQrExample from '@/assets/wechat-receive-qr-example.png'
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
}
}
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="form.account_type === 'wechat' ? '收款二维码' : '凭证截图'">
<div v-if="form.account_type === 'wechat'" class="wechat-qr-tip">
<div>
<strong>微信收款二维码上传提醒</strong>
<p>请在微信收款码页面点击保存收款码,上传保存后的二维码图片。</p>
<p>不要上传收款码截图、聊天截图或带页面背景的整屏截图。</p>
</div>
<img :src="wechatReceiveQrExample" alt="微信收款二维码示例" />
</div>
<div style="font-size: 12px; color: #999; margin-bottom: 8px">
{{
form.account_type === 'wechat'
? '请上传保存后的微信收款二维码图片(可选,最多3张)'
: '可上传收款码截图等凭证(可选,最多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;
}
.wechat-qr-tip {
display: grid;
grid-template-columns: minmax(0, 1fr) 128px;
gap: 12px;
align-items: center;
width: 100%;
margin-bottom: 12px;
border: 1px solid #fed7aa;
border-radius: 8px;
background: #fff7ed;
padding: 12px;
color: #92400e;
}
.wechat-qr-tip strong {
display: block;
margin-bottom: 6px;
color: #c2410c;
font-size: 13px;
}
.wechat-qr-tip p {
margin: 0;
font-size: 12px;
line-height: 1.6;
}
.wechat-qr-tip img {
display: block;
width: 128px;
border-radius: 8px;
border: 1px solid #fde6c8;
background: #ffffff;
}
.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>