505 lines
14 KiB
Vue
505 lines
14 KiB
Vue
<template>
|
||
<div class="rechange-page table_form-container">
|
||
<!-- 页面标题 -->
|
||
<div class="page-title">存款</div>
|
||
|
||
<!-- 存款渠道列表:每行提供一个"存款"按钮 -->
|
||
<RechangeTable :table-data="channelList" :columns="tableColumns" :highlight-current-row="false">
|
||
<template #action="{ row }">
|
||
<el-button type="primary" size="small" @click="handleOpenDialog(row)">
|
||
存款
|
||
</el-button>
|
||
</template>
|
||
</RechangeTable>
|
||
</div>
|
||
|
||
<!-- 存款弹窗 -->
|
||
<RechangeDialog class="rechange-pay-dialog" :visible="dialogVisible" title="存款" width="600px" :show-footer="false"
|
||
@cancel="handleCancel" @close="handleClose">
|
||
<el-form ref="rechangeFormRef" :model="rechangeForm" :rules="rechangeFormRules" label-position="top">
|
||
<!-- 渠道信息 -->
|
||
<div class="channel-card">
|
||
<div class="channel-info">
|
||
<div class="channel-name">{{ rechangeForm.channelName }}</div>
|
||
<div class="channel-meta">
|
||
<span></span>
|
||
</div>
|
||
</div>
|
||
<div class="channel-meta">支持货币: {{ rechangeForm.currencySymbol }}</div>
|
||
</div>
|
||
|
||
<!-- 银行卡信息(channel_code = BANK 时显示) -->
|
||
<template v-if="rechangeForm.channelCode === 'BANK'">
|
||
<div class="info-section">
|
||
<div class="section-title">收款信息</div>
|
||
<div class="info-grid">
|
||
<div class="info-item">
|
||
<span class="label">账户持有人姓名</span>
|
||
<span class="value">{{ rechangeForm.beneficiaryName }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">银行名称</span>
|
||
<span class="value">{{ rechangeForm.beneficiaryBankName }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">银行识别代码</span>
|
||
<span class="value">{{ rechangeForm.swiftBicCode }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="label">收款人银行账号</span>
|
||
<span class="value">{{ rechangeForm.beneficiaryBankAccount }}</span>
|
||
</div>
|
||
<div class="info-item full-width">
|
||
<span class="label">银行地址</span>
|
||
<span class="value">{{ rechangeForm.beneficiaryBankAddress }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 钱包地址信息(其他渠道显示) -->
|
||
<template v-else>
|
||
<div class="info-section">
|
||
<div class="section-title">收款信息</div>
|
||
<div class="info-item full-width">
|
||
<span class="label">钱包收款地址</span>
|
||
<span class="value address-value">{{ rechangeForm.address }}</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- 充值金额 -->
|
||
<el-form-item label="充值金额" prop="depositAmount">
|
||
<el-input v-model="rechangeForm.depositAmount" placeholder="请输入充值金额" size="large" @input="handleAmountInput">
|
||
<template #suffix>
|
||
<span class="currency-suffix">{{ rechangeForm.currencySymbol }}</span>
|
||
</template>
|
||
</el-input>
|
||
</el-form-item>
|
||
|
||
<!-- 上传凭证 -->
|
||
<el-form-item label="上传凭证" prop="voucherUrl">
|
||
<el-upload class="voucher-uploader" action="" :show-file-list="false"
|
||
:http-request="handleUpload" :before-upload="beforeAvatarUpload" :loading="uploadLoading">
|
||
<img v-if="voucherUrl" :src="voucherUrl" class="voucher-image" />
|
||
<div v-else class="upload-placeholder">
|
||
<el-icon class="upload-icon">
|
||
<Plus />
|
||
</el-icon>
|
||
<span class="upload-text">点击上传凭证</span>
|
||
</div>
|
||
</el-upload>
|
||
</el-form-item>
|
||
</el-form>
|
||
|
||
<!-- 按钮区 -->
|
||
<div class="dialog-actions">
|
||
<el-button size="large" @click="handleCancel">取消</el-button>
|
||
<el-button type="primary" size="large" :loading="submitLoading" @click="handleSubmit">
|
||
确认充值
|
||
</el-button>
|
||
</div>
|
||
</RechangeDialog>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { Plus } from '@element-plus/icons-vue'
|
||
|
||
defineOptions({
|
||
name: 'Rechange'
|
||
})
|
||
import type { FormInstance, FormRules, UploadProps } from 'element-plus'
|
||
import { ElMessage } from 'element-plus'
|
||
import { nextTick, onMounted, ref } from 'vue'
|
||
import RechangeTable from '@/components/common/Table.vue'
|
||
import RechangeDialog from '@/components/common/Dialog.vue'
|
||
import { rechangeTableColumns } from './options'
|
||
import { uploadImageApi } from '@/api/modules/upload'
|
||
import { getRechargeChannelList, rechargeApi } from '@/api/modules/capital/rechangeChanl'
|
||
|
||
// 渠道数据接口
|
||
interface ChannelCurrency {
|
||
id: number
|
||
symbol: string
|
||
rate: string
|
||
}
|
||
|
||
interface ChannelRow {
|
||
id: number
|
||
name: string
|
||
channel_fee: string
|
||
type: number
|
||
beneficiary_name: string | null
|
||
beneficiary_bank_name: string | null
|
||
swift_bic_code: string | null
|
||
beneficiary_bank_address: string | null
|
||
beneficiary_bank_account: string | null
|
||
channel_code: string
|
||
currency: ChannelCurrency
|
||
address?: string
|
||
}
|
||
|
||
// 表格列配置
|
||
const tableColumns = ref(rechangeTableColumns)
|
||
|
||
// 弹窗基础状态
|
||
const dialogVisible = ref(false)
|
||
const rechangeFormRef = ref<FormInstance>()
|
||
const submitLoading = ref(false)
|
||
|
||
// 上传凭证相关
|
||
const voucherUrl = ref('')
|
||
const uploadLoading = ref(false)
|
||
|
||
// 渠道列表数据
|
||
const channelList = ref<ChannelRow[]>([])
|
||
|
||
// 弹窗表单数据
|
||
interface RechangeFormData {
|
||
id: number
|
||
channelName: string
|
||
channelCode: string
|
||
currencySymbol: string
|
||
channelFee: string
|
||
depositAmount: string
|
||
voucherUrl: string
|
||
beneficiaryName: string
|
||
beneficiaryBankName: string
|
||
swiftBicCode: string
|
||
beneficiaryBankAddress: string
|
||
beneficiaryBankAccount: string
|
||
address: string
|
||
}
|
||
|
||
const createDefaultForm = (): RechangeFormData => ({
|
||
id: 0,
|
||
channelName: '',
|
||
channelCode: '',
|
||
currencySymbol: '',
|
||
channelFee: '0',
|
||
depositAmount: '',
|
||
voucherUrl: '',
|
||
beneficiaryName: '',
|
||
beneficiaryBankName: '',
|
||
swiftBicCode: '',
|
||
beneficiaryBankAddress: '',
|
||
beneficiaryBankAccount: '',
|
||
address: '',
|
||
})
|
||
|
||
const rechangeForm = ref<RechangeFormData>(createDefaultForm())
|
||
|
||
// 表单校验规则
|
||
const rechangeFormRules = ref<FormRules<RechangeFormData>>({
|
||
depositAmount: [
|
||
{ required: true, message: '请输入充值金额', trigger: ['blur', 'change'] },
|
||
{ pattern: /^\d+$/, message: '请输入整数金额', trigger: ['blur', 'change'] },
|
||
],
|
||
voucherUrl: [
|
||
{ required: true, message: '请上传充值凭证', trigger: ['change'] },
|
||
],
|
||
})
|
||
|
||
// 获取渠道列表
|
||
const fetchChannelList = async () => {
|
||
try {
|
||
const res = await getRechargeChannelList() as ChannelRow[]
|
||
channelList.value = res || []
|
||
} catch (error) {
|
||
console.error('获取充值渠道列表失败', error)
|
||
}
|
||
}
|
||
|
||
// 打开弹窗
|
||
const handleOpenDialog = async (row: ChannelRow) => {
|
||
rechangeForm.value = {
|
||
id: row.id,
|
||
channelName: row.name,
|
||
channelCode: row.channel_code,
|
||
currencySymbol: row.currency.symbol,
|
||
channelFee: row.channel_fee || '0',
|
||
depositAmount: '',
|
||
voucherUrl: '',
|
||
beneficiaryName: row.beneficiary_name || '',
|
||
beneficiaryBankName: row.beneficiary_bank_name || '',
|
||
swiftBicCode: row.swift_bic_code || '',
|
||
beneficiaryBankAddress: row.beneficiary_bank_address || '',
|
||
beneficiaryBankAccount: row.beneficiary_bank_account || '',
|
||
address: (row as any).address || '',
|
||
}
|
||
voucherUrl.value = ''
|
||
dialogVisible.value = true
|
||
await nextTick()
|
||
rechangeFormRef.value?.clearValidate()
|
||
}
|
||
|
||
// 上传凭证相关
|
||
const handleAvatarSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||
if (response.code === 0) {
|
||
voucherUrl.value = response.url
|
||
rechangeForm.value.voucherUrl = response.url
|
||
ElMessage.success('上传成功')
|
||
} else {
|
||
ElMessage.error(response.msg || '上传失败')
|
||
}
|
||
uploadLoading.value = false
|
||
}
|
||
|
||
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
|
||
ElMessage.error('图片格式必须为 JPG 或 PNG!')
|
||
return false
|
||
} else if (rawFile.size / 1024 / 1024 > 2) {
|
||
ElMessage.error('图片大小不能超过 2MB!')
|
||
return false
|
||
}
|
||
uploadLoading.value = true
|
||
return true
|
||
}
|
||
|
||
// 自定义上传方法
|
||
const handleUpload = async (options: any) => {
|
||
const { file, onSuccess, onError } = options
|
||
try {
|
||
const res: any = await uploadImageApi(file)
|
||
voucherUrl.value = res.url
|
||
rechangeForm.value.voucherUrl = res.url
|
||
onSuccess(res)
|
||
// 上传成功后手动清除该字段的验证状态
|
||
nextTick(() => {
|
||
rechangeFormRef.value?.clearValidate('voucherUrl')
|
||
})
|
||
} catch (error) {
|
||
onError(error)
|
||
uploadLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 金额输入处理:只允许整数
|
||
const handleAmountInput = (value: string) => {
|
||
const filtered = value.replace(/\D/g, '')
|
||
rechangeForm.value.depositAmount = filtered
|
||
}
|
||
|
||
// 提交充值
|
||
const handleSubmit = async () => {
|
||
try {
|
||
await rechangeFormRef.value?.validate()
|
||
submitLoading.value = true
|
||
await rechargeApi({
|
||
channel_id: rechangeForm.value.id,
|
||
amount: rechangeForm.value.depositAmount,
|
||
img: rechangeForm.value.voucherUrl,
|
||
})
|
||
ElMessage.success('充值提交成功')
|
||
dialogVisible.value = false
|
||
fetchChannelList()
|
||
} catch (error: any) {
|
||
ElMessage.error(error?.msg || '充值提交失败')
|
||
} finally {
|
||
submitLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 取消弹窗
|
||
const handleCancel = () => {
|
||
dialogVisible.value = false
|
||
rechangeFormRef.value?.resetFields()
|
||
rechangeForm.value = createDefaultForm()
|
||
}
|
||
|
||
// 关闭弹窗
|
||
const handleClose = () => {
|
||
dialogVisible.value = false
|
||
rechangeFormRef.value?.resetFields()
|
||
rechangeForm.value = createDefaultForm()
|
||
}
|
||
|
||
// 页面加载时获取渠道列表
|
||
onMounted(() => {
|
||
fetchChannelList()
|
||
})
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.rechange-page {
|
||
max-height: 100%;
|
||
height: 100%;
|
||
.page-title {
|
||
margin-bottom: 16px;
|
||
font-size: 28px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
}
|
||
}
|
||
|
||
.channel-card {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
margin-bottom: 20px;
|
||
color: #fff;
|
||
|
||
.channel-info {
|
||
.channel-name {
|
||
font-size: 20px;
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.channel-meta {
|
||
display: flex;
|
||
gap: 16px;
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
}
|
||
}
|
||
|
||
.channel-fee {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
padding: 8px 16px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
border-radius: 20px;
|
||
}
|
||
}
|
||
|
||
.info-section {
|
||
background: #f5f7fa;
|
||
border-radius: 10px;
|
||
padding: 16px;
|
||
margin-bottom: 20px;
|
||
|
||
.section-title {
|
||
font-size: 16px;
|
||
font-weight: 600;
|
||
color: #303133;
|
||
margin-bottom: 16px;
|
||
padding-bottom: 12px;
|
||
border-bottom: 1px solid #e4e7ed;
|
||
}
|
||
|
||
.info-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 12px;
|
||
|
||
.info-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
|
||
&.full-width {
|
||
grid-column: span 2;
|
||
}
|
||
|
||
.label {
|
||
font-size: 13px;
|
||
color: #909399;
|
||
}
|
||
|
||
.value {
|
||
font-size: 14px;
|
||
color: #303133;
|
||
word-break: break-all;
|
||
|
||
&.address-value {
|
||
font-family: monospace;
|
||
background: #fff;
|
||
padding: 8px;
|
||
border-radius: 4px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.currency-suffix {
|
||
font-size: 16px;
|
||
font-weight: 500;
|
||
color: #606266;
|
||
}
|
||
|
||
// 上传凭证样式
|
||
.voucher-uploader {
|
||
width: 100%;
|
||
height: 160px;
|
||
border: 1px dashed var(--el-border-color);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
overflow: hidden;
|
||
transition: var(--el-transition-duration-fast);
|
||
|
||
&:hover {
|
||
border-color: var(--el-color-primary);
|
||
}
|
||
|
||
:deep(.el-upload) {
|
||
width: 100%;
|
||
height: 100%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.voucher-image {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.upload-placeholder {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 8px;
|
||
color: #909399;
|
||
|
||
.upload-icon {
|
||
font-size: 32px;
|
||
}
|
||
|
||
.upload-text {
|
||
font-size: 14px;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 按钮区域
|
||
.dialog-actions {
|
||
display: flex;
|
||
gap: 12px;
|
||
margin-top: 24px;
|
||
|
||
.el-button {
|
||
flex: 1;
|
||
height: 44px;
|
||
font-size: 16px;
|
||
border-radius: 8px;
|
||
}
|
||
}
|
||
|
||
// 深度覆盖 Element Plus Dialog 样式
|
||
:deep(.rechange-pay-dialog .el-dialog__header) {
|
||
padding: 20px 24px;
|
||
border-bottom: 1px solid #e4e7ed;
|
||
}
|
||
|
||
:deep(.rechange-pay-dialog .el-dialog__body) {
|
||
padding: 24px;
|
||
}
|
||
|
||
:deep(.rechange-pay-dialog .el-dialog) {
|
||
border-radius: 12px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
:deep(.el-form-item__label) {
|
||
font-weight: 500;
|
||
}
|
||
</style>
|