Merge remote-tracking branch 'origin/master'
# Conflicts: # src/views/profile/business/index.vue # src/views/profile/business/options.ts
This commit is contained in:
commit
9ff372afa5
|
|
@ -16,3 +16,13 @@ export const editChannel = (params: any) => {
|
|||
export const delChannel = (params: any) => {
|
||||
return request.post('/channel/delChannel', { ...params })
|
||||
}
|
||||
|
||||
// 获取用户充值渠道列表
|
||||
export const getRechargeChannelList = () => {
|
||||
return request.get('/user/regchargeChannel')
|
||||
}
|
||||
|
||||
// 用户充值
|
||||
export const rechargeApi = (params: any) => {
|
||||
return request.post('/user/recharge', { ...params })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,3 +61,28 @@ export async function logoutApi(params: { device_no: string }) {
|
|||
export async function sendCodeApi(params: SendCodeParams) {
|
||||
return request.post<string>('/sendCode', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 调整用户金额
|
||||
* @param params 调整金额参数(目标用户ID/调整金额)
|
||||
* @returns Promise<boolean> 调整成功返回true
|
||||
*/
|
||||
export async function updateCapitalApi(params: { target_user_id: number; amount: string }) {
|
||||
return request.post<boolean>('/user/updateCapital', params);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改用户信息
|
||||
* @param params 修改用户参数(用户ID/风控模板ID/手续费模板ID/用户名/是否升级/状态)
|
||||
* @returns Promise<boolean> 修改成功返回true
|
||||
*/
|
||||
export async function editUserApi(params: {
|
||||
user_id: number;
|
||||
risk_control_id?: string | number;
|
||||
fee_setting_id?: string | number;
|
||||
username?: string;
|
||||
is_levelup?: number | null;
|
||||
status?: number;
|
||||
}) {
|
||||
return request.post<boolean>('/user/editUser', params);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
.el-drawer__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
@use './user.scss';
|
||||
@use './system.scss';
|
||||
@use './element.scss';
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -3,60 +3,38 @@
|
|||
<el-form :model="searchForm" inline @submit.prevent>
|
||||
<!-- 动态生成的表单项 -->
|
||||
<template v-for="item in searchOptions" :key="item.prop">
|
||||
<el-form-item
|
||||
v-auth="item.auth || ''"
|
||||
:label="item.label"
|
||||
:prop="item.prop"
|
||||
v-if="!item.slot"
|
||||
:style="{
|
||||
minWidth: item.minWidth,
|
||||
width: item.width,
|
||||
flex: item.width ? '0 0 auto' : '',
|
||||
}"
|
||||
>
|
||||
<el-form-item v-auth="item.auth || ''" :label="item.label" :prop="item.prop" v-if="!item.slot" :style="{
|
||||
minWidth: item.minWidth,
|
||||
width: item.width,
|
||||
flex: item.width ? '0 0 auto' : '',
|
||||
}">
|
||||
<!-- 输入框 -->
|
||||
<el-input
|
||||
v-if="item.type === 'input'"
|
||||
v-model="searchForm[item.prop]"
|
||||
:placeholder="item.placeholder || `请输入${item.label}`"
|
||||
:clearable="item.clearable ?? true"
|
||||
:disabled="item.disabled"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
<el-input v-if="item.type === 'input'" v-model="searchForm[item.prop]"
|
||||
:placeholder="item.placeholder || `请输入${item.label}`" :clearable="item.clearable ?? true"
|
||||
:disabled="item.disabled" @blur="handleBlur" />
|
||||
|
||||
<!-- 选择器 -->
|
||||
<el-select
|
||||
v-else-if="item.type === 'select'"
|
||||
v-model="searchForm[item.prop]"
|
||||
:placeholder="item.placeholder || `请选择${item.label}`"
|
||||
:clearable="item.clearable ?? true"
|
||||
:disabled="item.disabled"
|
||||
>
|
||||
<el-option
|
||||
v-for="option in item.options"
|
||||
:key="option.value"
|
||||
:label="option.label"
|
||||
:value="option.value"
|
||||
/>
|
||||
<el-select v-else-if="item.type === 'select'" v-model="searchForm[item.prop]"
|
||||
:placeholder="item.placeholder || `请选择${item.label}`" :clearable="item.clearable ?? true"
|
||||
:disabled="item.disabled">
|
||||
<el-option v-for="option in item.options" :key="option.value" :label="option.label" :value="option.value" />
|
||||
</el-select>
|
||||
|
||||
<!-- 日期选择器(单个/范围) -->
|
||||
<el-date-picker
|
||||
v-else-if="item.type === 'date'"
|
||||
v-model="searchForm[item.prop]"
|
||||
:type="item.dateType || 'date'"
|
||||
:range-separator="item.rangeSeparator || '至'"
|
||||
:start-placeholder="item.startPlaceholder || '开始日期'"
|
||||
:end-placeholder="item.endPlaceholder || '结束日期'"
|
||||
:value-format="item.valueFormat || 'YYYY-MM-DD'"
|
||||
:clearable="item.clearable ?? true"
|
||||
:disabled-date="item.disabledDate"
|
||||
:locale="'zhCn'"
|
||||
/>
|
||||
<el-date-picker v-else-if="item.type === 'date'" v-model="searchForm[item.prop]"
|
||||
:type="item.dateType || 'date'" :range-separator="item.rangeSeparator || '至'"
|
||||
:placeholder="item.placeholder"
|
||||
:start-placeholder="item.startPlaceholder || '开始日期'" :end-placeholder="item.endPlaceholder || '结束日期'"
|
||||
:value-format="item.valueFormat || 'YYYY-MM-DD'" :clearable="item.clearable ?? true"
|
||||
:disabled-date="item.disabledDate" :locale="'zhCn'" />
|
||||
</el-form-item>
|
||||
|
||||
<!-- 自定义插槽项 -->
|
||||
<el-form-item :label="item.label" :prop="item.prop" v-else>
|
||||
<el-form-item v-else :label="item.label" :prop="item.prop" :style="{
|
||||
minWidth: item.minWidth,
|
||||
width: item.width,
|
||||
flex: item.width ? '0 0 auto' : '',
|
||||
}">
|
||||
<slot :name="item.prop" :form="searchForm" />
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
|
@ -137,20 +115,34 @@ const handleBlur = () => {
|
|||
|
||||
.el-form {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
|
||||
.el-form-item {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
row-gap: 8px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 0;
|
||||
margin-right: 16px;
|
||||
flex: 1 1 auto;
|
||||
min-width: 200px;
|
||||
}
|
||||
|
||||
:deep(.el-button) {
|
||||
margin-left: 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:deep(.el-form-item:last-child) {
|
||||
display: flex;
|
||||
width: fit-content;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
|
||||
.el-form-item__content {
|
||||
justify-content: flex-start;
|
||||
row-gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -142,7 +142,7 @@ defineExpose({
|
|||
<style scoped lang="scss">
|
||||
.table-container {
|
||||
width: 100%;
|
||||
height: calc(100vh - 180px);
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
// overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@
|
|||
<!-- 用户信息下拉菜单 -->
|
||||
<el-dropdown @command="handleCommand" class="user-dropdown">
|
||||
<div class="user-info">
|
||||
<el-avatar :size="32" :src="userAvatar" class="user-avatar">
|
||||
<el-icon><User /></el-icon>
|
||||
<el-avatar :size="32" class="user-avatar">
|
||||
<el-icon :size="20"><User /></el-icon>
|
||||
</el-avatar>
|
||||
<span class="username">{{ activeUserName }}</span>
|
||||
<el-icon class="dropdown-icon"><ArrowDown /></el-icon>
|
||||
|
|
@ -49,6 +49,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import {
|
||||
Bell,
|
||||
Sunny,
|
||||
|
|
@ -69,10 +70,8 @@ const router = useRouter()
|
|||
const userStore = useUserStore()
|
||||
const languageStore = useLanguageStore()
|
||||
|
||||
// 用户信息
|
||||
const userInfo = getStorage<any>('userInfo')
|
||||
const activeUserName = ref(userInfo?.username || 'Admin')
|
||||
const userAvatar = ref(userInfo?.avatar || '')
|
||||
// 用户信息 - 使用 userStore 的响应式数据
|
||||
const activeUserName = computed(() => userStore.userInfo?.username || '-')
|
||||
const unreadCount = ref(3) // 未读消息数量
|
||||
|
||||
// 全屏相关
|
||||
|
|
|
|||
|
|
@ -165,6 +165,19 @@ const switchAccount = async () => {
|
|||
clearUserInfo();
|
||||
router.push('/user/login');
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户信息
|
||||
* @param data 要更新的用户信息
|
||||
*/
|
||||
const updateUserInfo = (data: Partial<UserInfo>) => {
|
||||
if (userInfo.value) {
|
||||
// 合并更新用户信息
|
||||
userInfo.value = { ...userInfo.value, ...data };
|
||||
// 更新本地存储
|
||||
setStorage('userInfo', userInfo.value);
|
||||
}
|
||||
};
|
||||
return {
|
||||
// 状态
|
||||
token,
|
||||
|
|
@ -182,6 +195,7 @@ return {
|
|||
forgotPassword,
|
||||
changePassword,
|
||||
sendCode,
|
||||
switchAccount
|
||||
switchAccount,
|
||||
updateUserInfo
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,40 +1,20 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- 搜索 -->
|
||||
<CustomerListSearch
|
||||
:search-form="searchForm"
|
||||
:search-options="searchOptions"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
></CustomerListSearch>
|
||||
<CustomerListSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
|
||||
@reset="handleReset"></CustomerListSearch>
|
||||
|
||||
<!-- 表格 -->
|
||||
<CustomerListTable
|
||||
:table-data="tableTree"
|
||||
:columns="tableColumnsOptions"
|
||||
row-key="userId"
|
||||
:tree-props="treeProps"
|
||||
:default-sort="{ prop: 'createTime', order: 'descending' }"
|
||||
@expand-change="handleGetSubList"
|
||||
>
|
||||
<CustomerListTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="userId" :tree-props="treeProps"
|
||||
:default-sort="{ prop: 'createTime', order: 'descending' }" @expand-change="handleGetSubList">
|
||||
<template #marginRatio="{ row }">
|
||||
<el-button
|
||||
v-if="row.role_id === 5"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleMarginRatio(row)"
|
||||
>
|
||||
<el-button v-if="row.role_id === 5" type="primary" size="small" @click="handleMarginRatio(row)">
|
||||
修改
|
||||
</el-button>
|
||||
</template>
|
||||
<template #commissionRatio="{ row }">
|
||||
{{ row.commissionRatio }}
|
||||
<el-button
|
||||
v-if="row.role_id === 5"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleCommissionRatio(row)"
|
||||
>
|
||||
<el-button v-if="row.role_id === 5" type="primary" size="small" @click="handleCommissionRatio(row)">
|
||||
修改
|
||||
</el-button>
|
||||
</template>
|
||||
|
|
@ -43,62 +23,97 @@
|
|||
<el-button link type="primary" size="small" @click="openDetailDrawer(row)" v-if="row.userId">
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link type="primary" size="small" @click="handleEditCustomer(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link type="warning" size="small" @click="handleAdjustAmount(row)">
|
||||
调整金额
|
||||
</el-button>
|
||||
</template>
|
||||
</CustomerListTable>
|
||||
|
||||
<!-- 分页 -->
|
||||
<CustomerListPagination
|
||||
v-model="currentPage"
|
||||
v-model:pageSizeValue="pageSize"
|
||||
:total="total"
|
||||
@pagination-change="handlePaginationChange"
|
||||
/>
|
||||
<CustomerListPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
|
||||
@pagination-change="handlePaginationChange" />
|
||||
|
||||
<!-- dialog -->
|
||||
<CustomerListDialog
|
||||
:visible="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:type="dialogType"
|
||||
:fullscreen="dialogFullscreen"
|
||||
:show-footer="dialogType !== 'view'"
|
||||
@confirm="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
@close="handleClose"
|
||||
>
|
||||
<CustomerListTable
|
||||
:table-data="marginRatioTree"
|
||||
:columns="marginRatioTableColumnsOptions"
|
||||
row-key="id"
|
||||
v-if="dialogType === 'editMarginRatio'"
|
||||
style="height: 600px"
|
||||
>
|
||||
<CustomerListDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :fullscreen="dialogFullscreen"
|
||||
:show-footer="dialogType !== 'view'" @confirm="handleSubmit" @cancel="handleCancel" @close="handleClose">
|
||||
<CustomerListTable :table-data="marginRatioTree" :columns="marginRatioTableColumnsOptions" row-key="id"
|
||||
v-if="dialogType === 'editMarginRatio'" style="height: 600px">
|
||||
<template #assignedPointDiff="{ row }">
|
||||
<el-form
|
||||
:model="row"
|
||||
:rules="marginRatioRules(row)"
|
||||
:ref="(el) => setFormRef(el, row.id)"
|
||||
>
|
||||
<el-form :model="row" :rules="marginRatioRules(row)" :ref="(el) => setFormRef(el, row.id)">
|
||||
<el-form-item style="margin-bottom: 0" prop="assignedPointDifference">
|
||||
<el-input v-model="row.assignedPointDifference" placeholder="请输入点差" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</CustomerListTable>
|
||||
<CustomerListForm
|
||||
:form-data="commissionRatioForm"
|
||||
:form-rules="commissionRatioFormRules"
|
||||
:form-items="commissionRatioFormItemOptions"
|
||||
ref="commissionRatioFormRef"
|
||||
v-if="dialogType === 'editCommissionRatio'"
|
||||
></CustomerListForm>
|
||||
<CustomerListForm :form-data="commissionRatioForm" :form-rules="commissionRatioFormRules"
|
||||
:form-items="commissionRatioFormItemOptions" ref="commissionRatioFormRef"
|
||||
v-if="dialogType === 'editCommissionRatio'"></CustomerListForm>
|
||||
</CustomerListDialog>
|
||||
|
||||
<!-- 调整金额弹窗 -->
|
||||
<CustomerListDialog :visible="adjustAmountVisible" title="调整金额" type="adjust" @confirm="handleAdjustAmountSubmit"
|
||||
@cancel="handleAdjustAmountCancel" @close="handleAdjustAmountClose">
|
||||
<el-form :model="adjustAmountForm" :rules="adjustAmountRules" ref="adjustAmountFormRef" label-width="120px">
|
||||
<el-form-item label="账号名" prop="username">
|
||||
<el-input v-model="adjustAmountForm.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="币种" prop="currency">
|
||||
<el-input v-model="adjustAmountForm.currency" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前余额" prop="currentBalance">
|
||||
<el-input v-model="adjustAmountForm.currentBalance" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="调整金额" prop="adjustAmount">
|
||||
<el-input v-model="adjustAmountForm.adjustAmount" placeholder="请输入调整金额(可为负数)"
|
||||
@input="handleAdjustAmountInput" />
|
||||
</el-form-item>
|
||||
<el-form-item label="调整后余额" prop="afterBalance">
|
||||
<el-input v-model="adjustAmountForm.afterBalance" disabled />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</CustomerListDialog>
|
||||
|
||||
<!-- 修改客户弹窗 -->
|
||||
<CustomerListDialog :visible="editCustomerVisible" title="修改客户" type="editCustomer"
|
||||
@confirm="handleEditCustomerSubmit" @cancel="handleEditCustomerCancel" @close="handleEditCustomerClose">
|
||||
<el-form :model="editCustomerForm" :rules="editCustomerRules" ref="editCustomerFormRef" label-width="140px">
|
||||
<el-form-item label="手续费模板" prop="feeSettingId">
|
||||
<el-select v-model="editCustomerForm.feeSettingId" placeholder="请选择手续费模板" style="width: 100%">
|
||||
<el-option v-for="item in feeTemplateOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="风控模板" prop="riskControlId">
|
||||
<el-select v-model="editCustomerForm.riskControlId" placeholder="请选择风控模板" style="width: 100%">
|
||||
<el-option v-for="item in riskTemplateOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户名称" prop="username">
|
||||
<el-input v-model="editCustomerForm.username" placeholder="请输入客户名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="是否升级成代理" prop="isLevelUp">
|
||||
<el-radio-group v-model="editCustomerForm.isLevelUp" :disabled="editCustomerForm.roleId === 5">
|
||||
<el-radio :label="1">是</el-radio>
|
||||
<el-radio :label="2">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="选择状态" prop="status">
|
||||
<el-radio-group v-model="editCustomerForm.status">
|
||||
<el-radio :label="1">正常</el-radio>
|
||||
<el-radio :label="2">封禁</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</CustomerListDialog>
|
||||
|
||||
<!-- ===================== 【新增】客户详情抽屉 ===================== -->
|
||||
<CustomerDetailDrawer
|
||||
v-model:drawer-visible="detailDrawerVisible"
|
||||
:query-id="detailQueryId"
|
||||
:drawer-title="detailDrawerTitle"
|
||||
/>
|
||||
<CustomerDetailDrawer v-model:drawer-visible="detailDrawerVisible" :query-id="detailQueryId"
|
||||
:drawer-title="detailDrawerTitle" />
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -127,6 +142,11 @@ import {
|
|||
getSelfVarietyPointDiffConfigApi,
|
||||
editCustomerApi,
|
||||
} from '@/api/modules/agent/customerList'
|
||||
import { updateCapitalApi, editUserApi } from '@/api/modules/user'
|
||||
import {
|
||||
getFeeTemplateOptionsApi,
|
||||
getRiskTemplateOptionsApi,
|
||||
} from '@/api/modules/account/client'
|
||||
|
||||
// 公用方法
|
||||
import { getStorage } from '@/utils/storage'
|
||||
|
|
@ -231,6 +251,61 @@ const commissionRatioFormRules = {
|
|||
const commissionRatioFormItemOptions = ref(commissionRatioFormItem)
|
||||
const selectId = ref('')
|
||||
|
||||
/**
|
||||
* @description 定义修改客户弹窗数据
|
||||
*/
|
||||
const editCustomerVisible = ref(false)
|
||||
const editCustomerFormRef = ref()
|
||||
const editCustomerUserId = ref<number>(0)
|
||||
const editCustomerForm = ref({
|
||||
feeSettingId: '',
|
||||
riskControlId: '',
|
||||
username: '',
|
||||
isLevelUp: 2,
|
||||
status: 1,
|
||||
roleId: 0,
|
||||
})
|
||||
const editCustomerRules = {
|
||||
feeSettingId: [{ required: true, message: '请选择手续费模板', trigger: 'change' }],
|
||||
riskControlId: [{ required: true, message: '请选择风控模板', trigger: 'change' }],
|
||||
isLevelUp: [{ required: true, message: '请选择是否升级成代理', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
|
||||
}
|
||||
const feeTemplateOptions = ref<any[]>([])
|
||||
const riskTemplateOptions = ref<any[]>([])
|
||||
const templateOptionsFetched = ref(false)
|
||||
|
||||
/**
|
||||
* @description 定义调整金额弹窗数据
|
||||
*/
|
||||
const adjustAmountVisible = ref(false)
|
||||
const adjustAmountFormRef = ref()
|
||||
const adjustUserId = ref<number>(0)
|
||||
const adjustAmountForm = ref({
|
||||
username: '',
|
||||
currency: '',
|
||||
currentBalance: '',
|
||||
adjustAmount: '',
|
||||
afterBalance: '',
|
||||
})
|
||||
const adjustAmountRules = {
|
||||
adjustAmount: [
|
||||
{ required: true, message: '请输入调整金额', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
callback(new Error('请输入调整金额'))
|
||||
} else if (!/^-?\d+(\.\d{1,5})?$/.test(value)) {
|
||||
callback(new Error('请输入有效的金额格式,最多五位小数'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 角色映射方法
|
||||
*/
|
||||
|
|
@ -277,10 +352,10 @@ const getCustomerList = async (id: string) => {
|
|||
children:
|
||||
item.role_id === 5
|
||||
? [
|
||||
{
|
||||
hasChildren: true,
|
||||
},
|
||||
]
|
||||
{
|
||||
hasChildren: true,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
}))
|
||||
console.log('tableTree', tableTree.value)
|
||||
|
|
@ -399,10 +474,10 @@ const handleGetSubList = async (row: any) => {
|
|||
children:
|
||||
item.role_id === 5
|
||||
? [
|
||||
{
|
||||
hasChildren: true,
|
||||
},
|
||||
]
|
||||
{
|
||||
hasChildren: true,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
}))
|
||||
}
|
||||
|
|
@ -478,6 +553,134 @@ const handleClose = () => {
|
|||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 调整金额相关方法
|
||||
*/
|
||||
// 打开调整金额弹窗
|
||||
const handleAdjustAmount = (row: any) => {
|
||||
adjustAmountVisible.value = true
|
||||
adjustUserId.value = row.userId
|
||||
// 注意:这里需要从原始数据中获取余额,如果row中没有balance字段,可能需要重新获取
|
||||
// 暂时使用默认值,实际使用时需要根据数据结构调整
|
||||
const balance = row.balance || row.wallet_capital?.amount || '0'
|
||||
adjustAmountForm.value = {
|
||||
username: row.accountName,
|
||||
currency: 'USDT', // 默认币种,可根据实际情况调整
|
||||
currentBalance: balance,
|
||||
adjustAmount: '',
|
||||
afterBalance: balance,
|
||||
}
|
||||
}
|
||||
|
||||
// 调整金额输入变化时计算调整后余额
|
||||
const handleAdjustAmountInput = () => {
|
||||
const currentBalance = parseFloat(adjustAmountForm.value.currentBalance) || 0
|
||||
const adjustAmount = parseFloat(adjustAmountForm.value.adjustAmount) || 0
|
||||
const afterBalance = currentBalance + adjustAmount
|
||||
adjustAmountForm.value.afterBalance = afterBalance.toFixed(5)
|
||||
}
|
||||
|
||||
// 提交调整金额
|
||||
const handleAdjustAmountSubmit = async () => {
|
||||
try {
|
||||
await adjustAmountFormRef.value?.validate()
|
||||
await updateCapitalApi({
|
||||
target_user_id: adjustUserId.value,
|
||||
amount: adjustAmountForm.value.adjustAmount,
|
||||
})
|
||||
await getCustomerList(userId.value)
|
||||
adjustAmountVisible.value = false
|
||||
} catch (err) {
|
||||
console.error('调整金额表单验证失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消调整金额
|
||||
const handleAdjustAmountCancel = () => {
|
||||
adjustAmountVisible.value = false
|
||||
}
|
||||
|
||||
// 关闭调整金额弹窗
|
||||
const handleAdjustAmountClose = () => {
|
||||
adjustAmountVisible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 修改客户相关方法
|
||||
*/
|
||||
// 获取模板选项
|
||||
const getTemplateOptions = async () => {
|
||||
if (templateOptionsFetched.value) return
|
||||
|
||||
try {
|
||||
const [feeTemplateRes, riskTemplateRes] = await Promise.all([
|
||||
getFeeTemplateOptionsApi(),
|
||||
getRiskTemplateOptionsApi(),
|
||||
])
|
||||
|
||||
feeTemplateOptions.value = Object.entries(feeTemplateRes as Record<string, string>).map(
|
||||
([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
}),
|
||||
)
|
||||
riskTemplateOptions.value = Object.entries(riskTemplateRes as Record<string, string>).map(
|
||||
([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
}),
|
||||
)
|
||||
|
||||
templateOptionsFetched.value = true
|
||||
} catch (error) {
|
||||
console.error('获取模板选项失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 打开修改客户弹窗
|
||||
const handleEditCustomer = async (row: any) => {
|
||||
await getTemplateOptions()
|
||||
editCustomerVisible.value = true
|
||||
editCustomerUserId.value = row.userId
|
||||
editCustomerForm.value = {
|
||||
feeSettingId: row.feeSettingId || '',
|
||||
riskControlId: row.riskControlId || '',
|
||||
username: row.accountName || '',
|
||||
isLevelUp: (row.role_id === 5) ? 1 : 2,
|
||||
status: row.status === '正常' ? 1 : 2,
|
||||
roleId: row.role_id,
|
||||
}
|
||||
}
|
||||
|
||||
// 提交修改客户
|
||||
const handleEditCustomerSubmit = async () => {
|
||||
try {
|
||||
await editCustomerFormRef.value?.validate()
|
||||
await editUserApi({
|
||||
user_id: editCustomerUserId.value,
|
||||
fee_setting_id: editCustomerForm.value.feeSettingId,
|
||||
risk_control_id: editCustomerForm.value.riskControlId,
|
||||
username: editCustomerForm.value.username,
|
||||
is_levelup: editCustomerForm.value.isLevelUp,
|
||||
status: editCustomerForm.value.status,
|
||||
})
|
||||
await getCustomerList(userId.value)
|
||||
editCustomerVisible.value = false
|
||||
} catch (err) {
|
||||
console.error('修改客户表单验证失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消修改客户
|
||||
const handleEditCustomerCancel = () => {
|
||||
editCustomerVisible.value = false
|
||||
}
|
||||
|
||||
// 关闭修改客户弹窗
|
||||
const handleEditCustomerClose = () => {
|
||||
editCustomerVisible.value = false
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
@ -485,5 +688,4 @@ const handleClose = () => {
|
|||
text-align: center;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
@ -3,12 +3,12 @@ export const search = [
|
|||
{ prop: 'accountName', label: '账号名称', type: 'input' as const, placeholder: '请输入账号名称', width: '150px' },
|
||||
// 开始时间
|
||||
{
|
||||
prop: "startTime", label: '注册开始时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
|
||||
prop: "startTime", label: '注册开始时间', type: 'date' as const, dateType: 'date' as const,placeholder: '请选择注册开始时间', width: '270px', // 时间范围选择器
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
},
|
||||
// 结束时间
|
||||
{
|
||||
prop: "endTime", label: '注册结束时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
|
||||
prop: "endTime", label: '注册结束时间', type: 'date' as const, dateType: 'date' as const,placeholder: '请选择注册结束时间', width: '270px', // 时间范围选择器
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
},
|
||||
// 状态
|
||||
|
|
@ -17,23 +17,23 @@ export const search = [
|
|||
|
||||
export const tableColumns = [
|
||||
// 账号名称
|
||||
{ label: '账号名称', prop: 'accountName'},
|
||||
{ label: '账号名称', prop: 'accountName', },
|
||||
// 账号id
|
||||
{ prop: 'userId', label: '账号id' },
|
||||
// 状态
|
||||
{ prop: 'status', label: '状态' },
|
||||
// 账号角色
|
||||
{ prop: 'role', label: '账号角色' },
|
||||
{ prop: 'role', label: '账号角色', width: 100 },
|
||||
// 账号数量
|
||||
{ prop: 'accountQty', label: '账号数量' },
|
||||
{ prop: 'accountQty', label: '账号数量', width: 100 },
|
||||
// 点差分配比例
|
||||
{ prop: 'marginRatio', label: '点差分配比例', slotName: 'marginRatio' },
|
||||
{ prop: 'marginRatio', label: '点差分配比例', slotName: 'marginRatio' , width: 140},
|
||||
// 手续费分成比例
|
||||
{ prop: 'commissionRatio', label: '手续费分成比例', slotName: 'commissionRatio' },
|
||||
{ prop: 'commissionRatio', label: '手续费分成比例', slotName: 'commissionRatio' , width: 140},
|
||||
// 注册时间
|
||||
{ prop: 'createTime', label: '注册时间', sortable: true },
|
||||
{ prop: 'createTime', label: '注册时间', sortable: true , minWidth: 180},
|
||||
// 操作
|
||||
{ label: '操作', slotName: 'action' }
|
||||
{ label: '操作', slotName: 'action', width: 200}
|
||||
]
|
||||
|
||||
// 点差表格配置
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
<!-- 页面标题 -->
|
||||
<div class="page-title">存款</div>
|
||||
|
||||
<!-- 存款渠道列表:每行提供一个“存款”按钮 -->
|
||||
<!-- 存款渠道列表:每行提供一个"存款"按钮 -->
|
||||
<RechangeTable :table-data="channelList" :columns="tableColumns" :highlight-current-row="false">
|
||||
<template #action="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleOpenDialog(row)">
|
||||
|
|
@ -13,218 +13,318 @@
|
|||
</RechangeTable>
|
||||
</div>
|
||||
|
||||
<!-- 存款弹窗:改为和设计图一致的支付面板布局 -->
|
||||
<RechangeDialog class="rechange-pay-dialog" :visible="dialogVisible" title=" " width="640px" :show-footer="false"
|
||||
<!-- 存款弹窗 -->
|
||||
<RechangeDialog class="rechange-pay-dialog" :visible="dialogVisible" title="存款" width="600px" :show-footer="false"
|
||||
@cancel="handleCancel" @close="handleClose">
|
||||
<div class="dialog-content">
|
||||
<!-- 顶部标题:展示当前点击的存款渠道名称 -->
|
||||
<div class="dialog-page-title">{{ rechangeForm.channelName || '存款渠道名称' }}</div>
|
||||
|
||||
<!-- 存款面板:当前为纯前端演示,后续可在确认支付时接入真实接口 -->
|
||||
<el-form ref="rechangeFormRef" :model="rechangeForm" :rules="rechangeFormRules" label-position="top"
|
||||
class="pay-form">
|
||||
<el-form-item prop="depositAccount">
|
||||
<template #label>
|
||||
<span class="required-label">存款账户</span>
|
||||
</template>
|
||||
<el-input v-model="rechangeForm.depositAccount" disabled />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="supportedCurrency">
|
||||
<template #label>
|
||||
<span class="required-label">支持币种</span>
|
||||
</template>
|
||||
<div class="currency-box">{{ rechangeForm.supportedCurrency }}</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="depositAmount">
|
||||
<template #label>
|
||||
<span class="required-label">存款金额</span>
|
||||
</template>
|
||||
|
||||
<el-input v-model="rechangeForm.depositAmount" placeholder="请输入金额">
|
||||
<template #prepend>{{ rechangeForm.amountCurrency }}</template>
|
||||
</el-input>
|
||||
|
||||
<!-- 快捷金额:模拟截图中的金额快捷按钮 -->
|
||||
<div class="quick-amounts">
|
||||
<el-button v-for="amount in rechangeQuickAmounts" :key="amount" plain size="small"
|
||||
@click="handleQuickAmount(amount)">
|
||||
{{ amount }}
|
||||
</el-button>
|
||||
<el-form ref="rechangeFormRef" :model="rechangeForm" :rules="rechangeFormRules" label-position="top">
|
||||
<!-- 渠道信息 -->
|
||||
<div class="channel-card">
|
||||
<div class="channel-info">
|
||||
<div class="channel-name">{{ rechangeForm.channelName }}</div>
|
||||
<div class="channel-meta">
|
||||
<span></span>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 按钮区:上一步用于关闭弹窗返回列表,确认支付用于提交当前输入 -->
|
||||
<div class="dialog-actions">
|
||||
<el-button class="prev-btn" @click="handlePrevStep">上一步</el-button>
|
||||
<el-button type="primary" class="confirm-btn" @click="handleSubmit">确认支付</el-button>
|
||||
</div>
|
||||
<div class="channel-meta">支持货币: {{ rechangeForm.currencySymbol }}</div>
|
||||
</div>
|
||||
|
||||
<!-- 银行卡信息(channel_code = BANK 时显示) -->
|
||||
<template v-if="rechangeForm.channelCode === 'BANK'">
|
||||
<div class="info-section">
|
||||
<div class="section-title">收款信息</div>
|
||||
<div class="info-grid">
|
||||
<div class="info-item">
|
||||
<span class="label">账户持有人姓名</span>
|
||||
<span class="value">{{ rechangeForm.beneficiaryName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">银行名称</span>
|
||||
<span class="value">{{ rechangeForm.beneficiaryBankName }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">银行识别代码</span>
|
||||
<span class="value">{{ rechangeForm.swiftBicCode }}</span>
|
||||
</div>
|
||||
<div class="info-item">
|
||||
<span class="label">收款人银行账号</span>
|
||||
<span class="value">{{ rechangeForm.beneficiaryBankAccount }}</span>
|
||||
</div>
|
||||
<div class="info-item full-width">
|
||||
<span class="label">银行地址</span>
|
||||
<span class="value">{{ rechangeForm.beneficiaryBankAddress }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 钱包地址信息(其他渠道显示) -->
|
||||
<template v-else>
|
||||
<div class="info-section">
|
||||
<div class="section-title">收款信息</div>
|
||||
<div class="info-item full-width">
|
||||
<span class="label">钱包收款地址</span>
|
||||
<span class="value address-value">{{ rechangeForm.address }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 充值金额 -->
|
||||
<el-form-item label="充值金额" prop="depositAmount">
|
||||
<el-input v-model="rechangeForm.depositAmount" placeholder="请输入充值金额" size="large" @input="handleAmountInput">
|
||||
<template #suffix>
|
||||
<span class="currency-suffix">{{ rechangeForm.currencySymbol }}</span>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
|
||||
<!-- 上传凭证 -->
|
||||
<el-form-item label="上传凭证" prop="voucherUrl">
|
||||
<el-upload class="voucher-uploader" action="" :show-file-list="false"
|
||||
:http-request="handleUpload" :before-upload="beforeAvatarUpload" :loading="uploadLoading">
|
||||
<img v-if="voucherUrl" :src="voucherUrl" class="voucher-image" />
|
||||
<div v-else class="upload-placeholder">
|
||||
<el-icon class="upload-icon">
|
||||
<Plus />
|
||||
</el-icon>
|
||||
<span class="upload-text">点击上传凭证</span>
|
||||
</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<!-- 按钮区 -->
|
||||
<div class="dialog-actions">
|
||||
<el-button size="large" @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" size="large" :loading="submitLoading" @click="handleSubmit">
|
||||
确认充值
|
||||
</el-button>
|
||||
</div>
|
||||
</RechangeDialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Plus } from '@element-plus/icons-vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Rechange'
|
||||
name: 'Rechange'
|
||||
})
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { FormInstance, FormRules, UploadProps } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { nextTick, onMounted, ref } from 'vue'
|
||||
import RechangeTable from '@/components/common/Table.vue'
|
||||
import RechangeDialog from '@/components/common/Dialog.vue'
|
||||
import { rechangeQuickAmounts, rechangeTableColumns } from './options'
|
||||
import { rechangeTableColumns } from './options'
|
||||
import { uploadImageApi } from '@/api/modules/upload'
|
||||
import { getRechargeChannelList, rechargeApi } from '@/api/modules/capital/rechangeChanl'
|
||||
|
||||
// 列表单行数据结构:额外保留弹窗里会用到的账户和金额币种字段。
|
||||
interface RechangeRow {
|
||||
// 渠道数据接口
|
||||
interface ChannelCurrency {
|
||||
id: number
|
||||
depositChannel: string
|
||||
channelName: string
|
||||
supportedCurrency: string
|
||||
processingTime: string
|
||||
singleAmount: string
|
||||
depositAccount: string
|
||||
amountCurrency: string
|
||||
symbol: string
|
||||
rate: string
|
||||
}
|
||||
|
||||
// 弹窗表单数据结构:当前只维护弹窗所需字段。
|
||||
interface RechangeFormData {
|
||||
interface ChannelRow {
|
||||
id: number
|
||||
channelName: string
|
||||
depositAccount: string
|
||||
supportedCurrency: string
|
||||
amountCurrency: string
|
||||
depositAmount: string
|
||||
name: string
|
||||
channel_fee: string
|
||||
type: number
|
||||
beneficiary_name: string | null
|
||||
beneficiary_bank_name: string | null
|
||||
swift_bic_code: string | null
|
||||
beneficiary_bank_address: string | null
|
||||
beneficiary_bank_account: string | null
|
||||
channel_code: string
|
||||
currency: ChannelCurrency
|
||||
address?: string
|
||||
}
|
||||
|
||||
// mock 列表数据:当前仅用于页面展示。
|
||||
const channelList = ref<RechangeRow[]>([
|
||||
{
|
||||
id: 1,
|
||||
depositChannel: 'BANK-CNY',
|
||||
channelName: '存款渠道名称',
|
||||
supportedCurrency: 'CNY',
|
||||
processingTime: '5-10分钟',
|
||||
singleAmount: '100 - 50,000',
|
||||
depositAccount: '只能在总钱包充值',
|
||||
amountCurrency: 'USD',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
depositChannel: 'BANK-USD',
|
||||
channelName: '银行卡入金',
|
||||
supportedCurrency: 'USD',
|
||||
processingTime: '5-10分钟',
|
||||
singleAmount: '100 - 50,000',
|
||||
depositAccount: '只能在总钱包充值',
|
||||
amountCurrency: 'USD',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
depositChannel: 'USDT-TRC20',
|
||||
channelName: '数字货币入金',
|
||||
supportedCurrency: 'USDT',
|
||||
processingTime: '1-3分钟',
|
||||
singleAmount: '50 - 100,000',
|
||||
depositAccount: '只能在总钱包充值',
|
||||
amountCurrency: 'USDT',
|
||||
},
|
||||
])
|
||||
|
||||
// 表格列配置
|
||||
const tableColumns = ref(rechangeTableColumns)
|
||||
|
||||
// 弹窗基础状态
|
||||
const dialogVisible = ref(false)
|
||||
const currentRowId = ref<number | null>(null)
|
||||
const rechangeFormRef = ref<FormInstance>()
|
||||
const submitLoading = ref(false)
|
||||
|
||||
// 上传凭证相关
|
||||
const voucherUrl = ref('')
|
||||
const uploadLoading = ref(false)
|
||||
|
||||
// 渠道列表数据
|
||||
const channelList = ref<ChannelRow[]>([])
|
||||
|
||||
// 弹窗表单数据
|
||||
interface RechangeFormData {
|
||||
id: number
|
||||
channelName: string
|
||||
channelCode: string
|
||||
currencySymbol: string
|
||||
channelFee: string
|
||||
depositAmount: string
|
||||
voucherUrl: string
|
||||
beneficiaryName: string
|
||||
beneficiaryBankName: string
|
||||
swiftBicCode: string
|
||||
beneficiaryBankAddress: string
|
||||
beneficiaryBankAccount: string
|
||||
address: string
|
||||
}
|
||||
|
||||
// 默认表单:弹窗关闭时回到初始状态。
|
||||
const createDefaultForm = (): RechangeFormData => ({
|
||||
id: 0,
|
||||
channelName: '',
|
||||
depositAccount: '',
|
||||
supportedCurrency: '',
|
||||
amountCurrency: '',
|
||||
channelCode: '',
|
||||
currencySymbol: '',
|
||||
channelFee: '0',
|
||||
depositAmount: '',
|
||||
voucherUrl: '',
|
||||
beneficiaryName: '',
|
||||
beneficiaryBankName: '',
|
||||
swiftBicCode: '',
|
||||
beneficiaryBankAddress: '',
|
||||
beneficiaryBankAccount: '',
|
||||
address: '',
|
||||
})
|
||||
|
||||
const rechangeForm = ref<RechangeFormData>(createDefaultForm())
|
||||
|
||||
// 表单校验规则:当前只校验本次存款金额。
|
||||
// 表单校验规则
|
||||
const rechangeFormRules = ref<FormRules<RechangeFormData>>({
|
||||
depositAmount: [
|
||||
{ required: true, message: '请输入存款金额', trigger: ['blur', 'change'] },
|
||||
{ pattern: /^\d+(\.\d{1,2})?$/, message: '金额格式不正确,最多保留2位小数', trigger: ['blur', 'change'] },
|
||||
{ required: true, message: '请输入充值金额', trigger: ['blur', 'change'] },
|
||||
{ pattern: /^\d+$/, message: '请输入整数金额', trigger: ['blur', 'change'] },
|
||||
],
|
||||
voucherUrl: [
|
||||
{ required: true, message: '请上传充值凭证', trigger: ['change'] },
|
||||
],
|
||||
})
|
||||
|
||||
// 重置弹窗表单:统一给关闭、取消、提交成功等场景复用。
|
||||
const resetDialogForm = () => {
|
||||
rechangeForm.value = createDefaultForm()
|
||||
currentRowId.value = null
|
||||
rechangeFormRef.value?.clearValidate()
|
||||
// 获取渠道列表
|
||||
const fetchChannelList = async () => {
|
||||
try {
|
||||
const res = await getRechargeChannelList() as ChannelRow[]
|
||||
channelList.value = res || []
|
||||
} catch (error) {
|
||||
console.error('获取充值渠道列表失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 打开弹窗:把当前行参数带入支付面板。
|
||||
const handleOpenDialog = async (row: RechangeRow) => {
|
||||
currentRowId.value = row.id
|
||||
// 打开弹窗
|
||||
const handleOpenDialog = async (row: ChannelRow) => {
|
||||
rechangeForm.value = {
|
||||
id: row.id,
|
||||
channelName: row.channelName,
|
||||
depositAccount: row.depositAccount,
|
||||
supportedCurrency: row.supportedCurrency,
|
||||
amountCurrency: row.amountCurrency,
|
||||
channelName: row.name,
|
||||
channelCode: row.channel_code,
|
||||
currencySymbol: row.currency.symbol,
|
||||
channelFee: row.channel_fee || '0',
|
||||
depositAmount: '',
|
||||
voucherUrl: '',
|
||||
beneficiaryName: row.beneficiary_name || '',
|
||||
beneficiaryBankName: row.beneficiary_bank_name || '',
|
||||
swiftBicCode: row.swift_bic_code || '',
|
||||
beneficiaryBankAddress: row.beneficiary_bank_address || '',
|
||||
beneficiaryBankAccount: row.beneficiary_bank_account || '',
|
||||
address: (row as any).address || '',
|
||||
}
|
||||
voucherUrl.value = ''
|
||||
dialogVisible.value = true
|
||||
await nextTick()
|
||||
rechangeFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
// 快捷金额按钮:点击后直接回填到金额输入框。
|
||||
const handleQuickAmount = (amount: string) => {
|
||||
rechangeForm.value.depositAmount = amount
|
||||
// 上传凭证相关
|
||||
const handleAvatarSuccess: UploadProps['onSuccess'] = (response: any) => {
|
||||
if (response.code === 0) {
|
||||
voucherUrl.value = response.url
|
||||
rechangeForm.value.voucherUrl = response.url
|
||||
ElMessage.success('上传成功')
|
||||
} else {
|
||||
ElMessage.error(response.msg || '上传失败')
|
||||
}
|
||||
uploadLoading.value = false
|
||||
}
|
||||
|
||||
// 上一步:当前等价于关闭弹窗,返回列表。
|
||||
const handlePrevStep = () => {
|
||||
dialogVisible.value = false
|
||||
resetDialogForm()
|
||||
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
|
||||
if (rawFile.type !== 'image/jpeg' && rawFile.type !== 'image/png') {
|
||||
ElMessage.error('图片格式必须为 JPG 或 PNG!')
|
||||
return false
|
||||
} else if (rawFile.size / 1024 / 1024 > 2) {
|
||||
ElMessage.error('图片大小不能超过 2MB!')
|
||||
return false
|
||||
}
|
||||
uploadLoading.value = true
|
||||
return true
|
||||
}
|
||||
|
||||
// 提交:当前先做前端校验和成功提示,后续再接支付接口。
|
||||
// 自定义上传方法
|
||||
const handleUpload = async (options: any) => {
|
||||
const { file, onSuccess, onError } = options
|
||||
try {
|
||||
const res: any = await uploadImageApi(file)
|
||||
voucherUrl.value = res.url
|
||||
rechangeForm.value.voucherUrl = res.url
|
||||
onSuccess(res)
|
||||
// 上传成功后手动清除该字段的验证状态
|
||||
nextTick(() => {
|
||||
rechangeFormRef.value?.clearValidate('voucherUrl')
|
||||
})
|
||||
} catch (error) {
|
||||
onError(error)
|
||||
uploadLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 金额输入处理:只允许整数
|
||||
const handleAmountInput = (value: string) => {
|
||||
const filtered = value.replace(/\D/g, '')
|
||||
rechangeForm.value.depositAmount = filtered
|
||||
}
|
||||
|
||||
// 提交充值
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await rechangeFormRef.value?.validate()
|
||||
ElMessage.success(`已进入支付确认流程,渠道ID:${currentRowId.value}`)
|
||||
submitLoading.value = true
|
||||
await rechargeApi({
|
||||
channel_id: rechangeForm.value.id,
|
||||
amount: rechangeForm.value.depositAmount,
|
||||
img: rechangeForm.value.voucherUrl,
|
||||
})
|
||||
ElMessage.success('充值提交成功')
|
||||
dialogVisible.value = false
|
||||
resetDialogForm()
|
||||
} catch (error) {
|
||||
console.error('存款表单校验失败', error)
|
||||
fetchChannelList()
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error?.msg || '充值提交失败')
|
||||
} finally {
|
||||
submitLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 取消弹窗
|
||||
const handleCancel = () => {
|
||||
dialogVisible.value = false
|
||||
resetDialogForm()
|
||||
rechangeFormRef.value?.resetFields()
|
||||
rechangeForm.value = createDefaultForm()
|
||||
}
|
||||
|
||||
// 关闭弹窗
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
resetDialogForm()
|
||||
rechangeFormRef.value?.resetFields()
|
||||
rechangeForm.value = createDefaultForm()
|
||||
}
|
||||
|
||||
// 页面加载时获取渠道列表
|
||||
onMounted(() => {
|
||||
fetchChannelList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.rechange-page {
|
||||
height: 100%;
|
||||
|
||||
// 页面标题样式:贴近截图的简洁列表页风格。
|
||||
max-height: 100%;
|
||||
height: 100%;
|
||||
.page-title {
|
||||
margin-bottom: 16px;
|
||||
font-size: 28px;
|
||||
|
|
@ -233,99 +333,172 @@ const handleClose = () => {
|
|||
}
|
||||
}
|
||||
|
||||
.dialog-content {
|
||||
padding: 6px 8px 0;
|
||||
}
|
||||
|
||||
.dialog-page-title {
|
||||
margin-bottom: 24px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.pay-form {
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
: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;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 1px;
|
||||
color: #f56c6c;
|
||||
content: '*';
|
||||
}
|
||||
}
|
||||
|
||||
.currency-box {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 116px;
|
||||
height: 60px;
|
||||
padding: 0 24px;
|
||||
font-size: 18px;
|
||||
color: #303133;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.quick-amounts {
|
||||
.channel-card {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 12px;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
color: #fff;
|
||||
|
||||
:deep(.el-button) {
|
||||
min-width: 72px;
|
||||
margin-left: 0;
|
||||
color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
.channel-info {
|
||||
.channel-name {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.channel-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
}
|
||||
|
||||
.channel-fee {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
padding: 8px 16px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-section {
|
||||
background: #f5f7fa;
|
||||
border-radius: 10px;
|
||||
padding: 16px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.section-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 12px;
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
&.full-width {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 13px;
|
||||
color: #909399;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 14px;
|
||||
color: #303133;
|
||||
word-break: break-all;
|
||||
|
||||
&.address-value {
|
||||
font-family: monospace;
|
||||
background: #fff;
|
||||
padding: 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.currency-suffix {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
// 上传凭证样式
|
||||
.voucher-uploader {
|
||||
width: 100%;
|
||||
height: 160px;
|
||||
border: 1px dashed var(--el-border-color);
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
transition: var(--el-transition-duration-fast);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
:deep(.el-upload) {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.voucher-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.upload-placeholder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
color: #909399;
|
||||
|
||||
.upload-icon {
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.upload-text {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 按钮区域
|
||||
.dialog-actions {
|
||||
display: flex;
|
||||
gap: 44px;
|
||||
margin-top: 44px;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.prev-btn,
|
||||
.confirm-btn {
|
||||
min-width: 98px;
|
||||
height: 44px;
|
||||
}
|
||||
|
||||
.prev-btn {
|
||||
color: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
|
||||
.el-button {
|
||||
flex: 1;
|
||||
height: 44px;
|
||||
font-size: 16px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 深度覆盖 Element Plus Dialog 样式
|
||||
:deep(.rechange-pay-dialog .el-dialog__header) {
|
||||
display: none;
|
||||
padding: 20px 24px;
|
||||
border-bottom: 1px solid #e4e7ed;
|
||||
}
|
||||
|
||||
:deep(.rechange-pay-dialog .el-dialog__body) {
|
||||
padding: 26px 26px 30px;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
:deep(.rechange-pay-dialog .el-dialog) {
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
// 存款列表表格列配置:当前页面仍然保留列表 + 行内操作按钮的结构。
|
||||
// 存款列表表格列配置
|
||||
export const rechangeTableColumns = [
|
||||
{ prop: 'depositChannel', label: '存款渠道', align: 'center' as const },
|
||||
{ prop: 'channelName', label: '渠道名称', align: 'center' as const },
|
||||
{ prop: 'supportedCurrency', label: '支持货币', align: 'center' as const },
|
||||
{ prop: 'processingTime', label: '预计处理时间', align: 'center' as const },
|
||||
{ prop: 'singleAmount', label: '单笔存款金额', align: 'center' as const },
|
||||
{ prop: 'name', label: '渠道名称', align: 'center' as const },
|
||||
{
|
||||
prop: 'currency.symbol',
|
||||
label: '支持货币',
|
||||
align: 'center' as const
|
||||
},
|
||||
{ prop: 'channel_fee', label: '渠道费用', align: 'center' as const },
|
||||
{ label: '操作', slotName: 'action', align: 'center' as const, width: 120 },
|
||||
]
|
||||
|
||||
// 存款弹窗快捷金额:点击后直接回填到金额输入框。
|
||||
export const rechangeQuickAmounts = ['100.00', '500.00', '1000.00', '2000.00', '5000.00']
|
||||
|
|
|
|||
|
|
@ -92,7 +92,9 @@ const formData = ref({
|
|||
beneficiary_name: '',
|
||||
beneficiary_bank_name: '',
|
||||
swift_bic_code: '',
|
||||
beneficiary_bank_account: '',
|
||||
beneficiary_bank_address: '',
|
||||
address: '',
|
||||
channel_code: '',
|
||||
status: 1,
|
||||
})
|
||||
|
|
@ -128,7 +130,9 @@ const resetFormData = () => {
|
|||
beneficiary_name: '',
|
||||
beneficiary_bank_name: '',
|
||||
swift_bic_code: '',
|
||||
bank_account: '',
|
||||
beneficiary_bank_address: '',
|
||||
address: '',
|
||||
channel_code: '',
|
||||
}
|
||||
}
|
||||
|
|
@ -195,3 +199,4 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -52,12 +52,24 @@ export const formItemsData = [
|
|||
type: 'input',
|
||||
placeholder: '请输入银行识别代码',
|
||||
},
|
||||
{
|
||||
label: '银行账号',
|
||||
prop: 'beneficiary_bank_account',
|
||||
type: 'input',
|
||||
placeholder: '请输入银行账号',
|
||||
},
|
||||
{
|
||||
label: '银行地址',
|
||||
prop: 'beneficiary_bank_address',
|
||||
type: 'input',
|
||||
placeholder: '请输入银行地址',
|
||||
},
|
||||
{
|
||||
label: 'U钱包收款地址',
|
||||
prop: 'address',
|
||||
type: 'input',
|
||||
placeholder: '请输入U钱包收款地址',
|
||||
},
|
||||
{ label: '渠道编码', prop: 'channel_code', type: 'input', placeholder: '请输入渠道编码' },
|
||||
{ label: '状态', prop: 'status', type: 'radio' },
|
||||
]
|
||||
|
|
@ -73,7 +85,9 @@ export const formRulesData = {
|
|||
beneficiary_name: [{ required: true, message: '请输入账户持有人姓名', trigger: ['change'] }],
|
||||
beneficiary_bank_name: [{ required: true, message: '请输入银行名称', trigger: ['change'] }],
|
||||
swift_bic_code: [{ required: true, message: '请输入银行识别代码', trigger: ['change'] }],
|
||||
beneficiary_bank_account: [{ required: true, message: '请输入银行账号', trigger: ['change'] }],
|
||||
beneficiary_bank_address: [{ required: true, message: '请输入银行地址', trigger: ['change'] }],
|
||||
address: [{ required: true, message: '请输入U钱包收款地址', trigger: ['change'] }],
|
||||
channel_code: [{ required: true, message: '请输入渠道编码', trigger: ['change'] }],
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -85,37 +85,28 @@
|
|||
</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"
|
||||
:model="verifyForm"
|
||||
:rules="verifyFormRules"
|
||||
ref="verifyFormRef"
|
||||
>
|
||||
<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 :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>
|
||||
|
|
@ -129,10 +120,13 @@ defineOptions({
|
|||
})
|
||||
//组件
|
||||
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'
|
||||
|
|
@ -142,7 +136,10 @@ 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
|
||||
|
|
@ -160,6 +157,13 @@ const createDefaultTransferForm = () => ({
|
|||
// 最大可转账金额
|
||||
MaxOutAmount: '',
|
||||
type: '',
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 创建默认验证码表单数据
|
||||
*/
|
||||
const createDefaultVerifyForm = () => ({
|
||||
verifyType: 'email',
|
||||
code: '',
|
||||
})
|
||||
|
||||
|
|
@ -167,11 +171,25 @@ const createDefaultTransferForm = () => ({
|
|||
* @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,
|
||||
)
|
||||
|
|
@ -202,10 +220,9 @@ const transferFormOptionsMap = ref({
|
|||
*/
|
||||
//获取userInfo
|
||||
const userInfo = getStorage<any>('userInfo')
|
||||
const verifyType = ref('email')
|
||||
|
||||
// 发送验证码
|
||||
const sendState = ref<any>({
|
||||
// 发送验证码状态
|
||||
const sendState = ref({
|
||||
isCounting: false,
|
||||
countdown: 0,
|
||||
})
|
||||
|
|
@ -220,78 +237,85 @@ const { isCounting: emailIsCounting, countdown: emailCountdown } = emailCaptchaS
|
|||
// 手机验证码状态
|
||||
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 currentIsCounting = computed(() =>
|
||||
verifyForm.value.verifyType === 'email' ? emailIsCounting.value : phoneIsCounting.value
|
||||
)
|
||||
const currentCountdown = computed(() =>
|
||||
verifyForm.value.verifyType === 'email' ? emailCountdown.value : phoneCountdown.value
|
||||
)
|
||||
|
||||
// 定义发送验证码接口
|
||||
const sendCodeApi = async () => {
|
||||
// 发送验证码参数
|
||||
const sendParams = {
|
||||
email: userInfo.email,
|
||||
mobile: userInfo.mobile,
|
||||
reg_type: verifyType.value === 'email' ? 1 : 2,
|
||||
type: 'resetPass_code',
|
||||
// 更新sendState的方法
|
||||
const updateSendState = () => {
|
||||
const type = verifyForm.value.verifyType
|
||||
// 创建新对象以触发响应式更新
|
||||
sendState.value = {
|
||||
isCounting: type === 'email' ? emailIsCounting.value : phoneIsCounting.value,
|
||||
countdown: type === 'email' ? emailCountdown.value : phoneCountdown.value,
|
||||
}
|
||||
return userStore.sendCode(sendParams)
|
||||
}
|
||||
|
||||
// 监听倒计时状态变化,同步到 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 = {
|
||||
reg_type: userStore.userInfo!.reg_type,
|
||||
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 currentType = verifyType.value
|
||||
// 确定类型:手机/邮箱
|
||||
const type: string = currentType || 'email'
|
||||
const type = verifyForm.value.verifyType
|
||||
|
||||
if (type === 'phone') {
|
||||
// 场景1:发送手机验证码
|
||||
if (!userInfo.phone) {
|
||||
if (!userInfo.mobile) {
|
||||
ElMessage.error('请先绑定手机号')
|
||||
return
|
||||
}
|
||||
|
||||
// 执行发送
|
||||
await sendCaptcha(type, userInfo.phone, phoneCaptchaState, () => sendCodeApi())
|
||||
updateSendState(type)
|
||||
await sendCaptcha(type, userInfo.mobile, phoneCaptchaState, sendCodeApi)
|
||||
} else {
|
||||
// 场景2:发送邮箱验证码
|
||||
if (!userInfo.email) {
|
||||
|
|
@ -300,8 +324,7 @@ const handleSendCode = async () => {
|
|||
}
|
||||
|
||||
// 执行发送
|
||||
await sendCaptcha(type, userInfo.email, emailCaptchaState, () => sendCodeApi())
|
||||
updateSendState(type)
|
||||
await sendCaptcha(type, userInfo.email, emailCaptchaState, sendCodeApi)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -357,6 +380,9 @@ const resetTransferForm = async () => {
|
|||
MaxOutAmount: getDefaultMaxOutAmount(),
|
||||
})
|
||||
|
||||
// 重置验证码表单
|
||||
Object.assign(verifyForm.value, createDefaultVerifyForm())
|
||||
|
||||
await nextTick()
|
||||
verifyFormRef.value?.clearValidate?.()
|
||||
}
|
||||
|
|
@ -376,16 +402,12 @@ const handleTransferTabClick = async (type: string) => {
|
|||
* @description 切换验证方式
|
||||
* @param type 验证方式
|
||||
*/
|
||||
const handleVerifyTabClick = (type: string) => {
|
||||
verifyType.value = type
|
||||
}
|
||||
|
||||
const handleVerifyParams = (type: string) => {
|
||||
if (type === 'external') {
|
||||
if (verifyType.value === 'email') {
|
||||
if (verifyForm.value.verifyType === 'email') {
|
||||
return userInfo.email
|
||||
} else {
|
||||
return userInfo.phone
|
||||
return userInfo.mobile
|
||||
}
|
||||
} else {
|
||||
return transferForm.value.transferInAccount
|
||||
|
|
@ -418,8 +440,8 @@ const handleSubmit = async () => {
|
|||
} else {
|
||||
await transferApi({
|
||||
...params,
|
||||
verify_type: verifyType.value === 'email' ? '1' : '2',
|
||||
verify_code: transferForm.value.code,
|
||||
verify_type: verifyForm.value.verifyType === 'email' ? '1' : '2',
|
||||
verify_code: verifyForm.value.code,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -503,17 +525,26 @@ const handleSubmit = async () => {
|
|||
}
|
||||
|
||||
.verify {
|
||||
.tab,
|
||||
.username {
|
||||
margin-bottom: 10px;
|
||||
:deep(.el-radio-group) {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.verify-account {
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.verify-form {
|
||||
.el-form-item {
|
||||
:deep(.el-form-item__content) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
max-width: 500px;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -194,7 +194,7 @@
|
|||
>
|
||||
<el-button
|
||||
:type="dialogType === 'leverage' ? 'primary' : 'default'"
|
||||
@click="dialogType = 'leverage'"
|
||||
@click="handleChangeToLeverage"
|
||||
>修改杠杆</el-button
|
||||
>
|
||||
</div>
|
||||
|
|
@ -249,7 +249,7 @@ const dialogVisible = ref(false)
|
|||
const dialogTitle = ref('')
|
||||
const dialogType = ref('add')
|
||||
|
||||
const accountList = ref([])
|
||||
const accountList = ref<any[]>([])
|
||||
const getAccoundList = async () => {
|
||||
const res = await getAllAccount({ username: '' })
|
||||
console.log(res)
|
||||
|
|
@ -432,6 +432,18 @@ const handleSetting = (id: string) => {
|
|||
dialogType.value = 'setting'
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 修改杠杆
|
||||
*/
|
||||
const handleChangeToLeverage = () => {
|
||||
// 切换到杠杆表单时,设置默认值为当前账户的杠杆值
|
||||
const currentAccount = accountList.value.find((item: any) => item.account_id === account_id)
|
||||
if (currentAccount?.wallets_trade?.lever) {
|
||||
subAccountForm.value.leverage = currentAccount.wallets_trade.lever
|
||||
}
|
||||
dialogType.value = 'leverage'
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 复制推广二维码和推广链接方法
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -35,18 +35,27 @@ import { search, tradeOrderTableColumns, positionOrderTableColumns } from './opt
|
|||
import { getCustomTradeOrderListApi, getSubAccountListApi } from '@/api/modules/agent/customerTradeOrder'
|
||||
// 本地存储
|
||||
import { getStorage } from '@/utils/storage'
|
||||
// 日期处理
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
// 公用方法
|
||||
|
||||
/**
|
||||
* @description: 定义搜索数据
|
||||
*/
|
||||
// 获取近一周的时间范围
|
||||
const dateRange = {
|
||||
start: dayjs().subtract(7, 'day').format('YYYY-MM-DD'),
|
||||
end: dayjs().format('YYYY-MM-DD'),
|
||||
}
|
||||
const searchForm = ref({
|
||||
accountName: '',
|
||||
orderId: '2',
|
||||
tradeType: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
start_time: dateRange.start,
|
||||
end_time: dateRange.end,
|
||||
startTime: dateRange.start,
|
||||
endTime: dateRange.end,
|
||||
subAccountName: '',
|
||||
})
|
||||
const searchOptions = ref(search)
|
||||
|
|
@ -78,8 +87,8 @@ const getCustomerTradeOrderList = async () => {
|
|||
account_id: searchForm.value.subAccountName,
|
||||
order_no: '',
|
||||
type: searchForm.value.tradeType,
|
||||
start_time: '',
|
||||
end_time: '',
|
||||
start_time: dateRange.start,
|
||||
end_time: dateRange.end,
|
||||
}
|
||||
console.log(params)
|
||||
const data: any = await getCustomTradeOrderListApi(params)
|
||||
|
|
@ -94,6 +103,7 @@ const getCustomerTradeOrderList = async () => {
|
|||
quantity: item.num,
|
||||
openPrice: item.build_price,
|
||||
closePrice: item.close_price,
|
||||
closeProfitLoss: item.profit_loss,
|
||||
amount: item.amount,
|
||||
createdTime: item.created_at,
|
||||
}))
|
||||
|
|
@ -147,8 +157,10 @@ const handleReset = () => {
|
|||
accountName: '',
|
||||
orderId: '',
|
||||
tradeType: '',
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
startTime: dateRange.start,
|
||||
endTime: dateRange.end,
|
||||
start_time: dateRange.start,
|
||||
end_time: dateRange.end,
|
||||
subAccountName: '',
|
||||
}
|
||||
getCustomerTradeOrderList()
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ export const positionOrderTableColumns = [
|
|||
//平仓价格
|
||||
{ label: '平仓价格', prop: 'closePrice' },
|
||||
//金额
|
||||
{ label: '金额', prop: 'amount' },
|
||||
{ label: '平仓盈亏', prop: 'closeProfitLoss', align: 'center' as const, width: '150px' },
|
||||
//创建时间
|
||||
{ label: '创建时间', prop: 'createdTime' },
|
||||
]
|
||||
|
|
@ -1,99 +1,107 @@
|
|||
<template>
|
||||
<div>
|
||||
<!-- 搜索 -->
|
||||
<CustomerListSearch
|
||||
:search-form="searchForm"
|
||||
:search-options="searchOptions"
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
></CustomerListSearch>
|
||||
<CustomerListSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
|
||||
@reset="handleReset"></CustomerListSearch>
|
||||
<!-- 表格 -->
|
||||
<CustomerListTable
|
||||
:table-data="tableTree"
|
||||
:columns="tableColumnsOptions"
|
||||
row-key="userId"
|
||||
:default-sort="{ prop: 'createTime', order: 'descending' }"
|
||||
@expand-change="handleGetSubList"
|
||||
>
|
||||
<CustomerListTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="userId"
|
||||
:default-sort="{ prop: 'createTime', order: 'descending' }" @expand-change="handleGetSubList">
|
||||
<template #marginRatio="{ row }">
|
||||
<el-button
|
||||
v-if="row.role_id === 5"
|
||||
type="primary"
|
||||
size="small"
|
||||
v-auth="'agent_getSelfVarietyPointDiffConfig'"
|
||||
@click="handleMarginRatio(row)"
|
||||
>
|
||||
<el-button v-if="row.role_id === 5" type="primary" size="small" v-auth="'agent_getSelfVarietyPointDiffConfig'"
|
||||
@click="handleMarginRatio(row)">
|
||||
修改
|
||||
</el-button>
|
||||
</template>
|
||||
<template #commissionRatio="{ row }">
|
||||
{{ row.commissionRatio }}
|
||||
<el-button
|
||||
v-if="row.role_id === 5"
|
||||
type="primary"
|
||||
size="small"
|
||||
@click="handleCommissionRatio(row)"
|
||||
v-auth="'agent_editAgent'"
|
||||
>
|
||||
<el-button v-if="row.role_id === 5" type="primary" size="small" @click="handleCommissionRatio(row)"
|
||||
v-auth="'agent_editAgent'">
|
||||
修改
|
||||
</el-button>
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button link type="primary" size="small" @click="handleDetail(row)" v-if="row.userId">
|
||||
<!-- <el-button link type="primary" size="small" @click="handleDetail(row)" v-if="row.userId">
|
||||
查看详情
|
||||
</el-button> -->
|
||||
<!-- <el-divider direction="vertical" /> -->
|
||||
<el-button link type="primary" size="small" @click="handleEditCustomer(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link type="warning" size="small" @click="handleAdjustAmount(row)">
|
||||
调整金额
|
||||
</el-button>
|
||||
</template>
|
||||
</CustomerListTable>
|
||||
<!-- 分页 -->
|
||||
<CustomerListPagination
|
||||
v-model="currentPage"
|
||||
v-model:pageSizeValue="pageSize"
|
||||
:total="total"
|
||||
@pagination-change="handlePaginationChange"
|
||||
/>
|
||||
<CustomerListPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
|
||||
@pagination-change="handlePaginationChange" />
|
||||
<!-- dialog -->
|
||||
<CustomerListDialog
|
||||
:visible="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:type="dialogType"
|
||||
:fullscreen="dialogFullscreen"
|
||||
:show-footer="dialogType !== 'view'"
|
||||
@confirm="handleSubmit"
|
||||
@cancel="handleCancel"
|
||||
@close="handleClose"
|
||||
>
|
||||
<CustomerListTable
|
||||
:table-data="marginRatioTree"
|
||||
:columns="marginRatioTableColumnsOptions"
|
||||
row-key="id"
|
||||
v-if="dialogType === 'editMarginRatio'"
|
||||
style="height: 500px"
|
||||
>
|
||||
<CustomerListDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :fullscreen="dialogFullscreen"
|
||||
:show-footer="dialogType !== 'view'" @confirm="handleSubmit" @cancel="handleCancel" @close="handleClose">
|
||||
<CustomerListTable :table-data="marginRatioTree" :columns="marginRatioTableColumnsOptions" row-key="id"
|
||||
v-if="dialogType === 'editMarginRatio'" style="height: 500px">
|
||||
<template #assignedPointDiff="{ row }">
|
||||
<el-form
|
||||
:model="row"
|
||||
:rules="marginRatioRules(row)"
|
||||
:ref="(el) => setFormRef(el, row.id)"
|
||||
>
|
||||
<el-form :model="row" :rules="marginRatioRules(row)" :ref="(el) => setFormRef(el, row.id)">
|
||||
<el-form-item prop="assignedPointDifference">
|
||||
<el-input
|
||||
v-model="row.assignedPointDifference"
|
||||
size="small"
|
||||
placeholder="请输入点差"
|
||||
/>
|
||||
<el-input v-model="row.assignedPointDifference" size="small" placeholder="请输入点差" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</CustomerListTable>
|
||||
<CustomerListForm
|
||||
:form-data="commissionRatioForm"
|
||||
:form-rules="commissionRatioFormRules"
|
||||
:form-items="commissionRatioFormItemOptions"
|
||||
ref="commissionRatioFormRef"
|
||||
v-if="dialogType === 'editCommissionRatio'"
|
||||
></CustomerListForm>
|
||||
<CustomerListForm :form-data="commissionRatioForm" :form-rules="commissionRatioFormRules"
|
||||
:form-items="commissionRatioFormItemOptions" ref="commissionRatioFormRef"
|
||||
v-if="dialogType === 'editCommissionRatio'"></CustomerListForm>
|
||||
</CustomerListDialog>
|
||||
|
||||
<!-- 调整金额弹窗 -->
|
||||
<CustomerListDialog :visible="adjustAmountVisible" title="调整金额" type="adjust" @confirm="handleAdjustAmountSubmit"
|
||||
@cancel="handleAdjustAmountCancel" @close="handleAdjustAmountClose">
|
||||
<el-form :model="adjustAmountForm" :rules="adjustAmountRules" ref="adjustAmountFormRef" label-width="120px">
|
||||
<el-form-item label="账号名" prop="username">
|
||||
<el-input v-model="adjustAmountForm.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="币种" prop="currency">
|
||||
<el-input v-model="adjustAmountForm.currency" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前余额" prop="currentBalance">
|
||||
<el-input v-model="adjustAmountForm.currentBalance" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="调整金额" prop="adjustAmount">
|
||||
<el-input v-model="adjustAmountForm.adjustAmount" placeholder="请输入调整金额(可为负数)"
|
||||
@input="handleAdjustAmountInput" />
|
||||
</el-form-item>
|
||||
<el-form-item label="调整后余额" prop="afterBalance">
|
||||
<el-input v-model="adjustAmountForm.afterBalance" disabled />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</CustomerListDialog>
|
||||
|
||||
<!-- 修改客户弹窗 -->
|
||||
<CustomerListDialog :visible="editCustomerVisible" title="修改客户" type="editCustomer"
|
||||
@confirm="handleEditCustomerSubmit" @cancel="handleEditCustomerCancel" @close="handleEditCustomerClose">
|
||||
<el-form :model="editCustomerForm" :rules="editCustomerRules" ref="editCustomerFormRef" label-width="140px">
|
||||
<el-form-item label="手续费模板" prop="feeSettingId">
|
||||
<el-select v-model="editCustomerForm.feeSettingId" placeholder="请选择手续费模板" style="width: 100%">
|
||||
<el-option v-for="item in feeTemplateOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="风控模板" prop="riskControlId">
|
||||
<el-select v-model="editCustomerForm.riskControlId" placeholder="请选择风控模板" style="width: 100%">
|
||||
<el-option v-for="item in riskTemplateOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="客户名称" prop="username">
|
||||
<el-input v-model="editCustomerForm.username" placeholder="请输入客户名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="选择状态" prop="status">
|
||||
<el-radio-group v-model="editCustomerForm.status">
|
||||
<el-radio :label="1">正常</el-radio>
|
||||
<el-radio :label="2">封禁</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</CustomerListDialog>
|
||||
<!-- dialog表格 -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -113,6 +121,8 @@ import { search, tableColumns, marginRatioTableColumns, commissionRatioFormItem
|
|||
|
||||
// 接口
|
||||
import { getSelfVarietyPointDiffConfigApi, editCustomerApi } from '@/api/modules/agent/customerList'
|
||||
import { updateCapitalApi, editUserApi } from '@/api/modules/user'
|
||||
import { getFeeTemplateOptionsApi, getRiskTemplateOptionsApi } from '@/api/modules/account/client'
|
||||
import { getAgentListApi } from '@/api/modules/profile/agent.ts'
|
||||
|
||||
// 公用方法
|
||||
|
|
@ -212,6 +222,59 @@ const commissionRatioFormRules = {
|
|||
const commissionRatioFormItemOptions = ref(commissionRatioFormItem)
|
||||
const selectId = ref('')
|
||||
|
||||
/**
|
||||
* @description 定义修改客户弹窗数据
|
||||
*/
|
||||
const editCustomerVisible = ref(false)
|
||||
const editCustomerFormRef = ref()
|
||||
const editCustomerUserId = ref<number>(0)
|
||||
const editCustomerForm = ref({
|
||||
feeSettingId: '',
|
||||
riskControlId: '',
|
||||
username: '',
|
||||
status: 1,
|
||||
roleId: 0,
|
||||
})
|
||||
const editCustomerRules = {
|
||||
feeSettingId: [{ required: true, message: '请选择手续费模板', trigger: 'change' }],
|
||||
riskControlId: [{ required: true, message: '请选择风控模板', trigger: 'change' }],
|
||||
status: [{ required: true, message: '请选择状态', trigger: 'change' }],
|
||||
}
|
||||
const feeTemplateOptions = ref<any[]>([])
|
||||
const riskTemplateOptions = ref<any[]>([])
|
||||
const templateOptionsFetched = ref(false)
|
||||
|
||||
/**
|
||||
* @description 定义调整金额弹窗数据
|
||||
*/
|
||||
const adjustAmountVisible = ref(false)
|
||||
const adjustAmountFormRef = ref()
|
||||
const adjustUserId = ref<number>(0)
|
||||
const adjustAmountForm = ref({
|
||||
username: '',
|
||||
currency: '',
|
||||
currentBalance: '',
|
||||
adjustAmount: '',
|
||||
afterBalance: '',
|
||||
})
|
||||
const adjustAmountRules = {
|
||||
adjustAmount: [
|
||||
{ required: true, message: '请输入调整金额', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
callback(new Error('请输入调整金额'))
|
||||
} else if (!/^-?\d+(\.\d{1,5})?$/.test(value)) {
|
||||
callback(new Error('请输入有效的金额格式,最多五位小数'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 角色映射方法
|
||||
*/
|
||||
|
|
@ -355,10 +418,10 @@ const handleGetSubList = async (row: any) => {
|
|||
children:
|
||||
item.role_id === 5
|
||||
? [
|
||||
{
|
||||
hasChildren: true,
|
||||
},
|
||||
]
|
||||
{
|
||||
hasChildren: true,
|
||||
},
|
||||
]
|
||||
: undefined,
|
||||
}))
|
||||
}
|
||||
|
|
@ -435,6 +498,131 @@ const handleClose = () => {
|
|||
/**
|
||||
* @description: 定义弹窗表格方法
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description 调整金额相关方法
|
||||
*/
|
||||
// 打开调整金额弹窗
|
||||
const handleAdjustAmount = (row: any) => {
|
||||
adjustAmountVisible.value = true
|
||||
adjustUserId.value = row.userId
|
||||
const balance = row.balance || row.wallet_capital?.amount || '0'
|
||||
adjustAmountForm.value = {
|
||||
username: row.accountName,
|
||||
currency: 'USDT',
|
||||
currentBalance: balance,
|
||||
adjustAmount: '',
|
||||
afterBalance: balance,
|
||||
}
|
||||
}
|
||||
|
||||
// 调整金额输入变化时计算调整后余额
|
||||
const handleAdjustAmountInput = () => {
|
||||
const currentBalance = parseFloat(adjustAmountForm.value.currentBalance) || 0
|
||||
const adjustAmount = parseFloat(adjustAmountForm.value.adjustAmount) || 0
|
||||
const afterBalance = currentBalance + adjustAmount
|
||||
adjustAmountForm.value.afterBalance = afterBalance.toFixed(5)
|
||||
}
|
||||
|
||||
// 提交调整金额
|
||||
const handleAdjustAmountSubmit = async () => {
|
||||
try {
|
||||
await adjustAmountFormRef.value?.validate()
|
||||
await updateCapitalApi({
|
||||
target_user_id: adjustUserId.value,
|
||||
amount: adjustAmountForm.value.adjustAmount,
|
||||
})
|
||||
await getCustomerList()
|
||||
adjustAmountVisible.value = false
|
||||
} catch (err) {
|
||||
console.error('调整金额表单验证失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消调整金额
|
||||
const handleAdjustAmountCancel = () => {
|
||||
adjustAmountVisible.value = false
|
||||
}
|
||||
|
||||
// 关闭调整金额弹窗
|
||||
const handleAdjustAmountClose = () => {
|
||||
adjustAmountVisible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 修改客户相关方法
|
||||
*/
|
||||
// 获取模板选项
|
||||
const getTemplateOptions = async () => {
|
||||
if (templateOptionsFetched.value) return
|
||||
|
||||
try {
|
||||
const [feeTemplateRes, riskTemplateRes] = await Promise.all([
|
||||
getFeeTemplateOptionsApi(),
|
||||
getRiskTemplateOptionsApi(),
|
||||
])
|
||||
|
||||
feeTemplateOptions.value = Object.entries(feeTemplateRes as Record<string, string>).map(
|
||||
([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
}),
|
||||
)
|
||||
riskTemplateOptions.value = Object.entries(riskTemplateRes as Record<string, string>).map(
|
||||
([label, value]) => ({
|
||||
label,
|
||||
value,
|
||||
}),
|
||||
)
|
||||
|
||||
templateOptionsFetched.value = true
|
||||
} catch (error) {
|
||||
console.error('获取模板选项失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 打开修改客户弹窗
|
||||
const handleEditCustomer = async (row: any) => {
|
||||
await getTemplateOptions()
|
||||
editCustomerVisible.value = true
|
||||
editCustomerUserId.value = row.userId
|
||||
editCustomerForm.value = {
|
||||
feeSettingId: row.feeSettingId || '',
|
||||
riskControlId: row.riskControlId || '',
|
||||
username: row.accountName || '',
|
||||
status: row.status === '正常' ? 1 : 2,
|
||||
roleId: row.role_id,
|
||||
}
|
||||
}
|
||||
|
||||
// 提交修改客户
|
||||
const handleEditCustomerSubmit = async () => {
|
||||
try {
|
||||
await editCustomerFormRef.value?.validate()
|
||||
await editUserApi({
|
||||
user_id: editCustomerUserId.value,
|
||||
fee_setting_id: editCustomerForm.value.feeSettingId,
|
||||
risk_control_id: editCustomerForm.value.riskControlId,
|
||||
username: editCustomerForm.value.username,
|
||||
is_levelup: null,
|
||||
status: editCustomerForm.value.status,
|
||||
})
|
||||
await getCustomerList()
|
||||
editCustomerVisible.value = false
|
||||
} catch (err) {
|
||||
console.error('修改客户表单验证失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消修改客户
|
||||
const handleEditCustomerCancel = () => {
|
||||
editCustomerVisible.value = false
|
||||
}
|
||||
|
||||
// 关闭修改客户弹窗
|
||||
const handleEditCustomerClose = () => {
|
||||
editCustomerVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export const tableColumns = [
|
|||
// 注册时间
|
||||
{ prop: 'createTime', label: '注册时间', sortable: true },
|
||||
// 操作
|
||||
// { label: '操作', slotName: 'action' }
|
||||
{ label: '操作', slotName: 'action', width: '150px'}
|
||||
]
|
||||
|
||||
// 点差表格配置
|
||||
|
|
|
|||
|
|
@ -93,81 +93,69 @@
|
|||
@close="handleClose"
|
||||
>
|
||||
<!-- 表单 -->
|
||||
<MarketForm
|
||||
:form-data="marketForm"
|
||||
:form-rules="marketFormRules"
|
||||
:form-items="marketFormItemOptions"
|
||||
:options-map="marketFormOptionsMap"
|
||||
ref="marketFormRef"
|
||||
v-if="dialogType !== 'view'"
|
||||
>
|
||||
<template #phoneOrEmail="{ formData }" v-if="dialogType === 'add'">
|
||||
<div class="phone-email">
|
||||
<div class="tab-btn">
|
||||
<el-button
|
||||
:type="activeTab === 'email' ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="activeTab = 'email'"
|
||||
>
|
||||
邮箱
|
||||
</el-button>
|
||||
<el-button
|
||||
:type="activeTab === 'phone' ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="activeTab = 'phone'"
|
||||
>
|
||||
手机号
|
||||
</el-button>
|
||||
<div v-if="dialogType !== 'view'" class="drawer-form">
|
||||
<MarketForm :form-data="marketForm" :form-rules="marketFormRules" :form-items="marketFormItemOptions"
|
||||
:options-map="marketFormOptionsMap" ref="marketFormRef">
|
||||
<template #phoneOrEmail="{ formData }" v-if="dialogType === 'add'">
|
||||
<div class="phone-email">
|
||||
<div class="tab-btn">
|
||||
<el-button :type="activeTab === 'email' ? 'primary' : 'default'" size="small"
|
||||
@click="activeTab = 'email'">
|
||||
邮箱
|
||||
</el-button>
|
||||
<el-button :type="activeTab === 'phone' ? 'primary' : 'default'" size="small"
|
||||
@click="activeTab = 'phone'">
|
||||
手机号
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="group">
|
||||
<el-form-item prop="email" v-if="activeTab === 'email'">
|
||||
<el-input v-model="marketForm.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="phone" class="phone-item" v-if="activeTab === 'phone'">
|
||||
<el-select v-model="marketForm.countryCode" placeholder="请选择国家区号">
|
||||
<el-option v-for="item in countryOptions" :key="item.code" :label="item.code" :value="item.code">
|
||||
<div class="country-option">
|
||||
<span class="country-name">{{ item.name }}</span>
|
||||
<span class="country-code">{{ item.code }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-input v-model="marketForm.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
<div class="group">
|
||||
<el-form-item prop="email" v-if="activeTab === 'email'">
|
||||
<el-input v-model="marketForm.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="phone" class="phone-item" v-if="activeTab === 'phone'">
|
||||
<el-select v-model="marketForm.countryCode" placeholder="请选择国家区号">
|
||||
<el-option
|
||||
v-for="item in countryOptions"
|
||||
:key="item.code"
|
||||
:label="item.code"
|
||||
:value="item.code"
|
||||
>
|
||||
<div class="country-option">
|
||||
<span class="country-name">{{ item.name }}</span>
|
||||
<span class="country-code">{{ item.code }}</span>
|
||||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-input v-model="marketForm.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</MarketForm>
|
||||
</template>
|
||||
</MarketForm>
|
||||
</div>
|
||||
|
||||
<!-- 点差表格 -->
|
||||
<MarketTable
|
||||
:table-data="marketPointTree"
|
||||
:columns="marketPointTableColumnsOptions"
|
||||
row-key="id"
|
||||
style="height: 300px"
|
||||
>
|
||||
<template #assignedPointDiff="{ row }">
|
||||
<el-form :model="row" :rules="rules(marketForm, row)">
|
||||
<el-form-item prop="pointDifference">
|
||||
<el-input v-model="row.pointDifference" size="small" placeholder="请输入点差" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</MarketTable>
|
||||
</MarketDialog>
|
||||
<div class="drawer-table">
|
||||
<div class="table-header" v-if="dialogType !== 'view'">点差配置</div>
|
||||
<div class="table-wrapper">
|
||||
<MarketTable :table-data="marketPointTree" :columns="marketPointTableColumnsOptions" row-key="id">
|
||||
<template #assignedPointDiff="{ row }">
|
||||
<el-form :model="row" :rules="rules(marketForm, row)">
|
||||
<el-form-item prop="pointDifference">
|
||||
<el-input v-model="row.pointDifference" size="small" placeholder="请输入点差" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
</MarketTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<MarketDialog
|
||||
:visible="rightClickDialogVisible"
|
||||
:title="rightClickDialogTitleEnum[rightClickDialogType]"
|
||||
@confirm="handleRightClickDialogSubmit"
|
||||
@cancel="handleRightClickDialogCancel"
|
||||
@close="handleRightClickDialogClose"
|
||||
>
|
||||
<!-- 底部按钮 -->
|
||||
<template #footer v-if="dialogType !== 'view'">
|
||||
<div class="drawer-footer">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
<el-button type="primary" @click="handleSubmit">确定</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-drawer>
|
||||
|
||||
<MarketDialog :visible="rightClickDialogVisible" :title="rightClickDialogTitleEnum[rightClickDialogType]"
|
||||
@confirm="handleRightClickDialogSubmit" @cancel="handleRightClickDialogCancel" @close="handleRightClickDialogClose">
|
||||
<!-- 'account' | 'acceptOrders' | 'transfer' | 'deposit' | 'withdraw' -->
|
||||
<MarketForm
|
||||
:form-data="rightClickMarketForm"
|
||||
|
|
@ -178,6 +166,7 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
defineOptions({
|
||||
name: 'Business',
|
||||
})
|
||||
|
|
@ -247,7 +236,6 @@ const currentPage = ref(1)
|
|||
const dialogVisible = ref<boolean>(false)
|
||||
const dialogTitle = ref<string>('')
|
||||
const dialogType = ref<string>('add')
|
||||
const dialogFullscreen = ref(false)
|
||||
|
||||
/**
|
||||
* @description 定义dialog表单数据
|
||||
|
|
@ -269,7 +257,14 @@ const marketForm = ref({
|
|||
})
|
||||
const marketFormRules = rules(marketForm.value)
|
||||
const marketFormRef = ref()
|
||||
const marketFormItemOptions = ref(marketFormItem)
|
||||
const marketFormItemOptions = computed(() => {
|
||||
return marketFormItem.map((item) => {
|
||||
if (item.prop === 'parentAccountName' || item.prop === 'level') {
|
||||
return { ...item, disabled: dialogType.value === 'edit' }
|
||||
}
|
||||
return item
|
||||
})
|
||||
})
|
||||
interface SelectOption {
|
||||
label: string
|
||||
value: string | number
|
||||
|
|
@ -497,8 +492,8 @@ const handleMarketFormLinkage = async (type: 'level' | 'parentAccountName', valu
|
|||
}))
|
||||
}
|
||||
|
||||
// 2. 上级账户变更:获取点差列表
|
||||
if (type === 'parentAccountName') {
|
||||
// 2. 上级账户变更:获取点差列表(仅添加模式)
|
||||
if (type === 'parentAccountName' && dialogType.value === 'add') {
|
||||
// 清空点差数据
|
||||
marketPointTree.value = []
|
||||
|
||||
|
|
@ -521,7 +516,8 @@ const handleMarketFormLinkage = async (type: 'level' | 'parentAccountName', valu
|
|||
...item,
|
||||
varietyName: item.name,
|
||||
varietyCode: item.code,
|
||||
pointDifference: item.target_point_diff,
|
||||
point_diff: item.target_point_diff, // 添加时:可分配点差取 target_point_diff
|
||||
pointDifference: 0, // 添加时已分配点差初始化为0
|
||||
assignedPointDifference: '',
|
||||
}))
|
||||
}
|
||||
|
|
@ -632,7 +628,6 @@ const handleAddMarket = async () => {
|
|||
dialogType.value = 'add'
|
||||
// 2. 重置表单数据
|
||||
marketForm.value = { ...initMarketForm }
|
||||
dialogFullscreen.value = false
|
||||
}
|
||||
|
||||
// 编辑做市商
|
||||
|
|
@ -666,6 +661,9 @@ const handleEditMarket = async () => {
|
|||
console.log(marketForm.value)
|
||||
|
||||
try {
|
||||
// 先设置编辑模式,避免 watch 触发获取点差接口
|
||||
dialogType.value = 'edit'
|
||||
|
||||
// 先加载上级选项
|
||||
await handleMarketFormLinkage('level', marketForm.value.level)
|
||||
if (parentId) {
|
||||
|
|
@ -679,16 +677,15 @@ const handleEditMarket = async () => {
|
|||
...item,
|
||||
varietyName: item.name,
|
||||
varietyCode: item.code,
|
||||
pointDifference: item.point_diff,
|
||||
assignedPointDifference: item.target_point_diff || '', // 确保赋值正确
|
||||
point_diff: item.point_diff, // 编辑时:可分配点差取 point_diff
|
||||
pointDifference: item.target_point_diff,
|
||||
assignedPointDifference: item.target_point_diff || '',
|
||||
}))
|
||||
}
|
||||
|
||||
// 所有数据加载完成后,再打开弹窗
|
||||
// 所有数据加载完成后,再打开抽屉
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '编辑'
|
||||
dialogType.value = 'edit'
|
||||
dialogFullscreen.value = false
|
||||
} catch (error) {
|
||||
console.error('编辑数据加载失败:', error)
|
||||
}
|
||||
|
|
@ -700,7 +697,7 @@ const handleEditMarket = async () => {
|
|||
// 查看点差
|
||||
const handleViewPointDiff = async (row: any) => {
|
||||
try {
|
||||
const data = await getPointDiffConfigListApi({ user_id: row.role_id })
|
||||
const data = await getPointDiffConfigListApi({ user_id: row.id })
|
||||
marketPointTree.value = data.map((item: any) => ({
|
||||
...item,
|
||||
varietyName: item.name,
|
||||
|
|
@ -709,11 +706,10 @@ const handleViewPointDiff = async (row: any) => {
|
|||
assignedPointDifference: item.target_point_diff || '',
|
||||
}))
|
||||
|
||||
// 数据加载完成后再打开弹窗
|
||||
// 数据加载完成后再打开抽屉
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '查看点差'
|
||||
dialogType.value = 'view'
|
||||
dialogFullscreen.value = true
|
||||
} catch (error) {
|
||||
console.error('加载点差数据失败:', error)
|
||||
}
|
||||
|
|
@ -820,13 +816,7 @@ const handleSubmit = async () => {
|
|||
// 取消
|
||||
const handleCancel = async () => {
|
||||
dialogVisible.value = false
|
||||
// 重置点差数据和dialog类型
|
||||
}
|
||||
|
||||
// 关闭
|
||||
const handleClose = async () => {
|
||||
dialogVisible.value = false
|
||||
// 重置点差数据和dialog类型
|
||||
// 重置点差数据和drawer类型
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -834,6 +824,7 @@ const handleClose = async () => {
|
|||
:deep(.el-form-item__content) {
|
||||
margin-left: 0 !important;
|
||||
}
|
||||
|
||||
.market {
|
||||
.search-container {
|
||||
:deep(.el-form) {
|
||||
|
|
@ -877,7 +868,49 @@ const handleClose = async () => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.table-container {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
// 抽屉样式
|
||||
.drawer-form {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.drawer-table {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
min-height: 0;
|
||||
max-height: 100%;
|
||||
|
||||
.table-header {
|
||||
flex-shrink: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid #ebeef5;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -57,45 +57,35 @@ export const marketTableColumns = [
|
|||
|
||||
//做市商添加配置
|
||||
export const marketFormItem = [
|
||||
//邮箱或手机号
|
||||
{ prop: 'phoneOrEmail', label: '', type: 'slot' as const, slotName: 'phoneOrEmail' },
|
||||
//账户名称
|
||||
{ label: '账户名称', prop: 'accountName', type: 'input' as const, placeholder: '请输入账户名称' },
|
||||
//级别
|
||||
{ label: '级别', prop: 'level', type: 'select' as const, placeholder: '请选择级别' },
|
||||
//所属上级
|
||||
{
|
||||
label: '所属上级',
|
||||
prop: 'parentAccountName',
|
||||
type: 'select' as const,
|
||||
placeholder: '请选择所属上级账户名称',
|
||||
},
|
||||
// 风控模板
|
||||
{
|
||||
label: '风控模板',
|
||||
prop: 'riskControlTemplate',
|
||||
type: 'select' as const,
|
||||
placeholder: '请选择风控模板',
|
||||
},
|
||||
//头寸
|
||||
{ label: '头寸', prop: 'positionRatio', type: 'input' as const, placeholder: '请输入头寸' },
|
||||
//手续费
|
||||
{ label: '手续费', prop: 'commissionRatio', type: 'input' as const, placeholder: '请输入手续费' },
|
||||
//邮箱或手机号
|
||||
{ prop: 'phoneOrEmail', label: '', type: 'slot' as const, slotName: 'phoneOrEmail' },
|
||||
//账户名称
|
||||
{ label: '账户名称', prop: 'accountName', type: 'input' as const, placeholder: '请输入账户名称', },
|
||||
//级别
|
||||
{ label: '级别', prop: 'level', type: 'select' as const, placeholder: '请选择级别' },
|
||||
//所属上级
|
||||
{ label: '所属上级', prop: 'parentAccountName', type: 'select' as const, placeholder: '请选择所属上级账户名称', },
|
||||
// 风控模板
|
||||
{ label: '风控模板', prop: 'riskControlTemplate', type: 'select' as const, placeholder: '请选择风控模板' },
|
||||
//头寸
|
||||
{ label: '头寸', prop: 'positionRatio', type: 'input' as const, placeholder: '请输入头寸', },
|
||||
//手续费
|
||||
{ label: '手续费', prop: 'commissionRatio', type: 'input' as const, placeholder: '请输入手续费', },
|
||||
]
|
||||
|
||||
// 国家码选项
|
||||
interface CountryItem {
|
||||
name: string
|
||||
code: string
|
||||
name: string
|
||||
code: string
|
||||
}
|
||||
export const countryOptions = ref<CountryItem[]>([
|
||||
{ name: '中国', code: '+86' },
|
||||
{ name: '中国台湾', code: '+886' },
|
||||
{ name: '中国香港', code: '+852' },
|
||||
{ name: '中国澳门', code: '+853' },
|
||||
{ name: '韩国', code: '+82' },
|
||||
{ name: '美国', code: '+1' },
|
||||
{ name: '新加坡', code: '+65' },
|
||||
{ name: '中国', code: '+86' },
|
||||
{ name: '中国台湾', code: '+886' },
|
||||
{ name: '中国香港', code: '+852' },
|
||||
{ name: '中国澳门', code: '+853' },
|
||||
{ name: '韩国', code: '+82' },
|
||||
{ name: '美国', code: '+1' },
|
||||
{ name: '新加坡', code: '+65' }
|
||||
])
|
||||
|
||||
//编辑点差表格列配置
|
||||
|
|
|
|||
|
|
@ -377,6 +377,8 @@ const handleEditConfirm = async () => {
|
|||
const params: EditUsernameParams = { username: editForm.value };
|
||||
await editUsernameApi(params);
|
||||
userInfo.username = editForm.value;
|
||||
// 更新全局用户信息
|
||||
userStore.updateUserInfo({ username: editForm.value });
|
||||
}
|
||||
if (editField.value === 'mobile') {
|
||||
const mobile = editForm.countryCode + editForm.value;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@
|
|||
<el-button type="primary" size="small" @click="handleChange(row)" v-anth="'user_editUser'">
|
||||
修改
|
||||
</el-button>
|
||||
<el-button type="warning" size="small" @click="handleAdjustAmount(row)">
|
||||
调整金额
|
||||
</el-button>
|
||||
</template>
|
||||
</ClientTable>
|
||||
<!-- 分页组件 -->
|
||||
|
|
@ -92,6 +95,38 @@
|
|||
</template>
|
||||
</ClientForm>
|
||||
</ClientDialog>
|
||||
|
||||
<!-- 调整金额弹窗 -->
|
||||
<ClientDialog
|
||||
:visible="adjustAmountVisible"
|
||||
title="调整金额"
|
||||
type="adjust"
|
||||
@confirm="handleAdjustAmountSubmit"
|
||||
@cancel="handleAdjustAmountCancel"
|
||||
@close="handleAdjustAmountClose"
|
||||
>
|
||||
<el-form :model="adjustAmountForm" :rules="adjustAmountRules" ref="adjustAmountFormRef" label-width="120px">
|
||||
<el-form-item label="账号名" prop="username">
|
||||
<el-input v-model="adjustAmountForm.username" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="币种" prop="currency">
|
||||
<el-input v-model="adjustAmountForm.currency" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="当前余额" prop="currentBalance">
|
||||
<el-input v-model="adjustAmountForm.currentBalance" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="调整金额" prop="adjustAmount">
|
||||
<el-input
|
||||
v-model="adjustAmountForm.adjustAmount"
|
||||
placeholder="请输入调整金额(可为负数)"
|
||||
@input="handleAdjustAmountInput"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="调整后余额" prop="afterBalance">
|
||||
<el-input v-model="adjustAmountForm.afterBalance" disabled />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</ClientDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -114,6 +149,7 @@ import {
|
|||
createClientApi,
|
||||
editClientApi,
|
||||
} from '@/api/modules/account/client'
|
||||
import { updateCapitalApi } from '@/api/modules/user'
|
||||
|
||||
// 配置
|
||||
import {
|
||||
|
|
@ -154,6 +190,37 @@ const pageSize = ref(10)
|
|||
const dialogTitle = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
|
||||
/**
|
||||
* @description 定义调整金额弹窗数据
|
||||
*/
|
||||
const adjustAmountVisible = ref(false)
|
||||
const adjustAmountFormRef = ref()
|
||||
const adjustUserId = ref<number>(0)
|
||||
const adjustAmountForm = ref({
|
||||
username: '',
|
||||
currency: '',
|
||||
currentBalance: '',
|
||||
adjustAmount: '',
|
||||
afterBalance: '',
|
||||
})
|
||||
const adjustAmountRules = {
|
||||
adjustAmount: [
|
||||
{ required: true, message: '请输入调整金额', trigger: 'blur' },
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (value === '' || value === null || value === undefined) {
|
||||
callback(new Error('请输入调整金额'))
|
||||
} else if (!/^-?\d+(\.\d{1,5})?$/.test(value)) {
|
||||
callback(new Error('请输入有效的金额格式,最多五位小数'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
/**
|
||||
* @description 定义dialog中form的数据
|
||||
*/
|
||||
|
|
@ -403,6 +470,55 @@ const handleClose = () => {
|
|||
// 关闭弹窗
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 调整金额相关方法
|
||||
*/
|
||||
// 打开调整金额弹窗
|
||||
const handleAdjustAmount = (row: any) => {
|
||||
adjustAmountVisible.value = true
|
||||
adjustUserId.value = row.id
|
||||
adjustAmountForm.value = {
|
||||
username: row.username,
|
||||
currency: 'USDT', // 默认币种,可根据实际情况调整
|
||||
currentBalance: row.wallet_capital?.amount || '0',
|
||||
adjustAmount: '',
|
||||
afterBalance: row.wallet_capital?.amount || '0',
|
||||
}
|
||||
}
|
||||
|
||||
// 调整金额输入变化时计算调整后余额
|
||||
const handleAdjustAmountInput = () => {
|
||||
const currentBalance = parseFloat(adjustAmountForm.value.currentBalance) || 0
|
||||
const adjustAmount = parseFloat(adjustAmountForm.value.adjustAmount) || 0
|
||||
const afterBalance = currentBalance + adjustAmount
|
||||
adjustAmountForm.value.afterBalance = afterBalance.toFixed(5)
|
||||
}
|
||||
|
||||
// 提交调整金额
|
||||
const handleAdjustAmountSubmit = async () => {
|
||||
try {
|
||||
await adjustAmountFormRef.value?.validate()
|
||||
await updateCapitalApi({
|
||||
target_user_id: adjustUserId.value,
|
||||
amount: adjustAmountForm.value.adjustAmount,
|
||||
})
|
||||
await getClientList()
|
||||
adjustAmountVisible.value = false
|
||||
} catch (err) {
|
||||
console.error('调整金额表单验证失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 取消调整金额
|
||||
const handleAdjustAmountCancel = () => {
|
||||
adjustAmountVisible.value = false
|
||||
}
|
||||
|
||||
// 关闭调整金额弹窗
|
||||
const handleAdjustAmountClose = () => {
|
||||
adjustAmountVisible.value = false
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
|
|
|||
|
|
@ -33,9 +33,11 @@
|
|||
:options-map="roleFormOptionsMap" :dialog-type="dialogType">
|
||||
<template #permissions="{ formData }">
|
||||
<el-tree ref="permissionTreeRef" :data="permissionTreeData" :props="treeProps" show-checkbox node-key="id"
|
||||
:default-checked-keys="formData.permissions" :default-expand-all="true" class="permission-tree" />
|
||||
:default-expand-all="true" :check-strictly="false"
|
||||
class="permission-tree" />
|
||||
</template>
|
||||
</RoleForm>
|
||||
|
||||
<template #footer>
|
||||
<div style="flex: auto">
|
||||
<el-button @click="handleCancel">取消</el-button>
|
||||
|
|
@ -226,6 +228,39 @@ const transformPermissionsToTree = (permissions: any[]) => {
|
|||
return Array.from(moduleMap.values()).sort((a, b) => a.id - b.id)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 过滤掉父节点ID,只保留子节点ID用于显示选中状态
|
||||
*/
|
||||
const filterParentNodes = (permissionIds: number[], treeData: any[]): number[] => {
|
||||
// 获取所有父节点ID
|
||||
const parentNodeIds = treeData.map(node => node.id)
|
||||
// 过滤掉父节点ID,只保留子节点ID
|
||||
return permissionIds.filter(id => !parentNodeIds.includes(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 根据选中的子节点计算父节点ID(半选中的父节点)
|
||||
*/
|
||||
const getParentNodeIds = (checkedKeys: number[], treeData: any[]): number[] => {
|
||||
const parentIds: number[] = []
|
||||
|
||||
treeData.forEach(parent => {
|
||||
// 获取该父节点下所有子节点的ID
|
||||
const childIds = parent.children?.map((child: any) => child.id) || []
|
||||
// 检查是否有子节点被选中
|
||||
const hasCheckedChild = childIds.some((id: number) => checkedKeys.includes(id))
|
||||
// 检查是否所有子节点都被选中
|
||||
const allChildrenChecked = childIds.length > 0 && childIds.every((id: number) => checkedKeys.includes(id))
|
||||
|
||||
// 如果有子节点被选中,但不是全部选中,则父节点为半选中状态
|
||||
if (hasCheckedChild && !allChildrenChecked) {
|
||||
parentIds.push(parent.id)
|
||||
}
|
||||
})
|
||||
|
||||
return parentIds
|
||||
}
|
||||
|
||||
// 角色权限获取和设置
|
||||
const handlePermission = async (row: any) => {
|
||||
dialogVisible.value = true
|
||||
|
|
@ -237,8 +272,18 @@ const handlePermission = async (row: any) => {
|
|||
const data = await getRolePermissionApi({ id: row.id })
|
||||
allPermissions.value = data.all_permission_info
|
||||
permissionTreeData.value = transformPermissionsToTree(data.all_permission_info)
|
||||
roleForm.value.permissions = data.permission_ids || []
|
||||
|
||||
// 过滤掉父节点ID,只保留子节点ID
|
||||
const childPermissionIds = filterParentNodes(data.permission_ids || [], permissionTreeData.value)
|
||||
roleForm.value.permissions = childPermissionIds
|
||||
|
||||
console.log('树形权限数据', permissionTreeData.value)
|
||||
console.log('过滤后的权限ID(仅子节点):', childPermissionIds)
|
||||
|
||||
// 等待DOM更新后,手动设置选中状态
|
||||
await nextTick()
|
||||
// 使用 setCheckedKeys 设置选中状态,只设置子节点
|
||||
permissionTreeRef.value?.setCheckedKeys(childPermissionIds, false)
|
||||
} catch (err) {
|
||||
console.error('获取角色权限失败', err)
|
||||
}
|
||||
|
|
@ -262,14 +307,20 @@ const handleSubmit = async () => {
|
|||
await editRoleApi(roleForm.value)
|
||||
await fetchRoleList()
|
||||
} else {
|
||||
// 获取Tree组件选中的所有节点(不包括半选节点)
|
||||
// 获取Tree组件选中的所有节点(包括完全选中和半选中的节点)
|
||||
const checkedKeys = permissionTreeRef.value?.getCheckedKeys() || []
|
||||
const halfCheckedKeys = permissionTreeRef.value?.getHalfCheckedKeys() || []
|
||||
|
||||
// 合并完全选中和半选中的节点ID
|
||||
const allSelectedKeys = [...checkedKeys, ...halfCheckedKeys]
|
||||
|
||||
console.log('设置角色权限', checkedKeys.join(','))
|
||||
console.log('设置角色权限 - 完全选中:', checkedKeys)
|
||||
console.log('设置角色权限 - 半选中:', halfCheckedKeys)
|
||||
console.log('设置角色权限 - 合并后:', allSelectedKeys)
|
||||
|
||||
await setRolePermissionApi({
|
||||
id: permissionIds.value,
|
||||
permission_ids: checkedKeys.join(','),
|
||||
permission_ids: allSelectedKeys.join(','),
|
||||
})
|
||||
await fetchRoleList()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<UserText :user-text="userText" @back="handleBack" />
|
||||
<UserTabs v-model="activeTab" :hide-tab="hideTab" :tab-list="tabList" />
|
||||
|
||||
<el-form class="user-form" ref="loginFormRef" :model="loginForm" :rules="loginRules">
|
||||
<el-form class="user-form" ref="loginFormRef" :model="loginForm" :rules="loginRules" @submit.prevent="handleLogin">
|
||||
<Transition name="form-switch" mode="out-in">
|
||||
<el-form-item v-if="activeTab === 'email'" key="form-email-item" prop="email">
|
||||
<el-input v-model="loginForm.email" placeholder="请输入邮箱" clearable size="large" />
|
||||
|
|
@ -35,7 +35,7 @@
|
|||
</div>
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-input v-model="loginForm.phone" placeholder="请输入手机号" clearable size="large" />
|
||||
<el-input v-model="loginForm.phone" placeholder="请输入手机号" clearable size="large" @keyup.enter.prevent="handleLogin"/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-else-if="activeTab === 'password'" key="form-password-item" prop="password">
|
||||
|
|
@ -137,6 +137,8 @@ function generateTimestampWithRandomNum() {
|
|||
|
||||
// 登录
|
||||
const handleLogin = async () => {
|
||||
console.log('触发登录');
|
||||
|
||||
if (!loginFormRef.value) return
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,13 @@
|
|||
<el-input v-for="(item, index) in inputList" :key="index" v-model="inputList[index]" class="captcha-input-item"
|
||||
:maxlength="1" @input="handleInput(index, $event)" @focus="handleFocus(index)" @keyup.delete="handleDelete(index)"
|
||||
ref="inputRefs" type="text" />
|
||||
<el-button type="primary" class="form-btn" v-if="showSendButton" @click="sendCode" :disabled="sendState.isCounting">{{
|
||||
sendState.isCounting ? `${sendState.countdown}s后重新获取` : '发送验证码' }}</el-button>
|
||||
<el-button type="primary" class="form-btn" v-if="showSendButton" @click="sendCode" :disabled="isCounting">{{
|
||||
isCounting ? `${countdown}s后重新获取` : '发送验证码' }}</el-button>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, nextTick, toRefs, reactive, computed } from 'vue'
|
||||
|
||||
defineOptions({
|
||||
name: 'Captcha'
|
||||
|
|
@ -39,6 +40,21 @@ const props = defineProps({
|
|||
default: 'code' // 和父组件rules中的key一致
|
||||
},
|
||||
})
|
||||
|
||||
// 本地倒计时状态
|
||||
const isCounting = ref(false)
|
||||
const countdown = ref(0)
|
||||
|
||||
// 监听外部 sendState 变化
|
||||
watch(
|
||||
() => props.sendState,
|
||||
(newVal) => {
|
||||
isCounting.value = newVal.isCounting
|
||||
countdown.value = newVal.countdown
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
const { showSendButton } = toRefs(props)
|
||||
const emit = defineEmits(['update:modelValue', 'change', 'finish', 'sendCode'])
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue