This commit is contained in:
李远 2026-03-26 17:16:40 +08:00
parent 749aac5f4b
commit a782aa74c4
7 changed files with 545 additions and 2 deletions

View File

@ -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)
}

View File

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

View File

@ -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: '转账明细',

View File

@ -256,6 +256,14 @@ const routes = [
title: '存款明细', // 左侧菜单显示的标题
}
},
{
path: 'transfer',
name: 'Transfer',
component: () => import('@/views/funding/transfer/index.vue'),
meta: {
title: '转账', // 左侧菜单显示的标题
}
},
{
path: 'transferDetail',
name: 'TransferDetail',

View File

@ -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
}
}
}
// activeTabsendState
const initSendState = () => {
const currentType = verifyType.value === 'email' ? 'email' : 'phone'
updateSendState(currentType)
}
onMounted(() => {
//
initSendState()
})
// verifyTypesendState
watch(
verifyType,
(newTab, oldTab) => {
verifyType.value = newTab;
console.log('verifyType.value', verifyType.value);
// taboldTaboldTabundefined
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>

View File

@ -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 },
]

View File

@ -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位数字'},
],
}
}