553 lines
15 KiB
Vue
553 lines
15 KiB
Vue
<template>
|
||
<div class="transfer table_form-container">
|
||
<div class="title">转账</div>
|
||
<el-card class="transfer-card">
|
||
<div class="transfer-item">
|
||
1.佣金钱包转出时,需要管理员审核通过后才能转账成功。其余内部转账操作无需通过管理员审核,如发现对应账号没有收到转账资金,请于客服人员联系。
|
||
</div>
|
||
<div class="transfer-item">
|
||
2.如转出账号与转入账号的货币单位不同,系统将按照当日汇率进行换算
|
||
</div>
|
||
<div class="transfer-item">
|
||
3.若有在途订单时进行了MT账号的转出操作,因浮动盈亏影响,可能会导致转出失败
|
||
</div>
|
||
<div class="transfer-item">
|
||
4.第三方转账只支持钱包之间转账,且会转到对方主钱包(佣金钱包不允许转入),需要管理员审核通过后才能转账成功
|
||
</div>
|
||
</el-card>
|
||
<div class="transfer-type">
|
||
<div class="type-title">转账类型</div>
|
||
<div class="tab">
|
||
<el-button
|
||
:type="transferType === 'internal' ? 'primary' : 'default'"
|
||
@click="handleTransferTabClick('internal')"
|
||
>内部转账</el-button
|
||
>
|
||
<el-button
|
||
:type="transferType === 'external' ? 'primary' : 'default'"
|
||
@click="handleTransferTabClick('external')"
|
||
>第三方转账</el-button
|
||
>
|
||
</div>
|
||
</div>
|
||
<div class="transfer-form">
|
||
<div class="external-item" v-if="transferType === 'external'">
|
||
<div class="externa-title">转出账号: 主钱包</div>
|
||
<el-card class="external-card">
|
||
<el-icon>
|
||
<svg
|
||
t="1774503695687"
|
||
class="icon"
|
||
viewBox="0 0 1024 1024"
|
||
version="1.1"
|
||
xmlns="http://www.w3.org/2000/svg"
|
||
p-id="2732"
|
||
width="200"
|
||
height="200"
|
||
>
|
||
<path
|
||
d="M512.54 156.23a353.21 353.21 0 1 1-138 27.83 352.25 352.25 0 0 1 138-27.83m0-60C283.61 96.23 98 281.81 98 510.74s185.61 414.51 414.54 414.51 414.51-185.58 414.51-414.51S741.47 96.23 512.54 96.23z"
|
||
p-id="2733"
|
||
></path>
|
||
<path
|
||
d="M512 478a29.89 29.89 0 0 1-21.13-8.71l-133-132a30 30 0 1 1 42.26-42.58l133 132A30 30 0 0 1 512 478z"
|
||
p-id="2734"
|
||
></path>
|
||
<path
|
||
d="M512 478a30 30 0 0 1-21.21-51.21l128-128a30 30 0 0 1 42.42 42.42l-128 128A29.88 29.88 0 0 1 512 478z"
|
||
p-id="2735"
|
||
></path>
|
||
<path
|
||
d="M660 529H366a30 30 0 0 1 0-60h294a30 30 0 0 1 0 60z m0 95H366a30 30 0 0 1 0-60h294a30 30 0 0 1 0 60z"
|
||
p-id="2736"
|
||
></path>
|
||
<path
|
||
d="M512 776a30 30 0 0 1-30-30V448a30 30 0 0 1 60 0v298a30 30 0 0 1-30 30z"
|
||
p-id="2737"
|
||
></path>
|
||
</svg>
|
||
</el-icon>
|
||
<div class="money">
|
||
<span>USD</span>
|
||
<span>{{ transferForm.MaxOutAmount }}</span>
|
||
</div>
|
||
</el-card>
|
||
</div>
|
||
<TransferForm
|
||
:key="transferType"
|
||
class="transfer-main-form"
|
||
:form-data="transferForm"
|
||
:form-rules="transferFormRules"
|
||
:form-items="transferFormItemOptions"
|
||
:options-map="transferFormOptionsMap"
|
||
ref="mainTransferFormRef"
|
||
>
|
||
</TransferForm>
|
||
<div class="transfer-type verify" v-if="transferType === 'external'">
|
||
<div class="type-title">验证方式</div>
|
||
<el-form
|
||
class="verify-form"
|
||
:model="verifyForm"
|
||
:rules="verifyFormRules"
|
||
ref="verifyFormRef"
|
||
>
|
||
<el-form-item :label="t('personalCenter.verifyMethod')" prop="verifyType">
|
||
<el-radio-group v-model="verifyForm.verifyType" @change="onVerifyTypeChange">
|
||
<el-radio label="email" :disabled="!userInfo.email">邮箱验证</el-radio>
|
||
<el-radio label="phone" :disabled="!userInfo.mobile">手机号验证</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item :label="t('personalCenter.verifyAccount')">
|
||
<span class="verify-account">{{ verifyForm.verifyType === 'email' ? userInfo.email : userInfo.mobile }}</span>
|
||
</el-form-item>
|
||
<UserCaptcha
|
||
prop="code"
|
||
v-model="verifyForm.code"
|
||
@sendCode="handleSendCode"
|
||
:show-send-button="true"
|
||
:send-state="sendState"
|
||
/>
|
||
</el-form>
|
||
</div>
|
||
<el-button type="primary" @click="handleSubmit" v-auth="'user_transfer'">提交转账</el-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
<script setup lang="ts">
|
||
|
||
defineOptions({
|
||
name: 'Transfer'
|
||
})
|
||
//组件
|
||
import TransferForm from '@/components/common/Form.vue'
|
||
import UserCaptcha from '@/views/user/components/Captcha.vue'
|
||
import { useI18n } from 'vue-i18n'
|
||
|
||
//配置
|
||
import { internalFormItem, externalFormItem } from './options'
|
||
import { rules } from './rules'
|
||
import { codeRules } from '@/views/user/config/rules'
|
||
|
||
//接口
|
||
import { getTransferWalletOptionsApi, transferApi } from '@/api/modules/funding/transfer'
|
||
|
||
//方法
|
||
import { getStorage } from '@/utils/storage'
|
||
|
||
import { useUserStore } from '@/stores/modules/user'
|
||
const userStore = useUserStore()
|
||
console.log('userStore:', userStore);
|
||
import { sendCaptcha, initCaptchaState, clearCaptchaTimer } from '@/utils/sendCaptcha'
|
||
|
||
const { t } = useI18n()
|
||
/**
|
||
* @description 转账类型
|
||
* @default internal
|
||
*/
|
||
const transferType = ref('internal')
|
||
|
||
/**
|
||
* @description 创建默认转账表单数据
|
||
*/
|
||
const createDefaultTransferForm = () => ({
|
||
transferOutAccount: -1,
|
||
transferInAccount: '',
|
||
transferAmount: '',
|
||
remark: '',
|
||
// 最大可转账金额
|
||
MaxOutAmount: '',
|
||
type: '',
|
||
})
|
||
|
||
/**
|
||
* @description 创建默认验证码表单数据
|
||
*/
|
||
const createDefaultVerifyForm = () => ({
|
||
verifyType: 'email',
|
||
code: '',
|
||
})
|
||
|
||
/**
|
||
* @description 转账表单数据
|
||
*/
|
||
const transferForm = ref(createDefaultTransferForm())
|
||
|
||
/**
|
||
* @description 验证码表单数据
|
||
*/
|
||
const verifyForm = ref(createDefaultVerifyForm())
|
||
|
||
/**
|
||
* @description 主转账表单和验证码表单引用
|
||
*/
|
||
const mainTransferFormRef = ref()
|
||
const verifyFormRef = ref()
|
||
|
||
/**
|
||
* @description 验证码表单校验规则
|
||
*/
|
||
const verifyFormRules = ref({
|
||
verifyType: [{ required: true, message: t('personalCenter.pleaseSelectVerifyMethod'), trigger: 'change' }],
|
||
code: codeRules().code,
|
||
})
|
||
const transferFormItemOptions = computed(() =>
|
||
transferType.value === 'internal' ? internalFormItem : externalFormItem,
|
||
)
|
||
|
||
watch(
|
||
() => transferForm.value.transferOutAccount,
|
||
(val) => {
|
||
console.log(
|
||
'transferForm.value.transferOutAccount',
|
||
val,
|
||
transferFormOptionsMap.value.transferOutAccount,
|
||
)
|
||
transferForm.value.transferInAccount = null
|
||
transferFormOptionsMap.value.transferInAccount =
|
||
transferFormOptionsMap.value.transferOutAccount.filter((item: any) => item.value !== val)
|
||
},
|
||
{ inmediate: true, deep: true },
|
||
)
|
||
|
||
const transferFormRules = ref(rules(transferForm.value))
|
||
const transferFormOptionsMap = ref({
|
||
transferOutAccount: [],
|
||
transferInAccount: [],
|
||
}) as any
|
||
|
||
/**
|
||
* @description 验证方式
|
||
*/
|
||
//获取userInfo
|
||
const userInfo = getStorage<any>('userInfo')
|
||
|
||
// 发送验证码状态
|
||
const sendState = ref({
|
||
isCounting: false,
|
||
countdown: 0,
|
||
})
|
||
|
||
//初始化验证码倒计时状态
|
||
const emailCaptchaState = initCaptchaState('email') // 邮箱专属
|
||
const phoneCaptchaState = initCaptchaState('phone') // 手机专属
|
||
|
||
// 邮箱验证码状态
|
||
const { isCounting: emailIsCounting, countdown: emailCountdown } = emailCaptchaState
|
||
|
||
// 手机验证码状态
|
||
const { isCounting: phoneIsCounting, countdown: phoneCountdown } = phoneCaptchaState
|
||
|
||
// 当前验证方式的倒计时状态
|
||
const currentIsCounting = computed(() =>
|
||
verifyForm.value.verifyType === 'email' ? emailIsCounting.value : phoneIsCounting.value
|
||
)
|
||
const currentCountdown = computed(() =>
|
||
verifyForm.value.verifyType === 'email' ? emailCountdown.value : phoneCountdown.value
|
||
)
|
||
|
||
// 更新sendState的方法
|
||
const updateSendState = () => {
|
||
const type = verifyForm.value.verifyType
|
||
// 创建新对象以触发响应式更新
|
||
sendState.value = {
|
||
isCounting: type === 'email' ? emailIsCounting.value : phoneIsCounting.value,
|
||
countdown: type === 'email' ? emailCountdown.value : phoneCountdown.value,
|
||
}
|
||
}
|
||
|
||
// 监听倒计时状态变化,同步到 sendState
|
||
watch(
|
||
emailIsCounting,
|
||
() => updateSendState()
|
||
)
|
||
watch(
|
||
emailCountdown,
|
||
() => updateSendState()
|
||
)
|
||
watch(
|
||
phoneIsCounting,
|
||
() => updateSendState()
|
||
)
|
||
watch(
|
||
phoneCountdown,
|
||
() => updateSendState()
|
||
)
|
||
|
||
// 切换验证方式时清除验证码
|
||
const onVerifyTypeChange = () => {
|
||
verifyForm.value.code = ''
|
||
nextTick(() => {
|
||
verifyFormRef.value?.clearValidate(['code'])
|
||
})
|
||
}
|
||
onMounted(() => {
|
||
// 页面加载完成
|
||
})
|
||
|
||
// 定义发送验证码接口
|
||
const sendCodeApi = async (receiver: string) => {
|
||
// 发送验证码参数
|
||
const type = verifyForm.value.verifyType
|
||
const params: any = {
|
||
type: 'transfer_code',
|
||
}
|
||
if (type === 'email') {
|
||
params.email = receiver
|
||
} else {
|
||
params.mobile = receiver
|
||
// 从手机号中提取区号
|
||
const match = receiver.match(/^\+(\d{1,4})/)
|
||
params.mobile_area = match ? '+' + match[1] : '+86'
|
||
}
|
||
return userStore.sendCode(params)
|
||
}
|
||
|
||
//发送验证码
|
||
const handleSendCode = async () => {
|
||
const type = verifyForm.value.verifyType
|
||
|
||
if (type === 'phone') {
|
||
// 场景1:发送手机验证码
|
||
if (!userInfo.mobile) {
|
||
ElMessage.error('请先绑定手机号')
|
||
return
|
||
}
|
||
|
||
// 执行发送
|
||
await sendCaptcha(type, userInfo.mobile, phoneCaptchaState, sendCodeApi)
|
||
} else {
|
||
// 场景2:发送邮箱验证码
|
||
if (!userInfo.email) {
|
||
ElMessage.error('请先绑定邮箱')
|
||
return
|
||
}
|
||
|
||
// 执行发送
|
||
await sendCaptcha(type, userInfo.email, emailCaptchaState, sendCodeApi)
|
||
}
|
||
}
|
||
|
||
// 组件卸载时清除定时器(关键:防止内存泄漏)
|
||
onUnmounted(() => {
|
||
clearCaptchaTimer(emailCaptchaState)
|
||
clearCaptchaTimer(phoneCaptchaState)
|
||
})
|
||
/**
|
||
* @description 监听transferOutAccount变化,获取对应的MaxAmount
|
||
*/
|
||
watch(
|
||
[
|
||
() => transferForm.value.transferOutAccount,
|
||
() => transferFormOptionsMap.value.transferOutAccount,
|
||
],
|
||
([newVal]) => {
|
||
if (!newVal || !transferFormOptionsMap.value.transferOutAccount.length) return
|
||
|
||
const item = transferFormOptionsMap.value.transferOutAccount.find(
|
||
(item: any) => item.value === newVal,
|
||
)
|
||
transferForm.value.MaxOutAmount = item?.amount || 0
|
||
},
|
||
{ immediate: true },
|
||
)
|
||
/**
|
||
* @description 统一获取数据
|
||
*/
|
||
const getTransferWalletOptions = async () => {
|
||
const data: any = await getTransferWalletOptionsApi()
|
||
transferFormOptionsMap.value.transferOutAccount = data
|
||
transferFormOptionsMap.value.transferInAccount = data.filter((item: any) => item.value !== -1)
|
||
}
|
||
/**
|
||
* @description 页面加载完成需要渲染的数据
|
||
*/
|
||
onMounted(() => {
|
||
getTransferWalletOptions()
|
||
})
|
||
|
||
// 获取默认主钱包最大可转金额,切换标签或提交成功后用于恢复初始展示。
|
||
const getDefaultMaxOutAmount = () => {
|
||
return (
|
||
transferFormOptionsMap.value.transferOutAccount.find((item: any) => item.value === -1)
|
||
?.amount || ''
|
||
)
|
||
}
|
||
|
||
// 重置转账表单:切换内部/第三方转账时清空所有输入框。
|
||
const resetTransferForm = async () => {
|
||
Object.assign(transferForm.value, createDefaultTransferForm(), {
|
||
MaxOutAmount: getDefaultMaxOutAmount(),
|
||
})
|
||
|
||
// 重置验证码表单
|
||
Object.assign(verifyForm.value, createDefaultVerifyForm())
|
||
|
||
await nextTick()
|
||
verifyFormRef.value?.clearValidate?.()
|
||
}
|
||
|
||
/**
|
||
* @description 切换转账类型
|
||
* @param type 转账类型
|
||
*/
|
||
const handleTransferTabClick = async (type: string) => {
|
||
// 只有内部/第三方转账类型真的发生切换时,才清空当前表单内容。
|
||
if (transferType.value === type) return
|
||
|
||
transferType.value = type
|
||
await resetTransferForm()
|
||
}
|
||
/**
|
||
* @description 切换验证方式
|
||
* @param type 验证方式
|
||
*/
|
||
const handleVerifyParams = (type: string) => {
|
||
if (type === 'external') {
|
||
if (verifyForm.value.verifyType === 'email') {
|
||
return userInfo.email
|
||
} else {
|
||
return userInfo.mobile
|
||
}
|
||
} else {
|
||
return transferForm.value.transferInAccount
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @description 提交转账表单
|
||
*/
|
||
const handleSubmit = async () => {
|
||
await mainTransferFormRef.value?.validate?.()
|
||
|
||
if (transferType.value === 'external') {
|
||
await verifyFormRef.value?.validate?.()
|
||
}
|
||
|
||
const params = {
|
||
type: transferType.value === 'internal' ? '1' : '2',
|
||
from: String(transferForm.value.transferOutAccount) + '',
|
||
to: handleVerifyParams(transferType.value) + '',
|
||
amount: transferForm.value.transferAmount,
|
||
verify_type: '',
|
||
verify_code: '',
|
||
description: transferForm.value.remark,
|
||
}
|
||
console.log(params)
|
||
|
||
if (transferType.value === 'internal') {
|
||
await transferApi(params)
|
||
} else {
|
||
await transferApi({
|
||
...params,
|
||
verify_type: verifyForm.value.verifyType === 'email' ? '1' : '2',
|
||
verify_code: verifyForm.value.code,
|
||
})
|
||
}
|
||
|
||
await resetTransferForm()
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.transfer {
|
||
height: 100%;
|
||
overflow-y: scroll;
|
||
.title {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.transfer-card {
|
||
background: rgb(160, 207, 255, 0.5);
|
||
margin-bottom: 10px;
|
||
|
||
:deep(.el-card__body) {
|
||
padding: 10px 20px;
|
||
}
|
||
|
||
.transfer-item {
|
||
line-height: 35px;
|
||
}
|
||
}
|
||
|
||
.transfer-type {
|
||
margin-bottom: 10px;
|
||
|
||
.type-title {
|
||
margin-bottom: 10px;
|
||
}
|
||
}
|
||
|
||
:deep(.el-form) {
|
||
.el-form-item {
|
||
width: 400px;
|
||
}
|
||
}
|
||
|
||
.transfer-form {
|
||
// 主转账表单的错误提示改为占位展示,避免长校验文案跑到“备注”下面。
|
||
:deep(.transfer-main-form .el-form-item) {
|
||
margin-bottom: 24px;
|
||
}
|
||
|
||
:deep(.transfer-main-form .el-form-item__error) {
|
||
position: static;
|
||
padding-top: 4px;
|
||
line-height: 18px;
|
||
}
|
||
|
||
.external-item {
|
||
margin-bottom: 10px;
|
||
|
||
.externa-title {
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.external-card {
|
||
width: fit-content;
|
||
|
||
:deep(.el-card__body) {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 10px 20px;
|
||
}
|
||
|
||
.el-icon {
|
||
color: #409eff;
|
||
font-size: 34px;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.money {
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.verify {
|
||
:deep(.el-radio-group) {
|
||
display: flex;
|
||
gap: 20px;
|
||
}
|
||
|
||
.verify-account {
|
||
font-weight: 500;
|
||
color: #333;
|
||
}
|
||
}
|
||
|
||
.verify-form {
|
||
max-width: 500px;
|
||
|
||
:deep(.el-form-item) {
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
:deep(.el-form-item__content) {
|
||
flex-wrap: nowrap;
|
||
}
|
||
}
|
||
}
|
||
</style>
|