foreign-admin-ui/src/views/agent/user/index.vue

732 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="table_form-container">
<!-- 搜索 -->
<CustomerListSearch :search-form="searchForm" :search-options="searchOptions" :button-loading="searchLoading" @search="handleSearch"
@reset="handleReset"></CustomerListSearch>
<!-- 表格 -->
<CustomerListTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="userId" :tree-props="treeProps"
:default-sort="{ prop: 'createTime', order: 'descending' }" :loading="loading" @expand-change="handleGetSubList">
<template #marginRatio="{ 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>
</template>
<template #action="{ row }">
<!-- 👇 这里改成打开抽屉,不再跳转路由 -->
<el-button link type="primary" size="small" @click="openDetailDrawer(row)" v-if="row.userId">
查看详情
</el-button>
<el-divider direction="vertical" v-if="+row.role_id !== 5"/>
<el-button link type="primary" size="small" @click="handleEditCustomer(row)" v-if="+row.role_id !== 5">
升级代理{{ row.roleId }}
</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" />
<!-- dialog -->
<CustomerListDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :fullscreen="dialogFullscreen"
:show-footer="dialogType !== 'view'" :button-loading="submitLoading" @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-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>
</CustomerListDialog>
<!-- 调整金额弹窗 -->
<CustomerListDialog :visible="adjustAmountVisible" title="调整金额" type="adjust" :button-loading="adjustAmountLoading" @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"
:button-loading="editCustomerLoading" @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" />
</div>
</template>
<script setup lang="ts">
defineOptions({
name: 'User'
})
// 组件
import CustomerListSearch from '@/components/common/Search.vue'
import CustomerListTable from '@/components/common/Table.vue'
import { usePageLoading, useButtonLoading } from '@/composables/useLoading'
import CustomerListPagination from '@/components/common/Pagination.vue'
import CustomerListDialog from '@/components/common/Dialog.vue'
import CustomerListForm from '@/components/common/Form.vue'
// 👇 引入同目录的抽屉组件default.vue
import CustomerDetailDrawer from './default.vue'
// 配置以及表单验证
import { search, tableColumns, marginRatioTableColumns, commissionRatioFormItem } from './options'
// 接口
import {
getCustomerListApi,
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'
const router = useRouter()
/**
* @description: 获取用户id
*/
const userInfo: any = getStorage('userInfo')
const userId = ref(userInfo?.id || '')
/**
* @description: 定义搜索数据
*/
const searchForm = ref({
accountName: '',
startTime: '',
endTime: '',
status: '',
})
const searchOptions = ref(search)
const tableColumnsOptions = ref(tableColumns)
const { loading, startLoading, stopLoading } = usePageLoading()
const { buttonLoading: searchLoading, startButtonLoading: startSearchLoading, stopButtonLoading: stopSearchLoading } = useButtonLoading()
const { buttonLoading: submitLoading, startButtonLoading: startSubmitLoading, stopButtonLoading: stopSubmitLoading } = useButtonLoading()
const { buttonLoading: adjustAmountLoading, startButtonLoading: startAdjustAmountLoading, stopButtonLoading: stopAdjustAmountLoading } = useButtonLoading()
const { buttonLoading: editCustomerLoading, startButtonLoading: startEditCustomerLoading, stopButtonLoading: stopEditCustomerLoading } = useButtonLoading()
/**
* @description: 定义表格数据
*/
const tableTree = ref([])
const treeProps = { children: 'children', hasChildren: 'hasChildren' }
/**
* @description: 定义分页数据
*/
const total = ref(0)
const currentPage = ref(1)
const pageSize = ref(10)
/**
* @description: 定义弹窗数据
*/
const dialogVisible = ref<boolean>(false)
const dialogTitle = ref<string>('')
const dialogType = ref<string>('')
const dialogFullscreen = ref(false)
// ===================== 【新增】详情抽屉状态 =====================
const detailDrawerVisible = ref(false)
const detailQueryId = ref('') // 传给抽屉的查询ID
const detailDrawerTitle = ref('') // 传给抽屉的标题
/**
* @description: 定义点差表格数据
*/
const marginRatioTree = ref([])
const marginRatioTableColumnsOptions = ref(marginRatioTableColumns)
// 存储所有行的表单
const formRefs: any = ref({})
// 动态存储每行表单
const setFormRef = (el: any, id: any) => {
if (el) formRefs.value[id] = el
}
// 点差验证规则
const marginRatioRules = (row: any) => {
return {
assignedPointDifference: [
{ required: true, message: '请输入数字', trigger: ['blur', 'change'] },
{
pattern: /^\d+(\.\d+)?$/,
message: '只能输入数字(可带小数)',
trigger: ['blur', 'change'],
},
{
validator: (rule: any, value: any, callback: any) => {
const inputVal = Number(value || 0)
const maxVal = Number(row.pointDifference || 0)
if (inputVal > maxVal) {
callback(new Error('不能大于可分配点数'))
} else {
callback()
}
},
trigger: ['blur', 'change'],
},
],
}
}
/**
* @description: 定义手续费form数据
*/
const commissionRatioForm = ref({
commissionRatio: '',
})
const commissionRatioFormRef = ref()
const commissionRatioFormRules = {
commissionRatio: [
{ required: true, message: '请输入手续费分成比例', trigger: ['blur', 'change'] },
{ pattern: /^\d+(\.\d+)?$/, message: '只能输入数字(可带小数)', trigger: ['blur', 'change'] },
],
}
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: 角色映射方法
*/
const roleMap: any = {
1: '平台',
2: '承包商',
3: '特殊做市商',
4: '普通做市商',
5: '代理',
6: '用户',
}
/**
* @description: 定义公共请求数据方法
*/
// 获取客户列表
const getCustomerList = async (id: string) => {
startLoading()
try {
const params = {
user_id: id,
page: currentPage.value,
page_size: pageSize.value,
user_name: searchForm.value.accountName,
reg_start_time:
searchForm.value.startTime.length > 0
? `${searchForm.value.startTime} 0:00:00`
: '2026-01-01 0:00:00',
reg_end_time:
searchForm.value.endTime.length > 0
? `${searchForm.value.endTime} 23:59:59`
: '2026-03-01 23:59:59',
status: searchForm.value.status,
}
const data: any = await getCustomerListApi(params)
tableTree.value = data.data.map((item: any) => ({
accountName: item.username,
userId: item.id,
status: item.status === 1 ? '正常' : '禁用',
role: roleMap[item.role_id] || '未知',
accountQty: item.account[0].account_count,
commissionRatio: item.wallet_capital.fee_handling_percent,
createTime: item.created_at,
role_id: item.role_id,
children:
item.role_id === 5
? [
{
hasChildren: true,
},
]
: undefined,
}))
console.log('tableTree', tableTree.value)
//分页数据
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
} catch (err) {
console.error('获取客户列表失败:', err)
} finally {
stopLoading()
}
}
//获取代理点差数据
const getSelfVarietyPointDiffConfig = async (id: string) => {
try {
const params = {
user_id: id,
}
const data: any = await getSelfVarietyPointDiffConfigApi(params)
marginRatioTree.value = data.map((item: any) => {
return {
id: item.id,
varietyName: item.name,
varietyCode: item.code,
pointDifference: item.point_diff,
assignedPointDifference: item.target_point_diff,
}
})
console.log('marginRatioTree', marginRatioTree.value)
} catch (err) {
console.error('获取代理点差数据失败:', err)
}
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getCustomerList(userId.value)
})
/**
* @description: 定义搜索方法
*/
const handleSearch = async () => {
currentPage.value = 1
startSearchLoading()
try {
await getCustomerList(userId.value)
} catch (err) {
console.error('搜索失败:', err)
} finally {
stopSearchLoading()
}
}
const handleReset = () => {
searchForm.value = {
accountName: '',
startTime: '',
endTime: '',
status: '',
}
handleSearch()
}
/**
* @description: 定义表格方法
*/
// ===================== 【新增】打开详情抽屉把ID传过去 =====================
const openDetailDrawer = (row: any) => {
detailQueryId.value = row.userId
detailDrawerTitle.value = `${row.accountName} - 客户详情`
detailDrawerVisible.value = true
}
// 👇 旧的路由跳转可以删掉或保留
const handleDetail = (row: any) => {
console.log('查看详情(旧路由)', row)
// router.push(`/agent/customerDetail/${row.userId}`)
}
// 修改点差
const handleMarginRatio = (row: any) => {
getSelfVarietyPointDiffConfig(row.userId)
console.log('修改点差', row)
dialogVisible.value = true
dialogTitle.value = '修改点差'
dialogType.value = 'editMarginRatio'
selectId.value = row.userId
}
// 修改手续费比例
const handleCommissionRatio = (row: any) => {
console.log('修改手续费比例', row)
dialogVisible.value = true
dialogTitle.value = '修改手续费'
dialogType.value = 'editCommissionRatio'
selectId.value = row.userId
}
// 获取子列表
const handleGetSubList = async (row: any) => {
try {
const params = {
user_name: '',
user_id: row.userId,
page: currentPage.value,
page_size: pageSize.value,
reg_start_time: '',
reg_end_time: '',
status: '',
}
const data: any = await getCustomerListApi(params)
if (data.data.length === 0) {
ElMessage({
message: '暂无数据',
type: 'error',
})
row.children = []
return
}
row.children = data.data.map((item: any) => ({
accountName: item.username,
userId: item.id,
status: item.status === 1 ? '正常' : '禁用',
role: roleMap[item.role_id] || '未知',
accountQty: item.account[0].account_count,
commissionRatio: item.wallet_capital.fee_handling_percent,
createTime: item.created_at,
role_id: item.role_id,
children:
item.role_id === 5
? [
{
hasChildren: true,
},
]
: undefined,
}))
} catch (err) {
console.error('获取子列表失败:', err)
}
}
/**
* @description: 定义分页方法
*/
const handlePaginationChange = (page: { currentPage: number; pageSize: number }) => {
currentPage.value = page.currentPage
pageSize.value = page.pageSize
getCustomerList(userId.value)
}
/**
* @description: 定义弹窗方法
*/
//提交
const handleSubmit = async () => {
startSubmitLoading()
try {
const params = {
user_id: selectId.value,
toucun_percent: '',
point_diffs_json: '',
fee_handling_percent: '',
fee_setting_id: '',
risk_control_id: '',
}
if (dialogType.value === 'editMarginRatio') {
// 逐行验证
for (const row of marginRatioTree.value) {
const form = formRefs.value[(row as any).id]
if (!form) continue
try {
await form.validate()
} catch (err) {
console.error(`${(row as any).id} 行验证不通过,请检查`)
return
}
}
//转换为接口需要的格式 { id: assignedPointDifference }
const marginRatioJSON = marginRatioTree.value.reduce((acc: any, cur: any) => {
acc[cur.id] = cur.assignedPointDifference
return acc
}, {})
// 调用接口
try {
await editCustomerApi({ ...params, point_diffs_json: JSON.stringify(marginRatioJSON), base_ts:null })
dialogVisible.value = false
await getCustomerList(userId.value)
} catch (err) {
console.error('提交失败:', err)
}
}
if (dialogType.value === 'editCommissionRatio') {
await commissionRatioFormRef.value.validate()
try {
await editCustomerApi({
...params,
fee_handling_percent: commissionRatioForm.value.commissionRatio, base_ts:null
})
dialogVisible.value = false
await getCustomerList(userId.value)
} catch (err) {
console.error('提交失败:', err)
}
}
} finally {
stopSubmitLoading()
}
}
// 取消
const handleCancel = () => {
dialogVisible.value = false
}
// 关闭
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 () => {
startAdjustAmountLoading()
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)
} finally {
stopAdjustAmountLoading()
}
}
// 取消调整金额
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 () => {
startEditCustomerLoading()
try {
await editCustomerFormRef.value?.validate()
await editUserApi({
user_id: editCustomerUserId.value,
fee_setting_id: null,
risk_control_id: null,
username: null,
is_levelup: editCustomerForm.value.isLevelUp,
status: null,
})
await getCustomerList(userId.value)
editCustomerVisible.value = false
} catch (err) {
console.error('修改客户表单验证失败', err)
} finally {
stopEditCustomerLoading()
}
}
// 取消修改客户
const handleEditCustomerCancel = () => {
editCustomerVisible.value = false
}
// 关闭修改客户弹窗
const handleEditCustomerClose = () => {
editCustomerVisible.value = false
}
</script>
<style scoped lang="scss">
:deep(.placeholder-row) {
text-align: center;
color: #909399;
}
</style>