This commit is contained in:
2026-06-11 10:24:26 +08:00
parent ffa6106b79
commit d201e42309
2 changed files with 359 additions and 81 deletions

View File

@ -319,7 +319,7 @@ const handleLogout = () => {
.logo-section { .logo-section {
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%; height: 100%;
gap: 8px; gap: 8px;
width: 100%; width: 100%;
padding: 4px 0 ; padding: 4px 0 ;

View File

@ -15,109 +15,199 @@
</div> </div>
<!-- 存款弹窗 --> <!-- 存款弹窗 -->
<RechangeDialog class="rechange-pay-dialog" :visible="dialogVisible" title="存款" width="500px" :show-footer="false" <RechangeDialog class="rechange-pay-dialog" :visible="dialogVisible" title="存款" width="600px" :show-footer="false"
@cancel="handleCancel" @close="handleClose"> @cancel="handleCancel" @close="handleClose">
<!-- 步骤1选择金额和支付方式 --> <!-- type === 8 时显示新样式选择金额和支付方式 -->
<template v-if="payStep === 1"> <template v-if="rechangeForm.type === 8">
<!-- 渠道信息 --> <!-- 步骤1选择金额和支付方式 -->
<div class="channel-card"> <template v-if="payStep === 1">
<div class="channel-info"> <!-- 渠道信息 -->
<div class="channel-name">{{ rechangeForm.channelName }}</div> <div class="channel-card">
<div class="channel-info">
<div class="channel-name">{{ rechangeForm.channelName }}</div>
</div>
<div class="channel-meta">支持货币: AUD (澳元)</div>
</div> </div>
<div class="channel-meta">支持货币: AUD (澳元)</div>
</div>
<!-- 快捷金额选择 --> <!-- 快捷金额选择 -->
<div class="quick-amount"> <div class="quick-amount">
<div class="section-title">选择充值金额</div> <div class="section-title">选择充值金额</div>
<div class="amount-list"> <div class="amount-list">
<div v-for="(amount, index) in quickAmounts" :key="index" class="amount-item" <div v-for="(amount, index) in quickAmounts" :key="index" class="amount-item"
:class="{ active: rechangeForm.depositAmount === amount.toString() }" :class="{ active: rechangeForm.depositAmount === amount.toString() }"
@click="handleQuickAmount(amount)"> @click="handleQuickAmount(amount)">
{{ amount }} AUD {{ amount }} AUD
</div>
</div> </div>
</div> </div>
</div>
<!-- 自定义金额 --> <!-- 自定义金额 -->
<el-form-item label="自定义金额"> <el-form-item label="自定义金额">
<el-input v-model="rechangeForm.depositAmount" placeholder="请输入充值金额" size="large" <el-input v-model="rechangeForm.depositAmount" placeholder="请输入充值金额" size="large"
@input="handleAmountInput"> @input="handleAmountInput">
<template #suffix> <template #suffix>
<span class="currency-suffix">AUD</span> <span class="currency-suffix">AUD</span>
</template> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<!-- 支付方式选择 --> <!-- 支付方式选择 -->
<div class="payment-methods"> <div class="payment-methods">
<div class="section-title">选择支付方式</div> <div class="section-title">选择支付方式</div>
<div class="method-list"> <div class="method-list">
<div v-for="method in paymentMethods" :key="method.code" class="method-item" <div v-for="method in paymentMethods" :key="method.code" class="method-item"
:class="{ active: selectedMethod === method.code }" @click="handleSelectMethod(method.code)"> :class="{ active: selectedMethod === method.code }" @click="handleSelectMethod(method.code)">
<div class="method-icon"> <div class="method-icon">
<img :src="method.icon" :alt="method.name" /> <img :src="method.icon" :alt="method.name" />
</div>
<span class="method-name">{{ method.name }}</span>
<div class="method-check" v-if="selectedMethod === method.code">
<el-icon>
<Check />
</el-icon>
</div>
</div> </div>
<span class="method-name">{{ method.name }}</span> </div>
<div class="method-check" v-if="selectedMethod === method.code"> </div>
<el-icon>
<Check /> <!-- 按钮区 -->
<div class="dialog-actions">
<el-button size="large" @click="handleCancel">取消</el-button>
<el-button type="primary" size="large" :loading="submitLoading"
:disabled="!rechangeForm.depositAmount || !selectedMethod" @click="handleCreateOrder">
确认支付
</el-button>
</div>
</template>
<!-- 步骤2支付中/跳转 -->
<template v-else-if="payStep === 2">
<!-- 移动端直接跳转 -->
<template v-if="isMobileDevice">
<div class="loading-section">
<el-icon class="is-loading" size="48">
<Loading />
</el-icon>
<div class="loading-text">正在跳转支付页面...</div>
</div>
</template>
<!-- 桌面端显示二维码 -->
<template v-else>
<div class="qrcode-section">
<div class="section-title">请使用{{ getMethodName(selectedMethod) }}扫码支付</div>
<div class="qrcode-container">
<div v-show="qrcodeLoading">
<el-icon class="is-loading" size="48">
<Loading />
</el-icon>
</div>
<div v-show="!qrcodeLoading">
<img v-if="qrcodeImageUrl" :src="qrcodeImageUrl" alt="支付二维码" class="qrcode-image" />
<div v-else ref="qrcodeRef" class="qrcode"></div>
</div>
</div>
<div class="qrcode-tip">请使用{{ getMethodName(selectedMethod) }}扫描上方二维码完成支付</div>
</div>
</template>
</template>
</template>
<!-- type !== 8 时显示老样式银行卡信息 + 上传凭证 -->
<template v-else>
<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> </el-icon>
<span class="upload-text">点击上传凭证</span>
</div> </div>
</div> </el-upload>
</div> </el-form-item>
</div> </el-form>
<!-- 按钮区 --> <!-- 按钮区 -->
<div class="dialog-actions"> <div class="dialog-actions">
<el-button size="large" @click="handleCancel">取消</el-button> <el-button size="large" @click="handleCancel">取消</el-button>
<el-button type="primary" size="large" :loading="submitLoading" <el-button type="primary" size="large" :loading="submitLoading" @click="handleOldSubmit">
:disabled="!rechangeForm.depositAmount || !selectedMethod" @click="handleCreateOrder"> 确认充值
确认支付
</el-button> </el-button>
</div> </div>
</template> </template>
<!-- 步骤2支付中/跳转 -->
<template v-else-if="payStep === 2">
<!-- 移动端直接跳转 -->
<template v-if="isMobileDevice">
<div class="loading-section">
<el-icon class="is-loading" size="48">
<Loading />
</el-icon>
<div class="loading-text">正在跳转支付页面...</div>
</div>
</template>
<!-- 桌面端显示二维码 -->
<template v-else>
<div class="qrcode-section">
<div class="section-title">请使用{{ getMethodName(selectedMethod) }}扫码支付</div>
<div class="qrcode-container">
<div v-show="qrcodeLoading">
<el-icon class="is-loading" size="48">
<Loading />
</el-icon>
</div>
<div v-show="!qrcodeLoading">
<img v-if="qrcodeImageUrl" :src="qrcodeImageUrl" alt="支付二维码" class="qrcode-image" />
<div v-else ref="qrcodeRef" class="qrcode"></div>
</div>
</div>
<div class="qrcode-tip">请使用{{ getMethodName(selectedMethod) }}扫描上方二维码完成支付</div>
</div>
</template>
</template>
</RechangeDialog> </RechangeDialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Check, Loading } from '@element-plus/icons-vue' import { Check, Loading, Plus } from '@element-plus/icons-vue'
defineOptions({ defineOptions({
name: 'Rechange' name: 'Rechange'
}) })
import type { FormInstance, FormRules } from 'element-plus' import type { FormInstance, FormRules, UploadProps } from 'element-plus'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { nextTick, onMounted, ref, computed } from 'vue' import { nextTick, onMounted, ref, computed } from 'vue'
import { useUserStore } from '@/stores/modules/user' import { useUserStore } from '@/stores/modules/user'
@ -125,12 +215,17 @@ import RechangeTable from '@/components/common/Table.vue'
import RechangeDialog from '@/components/common/Dialog.vue' import RechangeDialog from '@/components/common/Dialog.vue'
import { usePageLoading } from '@/composables/useLoading' import { usePageLoading } from '@/composables/useLoading'
import { rechangeTableColumns } from './options' import { rechangeTableColumns } from './options'
import { getRechargeChannelList, getApplyCustomerRef, getKycDetail, createBuyEx } from '@/api/modules/capital/rechangeChanl' import { getRechargeChannelList, getApplyCustomerRef, getKycDetail, createBuyEx, rechargeApi } from '@/api/modules/capital/rechangeChanl'
import { uploadImageApi } from '@/api/modules/upload'
import QRCode from 'qrcode' import QRCode from 'qrcode'
import alipayIcon from '@/assets/images/zfb.png' import alipayIcon from '@/assets/images/zfb.png'
import wechatIcon from '@/assets/images/wx.png' import wechatIcon from '@/assets/images/wx.png'
import unionpayIcon from '@/assets/images/ylzf.png' import unionpayIcon from '@/assets/images/ylzf.png'
//
const voucherUrl = ref('')
const uploadLoading = ref(false)
// //
const userStore = useUserStore() const userStore = useUserStore()
const userInfo = userStore.userInfo as any const userInfo = userStore.userInfo as any
@ -213,11 +308,13 @@ const channelList = ref<ChannelRow[]>([])
// //
interface RechangeFormData { interface RechangeFormData {
id: number id: number
type: number
channelName: string channelName: string
channelCode: string channelCode: string
currencySymbol: string currencySymbol: string
channelFee: string channelFee: string
depositAmount: string depositAmount: string
voucherUrl: string
beneficiaryName: string beneficiaryName: string
beneficiaryBankName: string beneficiaryBankName: string
swiftBicCode: string swiftBicCode: string
@ -228,11 +325,13 @@ interface RechangeFormData {
const createDefaultForm = (): RechangeFormData => ({ const createDefaultForm = (): RechangeFormData => ({
id: 0, id: 0,
type: 0,
channelName: '', channelName: '',
channelCode: '', channelCode: '',
currencySymbol: '', currencySymbol: '',
channelFee: '0', channelFee: '0',
depositAmount: '', depositAmount: '',
voucherUrl: '',
beneficiaryName: '', beneficiaryName: '',
beneficiaryBankName: '', beneficiaryBankName: '',
swiftBicCode: '', swiftBicCode: '',
@ -249,6 +348,9 @@ const rechangeFormRules = ref<FormRules<RechangeFormData>>({
{ required: true, message: '请输入充值金额', trigger: ['blur', 'change'] }, { required: true, message: '请输入充值金额', trigger: ['blur', 'change'] },
{ pattern: /^\d+$/, message: '请输入整数金额', trigger: ['blur', 'change'] }, { pattern: /^\d+$/, message: '请输入整数金额', trigger: ['blur', 'change'] },
], ],
voucherUrl: [
{ required: true, message: '请上传充值凭证', trigger: ['change'] },
],
}) })
// //
@ -290,11 +392,13 @@ const fetchChannelList = async () => {
const handleOpenDialog = async (row: ChannelRow) => { const handleOpenDialog = async (row: ChannelRow) => {
rechangeForm.value = { rechangeForm.value = {
id: row.id, id: row.id,
type: row.type,
channelName: row.name, channelName: row.name,
channelCode: row.channel_code, channelCode: row.channel_code,
currencySymbol: 'AUD', currencySymbol: row.currency?.symbol || 'AUD',
channelFee: row.channel_fee || '0', channelFee: row.channel_fee || '0',
depositAmount: '', depositAmount: '',
voucherUrl: '',
beneficiaryName: row.beneficiary_name || '', beneficiaryName: row.beneficiary_name || '',
beneficiaryBankName: row.beneficiary_bank_name || '', beneficiaryBankName: row.beneficiary_bank_name || '',
swiftBicCode: row.swift_bic_code || '', swiftBicCode: row.swift_bic_code || '',
@ -305,6 +409,7 @@ const handleOpenDialog = async (row: ChannelRow) => {
// //
payStep.value = 1 payStep.value = 1
selectedMethod.value = '' selectedMethod.value = ''
voucherUrl.value = ''
dialogVisible.value = true dialogVisible.value = true
await nextTick() await nextTick()
@ -381,6 +486,57 @@ const handleCreateOrder = async () => {
} }
} }
//
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
}
}
// type !== 8 使
const handleOldSubmit = 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 = () => { const handleCancel = () => {
dialogVisible.value = false dialogVisible.value = false
@ -390,6 +546,7 @@ const handleCancel = () => {
qrcodeLoading.value = false qrcodeLoading.value = false
payStep.value = 1 payStep.value = 1
selectedMethod.value = '' selectedMethod.value = ''
voucherUrl.value = ''
} }
// //
@ -660,4 +817,125 @@ onMounted(() => {
font-size: 14px; font-size: 14px;
margin-top: 16px; margin-top: 16px;
} }
// info-section
.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;
}
}
}
}
.info-item.full-width {
display: flex;
flex-direction: column;
gap: 6px;
.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;
}
}
}
}
//
.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;
}
}
}
</style> </style>