foreign-admin-ui/src/views/capital/rechange/index.vue

505 lines
14 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.

<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>