386 lines
10 KiB
Vue
386 lines
10 KiB
Vue
<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>
|