This commit is contained in:
parent
749aac5f4b
commit
a782aa74c4
|
|
@ -0,0 +1,10 @@
|
|||
import { request } from '@/api';
|
||||
|
||||
//获取钱包选项
|
||||
export const getTransferWalletOptionsApi = () => {
|
||||
return request.get('/user/getWalletOptions')
|
||||
}
|
||||
// 转账
|
||||
export const transferApi = (params: any) => {
|
||||
return request.post('/user/transfer', params)
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<el-row :gutter="props.rowGutter || 0">
|
||||
<template v-for="(item, index) in formItems" :key="index">
|
||||
<el-col :span="props.colSpan || 24">
|
||||
<el-form-item :label="item.label" :prop="item.prop" :required="item.required" label-position="left"
|
||||
<el-form-item :label="item.label" :prop="item.prop" :required="item.required" :label-position="item.labelPosition || 'left'"
|
||||
:label-width="item.labelWidth || '100px'" :class="item.class">
|
||||
<!-- 输入框 -->
|
||||
<el-input v-if="item.type === 'input'" v-model="formData[item.prop]"
|
||||
|
|
@ -87,7 +87,7 @@ interface FormItem {
|
|||
min?: number
|
||||
max?: number
|
||||
slotName?: string // 自定义插槽名称
|
||||
labelPosition?: 'left' | 'right'
|
||||
labelPosition?: 'left' | 'right' | 'top'
|
||||
labelWidth?: string
|
||||
class?: string
|
||||
dateType?: 'date' | 'daterange' | 'monthrange' | 'yearrange' | 'datetimerange'
|
||||
|
|
|
|||
|
|
@ -221,6 +221,12 @@ const staticMenuList = ref<ElMenuItem[]>(
|
|||
// path: '/funding/depositDetail', // index="6-4"
|
||||
// icon: ''
|
||||
// },
|
||||
{
|
||||
id: 64,
|
||||
label: '转账',
|
||||
path: '/funding/transfer', // index="6-4"
|
||||
icon: ''
|
||||
},
|
||||
{
|
||||
id: 65,
|
||||
label: '转账明细',
|
||||
|
|
|
|||
|
|
@ -256,6 +256,14 @@ const routes = [
|
|||
title: '存款明细', // 左侧菜单显示的标题
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'transfer',
|
||||
name: 'Transfer',
|
||||
component: () => import('@/views/funding/transfer/index.vue'),
|
||||
meta: {
|
||||
title: '转账', // 左侧菜单显示的标题
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'transferDetail',
|
||||
name: 'TransferDetail',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,436 @@
|
|||
<template>
|
||||
<div class="transfer">
|
||||
<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 :form-data="transferForm" :form-rules="transferFormRules"
|
||||
:form-items="transferFormItemOptions" :options-map="transferFormOptionsMap" ref="transferFormRef">
|
||||
</TransferForm>
|
||||
<div class="transfer-type verify" v-if="transferType === 'external'">
|
||||
<div class="type-title">验证方式</div>
|
||||
<div class="tab">
|
||||
<el-button :type="verifyType === 'email' ? 'primary' : 'default'"
|
||||
@click="handleVerifyTabClick('email')">邮箱验证</el-button>
|
||||
<el-button :type="verifyType === 'phone' ? 'primary' : 'default'"
|
||||
@click="handleVerifyTabClick('phone')">手机号验证</el-button>
|
||||
</div>
|
||||
<div class="username">{{ verifyType === "email" ? userInfo.email : userInfo.phone }}</div>
|
||||
<el-form class="verify-form" :model="transferForm" :rules="transferFormRules" ref="transferFormRef">
|
||||
<el-form-item prop="code">
|
||||
<el-input v-model="transferForm.code" style="max-width: 300px" placeholder="请输入验证码">
|
||||
|
||||
</el-input>
|
||||
<el-button type="primary" @click="handleSendCode" :disabled="sendState.isCounting">{{
|
||||
sendState.isCounting ? `${sendState.countdown}s后重新获取` : '发送验证码' }}</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
</div>
|
||||
<el-button type="primary" @click="handleSubmit">提交转账</el-button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
//组件
|
||||
import TransferForm from '@/components/common/Form.vue'
|
||||
|
||||
//配置
|
||||
import { internalFormItem, externalFormItem } from './options'
|
||||
import { rules } from './rules'
|
||||
|
||||
//接口
|
||||
import { getTransferWalletOptionsApi, transferApi } from '@/api/modules/funding/transfer'
|
||||
|
||||
//方法
|
||||
import { getStorage } from '@/utils/storage'
|
||||
|
||||
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
const userStore = useUserStore()
|
||||
import { sendCaptcha, initCaptchaState, clearCaptchaTimer } from '@/utils/sendCaptcha'
|
||||
/**
|
||||
* @description 转账类型
|
||||
* @default internal
|
||||
*/
|
||||
const transferType = ref('internal')
|
||||
|
||||
/**
|
||||
* @description 转账表单数据
|
||||
*/
|
||||
const transferForm = ref({
|
||||
transferOutAccount: -1,
|
||||
transferInAccount: '',
|
||||
transferAmount: '',
|
||||
remark: '',
|
||||
// 最大可转账金额
|
||||
MaxOutAmount: '',
|
||||
type: '',
|
||||
code: ''
|
||||
})
|
||||
/**
|
||||
* @description 转账表单引用
|
||||
*/
|
||||
const transferFormRef = ref()
|
||||
const transferFormItemOptions = computed(() => transferType.value === 'internal' ? internalFormItem : externalFormItem)
|
||||
const transferFormRules = ref(rules(transferForm.value))
|
||||
const transferFormOptionsMap = ref({
|
||||
transferOutAccount: [],
|
||||
transferInAccount: [],
|
||||
}) as any
|
||||
|
||||
/**
|
||||
* @description 验证方式
|
||||
*/
|
||||
//获取userInfo
|
||||
const userInfo = getStorage<any>('userInfo')
|
||||
const verifyType = ref('email')
|
||||
|
||||
// 发送验证码
|
||||
const sendState = ref<any>({
|
||||
isCounting: false,
|
||||
countdown: 0,
|
||||
})
|
||||
|
||||
//初始化验证码倒计时状态
|
||||
const emailCaptchaState = initCaptchaState('email'); // 邮箱专属
|
||||
const phoneCaptchaState = initCaptchaState('phone'); // 手机专属
|
||||
|
||||
// 邮箱验证码状态
|
||||
const { isCounting: emailIsCounting, countdown: emailCountdown } = emailCaptchaState
|
||||
|
||||
// 手机验证码状态
|
||||
const { isCounting: phoneIsCounting, countdown: phoneCountdown } = phoneCaptchaState
|
||||
|
||||
|
||||
// 统一更新sendState的方法
|
||||
const updateSendState = (type: string) => {
|
||||
if (type === 'email') {
|
||||
sendState.value = {
|
||||
isCounting: emailIsCounting,
|
||||
countdown: emailCountdown
|
||||
}
|
||||
} else {
|
||||
sendState.value = {
|
||||
isCounting: phoneIsCounting,
|
||||
countdown: phoneCountdown
|
||||
}
|
||||
}
|
||||
}
|
||||
// 初始化时根据当前activeTab更新sendState(避免刷新后丢失)
|
||||
const initSendState = () => {
|
||||
const currentType = verifyType.value === 'email' ? 'email' : 'phone'
|
||||
updateSendState(currentType)
|
||||
}
|
||||
onMounted(() => {
|
||||
// 页面加载时立即执行,恢复倒计时
|
||||
initSendState()
|
||||
})
|
||||
// 监听verifyType变化,同步sendState
|
||||
watch(
|
||||
verifyType,
|
||||
(newTab, oldTab) => {
|
||||
verifyType.value = newTab;
|
||||
console.log('verifyType.value', verifyType.value);
|
||||
// 只有“主动切换tab”(oldTab有值)时才处理,初始化时(oldTab为undefined)不触发
|
||||
if (oldTab === undefined) return
|
||||
|
||||
if (newTab === 'email' || newTab === 'phone') {
|
||||
// 主动切换到邮箱/手机页时,同步当前类型的倒计时状态(不再无脑重置)
|
||||
updateSendState(newTab)
|
||||
} else if (newTab === 'code') {
|
||||
// 切换到验证码页时,根据存储的类型更新状态
|
||||
const currentType: string = getStorage('forgotType', 'session') || 'email'
|
||||
updateSendState(currentType)
|
||||
}
|
||||
},
|
||||
{ immediate: false } // 关键:关闭立即执行,避免刷新时触发
|
||||
)
|
||||
|
||||
// 定义发送验证码接口
|
||||
const sendCodeApi = async () => {
|
||||
// 发送验证码参数
|
||||
const sendParams = {
|
||||
email: userInfo.email,
|
||||
mobile: userInfo.mobile,
|
||||
reg_type: verifyType.value === 'email' ? 1 : 2,
|
||||
type: 'resetPass_code'
|
||||
}
|
||||
return userStore.sendCode(sendParams)
|
||||
}
|
||||
|
||||
//发送验证码
|
||||
const handleSendCode = async () => {
|
||||
const currentType = verifyType.value;
|
||||
// 确定类型:手机/邮箱
|
||||
const type: string = currentType || 'email';
|
||||
|
||||
if (type === 'phone') {
|
||||
// 场景1:发送手机验证码
|
||||
if (!userInfo.phone) {
|
||||
ElMessage.error('请先绑定手机号');
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行发送
|
||||
await sendCaptcha(
|
||||
type,
|
||||
userInfo.phone,
|
||||
phoneCaptchaState,
|
||||
() => sendCodeApi()
|
||||
);
|
||||
updateSendState(type);
|
||||
|
||||
} else {
|
||||
// 场景2:发送邮箱验证码
|
||||
if (!userInfo.email) {
|
||||
ElMessage.error('请先绑定邮箱');
|
||||
return;
|
||||
}
|
||||
|
||||
// 执行发送
|
||||
await sendCaptcha(
|
||||
type,
|
||||
userInfo.email,
|
||||
emailCaptchaState,
|
||||
() => sendCodeApi()
|
||||
);
|
||||
updateSendState(type);
|
||||
}
|
||||
};
|
||||
|
||||
// 组件卸载时清除定时器(关键:防止内存泄漏)
|
||||
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
|
||||
}
|
||||
/**
|
||||
* @description 页面加载完成需要渲染的数据
|
||||
*/
|
||||
onMounted(() => {
|
||||
getTransferWalletOptions()
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 切换转账类型
|
||||
* @param type 转账类型
|
||||
*/
|
||||
const handleTransferTabClick = (type: string) => {
|
||||
transferType.value = type
|
||||
transferForm.value.MaxOutAmount = transferFormOptionsMap.value.transferOutAccount.find((item: any) => item.value === -1)?.amount
|
||||
}
|
||||
/**
|
||||
* @description 切换验证方式
|
||||
* @param type 验证方式
|
||||
*/
|
||||
const handleVerifyTabClick = (type: string) => {
|
||||
verifyType.value = type
|
||||
}
|
||||
|
||||
const handleVerifyParams = (type: string) => {
|
||||
if (type === 'external') {
|
||||
if (verifyType.value === 'email') {
|
||||
return userInfo.email
|
||||
} else {
|
||||
return userInfo.phone
|
||||
}
|
||||
} else {
|
||||
return '1'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 提交转账表单
|
||||
*/
|
||||
const handleSubmit = async () => {
|
||||
await transferFormRef.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)
|
||||
transferForm.value = {
|
||||
transferOutAccount: -1,
|
||||
transferInAccount: '',
|
||||
transferAmount: '',
|
||||
remark: '',
|
||||
// 最大可转账金额
|
||||
MaxOutAmount: '',
|
||||
type: '',
|
||||
code: ''
|
||||
|
||||
}
|
||||
} else {
|
||||
await transferApi({ ...params, verify_type: verifyType.value === 'email' ? '1' : '2', verify_code: transferForm.value.code })
|
||||
transferForm.value = {
|
||||
transferOutAccount: -1,
|
||||
transferInAccount: '',
|
||||
transferAmount: '',
|
||||
remark: '',
|
||||
// 最大可转账金额
|
||||
MaxOutAmount: '',
|
||||
type: '',
|
||||
code: ''
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.transfer {
|
||||
.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 {
|
||||
.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 {
|
||||
|
||||
.tab,
|
||||
.username {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.verify-form {
|
||||
.el-form-item {
|
||||
:deep(.el-form-item__content) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
export const transferTypeOptions = [
|
||||
{ label: '内部转账', value: 'internal' },
|
||||
{ label: '第三方转账', value: 'external' },
|
||||
]
|
||||
export const internalFormItem = [
|
||||
// 转出账号
|
||||
{ prop: 'transferOutAccount', label: '转出账号', type: 'select' as const, placeholder: '请选择转出账号', width: '150px', labelPosition: 'top' as const },
|
||||
// 转入账号
|
||||
{ prop: 'transferInAccount', label: '转入账号', type: 'select' as const, placeholder: '请选择转入账号', width: '150px', labelPosition: 'top' as const },
|
||||
// 转账金额
|
||||
{ prop: 'transferAmount', label: '转账金额', type: 'input' as const, placeholder: '请输入转账金额', width: '150px', labelPosition: 'top' as const },
|
||||
// 备注
|
||||
{ prop: 'remark', label: '备注', type: 'input' as const, placeholder: '请输入备注', width: '150px', labelPosition: 'top' as const },
|
||||
]
|
||||
|
||||
export const externalFormItem = [
|
||||
// 转入账号
|
||||
{ prop: 'transferInAccount', label: '转入账号', type: 'select' as const, placeholder: '请选择转入账号', width: '150px', labelPosition: 'top' as const },
|
||||
// 转账金额
|
||||
{ prop: 'transferAmount', label: '转账金额', type: 'input' as const, placeholder: '请输入转账金额', width: '150px', labelPosition: 'top' as const },
|
||||
// 备注
|
||||
{ prop: 'remark', label: '备注', type: 'input' as const, placeholder: '请输入备注', width: '150px', labelPosition: 'top' as const },
|
||||
]
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
import type { FormRules } from 'element-plus'
|
||||
export const rules = (form: any): FormRules => {
|
||||
return {
|
||||
transferOutAccount: [
|
||||
{ required: true, message: '请选择转出账号', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value && form.transferInAccount && value === form.transferInAccount) {
|
||||
callback(new Error('转出账号不能与转入账号相同'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
transferInAccount: [
|
||||
{ required: true, message: '请选择转入账号', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
if (value && form.transferOutAccount && value === form.transferOutAccount) {
|
||||
callback(new Error('转入账号不能与转出账号相同'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
transferAmount: [
|
||||
{ required: true, message: '请输入转账金额', trigger: ['blur'] },
|
||||
{
|
||||
pattern: /^\d+(\.\d{1,5})?$/,
|
||||
message: '请输入数字,小数不超过5位',
|
||||
},
|
||||
// 自定义验证:不能超过最大可转出金额
|
||||
{
|
||||
validator: (rule, value, callback) => {
|
||||
// 空值已经由必填验证处理
|
||||
if (!value) return callback()
|
||||
|
||||
const inputAmount = parseFloat(value)
|
||||
const maxAmount = parseFloat(form.MaxOutAmount || 0)
|
||||
|
||||
if (inputAmount > maxAmount) {
|
||||
callback(new Error(`不能超过最大可转出金额:${maxAmount}`))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: ['blur', 'change']
|
||||
}
|
||||
],
|
||||
remark: [{ required: false, message: '请输入备注', trigger: 'blur' }],
|
||||
code: [
|
||||
{ required: true, message: '请输入验证码', trigger: 'blur' },
|
||||
{pattern: /^\d{6}$/,message: '验证码必须为6位数字'},
|
||||
],
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue