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

553 lines
15 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="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>