This commit is contained in:
parent
6d180b94a8
commit
05650ed956
|
|
@ -49,8 +49,8 @@ export async function changePasswordApi(params: ChangePasswordParams) {
|
|||
* 退出登录
|
||||
* @returns Promise<void> 无返回值
|
||||
*/
|
||||
export async function logoutApi() {
|
||||
return request.post<void>('/logout');
|
||||
export async function logoutApi(params: { device_no: string }) {
|
||||
return request.post<void>('/logout', params);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -59,7 +59,8 @@ const handleChangePassword = () => {
|
|||
|
||||
const handleLogout = () => {
|
||||
// 示例:退出登录逻辑
|
||||
userStore.logout()
|
||||
const deviceNo = getStorage('device_no') as string
|
||||
userStore.logout({ device_no: deviceNo })
|
||||
}
|
||||
|
||||
// 处理菜单项点击
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const clearUserInfo = () => {
|
|||
removeStorage('userInfo');
|
||||
removeStorage('menusInfo');
|
||||
removeStorage('permissionsInfo');
|
||||
|
||||
removeStorage('device_no');
|
||||
console.log('用户信息已清空');
|
||||
};
|
||||
|
||||
|
|
@ -100,9 +100,9 @@ const register = async (params: RegisterParams) => {
|
|||
/**
|
||||
* 退出登录
|
||||
*/
|
||||
const logout = async () => {
|
||||
const logout = async (params: { device_no: string }) => {
|
||||
try {
|
||||
await logoutApi();
|
||||
await logoutApi(params);
|
||||
} catch (error) {
|
||||
console.error('退出登录接口调用失败', error);
|
||||
} finally {
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@
|
|||
</el-button>
|
||||
</template>
|
||||
</ClientSearch>
|
||||
<ClientTable :table-data="clientTree" :columns="clientTableColumns" row-key="id" :tree-props="treeProps">
|
||||
|
||||
<!-- 表格组件 -->
|
||||
<ClientTable :table-data="clientTree" :columns="clientTableColumns">
|
||||
<template #status="{ row }">
|
||||
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" active-text="启用"
|
||||
inactive-text="禁用" />
|
||||
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleChange(row)">
|
||||
|
|
@ -20,8 +21,9 @@
|
|||
</el-button>
|
||||
</template>
|
||||
</ClientTable>
|
||||
<ClientPagination :modelValue="clientSearchForm.currentPage" :pageSizeValue="clientSearchForm.pageSize"
|
||||
:total="total" @pagination-change="handlePaginationChange" />
|
||||
<!-- 分页组件 -->
|
||||
<ClientPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
|
||||
@pagination-change="handlePaginationChange" />
|
||||
</div>
|
||||
<!-- 客户弹窗 -->
|
||||
<ClientDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
|
||||
|
|
@ -31,31 +33,32 @@
|
|||
<template #phoneOrEmail="{ formData }">
|
||||
<div class="phone-email">
|
||||
<div class="tab-btn">
|
||||
<el-button type="primary" size="small" :class="{ 'active': activeTab === 'email' }"
|
||||
<el-button :type="activeTab === 'email' ? 'primary' : 'default'" size="small"
|
||||
@click="activeTab = 'email'">
|
||||
邮箱
|
||||
</el-button>
|
||||
<el-button type="primary" class="phone" size="small"
|
||||
:class="{ 'active': activeTab === 'phone' }" @click="activeTab = 'phone'">
|
||||
<el-button :type="activeTab === 'phone' ? 'primary' : 'default'" size="small"
|
||||
@click="activeTab = 'phone'">
|
||||
手机号
|
||||
</el-button>
|
||||
</div>
|
||||
<el-form-item prop="email" v-if="activeTab === 'email'" class="email-item">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="phone" v-else class="phone-item">
|
||||
<el-select v-model="formData.countryCode" :teleported="false" value-key="code" placeholder="国家"
|
||||
class="country-select">
|
||||
<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="formData.phone" placeholder="请输入手机号" clearable />
|
||||
</el-form-item>
|
||||
<div class="group">
|
||||
<el-form-item prop="email" v-if="activeTab === 'email'">
|
||||
<el-input v-model="clientForm.email" placeholder="请输入邮箱" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="phone" class="phone-item" v-if="activeTab === 'phone'">
|
||||
<el-select v-model="clientForm.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="clientForm.phone" placeholder="请输入手机号" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ClientForm>
|
||||
|
|
@ -76,31 +79,38 @@ import { getClientListApi, getFeeTemplateOptionsApi, getRiskTemplateOptionsApi,
|
|||
import { clientSearch, clientAddFormItem, clientChangeFormItem, countryOptions, clientTableColumns } from './options'
|
||||
import { rules } from './rules'
|
||||
|
||||
//定义客户搜索表单配置
|
||||
/**
|
||||
* @description 定义搜索数据
|
||||
*/
|
||||
const clientSearchOptions = ref(clientSearch)
|
||||
const clientSearchForm = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 1,
|
||||
username: '',
|
||||
status: '正常',
|
||||
startDateTime: '',
|
||||
endDateTime: '',
|
||||
})
|
||||
|
||||
//定义表格配置
|
||||
/**
|
||||
* @description 定义表格数据
|
||||
*/
|
||||
const clientTree = ref<any[]>([])
|
||||
const treeProps = ref({
|
||||
children: 'parent',
|
||||
label: 'username'
|
||||
})
|
||||
|
||||
// 定义分页配置
|
||||
/**
|
||||
* @description 定义分页数据
|
||||
*/
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10)
|
||||
|
||||
// 定义dialog配置
|
||||
/**
|
||||
* @description 定义dialog数据
|
||||
*/
|
||||
const dialogTitle = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
/**
|
||||
* @description 定义dialog中form的数据
|
||||
*/
|
||||
const clientForm = ref({
|
||||
//手续费模板
|
||||
feeTemplate: '',
|
||||
|
|
@ -116,28 +126,18 @@ const clientForm = ref({
|
|||
//是否变更客户名
|
||||
userName: '',
|
||||
status: 1,
|
||||
id: '',
|
||||
})
|
||||
|
||||
// 定义客户表单配置
|
||||
const clientFormRef = ref()
|
||||
const clientFormRules = ref(rules())
|
||||
const clientFormOptions = ref<any[]>(clientAddFormItem)
|
||||
// 定义客户表单选项切换
|
||||
const activeTab = ref('email')
|
||||
// 定义客户表单选项类型
|
||||
interface FormOption {
|
||||
label: string;
|
||||
value: string | number;
|
||||
}
|
||||
interface ClientFormOptionsMap {
|
||||
feeTemplate: FormOption[];
|
||||
riskTemplate: FormOption[];
|
||||
agentUsername: FormOption[];
|
||||
isUpgrade: FormOption[];
|
||||
status: FormOption[];
|
||||
}
|
||||
const clientFormOptionsMap = ref<ClientFormOptionsMap>({
|
||||
// 定义客户表单选项是否已获取
|
||||
const optionsFetched = ref(false)
|
||||
const useId = ref('')
|
||||
const clientFormOptions = computed(() => {
|
||||
return dialogType.value === 'add' ? clientAddFormItem : clientChangeFormItem;
|
||||
})
|
||||
const clientFormOptionsMap = ref<any>({
|
||||
feeTemplate: [],
|
||||
riskTemplate: [],
|
||||
agentUsername: [],
|
||||
|
|
@ -150,62 +150,12 @@ const clientFormOptionsMap = ref<ClientFormOptionsMap>({
|
|||
{ label: '禁用', value: 2 }
|
||||
],
|
||||
})
|
||||
// 定义客户表单选项切换
|
||||
const activeTab = ref('email')
|
||||
|
||||
// 获取账户列表
|
||||
const fetchClientList = async () => {
|
||||
const params = {
|
||||
page: clientSearchForm.value.currentPage,
|
||||
page_size: clientSearchForm.value.pageSize,
|
||||
username: clientSearchForm.value.username,
|
||||
status: clientSearchForm.value.status === '正常' ? 1 : 2,
|
||||
reg_start_time: clientSearchForm.value.startDateTime.length > 0 ? `${clientSearchForm.value.startDateTime} 0:00:00` : '',
|
||||
reg_end_time: clientSearchForm.value.endDateTime.length > 0 ? `${clientSearchForm.value.endDateTime} 23:59:59` : '',
|
||||
}
|
||||
const data = await getClientListApi(params)
|
||||
clientTree.value = data.data.map(item => ({
|
||||
...item,
|
||||
// 映射所属代理用户名
|
||||
agentUsername: item.par_uid === item.parent?.id ? item.parent?.username : '',
|
||||
}));
|
||||
//把接口返回的分页字段赋值给分页组件
|
||||
total.value = Number(data.total)
|
||||
clientSearchForm.value.currentPage = Number(data.current_page)
|
||||
clientSearchForm.value.pageSize = Number(data.per_page)
|
||||
}
|
||||
onMounted(async () => {
|
||||
await fetchClientList()
|
||||
})
|
||||
/**
|
||||
* @description: 搜索事件
|
||||
* @description 统一数据处理方法
|
||||
*/
|
||||
|
||||
// 搜索
|
||||
const handleSearch = async () => {
|
||||
try {
|
||||
await fetchClientList()
|
||||
} catch (err) {
|
||||
console.error('获取客户列表失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索重置
|
||||
const handleReset = () => {
|
||||
clientSearchForm.value = {
|
||||
currentPage: 1,
|
||||
pageSize: 1,
|
||||
username: '',
|
||||
status: '正常',
|
||||
startDateTime: '',
|
||||
endDateTime: '',
|
||||
}
|
||||
fetchClientList() // 重置后自动刷新列表
|
||||
}
|
||||
|
||||
// 客户表单选项切换
|
||||
const handleDialogType = (type: string) => {
|
||||
clientFormOptions.value = type === 'add' ? clientAddFormItem : clientChangeFormItem
|
||||
}
|
||||
|
||||
// 映射对象为数组通用方法
|
||||
const mapObjectToOptions = (obj: Record<string, string>) => {
|
||||
return Object.entries(obj).map(([label, value]) => ({
|
||||
|
|
@ -214,8 +164,34 @@ const mapObjectToOptions = (obj: Record<string, string>) => {
|
|||
}))
|
||||
}
|
||||
|
||||
//统一获取表单选项
|
||||
const optionsFetched = ref(false)
|
||||
/**
|
||||
* @description 统一获取数据方法
|
||||
*/
|
||||
const getClientList = async () => {
|
||||
const params = {
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
username: clientSearchForm.value.username,
|
||||
status: clientSearchForm.value.status === '正常' ? 1 : 2,
|
||||
reg_start_time: clientSearchForm.value.startDateTime.length > 0 ? `${clientSearchForm.value.startDateTime} 0:00:00` : '',
|
||||
reg_end_time: clientSearchForm.value.endDateTime.length > 0 ? `${clientSearchForm.value.endDateTime} 23:59:59` : '',
|
||||
}
|
||||
const data = await getClientListApi(params)
|
||||
// 处理表格数据
|
||||
clientTree.value = data.data.map(item => ({
|
||||
...item,
|
||||
//注册时间
|
||||
createdTime: item.created_at,
|
||||
// 映射所属代理用户名
|
||||
agentUsername: item.par_uid === item.parent?.id ? item.parent?.username : '',
|
||||
}));
|
||||
//分页数据
|
||||
total.value = Number(data.total)
|
||||
currentPage.value = Number(data.current_page)
|
||||
pageSize.value = Number(data.per_page)
|
||||
}
|
||||
|
||||
// 获取客户表单选项
|
||||
const getOptions = async () => {
|
||||
// 已获取过选项则直接返回,避免重复请求
|
||||
if (optionsFetched.value) return
|
||||
|
|
@ -227,7 +203,7 @@ const getOptions = async () => {
|
|||
getRiskTemplateOptionsApi()
|
||||
])
|
||||
|
||||
// 修正映射:label=名称,value=ID
|
||||
// label=名称,value=ID
|
||||
clientFormOptionsMap.value.agentUsername = mapObjectToOptions(agentRes as Record<string, string>)
|
||||
clientFormOptionsMap.value.feeTemplate = mapObjectToOptions(feeTemplateRes as Record<string, string>)
|
||||
clientFormOptionsMap.value.riskTemplate = mapObjectToOptions(riskTemplateRes as Record<string, string>)
|
||||
|
|
@ -235,57 +211,90 @@ const getOptions = async () => {
|
|||
optionsFetched.value = true // 标记已获取
|
||||
} catch (error) {
|
||||
console.error('获取表单选项失败:', error)
|
||||
ElMessage.error('获取表单选项失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
/**
|
||||
* @description 页面加载后获取数据
|
||||
*/
|
||||
onMounted(async () => {
|
||||
await getClientList()
|
||||
})
|
||||
|
||||
/**
|
||||
* @description 搜索方法
|
||||
*/
|
||||
const handleSearch = async () => {
|
||||
try {
|
||||
await getClientList()
|
||||
} catch (err) {
|
||||
console.error('获取客户列表失败', err)
|
||||
}
|
||||
}
|
||||
const handleReset = async () => {
|
||||
// 重置搜索表单
|
||||
clientSearchForm.value = {
|
||||
username: '',
|
||||
status: '正常',
|
||||
startDateTime: '',
|
||||
endDateTime: '',
|
||||
}
|
||||
try {
|
||||
await getClientList()
|
||||
} catch (err) {
|
||||
console.error('获取客户列表失败', err)
|
||||
}
|
||||
}
|
||||
// 添加客户
|
||||
const handleAdd = () => {
|
||||
// 打开添加客户弹窗
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '添加客户'
|
||||
dialogType.value = 'add'
|
||||
clientForm.value = {
|
||||
//手续费模板
|
||||
feeTemplate: '',
|
||||
//风控模板
|
||||
riskTemplate: '',
|
||||
//所属上级名
|
||||
agentUsername: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
countryCode: '+86',
|
||||
//是否升级
|
||||
isUpgrade: 1,
|
||||
//是否变更客户名
|
||||
userName: '',
|
||||
status: 1,
|
||||
}
|
||||
getOptions()
|
||||
}
|
||||
/**
|
||||
* @description 表格方法
|
||||
*/
|
||||
const handleChange = (row: any) => {
|
||||
// 打开修改客户弹窗
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '修改客户'
|
||||
dialogType.value = 'change'
|
||||
useId.value = row.id
|
||||
getOptions()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 表格修改事件
|
||||
* @description 分页方法
|
||||
*/
|
||||
|
||||
const handleChange = async (row: any) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '修改客户'
|
||||
dialogType.value = 'edit'
|
||||
handleDialogType(dialogType.value)
|
||||
//获取表单选项
|
||||
await getOptions()
|
||||
// 核心:将选中行的数值赋值给表单,尤其是id
|
||||
clientForm.value = {
|
||||
id: row.id, // 关键:传递用户ID
|
||||
feeTemplate: row.feeTemplate || '', // 回显手续费模板
|
||||
riskTemplate: row.riskTemplate || '', // 回显风控模板
|
||||
agentUsername: row.agentUsername || '', // 回显所属上级
|
||||
email: row.email || '', // 回显邮箱
|
||||
phone: row.phone || '', // 回显手机号
|
||||
countryCode: row.countryCode || '+86', // 回显国家码
|
||||
isUpgrade: row.isUpgrade || 1, // 回显是否升级
|
||||
userName: row.username || '', // 回显用户名
|
||||
status: row.status || 1, // 回显状态
|
||||
}
|
||||
}
|
||||
|
||||
// 分页事件
|
||||
const handlePaginationChange = (page: { currentPage: number, pageSize: number }) => {
|
||||
clientSearchForm.value.currentPage = page.currentPage
|
||||
clientSearchForm.value.pageSize = page.pageSize
|
||||
currentPage.value = page.currentPage
|
||||
pageSize.value = page.pageSize
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: dialog事件
|
||||
* @description 客户弹窗方法
|
||||
*/
|
||||
|
||||
// 客户添加
|
||||
const handleAdd = async () => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = 'add'
|
||||
dialogTitle.value = '添加客户'
|
||||
handleDialogType(dialogType.value)
|
||||
//获取表单选项
|
||||
await getOptions()
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await clientFormRef.value?.validate()
|
||||
|
|
@ -299,11 +308,11 @@ const handleSubmit = async () => {
|
|||
fee_setting_id: clientForm.value.feeTemplate,
|
||||
}
|
||||
await createClientApi(params)
|
||||
await fetchClientList()
|
||||
await getClientList()
|
||||
dialogVisible.value = false
|
||||
} else {
|
||||
const params = {
|
||||
user_id: clientForm.value.id,
|
||||
user_id: useId.value,
|
||||
risk_control_id: clientForm.value.riskTemplate,
|
||||
fee_setting_id: clientForm.value.feeTemplate,
|
||||
username: clientForm.value.userName,
|
||||
|
|
@ -311,87 +320,54 @@ const handleSubmit = async () => {
|
|||
status: clientForm.value.status,
|
||||
}
|
||||
await editClientApi(params)
|
||||
await fetchClientList()
|
||||
await getClientList()
|
||||
dialogVisible.value = false
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('客户表单验证失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
clientForm.value = {
|
||||
//手续费模板
|
||||
feeTemplate: '',
|
||||
//风控模板
|
||||
riskTemplate: '',
|
||||
//所属上级
|
||||
agentUsername: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
countryCode: '+86',
|
||||
//是否升级
|
||||
isUpgrade: 1,
|
||||
//是否变更客户名
|
||||
userName: '',
|
||||
status: 1,
|
||||
id: '',
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗取消
|
||||
const handleCancel = () => {
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false
|
||||
dialogType.value = 'add'
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 弹窗关闭
|
||||
const handleClose = () => {
|
||||
// 关闭弹窗
|
||||
dialogVisible.value = false
|
||||
dialogType.value = 'add'
|
||||
resetForm()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style scoped lang="scss">
|
||||
.phone-email {
|
||||
display: flex;
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
flex: 1;
|
||||
|
||||
.tab-btn {
|
||||
display: flex;
|
||||
margin-right: 10px;
|
||||
|
||||
.el-button {
|
||||
margin-left: 0;
|
||||
background-color: transparent;
|
||||
border: 1px solid #DCDFE6;
|
||||
color: #303133;
|
||||
height: 32px;
|
||||
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #409EFF;
|
||||
color: #fff;
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.email-item {
|
||||
.el-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.group {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
||||
.phone-item {
|
||||
.el-select {
|
||||
width: 120px;
|
||||
margin-right: 10px;
|
||||
.el-form-item {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.phone-item {
|
||||
:deep(.el-form-item__content) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.el-select {
|
||||
width: 110px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export const clientSearch = [
|
|||
export const clientTableColumns = [
|
||||
{ prop: 'id', label: 'ID', align: 'center' as const },
|
||||
{ prop: 'username', label: '用户名', align: 'center' as const },
|
||||
{ prop: 'created_at', label: '注册时间', align: 'center' as const },
|
||||
{ prop: 'createdTime', label: '注册时间', align: 'center' as const },
|
||||
{ prop: 'agentUsername', label: '所属代理', align: 'center' as const },
|
||||
{ label: '状态', slotName: 'status', align: 'center' as const },
|
||||
{ label: '操作', slotName: 'action', align: 'center' as const }
|
||||
|
|
@ -62,10 +62,10 @@ export const clientTableColumns = [
|
|||
export const clientAddFormItem = [
|
||||
// { prop: 'clientName', label: '客户名称', type: 'input' as const, placeholder: '客户名称' },
|
||||
// { prop: 'isUpgrade', label: '是否升级为代理', type: 'radio' as const, placeholder: '是否升级为代理' },
|
||||
{ prop: 'phoneOrEmail', label: '', type: 'slot' as const, slotName: 'phoneOrEmail' },
|
||||
{ prop: 'feeTemplate', label: '手续费模板', type: 'select' as const, placeholder: '选择手续费模板' },
|
||||
{ prop: 'riskTemplate', label: '风控模板', type: 'select' as const, placeholder: '选择风控模板' },
|
||||
{ prop: 'agentUsername', label: '所属上级', type: 'select' as const, placeholder: '所属上级' },
|
||||
{ prop: 'phoneOrEmail', label: '', type: 'slot' as const, slotName: 'phoneOrEmail' },
|
||||
]
|
||||
|
||||
//定义修改客户form配置
|
||||
|
|
|
|||
|
|
@ -0,0 +1,398 @@
|
|||
<template>
|
||||
<div class="client">
|
||||
<!-- 搜索组件 -->
|
||||
<ClientSearch :search-form="clientSearchForm" :search-options="clientSearchOptions" @search="handleSearch"
|
||||
@reset="handleReset">
|
||||
<template #button="{ form }">
|
||||
<el-button type="primary" @click="handleAdd()">
|
||||
添加客户
|
||||
</el-button>
|
||||
</template>
|
||||
</ClientSearch>
|
||||
<ClientTable :table-data="clientTree" :columns="clientTableColumns" row-key="id" :tree-props="treeProps">
|
||||
<template #status="{ row }">
|
||||
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" active-text="启用"
|
||||
inactive-text="禁用" />
|
||||
</template>
|
||||
<template #action="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleChange(row)">
|
||||
修改
|
||||
</el-button>
|
||||
</template>
|
||||
</ClientTable>
|
||||
<ClientPagination :modelValue="clientSearchForm.currentPage" :pageSizeValue="clientSearchForm.pageSize"
|
||||
:total="total" @pagination-change="handlePaginationChange" />
|
||||
</div>
|
||||
<!-- 客户弹窗 -->
|
||||
<ClientDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
|
||||
@cancel="handleCancel" @close="handleClose">
|
||||
<ClientForm :form-data="clientForm" :form-rules="clientFormRules" :form-items="clientFormOptions"
|
||||
:options-map="clientFormOptionsMap" ref="clientFormRef">
|
||||
<template #phoneOrEmail="{ formData }">
|
||||
<div class="phone-email">
|
||||
<div class="tab-btn">
|
||||
<el-button type="primary" size="small" :class="{ 'active': activeTab === 'email' }"
|
||||
@click="activeTab = 'email'">
|
||||
邮箱
|
||||
</el-button>
|
||||
<el-button type="primary" class="phone" size="small"
|
||||
:class="{ 'active': activeTab === 'phone' }" @click="activeTab = 'phone'">
|
||||
手机号
|
||||
</el-button>
|
||||
</div>
|
||||
<el-form-item prop="email" v-if="activeTab === 'email'" class="email-item">
|
||||
<el-input v-model="formData.email" placeholder="请输入邮箱"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="phone" v-else class="phone-item">
|
||||
<el-select v-model="formData.countryCode" :teleported="false" value-key="code" placeholder="国家"
|
||||
class="country-select">
|
||||
<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="formData.phone" placeholder="请输入手机号" clearable />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</template>
|
||||
</ClientForm>
|
||||
</ClientDialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
// 组件
|
||||
import ClientSearch from '@/components/common/Search.vue'
|
||||
import ClientTable from '@/components/common/Table.vue'
|
||||
import ClientPagination from '@/components/common/Pagination.vue'
|
||||
import ClientDialog from '@/components/common/Dialog.vue'
|
||||
import ClientForm from '@/components/common/Form.vue'
|
||||
// 接口
|
||||
import { getClientListApi, getFeeTemplateOptionsApi, getRiskTemplateOptionsApi, getAgentOptionsApi, createClientApi, editClientApi } from '@/api/modules/account/client'
|
||||
|
||||
// 配置
|
||||
import { clientSearch, clientAddFormItem, clientChangeFormItem, countryOptions, clientTableColumns } from './options'
|
||||
import { rules } from './rules'
|
||||
|
||||
//定义客户搜索表单配置
|
||||
const clientSearchOptions = ref(clientSearch)
|
||||
const clientSearchForm = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 1,
|
||||
username: '',
|
||||
status: '正常',
|
||||
startDateTime: '',
|
||||
endDateTime: '',
|
||||
})
|
||||
|
||||
//定义表格配置
|
||||
const clientTree = ref<any[]>([])
|
||||
const treeProps = ref({
|
||||
children: 'parent',
|
||||
label: 'username'
|
||||
})
|
||||
|
||||
// 定义分页配置
|
||||
const total = ref(0)
|
||||
|
||||
// 定义dialog配置
|
||||
const dialogTitle = ref('')
|
||||
const dialogVisible = ref(false)
|
||||
const dialogType = ref('add')
|
||||
const clientForm = ref({
|
||||
//手续费模板
|
||||
feeTemplate: '',
|
||||
//风控模板
|
||||
riskTemplate: '',
|
||||
//所属上级名
|
||||
agentUsername: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
countryCode: '+86',
|
||||
//是否升级
|
||||
isUpgrade: 1,
|
||||
//是否变更客户名
|
||||
userName: '',
|
||||
status: 1,
|
||||
id: '',
|
||||
})
|
||||
|
||||
// 定义客户表单配置
|
||||
const clientFormRef = ref()
|
||||
const clientFormRules = ref(rules())
|
||||
const clientFormOptions = ref<any[]>(clientAddFormItem)
|
||||
// 定义客户表单选项切换
|
||||
const activeTab = ref('email')
|
||||
// 定义客户表单选项类型
|
||||
interface FormOption {
|
||||
label: string;
|
||||
value: string | number;
|
||||
}
|
||||
interface ClientFormOptionsMap {
|
||||
feeTemplate: FormOption[];
|
||||
riskTemplate: FormOption[];
|
||||
agentUsername: FormOption[];
|
||||
isUpgrade: FormOption[];
|
||||
status: FormOption[];
|
||||
}
|
||||
const clientFormOptionsMap = ref<ClientFormOptionsMap>({
|
||||
feeTemplate: [],
|
||||
riskTemplate: [],
|
||||
agentUsername: [],
|
||||
isUpgrade: [
|
||||
{ label: '是', value: 1 },
|
||||
{ label: '否', value: 2 }
|
||||
],
|
||||
status: [
|
||||
{ label: '正常', value: 1 },
|
||||
{ label: '禁用', value: 2 }
|
||||
],
|
||||
})
|
||||
|
||||
// 获取账户列表
|
||||
const fetchClientList = async () => {
|
||||
const params = {
|
||||
page: clientSearchForm.value.currentPage,
|
||||
page_size: clientSearchForm.value.pageSize,
|
||||
username: clientSearchForm.value.username,
|
||||
status: clientSearchForm.value.status === '正常' ? 1 : 2,
|
||||
reg_start_time: clientSearchForm.value.startDateTime.length > 0 ? `${clientSearchForm.value.startDateTime} 0:00:00` : '',
|
||||
reg_end_time: clientSearchForm.value.endDateTime.length > 0 ? `${clientSearchForm.value.endDateTime} 23:59:59` : '',
|
||||
}
|
||||
const data = await getClientListApi(params)
|
||||
clientTree.value = data.data.map(item => ({
|
||||
...item,
|
||||
// 映射所属代理用户名
|
||||
agentUsername: item.par_uid === item.parent?.id ? item.parent?.username : '',
|
||||
}));
|
||||
//把接口返回的分页字段赋值给分页组件
|
||||
total.value = Number(data.total)
|
||||
clientSearchForm.value.currentPage = Number(data.current_page)
|
||||
clientSearchForm.value.pageSize = Number(data.per_page)
|
||||
}
|
||||
onMounted(async () => {
|
||||
await fetchClientList()
|
||||
})
|
||||
/**
|
||||
* @description: 搜索事件
|
||||
*/
|
||||
|
||||
// 搜索
|
||||
const handleSearch = async () => {
|
||||
try {
|
||||
await fetchClientList()
|
||||
} catch (err) {
|
||||
console.error('获取客户列表失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
// 搜索重置
|
||||
const handleReset = () => {
|
||||
clientSearchForm.value = {
|
||||
currentPage: 1,
|
||||
pageSize: 1,
|
||||
username: '',
|
||||
status: '正常',
|
||||
startDateTime: '',
|
||||
endDateTime: '',
|
||||
}
|
||||
fetchClientList() // 重置后自动刷新列表
|
||||
}
|
||||
|
||||
// 客户表单选项切换
|
||||
const handleDialogType = (type: string) => {
|
||||
clientFormOptions.value = type === 'add' ? clientAddFormItem : clientChangeFormItem
|
||||
}
|
||||
|
||||
// 映射对象为数组通用方法
|
||||
const mapObjectToOptions = (obj: Record<string, string>) => {
|
||||
return Object.entries(obj).map(([label, value]) => ({
|
||||
label, // 模板/代理名称
|
||||
value // 模板/代理ID
|
||||
}))
|
||||
}
|
||||
|
||||
//统一获取表单选项
|
||||
const optionsFetched = ref(false)
|
||||
const getOptions = async () => {
|
||||
// 已获取过选项则直接返回,避免重复请求
|
||||
if (optionsFetched.value) return
|
||||
|
||||
try {
|
||||
const [agentRes, feeTemplateRes, riskTemplateRes] = await Promise.all([
|
||||
getAgentOptionsApi(),
|
||||
getFeeTemplateOptionsApi(),
|
||||
getRiskTemplateOptionsApi()
|
||||
])
|
||||
|
||||
// 修正映射:label=名称,value=ID
|
||||
clientFormOptionsMap.value.agentUsername = mapObjectToOptions(agentRes as Record<string, string>)
|
||||
clientFormOptionsMap.value.feeTemplate = mapObjectToOptions(feeTemplateRes as Record<string, string>)
|
||||
clientFormOptionsMap.value.riskTemplate = mapObjectToOptions(riskTemplateRes as Record<string, string>)
|
||||
|
||||
optionsFetched.value = true // 标记已获取
|
||||
} catch (error) {
|
||||
console.error('获取表单选项失败:', error)
|
||||
ElMessage.error('获取表单选项失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 表格修改事件
|
||||
*/
|
||||
|
||||
const handleChange = async (row: any) => {
|
||||
dialogVisible.value = true
|
||||
dialogTitle.value = '修改客户'
|
||||
dialogType.value = 'edit'
|
||||
handleDialogType(dialogType.value)
|
||||
//获取表单选项
|
||||
await getOptions()
|
||||
// 核心:将选中行的数值赋值给表单,尤其是id
|
||||
clientForm.value = {
|
||||
id: row.id, // 关键:传递用户ID
|
||||
feeTemplate: row.feeTemplate || '', // 回显手续费模板
|
||||
riskTemplate: row.riskTemplate || '', // 回显风控模板
|
||||
agentUsername: row.agentUsername || '', // 回显所属上级
|
||||
email: row.email || '', // 回显邮箱
|
||||
phone: row.phone || '', // 回显手机号
|
||||
countryCode: row.countryCode || '+86', // 回显国家码
|
||||
isUpgrade: row.isUpgrade || 1, // 回显是否升级
|
||||
userName: row.username || '', // 回显用户名
|
||||
status: row.status || 1, // 回显状态
|
||||
}
|
||||
}
|
||||
|
||||
// 分页事件
|
||||
const handlePaginationChange = (page: { currentPage: number, pageSize: number }) => {
|
||||
clientSearchForm.value.currentPage = page.currentPage
|
||||
clientSearchForm.value.pageSize = page.pageSize
|
||||
handleSearch()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: dialog事件
|
||||
*/
|
||||
|
||||
// 客户添加
|
||||
const handleAdd = async () => {
|
||||
dialogVisible.value = true
|
||||
dialogType.value = 'add'
|
||||
dialogTitle.value = '添加客户'
|
||||
handleDialogType(dialogType.value)
|
||||
//获取表单选项
|
||||
await getOptions()
|
||||
}
|
||||
|
||||
const handleSubmit = async () => {
|
||||
try {
|
||||
await clientFormRef.value?.validate()
|
||||
if (dialogType.value === 'add') {
|
||||
const params = {
|
||||
reg_type: activeTab.value === 'email' ? 1 : 2,
|
||||
email: clientForm.value.email,
|
||||
mobile: clientForm.value.phone,
|
||||
parent_id: clientForm.value.agentUsername,
|
||||
risk_control_id: clientForm.value.riskTemplate,
|
||||
fee_setting_id: clientForm.value.feeTemplate,
|
||||
}
|
||||
await createClientApi(params)
|
||||
await fetchClientList()
|
||||
dialogVisible.value = false
|
||||
} else {
|
||||
const params = {
|
||||
user_id: clientForm.value.id,
|
||||
risk_control_id: clientForm.value.riskTemplate,
|
||||
fee_setting_id: clientForm.value.feeTemplate,
|
||||
username: clientForm.value.userName,
|
||||
is_levelup: clientForm.value.isUpgrade,
|
||||
status: clientForm.value.status,
|
||||
}
|
||||
await editClientApi(params)
|
||||
await fetchClientList()
|
||||
dialogVisible.value = false
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('客户表单验证失败', err)
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
clientForm.value = {
|
||||
//手续费模板
|
||||
feeTemplate: '',
|
||||
//风控模板
|
||||
riskTemplate: '',
|
||||
//所属上级
|
||||
agentUsername: '',
|
||||
email: '',
|
||||
phone: '',
|
||||
countryCode: '+86',
|
||||
//是否升级
|
||||
isUpgrade: 1,
|
||||
//是否变更客户名
|
||||
userName: '',
|
||||
status: 1,
|
||||
id: '',
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 弹窗取消
|
||||
const handleCancel = () => {
|
||||
dialogVisible.value = false
|
||||
dialogType.value = 'add'
|
||||
resetForm()
|
||||
}
|
||||
|
||||
// 弹窗关闭
|
||||
const handleClose = () => {
|
||||
dialogVisible.value = false
|
||||
dialogType.value = 'add'
|
||||
resetForm()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.phone-email {
|
||||
display: flex;
|
||||
|
||||
:deep(.el-form-item__content) {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
display: flex;
|
||||
margin-right: 10px;
|
||||
|
||||
.el-button {
|
||||
margin-left: 0;
|
||||
background-color: transparent;
|
||||
border: 1px solid #DCDFE6;
|
||||
color: #303133;
|
||||
height: 32px;
|
||||
|
||||
}
|
||||
|
||||
.active {
|
||||
background-color: #409EFF;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.email-item {
|
||||
.el-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.phone-item {
|
||||
.el-select {
|
||||
width: 120px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -88,6 +88,22 @@ const loginRules = ref<FormRules>({
|
|||
|
||||
//方法
|
||||
|
||||
/**
|
||||
* 生成时间戳拼接6位随机数字的字符串
|
||||
* @returns {string} 格式示例:1710688800000123456
|
||||
*/
|
||||
function generateTimestampWithRandomNum() {
|
||||
// 1. 获取当前时间戳(毫秒级)
|
||||
const timestamp = Date.now();
|
||||
|
||||
// 2. 生成6位随机数字(范围:000000 ~ 999999)
|
||||
// Math.random()生成0~1的随机数,乘以1000000后取整,不足6位补0
|
||||
const random6Num = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
|
||||
|
||||
// 3. 拼接并返回
|
||||
return timestamp + random6Num;
|
||||
}
|
||||
|
||||
// 登录
|
||||
const handleLogin = async () => {
|
||||
if (!loginFormRef.value) return;
|
||||
|
|
@ -120,15 +136,19 @@ const handleLogin = async () => {
|
|||
else if (currentTab === 'password') {
|
||||
// 校验密码+验证码
|
||||
await loginFormRef.value.validateField(['password', 'code']);
|
||||
// 生成device_no并保存到变量(关键修改1)
|
||||
const deviceNo = generateTimestampWithRandomNum();
|
||||
// 登录参数
|
||||
const loginParams = {
|
||||
email: loginForm.value.email,
|
||||
mobile: loginForm.value.phone,
|
||||
login_type: getStorage('loginType', 'session') === 'email' ? 1 : 2,
|
||||
code: loginForm.value.code,
|
||||
password: loginForm.value.password
|
||||
password: loginForm.value.password,
|
||||
device_no: deviceNo
|
||||
};
|
||||
await userStore.login(loginParams);
|
||||
setStorage('device_no', deviceNo);
|
||||
activeTab.value = 'email'; // 重置tab
|
||||
hideTab.value = false;
|
||||
loginForm.value = { // 清空表单
|
||||
|
|
|
|||
Loading…
Reference in New Issue