This commit is contained in:
parent
67a65ede6e
commit
f8f1159666
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { request } from '@/api'
|
||||||
|
|
||||||
|
// 获取用户钱包信息
|
||||||
|
export const getWalletCapital = () => {
|
||||||
|
return request.get('/user/getWalletCapital')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取取款渠道列表 (role_id >= 5 使用)
|
||||||
|
export const getWithdrawChannel = () => {
|
||||||
|
return request.get('/withdraw/withdrawChannel')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取代理取款渠道列表 (role_id < 5 使用)
|
||||||
|
export const getAgentWithdrawChannel = () => {
|
||||||
|
return request.get('/withdraw/agentWithdrawChannel')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 代理取款 (role_id < 5)
|
||||||
|
export const agentWithdraw = (params: {
|
||||||
|
channel_id: number
|
||||||
|
amount: string
|
||||||
|
withdraw_fee_handle: string
|
||||||
|
withdraw_point_diff: string
|
||||||
|
withdraw_service_fee: string
|
||||||
|
withdraw_head_fee: string
|
||||||
|
withdraw_risk_fee: string
|
||||||
|
withdraw_address: string
|
||||||
|
}) => {
|
||||||
|
return request.post('/withdraw/agentWithdraw', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户取款 (role_id >= 5)
|
||||||
|
export const withdraw = (params: {
|
||||||
|
channel_id: number
|
||||||
|
amount: string
|
||||||
|
withdraw_fee_handle: string
|
||||||
|
withdraw_point_diff: string
|
||||||
|
withdraw_blance: string
|
||||||
|
withdraw_address: string
|
||||||
|
beneficiary_name: string
|
||||||
|
beneficiary_bank_name: string
|
||||||
|
swift_bic_code: string
|
||||||
|
beneficiary_bank_address: string
|
||||||
|
beneficiary_bank_account: string
|
||||||
|
}) => {
|
||||||
|
return request.post('/withdraw/withdraw', params)
|
||||||
|
}
|
||||||
|
|
@ -98,5 +98,56 @@ export default {
|
||||||
5: 'Agent',
|
5: 'Agent',
|
||||||
6: 'User'
|
6: 'User'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
// Withdraw
|
||||||
|
withdraw: {
|
||||||
|
pageTitle: 'Withdraw',
|
||||||
|
withdraw: 'Withdraw',
|
||||||
|
dialog: {
|
||||||
|
title1: 'Withdraw',
|
||||||
|
title2: 'Confirm Withdraw Information',
|
||||||
|
amount: 'Withdraw Amount',
|
||||||
|
amountPlaceholder: 'Please enter withdraw amount',
|
||||||
|
walletBalance: 'Wallet Balance',
|
||||||
|
deductionDetails: 'Deduction Details',
|
||||||
|
type: 'Type',
|
||||||
|
originalBalance: 'Original Balance',
|
||||||
|
deductedAmount: 'Deducted Amount',
|
||||||
|
remaining: 'Remaining',
|
||||||
|
totalDeducted: 'Total Deducted',
|
||||||
|
channel: 'Channel',
|
||||||
|
withdrawAmount: 'Withdraw Amount',
|
||||||
|
walletAddress: 'Wallet Address',
|
||||||
|
walletAddressPlaceholder: 'Please enter wallet address',
|
||||||
|
beneficiaryName: 'Beneficiary Name',
|
||||||
|
beneficiaryNamePlaceholder: 'Please enter beneficiary name',
|
||||||
|
bankName: 'Bank Name',
|
||||||
|
bankNamePlaceholder: 'Please enter bank name',
|
||||||
|
swiftCode: 'SWIFT Code',
|
||||||
|
swiftCodePlaceholder: 'Please enter SWIFT code',
|
||||||
|
bankAddress: 'Bank Address',
|
||||||
|
bankAddressPlaceholder: 'Please enter bank address',
|
||||||
|
bankAccount: 'Bank Account',
|
||||||
|
bankAccountPlaceholder: 'Please enter bank account',
|
||||||
|
nextStep: 'Next Step',
|
||||||
|
confirmWithdraw: 'Confirm Withdraw',
|
||||||
|
submitSuccess: 'Withdraw application submitted',
|
||||||
|
},
|
||||||
|
wallet: {
|
||||||
|
feeHandling: 'Fee Handling',
|
||||||
|
pointDiff: 'Point Diff',
|
||||||
|
service: 'Service',
|
||||||
|
risk: 'Risk',
|
||||||
|
profit: 'Head Position',
|
||||||
|
headPosition: 'Head Position',
|
||||||
|
available: 'Available',
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
channelName: 'Channel Name',
|
||||||
|
channelFee: 'Channel Fee',
|
||||||
|
apiUrl: 'API URL',
|
||||||
|
apiAppid: 'API Appid',
|
||||||
|
status: 'Status',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,5 +98,56 @@ export default {
|
||||||
5: '代理',
|
5: '代理',
|
||||||
6: '用户'
|
6: '用户'
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
// 取款
|
||||||
|
withdraw: {
|
||||||
|
pageTitle: '取款',
|
||||||
|
withdraw: '取款',
|
||||||
|
dialog: {
|
||||||
|
title1: '取款',
|
||||||
|
title2: '确认取款信息',
|
||||||
|
amount: '取款金额',
|
||||||
|
amountPlaceholder: '请输入取款金额',
|
||||||
|
walletBalance: '钱包余额',
|
||||||
|
deductionDetails: '扣除明细',
|
||||||
|
type: '类型',
|
||||||
|
originalBalance: '原余额',
|
||||||
|
deductedAmount: '扣除金额',
|
||||||
|
remaining: '剩余',
|
||||||
|
totalDeducted: '合计扣除',
|
||||||
|
channel: '渠道',
|
||||||
|
withdrawAmount: '取款金额',
|
||||||
|
walletAddress: '钱包地址',
|
||||||
|
walletAddressPlaceholder: '请输入钱包地址',
|
||||||
|
beneficiaryName: '收款人姓名',
|
||||||
|
beneficiaryNamePlaceholder: '请输入收款人姓名',
|
||||||
|
bankName: '银行名称',
|
||||||
|
bankNamePlaceholder: '请输入银行名称',
|
||||||
|
swiftCode: 'SWIFT代码',
|
||||||
|
swiftCodePlaceholder: '请输入SWIFT代码',
|
||||||
|
bankAddress: '银行地址',
|
||||||
|
bankAddressPlaceholder: '请输入银行地址',
|
||||||
|
bankAccount: '银行账号',
|
||||||
|
bankAccountPlaceholder: '请输入银行账号',
|
||||||
|
nextStep: '下一步',
|
||||||
|
confirmWithdraw: '确认取款',
|
||||||
|
submitSuccess: '取款申请已提交',
|
||||||
|
},
|
||||||
|
wallet: {
|
||||||
|
feeHandling: '手续费',
|
||||||
|
pointDiff: '点差佣金',
|
||||||
|
service: '服务费',
|
||||||
|
risk: '风险金',
|
||||||
|
profit: '头寸',
|
||||||
|
headPosition: '头寸',
|
||||||
|
available: '可用余额',
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
channelName: '渠道名称',
|
||||||
|
channelFee: '渠道费用',
|
||||||
|
apiUrl: '接口地址',
|
||||||
|
apiAppid: '接口appid',
|
||||||
|
status: '状态',
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,812 @@
|
||||||
|
<template>
|
||||||
|
<Dialog
|
||||||
|
:visible="dialogVisible"
|
||||||
|
:title="currentStep === 1 ? t('withdraw.dialog.title1') : t('withdraw.dialog.title2')"
|
||||||
|
width="600px"
|
||||||
|
:show-footer="false"
|
||||||
|
@close="handleUpdateVisible"
|
||||||
|
>
|
||||||
|
<!-- 第一步:输入金额并展示计算明细 -->
|
||||||
|
<div v-if="currentStep === 1" class="step1-container">
|
||||||
|
<!-- 钱包余额展示 -->
|
||||||
|
<div class="wallet-section">
|
||||||
|
<div class="section-label">{{ t('withdraw.dialog.walletBalance') }}</div>
|
||||||
|
<div class="wallet-grid">
|
||||||
|
<div class="wallet-item">
|
||||||
|
<span class="wallet-label">{{ t('withdraw.wallet.feeHandling') }}</span>
|
||||||
|
<span class="wallet-value">{{ walletInfo.commission_fee_handling || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="wallet-item">
|
||||||
|
<span class="wallet-label">{{ t('withdraw.wallet.pointDiff') }}</span>
|
||||||
|
<span class="wallet-value">{{ walletInfo.commission_point_diff || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="wallet-item">
|
||||||
|
<span class="wallet-label">{{ t('withdraw.wallet.service') }}</span>
|
||||||
|
<span class="wallet-value">{{ walletInfo.service || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="wallet-item" v-if="showRiskFee">
|
||||||
|
<span class="wallet-label">{{ t('withdraw.wallet.risk') }}</span>
|
||||||
|
<span class="wallet-value">{{ walletInfo.risk || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="wallet-item">
|
||||||
|
<span class="wallet-label">{{ t('withdraw.wallet.profit') }}</span>
|
||||||
|
<span class="wallet-value">{{ walletInfo.closing_profit || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="wallet-item" v-if="roleId >= 5">
|
||||||
|
<span class="wallet-label">{{ t('withdraw.wallet.available') }}</span>
|
||||||
|
<span class="wallet-value available">{{ availableBalance.toFixed(2) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 金额输入 -->
|
||||||
|
<div class="amount-section">
|
||||||
|
<div class="section-label">
|
||||||
|
{{ t('withdraw.dialog.amount') }}
|
||||||
|
<span class="max-balance">(最大可取:{{ totalWalletBalance }} {{ currentCurrency }})</span>
|
||||||
|
</div>
|
||||||
|
<el-input
|
||||||
|
v-model="withdrawAmount"
|
||||||
|
type="number"
|
||||||
|
:placeholder="t('withdraw.dialog.amountPlaceholder')"
|
||||||
|
@input="handleAmountInput"
|
||||||
|
>
|
||||||
|
<template #prepend>{{ currentCurrency }}</template>
|
||||||
|
</el-input>
|
||||||
|
<div v-if="amountError" class="amount-error">{{ amountError }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 合计扣除 -->
|
||||||
|
<div v-if="totalDeducted && parseFloat(totalDeducted) > 0" class="total-section">
|
||||||
|
<div class="total-label">{{ t('withdraw.dialog.totalDeducted') }}:</div>
|
||||||
|
<div class="total-value">-{{ totalDeducted }} {{ currentCurrency }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 扣除计算明细 - 用盒子展示 -->
|
||||||
|
<div v-if="withdrawAmount && calculatedData.length > 0" class="calculation-section">
|
||||||
|
<div class="section-label">{{ t('withdraw.dialog.deductionDetails') }}</div>
|
||||||
|
<div class="calculation-grid">
|
||||||
|
<div v-for="(item, index) in calculatedData" :key="index" class="calc-item">
|
||||||
|
<div class="calc-type">{{ item.type }}</div>
|
||||||
|
<div class="calc-values">
|
||||||
|
<div class="calc-original">
|
||||||
|
<span class="label">{{ t('withdraw.dialog.originalBalance') }}</span>
|
||||||
|
<span class="value">{{ item.original }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="calc-deducted">
|
||||||
|
<span class="label">{{ t('withdraw.dialog.deductedAmount') }}</span>
|
||||||
|
<span class="value deducted">-{{ item.deducted }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="calc-remaining">
|
||||||
|
<span class="label">{{ t('withdraw.dialog.remaining') }}</span>
|
||||||
|
<span class="value">{{ item.remaining }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="calc-summary">
|
||||||
|
<span class="label">{{ t('withdraw.dialog.totalDeducted') }}:</span>
|
||||||
|
<span class="total-value">{{ totalDeducted }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 快捷金额 -->
|
||||||
|
<div class="quick-amounts">
|
||||||
|
<el-button
|
||||||
|
v-for="amount in quickAmounts"
|
||||||
|
:key="amount"
|
||||||
|
size="small"
|
||||||
|
@click="handleQuickAmount(amount)"
|
||||||
|
>
|
||||||
|
{{ amount }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="handleCancel">{{ t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" @click="handleNextStep">{{ t('withdraw.dialog.nextStep') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 第二步:确认表单 -->
|
||||||
|
<div v-else class="step2-container">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-position="top"
|
||||||
|
>
|
||||||
|
|
||||||
|
<!-- 金额信息(只读) -->
|
||||||
|
<div class="amount-info">
|
||||||
|
<div class="info-row">
|
||||||
|
<span class="info-label">{{ t('withdraw.dialog.withdrawAmount') }}:</span>
|
||||||
|
<span class="info-value">{{ withdrawAmount }} USD</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-row" v-for="(item, index) in calculatedData" :key="index">
|
||||||
|
<span class="info-label">{{ item.typeLabel }}:</span>
|
||||||
|
<span class="info-value">{{ item.deducted }} USD</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 钱包地址 -->
|
||||||
|
<el-form-item :label="t('withdraw.dialog.walletAddress')" prop="withdraw_address">
|
||||||
|
<el-input v-model="formData.withdraw_address" :placeholder="t('withdraw.dialog.walletAddressPlaceholder')" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<!-- 用户取款额外字段 (role_id >= 5) -->
|
||||||
|
<template v-if="!isAgent">
|
||||||
|
<el-form-item :label="t('withdraw.dialog.beneficiaryName')" prop="beneficiary_name">
|
||||||
|
<el-input v-model="formData.beneficiary_name" :placeholder="t('withdraw.dialog.beneficiaryNamePlaceholder')" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('withdraw.dialog.bankName')" prop="beneficiary_bank_name">
|
||||||
|
<el-input v-model="formData.beneficiary_bank_name" :placeholder="t('withdraw.dialog.bankNamePlaceholder')" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('withdraw.dialog.swiftCode')" prop="swift_bic_code">
|
||||||
|
<el-input v-model="formData.swift_bic_code" :placeholder="t('withdraw.dialog.swiftCodePlaceholder')" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('withdraw.dialog.bankAddress')" prop="beneficiary_bank_address">
|
||||||
|
<el-input v-model="formData.beneficiary_bank_address" :placeholder="t('withdraw.dialog.bankAddressPlaceholder')" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item :label="t('withdraw.dialog.bankAccount')" prop="beneficiary_bank_account">
|
||||||
|
<el-input v-model="formData.beneficiary_bank_account" :placeholder="t('withdraw.dialog.bankAccountPlaceholder')" />
|
||||||
|
</el-form-item>
|
||||||
|
</template>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 底部按钮 -->
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="handleBack">{{ t('common.cancel') }}</el-button>
|
||||||
|
<el-button type="primary" :loading="submitLoading" @click="handleSubmit">{{ t('withdraw.dialog.confirmWithdraw') }}</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import { useI18n } from 'vue-i18n'
|
||||||
|
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||||
|
import Dialog from '@/components/common/Dialog.vue'
|
||||||
|
import { getWalletCapital, getWithdrawChannel, getAgentWithdrawChannel, agentWithdraw, withdraw } from '@/api/modules/capital/withdraw'
|
||||||
|
import { getStorage } from '@/utils/storage'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
visible: boolean
|
||||||
|
channelId?: number
|
||||||
|
channelInfo?: {
|
||||||
|
id: number
|
||||||
|
symbol?: string
|
||||||
|
currency?: {
|
||||||
|
symbol: string
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:visible', value: boolean): void
|
||||||
|
(e: 'success'): void
|
||||||
|
}
|
||||||
|
|
||||||
|
interface WalletInfo {
|
||||||
|
user_id: string
|
||||||
|
amount: string
|
||||||
|
commission_point_diff: string
|
||||||
|
freeze: string
|
||||||
|
risk: string
|
||||||
|
service: string
|
||||||
|
toucun_percent: string
|
||||||
|
fee_handling_percent: string
|
||||||
|
commission_fee_handling: string
|
||||||
|
bail: string
|
||||||
|
bail_stop_order: string
|
||||||
|
bail_change_order: string
|
||||||
|
risk_control_id: string
|
||||||
|
fee_setting_id: string
|
||||||
|
closing_profit: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ChannelItem {
|
||||||
|
id: number
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CalculatedItem {
|
||||||
|
type: string
|
||||||
|
typeLabel: string
|
||||||
|
original: string
|
||||||
|
deducted: string
|
||||||
|
remaining: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<Props>()
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
const dialogVisible = ref(props.visible)
|
||||||
|
const currentStep = ref(1)
|
||||||
|
const withdrawAmount = ref('')
|
||||||
|
const submitLoading = ref(false)
|
||||||
|
const amountError = ref('')
|
||||||
|
|
||||||
|
// 处理金额输入
|
||||||
|
const handleAmountInput = (value: string) => {
|
||||||
|
const numValue = parseFloat(value)
|
||||||
|
if (isNaN(numValue)) {
|
||||||
|
amountError.value = ''
|
||||||
|
calculatedData.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numValue > totalWalletBalance.value) {
|
||||||
|
amountError.value = `输入金额不能超过最大可取金额 ${totalWalletBalance.value.toFixed(2)}`
|
||||||
|
calculatedData.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
amountError.value = ''
|
||||||
|
calculateWithdraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户信息
|
||||||
|
const userInfo: any = getStorage('userInfo')
|
||||||
|
const roleId = userInfo?.role_id || 6
|
||||||
|
|
||||||
|
// 角色判断
|
||||||
|
const isAgent = computed(() => roleId >= 5)
|
||||||
|
|
||||||
|
// 当前货币类型(从渠道信息获取)
|
||||||
|
const currentCurrency = computed(() => props.channelInfo?.currency?.symbol || '-')
|
||||||
|
|
||||||
|
// 计算可用余额 = amount - 所有钱包余额 - freeze
|
||||||
|
// availableBalance = amount - freeze - (手续费 + 点差 + 服务费 + 风险金 + 头寸)
|
||||||
|
const availableBalance = computed(() => {
|
||||||
|
const amount = parseFloat(walletInfo.value.amount || '0')
|
||||||
|
const freeze = parseFloat(walletInfo.value.freeze || '0')
|
||||||
|
// 钱包余额总和
|
||||||
|
const walletSum =
|
||||||
|
parseFloat(walletInfo.value.commission_fee_handling || '0') +
|
||||||
|
parseFloat(walletInfo.value.commission_point_diff || '0') +
|
||||||
|
parseFloat(walletInfo.value.service || '0') +
|
||||||
|
parseFloat(walletInfo.value.risk || '0') +
|
||||||
|
parseFloat(walletInfo.value.closing_profit || '0')
|
||||||
|
return Math.max(0, amount - freeze - walletSum)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 计算钱包余额总和(用于验证输入金额上限)
|
||||||
|
// roleId >= 5 时,totalWalletBalance 需要包含 availableBalance
|
||||||
|
const totalWalletBalance = computed(() => {
|
||||||
|
const amount = parseFloat(walletInfo.value.amount || '0')
|
||||||
|
const freeze = parseFloat(walletInfo.value.freeze || '0')
|
||||||
|
|
||||||
|
// 钱包余额总和(手续费 + 点差 + 服务费 + 风险金 + 头寸)
|
||||||
|
const walletSum =
|
||||||
|
parseFloat(walletInfo.value.commission_fee_handling || '0') +
|
||||||
|
parseFloat(walletInfo.value.commission_point_diff || '0') +
|
||||||
|
parseFloat(walletInfo.value.service || '0') +
|
||||||
|
parseFloat(walletInfo.value.risk || '0') +
|
||||||
|
parseFloat(walletInfo.value.closing_profit || '0')
|
||||||
|
|
||||||
|
// availableBalance = amount - freeze - walletSum
|
||||||
|
const availableBal = Math.max(0, amount - freeze - walletSum)
|
||||||
|
|
||||||
|
if (roleId >= 5) {
|
||||||
|
// roleId >= 5 时,总余额包含可用余额
|
||||||
|
return walletSum + availableBal
|
||||||
|
}
|
||||||
|
return walletSum
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
// 根据角色显示对应的费用项
|
||||||
|
const showRiskFee = computed(() => [1].includes(roleId))
|
||||||
|
|
||||||
|
// 费用项顺序
|
||||||
|
const feeOrder = computed(() => {
|
||||||
|
const orders: Record<number, string[]> = {
|
||||||
|
1: ['手续费', '点差', '服务费', '风险金', '头寸', '可用余额'], // 平台
|
||||||
|
2: ['手续费', '点差', '服务费', '可用余额'], // 承包商
|
||||||
|
3: ['手续费', '点差', '服务费', '头寸', '可用余额'], // 特殊做市商
|
||||||
|
4: ['手续费', '点差', '服务费', '头寸', '可用余额'], // 普通做市商
|
||||||
|
5: ['手续费', '点差', '可用余额'], // 代理
|
||||||
|
6: ['手续费', '点差', '可用余额'], // 用户
|
||||||
|
}
|
||||||
|
return orders[roleId] ?? orders[6] ?? []
|
||||||
|
})
|
||||||
|
|
||||||
|
// 费用项对应的字段
|
||||||
|
const feeFieldMap: Record<string, keyof WalletInfo> = {
|
||||||
|
'手续费': 'commission_fee_handling',
|
||||||
|
'点差': 'commission_point_diff',
|
||||||
|
'服务费': 'service',
|
||||||
|
'风险金': 'risk',
|
||||||
|
'头寸': 'closing_profit',
|
||||||
|
'可用余额': 'amount',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 费用项标签映射
|
||||||
|
const feeLabelMap: Record<string, string> = {
|
||||||
|
'手续费': '手续费佣金',
|
||||||
|
'点差': '点差佣金',
|
||||||
|
'服务费': '服务费',
|
||||||
|
'风险金': '风险金',
|
||||||
|
'头寸': '头寸',
|
||||||
|
'可用余额': '可用余额',
|
||||||
|
}
|
||||||
|
|
||||||
|
// 钱包信息
|
||||||
|
const walletInfo = ref<WalletInfo>({
|
||||||
|
user_id: '',
|
||||||
|
amount: '',
|
||||||
|
commission_point_diff: '0',
|
||||||
|
freeze: '0',
|
||||||
|
risk: '0',
|
||||||
|
service: '0',
|
||||||
|
toucun_percent: '0',
|
||||||
|
fee_handling_percent: '0',
|
||||||
|
commission_fee_handling: '0',
|
||||||
|
bail: '0',
|
||||||
|
bail_stop_order: '0',
|
||||||
|
bail_change_order: '0',
|
||||||
|
risk_control_id: '',
|
||||||
|
fee_setting_id: '',
|
||||||
|
closing_profit: '0',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 渠道列表
|
||||||
|
const channelList = ref<ChannelItem[]>([])
|
||||||
|
|
||||||
|
// 计算后的扣除明细
|
||||||
|
const calculatedData = ref<CalculatedItem[]>([])
|
||||||
|
|
||||||
|
// 合计扣除
|
||||||
|
const totalDeducted = computed(() => {
|
||||||
|
return calculatedData.value.reduce((sum, item) => sum + parseFloat(item.deducted || '0'), 0).toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 快捷金额
|
||||||
|
const quickAmounts = ['100', '500', '1000', '5000']
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formRef = ref<FormInstance>()
|
||||||
|
const formData = ref({
|
||||||
|
channel_id: '',
|
||||||
|
withdraw_address: '',
|
||||||
|
// 代理取款字段
|
||||||
|
withdraw_fee_handle: '',
|
||||||
|
withdraw_point_diff: '',
|
||||||
|
withdraw_service_fee: '',
|
||||||
|
withdraw_head_fee: '',
|
||||||
|
withdraw_risk_fee: '',
|
||||||
|
// 用户取款额外字段
|
||||||
|
beneficiary_name: '',
|
||||||
|
beneficiary_bank_name: '',
|
||||||
|
swift_bic_code: '',
|
||||||
|
beneficiary_bank_address: '',
|
||||||
|
beneficiary_bank_account: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表单验证规则
|
||||||
|
const formRules: FormRules = {
|
||||||
|
withdraw_address: [{ required: true, message: '请输入钱包地址', trigger: 'blur' }],
|
||||||
|
beneficiary_name: [{ required: true, message: '请输入收款人姓名', trigger: 'blur' }],
|
||||||
|
beneficiary_bank_name: [{ required: true, message: '请输入银行名称', trigger: 'blur' }],
|
||||||
|
swift_bic_code: [{ required: true, message: '请输入SWIFT代码', trigger: 'blur' }],
|
||||||
|
beneficiary_bank_address: [{ required: true, message: '请输入银行地址', trigger: 'blur' }],
|
||||||
|
beneficiary_bank_account: [{ required: true, message: '请输入银行账号', trigger: 'blur' }],
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听 visible 变化
|
||||||
|
watch(() => props.visible, (val) => {
|
||||||
|
dialogVisible.value = val
|
||||||
|
if (val) {
|
||||||
|
initData()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 初始化数据
|
||||||
|
const initData = async () => {
|
||||||
|
currentStep.value = 1
|
||||||
|
withdrawAmount.value = ''
|
||||||
|
calculatedData.value = []
|
||||||
|
|
||||||
|
// 获取钱包信息
|
||||||
|
try {
|
||||||
|
const res: any = await getWalletCapital()
|
||||||
|
if (res) {
|
||||||
|
walletInfo.value = {
|
||||||
|
commission_fee_handling: res.commission_fee_handling || '0',
|
||||||
|
commission_point_diff: res.commission_point_diff || '0',
|
||||||
|
service: res.service || '0',
|
||||||
|
risk: res.risk || '0',
|
||||||
|
closing_profit: res.closing_profit || '0',
|
||||||
|
toucun_percent: res.toucun_percent || '0',
|
||||||
|
amount: res.amount || '0',
|
||||||
|
user_id: res.user_id || '',
|
||||||
|
freeze: res.freeze || '0',
|
||||||
|
bail: res.bail || '0',
|
||||||
|
bail_stop_order: res.bail_stop_order || '0',
|
||||||
|
bail_change_order: res.bail_change_order || '0',
|
||||||
|
risk_control_id: res.risk_control_id || '',
|
||||||
|
fee_setting_id: res.fee_setting_id || '',
|
||||||
|
fee_handling_percent: res.fee_handling_percent || '0',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取钱包信息失败', error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取渠道列表
|
||||||
|
try {
|
||||||
|
const api = isAgent.value ? getWithdrawChannel : getAgentWithdrawChannel
|
||||||
|
const res: any = await api()
|
||||||
|
if (res && Array.isArray(res)) {
|
||||||
|
channelList.value = res.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
}))
|
||||||
|
} else if (res?.data) {
|
||||||
|
channelList.value = res.data.map((item: any) => ({
|
||||||
|
id: item.id,
|
||||||
|
name: item.name,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取渠道列表失败', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算扣除明细
|
||||||
|
const calculateWithdraw = () => {
|
||||||
|
const amount = parseFloat(withdrawAmount.value) || 0
|
||||||
|
if (amount <= 0) {
|
||||||
|
calculatedData.value = []
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const results: CalculatedItem[] = []
|
||||||
|
let remaining = amount
|
||||||
|
|
||||||
|
for (const feeType of feeOrder.value) {
|
||||||
|
const field = feeFieldMap[feeType]
|
||||||
|
if (!field) continue
|
||||||
|
|
||||||
|
const original = parseFloat(walletInfo.value[field] || '0')
|
||||||
|
if (original <= 0) continue
|
||||||
|
|
||||||
|
const deducted = Math.min(remaining, original).toFixed(2)
|
||||||
|
const newRemaining = Math.max(0, remaining - original)
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
type: feeType,
|
||||||
|
typeLabel: feeLabelMap[feeType] || feeType,
|
||||||
|
original: original.toFixed(2),
|
||||||
|
deducted: deducted,
|
||||||
|
remaining: Math.max(0, original - parseFloat(deducted)).toFixed(2),
|
||||||
|
})
|
||||||
|
|
||||||
|
remaining = newRemaining
|
||||||
|
if (remaining <= 0) break
|
||||||
|
}
|
||||||
|
|
||||||
|
calculatedData.value = results
|
||||||
|
}
|
||||||
|
|
||||||
|
// 快捷金额
|
||||||
|
const handleQuickAmount = (amount: string) => {
|
||||||
|
withdrawAmount.value = amount
|
||||||
|
calculateWithdraw()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 关闭弹窗
|
||||||
|
const handleUpdateVisible = (val: boolean) => {
|
||||||
|
emit('update:visible', val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取消
|
||||||
|
const handleCancel = () => {
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('update:visible', false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回上一步
|
||||||
|
const handleBack = () => {
|
||||||
|
currentStep.value = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下一步
|
||||||
|
const handleNextStep = () => {
|
||||||
|
const amount = parseFloat(withdrawAmount.value)
|
||||||
|
if (!withdrawAmount.value || amount <= 0) {
|
||||||
|
ElMessage.warning('请输入取款金额')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (amount > totalWalletBalance.value) {
|
||||||
|
ElMessage.warning(`输入金额不能超过最大可取金额 ${totalWalletBalance.value.toFixed(2)}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充表单数据
|
||||||
|
formData.value.withdraw_fee_handle = calculatedData.value.find(item => item.type === '手续费')?.deducted || '0'
|
||||||
|
formData.value.withdraw_point_diff = calculatedData.value.find(item => item.type === '点差')?.deducted || '0'
|
||||||
|
formData.value.withdraw_service_fee = calculatedData.value.find(item => item.type === '服务费')?.deducted || '0'
|
||||||
|
formData.value.withdraw_risk_fee = calculatedData.value.find(item => item.type === '风险金')?.deducted || '0'
|
||||||
|
formData.value.withdraw_head_fee = calculatedData.value.find(item => item.type === '头寸')?.deducted || '0'
|
||||||
|
|
||||||
|
currentStep.value = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
try {
|
||||||
|
await formRef.value?.validate()
|
||||||
|
} catch {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
submitLoading.value = true
|
||||||
|
|
||||||
|
try {
|
||||||
|
const params: any = {
|
||||||
|
channel_id: props.channelId,
|
||||||
|
amount: +withdrawAmount.value,
|
||||||
|
withdraw_fee_handle: formData.value.withdraw_fee_handle,
|
||||||
|
withdraw_point_diff: formData.value.withdraw_point_diff,
|
||||||
|
withdraw_address: formData.value.withdraw_address,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAgent.value) {
|
||||||
|
// 用户取款
|
||||||
|
params.withdraw_blance = walletInfo.value.amount
|
||||||
|
params.beneficiary_name = formData.value.beneficiary_name
|
||||||
|
params.beneficiary_bank_name = formData.value.beneficiary_bank_name
|
||||||
|
params.swift_bic_code = formData.value.swift_bic_code
|
||||||
|
params.beneficiary_bank_address = formData.value.beneficiary_bank_address
|
||||||
|
params.beneficiary_bank_account = formData.value.beneficiary_bank_account
|
||||||
|
|
||||||
|
await withdraw(params)
|
||||||
|
} else {
|
||||||
|
// 代理取款
|
||||||
|
params.withdraw_service_fee = formData.value.withdraw_service_fee
|
||||||
|
params.withdraw_head_fee = formData.value.withdraw_head_fee
|
||||||
|
params.withdraw_risk_fee = formData.value.withdraw_risk_fee
|
||||||
|
|
||||||
|
await agentWithdraw(params)
|
||||||
|
}
|
||||||
|
|
||||||
|
ElMessage.success('取款申请已提交')
|
||||||
|
dialogVisible.value = false
|
||||||
|
emit('update:visible', false)
|
||||||
|
emit('success')
|
||||||
|
} catch (error) {
|
||||||
|
console.error('提交失败', error)
|
||||||
|
} finally {
|
||||||
|
submitLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.step1-container {
|
||||||
|
.section-label {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding: 16px;
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
border-radius: 8px;
|
||||||
|
color: #fff;
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
color: rgba(255, 255, 255, 0.9);
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
border-radius: 6px;
|
||||||
|
|
||||||
|
.wallet-label {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgba(255, 255, 255, 0.8);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wallet-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-section {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
|
||||||
|
.max-balance {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.amount-error {
|
||||||
|
margin-top: 8px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-section {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 16px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: #fef0f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #fde2e2;
|
||||||
|
|
||||||
|
.total-label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-value {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calculation-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calculation-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calc-item {
|
||||||
|
padding: 16px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calc-type {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calc-values {
|
||||||
|
display: flex;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.calc-original,
|
||||||
|
.calc-deducted,
|
||||||
|
.calc-remaining {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.value {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
|
||||||
|
&.deducted {
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.calc-summary {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.total-value {
|
||||||
|
margin-left: 8px;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #f56c6c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-amounts {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid #ebeef5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step2-container {
|
||||||
|
.amount-info {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
.info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -1,330 +1,120 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="withdraw-page">
|
<div class="withdraw-page">
|
||||||
<!-- 第一步:输入页 -->
|
<Table
|
||||||
<div v-if="!isConfirmStep" class="withdraw-card">
|
ref="tableRef"
|
||||||
<div class="page-title">{{ withdrawBaseInfo.channelName }}</div>
|
:table-data="tableData"
|
||||||
|
:columns="tableColumnsOptions"
|
||||||
<!-- 输入表单:当前先做纯前端演示,后续可在此接入真实接口 -->
|
row-key="id"
|
||||||
<el-form ref="withdrawFormRef" :model="withdrawForm" :rules="withdrawFormRules" label-position="top"
|
v-loading="tableLoading"
|
||||||
class="withdraw-form">
|
@rowClick="handleRowClick"
|
||||||
<el-form-item prop="withdrawAccount">
|
>
|
||||||
<template #label>
|
<template #action="{ row }">
|
||||||
<span class="required-label">取款账户</span>
|
<el-button type="primary" size="small" @click.stop="handleWithdraw(row)">取款</el-button>
|
||||||
</template>
|
</template>
|
||||||
<el-input v-model="withdrawForm.withdrawAccount" disabled />
|
</Table>
|
||||||
</el-form-item>
|
<Pagination
|
||||||
|
v-model:page="queryParams.page"
|
||||||
<el-form-item prop="currency">
|
v-model:limit="queryParams.limit"
|
||||||
<template #label>
|
:total="total"
|
||||||
<span class="required-label">支持币种</span>
|
@pagination="getList"
|
||||||
</template>
|
/>
|
||||||
<div class="currency-box">{{ withdrawForm.currency }}</div>
|
<WithdrawDialog
|
||||||
</el-form-item>
|
:visible="withdrawDialogVisible"
|
||||||
|
:channel-id="currentChannelId"
|
||||||
<el-form-item prop="amount">
|
:channel-info="currentChannelInfo"
|
||||||
<template #label>
|
@update:visible="withdrawDialogVisible = $event"
|
||||||
<span class="required-label">取款金额</span>
|
@success="handleWithdrawSuccess"
|
||||||
</template>
|
/>
|
||||||
|
</div>
|
||||||
<el-input v-model="withdrawForm.amount" placeholder="请输入金额">
|
|
||||||
<template #prepend>{{ withdrawForm.currency }}</template>
|
|
||||||
</el-input>
|
|
||||||
|
|
||||||
<!-- 快捷金额:点击后直接回填金额输入框 -->
|
|
||||||
<div class="quick-amounts">
|
|
||||||
<el-button v-for="amount in withdrawQuickAmounts" :key="amount" plain size="small"
|
|
||||||
@click="handleQuickAmount(amount)">
|
|
||||||
{{ amount }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<div class="action-row">
|
|
||||||
<el-button type="primary" class="submit-btn" @click="handleNextStep">
|
|
||||||
确认
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 第二步:收款信息页,展示你图里的表单结构 -->
|
|
||||||
<div v-else class="withdraw-card receive-card">
|
|
||||||
<el-form ref="receiveFormRef" :model="receiveForm" :rules="receiveFormRules" label-position="top"
|
|
||||||
class="withdraw-form receive-form">
|
|
||||||
<!-- 收款信息字段:由配置驱动渲染,方便后续增删字段 -->
|
|
||||||
<el-form-item v-for="field in withdrawReceiveFields" :key="field.prop" :prop="field.prop">
|
|
||||||
<template #label>
|
|
||||||
<span class="required-label">{{ field.label }}</span>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<el-input v-model="receiveForm[field.prop as keyof WithdrawReceiveFormData]" />
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<!-- 第二步底部按钮:取消返回上一步,确定执行最终提交占位 -->
|
|
||||||
<div class="action-row confirm-actions">
|
|
||||||
<el-button class="cancel-btn" @click="handleBackToEdit">取消</el-button>
|
|
||||||
<el-button type="primary" class="submit-btn" @click="handleMockSubmit">确定</el-button>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, onMounted } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import Table from '@/components/common/Table.vue'
|
||||||
|
import Pagination from '@/components/common/Pagination.vue'
|
||||||
|
import WithdrawDialog from './components/WithdrawDialog.vue'
|
||||||
|
import { tableColumns } from './options'
|
||||||
|
import { getWithdrawChannel } from '@/api/modules/capital/withdraw'
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: 'Withdraw'
|
name: 'Withdraw'
|
||||||
})
|
})
|
||||||
import type { FormInstance, FormRules } from 'element-plus'
|
|
||||||
import type { LocationQueryValue } from 'vue-router'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import { computed, ref, watch } from 'vue'
|
|
||||||
import { useRoute, useRouter } from 'vue-router'
|
|
||||||
import { withdrawBaseInfo, withdrawQuickAmounts, withdrawReceiveFields } from './options'
|
|
||||||
|
|
||||||
// 第一步页面表单结构:当前只维护取款页本身所需字段。
|
interface TableRow {
|
||||||
interface WithdrawFormData {
|
id: number
|
||||||
channelName: string
|
name: string
|
||||||
withdrawAccount: string
|
channel_fee: string
|
||||||
currency: string
|
api_url: string
|
||||||
amount: string
|
api_appid: string
|
||||||
|
status: number
|
||||||
|
statusStr?: string
|
||||||
|
currency?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
// 第二步收款信息结构:对应你提供截图中的表单项。
|
const tableRef = ref()
|
||||||
interface WithdrawReceiveFormData {
|
const tableData = ref<TableRow[]>([])
|
||||||
bankName: string
|
const tableLoading = ref(false)
|
||||||
bankAddress: string
|
const total = ref(0)
|
||||||
swift: string
|
const withdrawDialogVisible = ref(false)
|
||||||
receiverName: string
|
const currentChannelId = ref<number>()
|
||||||
receiverAccount: string
|
const currentChannelInfo = ref<{ id: number; name: string; currency?: string }>()
|
||||||
currencyType: string
|
|
||||||
receiverAddress: string
|
|
||||||
remark: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const route = useRoute()
|
const queryParams = reactive({
|
||||||
const router = useRouter()
|
page: 1,
|
||||||
const withdrawFormRef = ref<FormInstance>()
|
limit: 10,
|
||||||
const receiveFormRef = ref<FormInstance>()
|
|
||||||
|
|
||||||
// 创建默认表单:首屏进入时使用本地静态值初始化。
|
|
||||||
const createDefaultForm = (): WithdrawFormData => ({
|
|
||||||
channelName: withdrawBaseInfo.channelName,
|
|
||||||
withdrawAccount: withdrawBaseInfo.withdrawAccount,
|
|
||||||
currency: withdrawBaseInfo.currency,
|
|
||||||
amount: '',
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 创建默认收款信息表单:第二步默认空白,货币类型会在读到 query 后自动同步。
|
// 表格列配置
|
||||||
const createDefaultReceiveForm = (): WithdrawReceiveFormData => ({
|
const tableColumnsOptions = tableColumns
|
||||||
bankName: '',
|
|
||||||
bankAddress: '',
|
// 获取列表数据
|
||||||
swift: '',
|
const getList = async () => {
|
||||||
receiverName: '',
|
tableLoading.value = true
|
||||||
receiverAccount: '',
|
try {
|
||||||
currencyType: '',
|
const res: any = await getWithdrawChannel()
|
||||||
receiverAddress: '',
|
if (res) {
|
||||||
remark: '',
|
tableData.value = res.map((item: any) => ({
|
||||||
|
...item,
|
||||||
|
statusStr: item.status === 1 ? '正常' : '禁用',
|
||||||
|
}))
|
||||||
|
total.value = res.length
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取列表失败', error)
|
||||||
|
} finally {
|
||||||
|
tableLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 行点击
|
||||||
|
const handleRowClick = (row: TableRow) => {
|
||||||
|
console.log('选中行', row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取款按钮
|
||||||
|
const handleWithdraw = (row: TableRow) => {
|
||||||
|
currentChannelId.value = row.id
|
||||||
|
currentChannelInfo.value = {
|
||||||
|
id: row.id,
|
||||||
|
name: row.name,
|
||||||
|
currency: row.currency || 'USD'
|
||||||
|
}
|
||||||
|
withdrawDialogVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 取款成功回调
|
||||||
|
const handleWithdrawSuccess = () => {
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
})
|
})
|
||||||
|
|
||||||
const withdrawForm = ref<WithdrawFormData>(createDefaultForm())
|
|
||||||
const receiveForm = ref<WithdrawReceiveFormData>(createDefaultReceiveForm())
|
|
||||||
|
|
||||||
// 第一步校验规则:本页重点校验取款金额。
|
|
||||||
const withdrawFormRules = ref<FormRules<WithdrawFormData>>({
|
|
||||||
amount: [
|
|
||||||
{ required: true, message: '请输入取款金额', trigger: ['blur', 'change'] },
|
|
||||||
{ pattern: /^\d+(\.\d{1,2})?$/, message: '金额格式不正确,最多保留2位小数', trigger: ['blur', 'change'] },
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
// 第二步校验规则:所有字段按设计稿都作为必填项处理。
|
|
||||||
const receiveFormRules = ref<FormRules<WithdrawReceiveFormData>>({
|
|
||||||
bankName: [{ required: true, message: '请输入收款银行名称', trigger: ['blur', 'change'] }],
|
|
||||||
bankAddress: [{ required: true, message: '请输入银行收款地址', trigger: ['blur', 'change'] }],
|
|
||||||
swift: [{ required: true, message: '请输入SWIFT', trigger: ['blur', 'change'] }],
|
|
||||||
receiverName: [{ required: true, message: '请输入收款人名称', trigger: ['blur', 'change'] }],
|
|
||||||
receiverAccount: [{ required: true, message: '请输入收款人账号', trigger: ['blur', 'change'] }],
|
|
||||||
currencyType: [{ required: true, message: '请输入货币类型', trigger: ['blur', 'change'] }],
|
|
||||||
receiverAddress: [{ required: true, message: '请输入收款人地址', trigger: ['blur', 'change'] }],
|
|
||||||
remark: [{ required: true, message: '请输入备注', trigger: ['blur', 'change'] }],
|
|
||||||
})
|
|
||||||
|
|
||||||
// 通过 query 中的 step 判断当前是否展示第二步页面。
|
|
||||||
const isConfirmStep = computed(() => route.query.step === 'confirm')
|
|
||||||
|
|
||||||
// 读取 query 参数时统一做兼容处理,避免 string / string[] 类型差异带来问题。
|
|
||||||
const getQueryValue = (value: LocationQueryValue | LocationQueryValue[] | undefined, fallback = '') => {
|
|
||||||
if (Array.isArray(value)) {
|
|
||||||
return value[0] ?? fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
return value ?? fallback
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当路由参数变化时,把第一页参数同步回来;第二步的货币类型也默认带入第一页币种。
|
|
||||||
const syncFormFromRoute = () => {
|
|
||||||
withdrawForm.value = {
|
|
||||||
channelName: getQueryValue(route.query.channelName, withdrawBaseInfo.channelName),
|
|
||||||
withdrawAccount: getQueryValue(route.query.withdrawAccount, withdrawBaseInfo.withdrawAccount),
|
|
||||||
currency: getQueryValue(route.query.currency, withdrawBaseInfo.currency),
|
|
||||||
amount: getQueryValue(route.query.amount),
|
|
||||||
}
|
|
||||||
|
|
||||||
receiveForm.value = {
|
|
||||||
...createDefaultReceiveForm(),
|
|
||||||
currencyType: getQueryValue(route.query.currency, withdrawBaseInfo.currency),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(() => route.query, syncFormFromRoute, { immediate: true })
|
|
||||||
|
|
||||||
// 快捷金额按钮:统一转成两位小数,和后续提交逻辑保持一致。
|
|
||||||
const handleQuickAmount = (amount: string) => {
|
|
||||||
withdrawForm.value.amount = amount
|
|
||||||
}
|
|
||||||
|
|
||||||
// 点击确认后跳转到第二步,并通过 query 带上当前参数。
|
|
||||||
const handleNextStep = async () => {
|
|
||||||
try {
|
|
||||||
await withdrawFormRef.value?.validate()
|
|
||||||
|
|
||||||
await router.push({
|
|
||||||
path: route.path,
|
|
||||||
query: {
|
|
||||||
step: 'confirm',
|
|
||||||
channelName: withdrawForm.value.channelName,
|
|
||||||
withdrawAccount: withdrawForm.value.withdrawAccount,
|
|
||||||
currency: withdrawForm.value.currency,
|
|
||||||
amount: withdrawForm.value.amount,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('取款表单校验失败', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 取消返回输入页:保留第一页参数,只移除步骤标记。
|
|
||||||
const handleBackToEdit = async () => {
|
|
||||||
await router.push({
|
|
||||||
path: route.path,
|
|
||||||
query: {
|
|
||||||
channelName: withdrawForm.value.channelName,
|
|
||||||
withdrawAccount: withdrawForm.value.withdrawAccount,
|
|
||||||
currency: withdrawForm.value.currency,
|
|
||||||
amount: withdrawForm.value.amount,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 最终提交占位:当前先校验第二步信息,并把两步数据合并后提示成功。
|
|
||||||
const handleMockSubmit = async () => {
|
|
||||||
try {
|
|
||||||
await receiveFormRef.value?.validate()
|
|
||||||
ElMessage.success(`已提交取款信息,金额:${withdrawForm.value.currency} ${withdrawForm.value.amount}`)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('收款信息表单校验失败', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.withdraw-page {
|
.withdraw-page {
|
||||||
min-height: 100%;
|
padding: 20px;
|
||||||
}
|
|
||||||
|
|
||||||
.withdraw-card {
|
|
||||||
width: 420px;
|
|
||||||
padding: 24px 24px 28px;
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
margin-bottom: 24px;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.withdraw-form {
|
|
||||||
:deep(.el-form-item) {
|
|
||||||
margin-bottom: 22px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-form-item__label) {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-input__wrapper),
|
|
||||||
:deep(.el-input-group__prepend) {
|
|
||||||
height: 46px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.required-label {
|
|
||||||
position: relative;
|
|
||||||
padding-left: 12px;
|
|
||||||
font-size: 16px;
|
|
||||||
color: #303133;
|
|
||||||
}
|
|
||||||
|
|
||||||
.currency-box {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-width: 92px;
|
|
||||||
height: 46px;
|
|
||||||
padding: 0 20px;
|
|
||||||
color: #303133;
|
|
||||||
background: #f5f7fa;
|
|
||||||
border: 1px solid #dcdfe6;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.quick-amounts {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 12px;
|
|
||||||
|
|
||||||
:deep(.el-button) {
|
|
||||||
min-width: 66px;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-row {
|
|
||||||
margin-top: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-btn {
|
|
||||||
min-width: 96px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.receive-card {
|
|
||||||
width: 360px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.receive-form {
|
|
||||||
:deep(.el-form-item) {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.confirm-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cancel-btn {
|
|
||||||
color: #3b82f6;
|
|
||||||
border-color: #3b82f6;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,45 +1,23 @@
|
||||||
// 取款页面的基础信息:当前先用本地静态数据,后续可替换成接口返回值。
|
// 取款页面表格列配置
|
||||||
export const withdrawBaseInfo = {
|
export const tableColumns = [
|
||||||
channelName: '取款渠道名称',
|
// 序号
|
||||||
withdrawAccount: '只能总钱包出账',
|
{ label: '序号', type: 'index', width: 60, align: 'center' as const },
|
||||||
currency: 'USD',
|
// 渠道名称
|
||||||
}
|
{ label: '渠道名称', prop: 'name' },
|
||||||
|
// 渠道费用
|
||||||
// 快捷金额按钮:用于快速回填输入框。
|
{ label: '渠道费用', prop: 'channel_fee' },
|
||||||
export const withdrawQuickAmounts = ['100.00', '500.00', '1000.00', '2000.00', '5000.00']
|
{
|
||||||
|
label: '出金货币',
|
||||||
// 第二步收款信息字段:统一由配置驱动,便于后续扩展或替换文案。
|
prop: 'currency.symbol',
|
||||||
export const withdrawReceiveFields = [
|
type: 'select',
|
||||||
{
|
placeholder: '请选择入金货币',
|
||||||
label: '收款银行名称',
|
},
|
||||||
prop: 'bankName',
|
// 接口地址
|
||||||
},
|
{ label: '接口地址', prop: 'api_url' },
|
||||||
{
|
// 接口appid
|
||||||
label: '银行收款地址',
|
{ label: '接口appid', prop: 'api_appid' },
|
||||||
prop: 'bankAddress',
|
// 状态
|
||||||
},
|
{ label: '状态', prop: 'statusStr' },
|
||||||
{
|
// 操作
|
||||||
label: 'SWIFT',
|
{ label: '操作', prop: 'action', width: 100, slotName: 'action' as const },
|
||||||
prop: 'swift',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '收款人名称',
|
|
||||||
prop: 'receiverName',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '收款人账号',
|
|
||||||
prop: 'receiverAccount',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '货币类型',
|
|
||||||
prop: 'currencyType',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '收款人地址',
|
|
||||||
prop: 'receiverAddress',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: '备注',
|
|
||||||
prop: 'remark',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue