foreign-admin-ui/src/views/profile/business/index.vue

787 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="market">
<!-- 搜索 -->
<MarketSearch :search-form="marketSearchForm" :search-options="marketSearchOptions" @search="handleSearch"
@reset="handleReset">
<template #button="{ form }">
<el-button type="primary" @click="handleAddMarket()" v-auth="'agent_createAgent'">
添加
</el-button>
<el-button type="primary" :disabled="!selectedRow" v-auth="'agent_editAgent'" @click="handleEditMarket()">
编辑
</el-button>
</template>
</MarketSearch>
<!-- 表格 -->
<MarketTable :table-data="marketTree" :columns="marketTableColumnsOptions" row-key="id" @row-click="handleRowClick"
@row-contextmenu="handleRowContextMenu">
<template #pointSpread="{ row }">
<el-button type="primary" size="small" @click="handleViewPointDiff(row)"> 查看 </el-button>
</template>
</MarketTable>
<!-- 行右键菜单 -->
<context-menu v-model:show="menuVisible" :options="menuOptions">
<context-menu-item label="调整基本账户" @click="handleAction('account')" />
<context-menu-item label="设置停止接单金额" @click="handleAction('acceptOrders')" />
<context-menu-item label="设置持仓转移金额" @click="handleAction('transfer')" />
<context-menu-item label="取款" divided @click="handleAction('deposit')" />
<context-menu-item label="存款" divided @click="handleAction('withdraw')" />
</context-menu>
<!-- 分页 -->
<MarketPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
<!-- 抽屉 -->
<el-drawer class="my-drawer" :append-to-body="false" v-model="dialogVisible" :title="dialogTitle"
:size="dialogType === 'view' ? '60%' : '50%'" :destroy-on-close="true">
<!-- 表单 -->
<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>
</template>
</MarketForm>
</div>
<!-- 点差表格 -->
<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>
<!-- 底部按钮 -->
<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 :marketForm="rightClickMarketForm" :form-rules="rightClickMarketFormRules"
:form-items="rightClickMarketFormItemOptions"></MarketForm>
</MarketDialog>
</template>
<script setup lang="ts">
defineOptions({
name: 'Business'
})
// 组件
import MarketSearch from '@/components/common/Search.vue'
import MarketTable from '@/components/common/Table.vue'
import MarketPagination from '@/components/common/Pagination.vue'
import MarketForm from '@/components/common/Form.vue'
//配置选项和表单验证
import {
marketSearch,
marketTableColumns,
marketFormItem,
marketPointTableColumns,
countryOptions,
viewMarketPointTableColumns,
} from './options'
import { useUserStore } from '@/stores/modules/user.ts'
import { rules } from './rules'
//接口
import {
getMarketListApi,
getPointDiffConfigListApi,
getParAgentListApi,
getRiskTemplateListApi,
addMarketApi,
editMarketApi,
} from '@/api/modules/account/market'
const userStore = useUserStore()
/**
* @description 定义搜索数据
*/
const marketSearchForm = ref({
accountName: '',
})
const marketSearchOptions = ref(marketSearch)
/**
* @description 定义表格数据
*/
const marketTree = ref([])
const marketTableColumnsOptions = ref(marketTableColumns)
/**
* @description 定义分页数据
*/
const total = ref(0)
const pageSize = ref(10)
const currentPage = ref(1)
/**
* @description 定义dialog数据
*/
const dialogVisible = ref<boolean>(false)
const dialogTitle = ref<string>('')
const dialogType = ref<string>('add')
/**
* @description 定义dialog表单数据
*/
const marketForm = ref({
//邮箱或手机号
email: '',
phone: '',
countryCode: '+86',
accountName: '',
level: '',
parentAccountName: '',
riskControlTemplate: '',
positionRatio: '',
commissionRatio: '',
//拿最大去比较值
maxPositionRatio: '',
maxCommissionRatio: '',
})
const marketFormRules = rules(marketForm.value)
const marketFormRef = ref()
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
row?: any // 存储完整对象
}
const marketFormOptionsMap = ref<{
level: SelectOption[]
parentAccountName: SelectOption[]
riskControlTemplate: SelectOption[]
}>({
level: [
// { label: '平台', value: '1' },
{ label: '承包商', value: '2' },
{ label: '特殊做市', value: '3' },
{ label: '普通做市', value: '4' },
{ label: '一级代理', value: '5' },
{ label: '二级代理', value: '6' },
],
parentAccountName: [],
riskControlTemplate: [],
})
const activeTab = ref('email')
/**
* @description 定义点差表格数据
*/
const marketPointTree = ref<any>([])
const marketPointTableColumnsOptions = computed(() => {
return dialogType.value !== 'view' ? marketPointTableColumns : viewMarketPointTableColumns
})
//保存当前选中的表格行
const selectedRow = ref<any>(null)
// 菜单状态
const menuVisible = ref(false)
const menuOptions = reactive({ x: 0, y: 0, zIndex: 3000 })
const currentRow = ref(null)
const rightClickDialogVisible = ref(false)
const rightClickDialogType = ref<'account' | 'acceptOrders' | 'transfer' | 'deposit' | 'withdraw'>(
'account',
)
const rightClickDialogTitleEnum = {
account: '调整基本账户',
acceptOrders: '设置停止接单金额',
transfer: '设置持仓转移金额',
deposit: '取款',
withdraw: '存款',
}
const rightClickMarketForm = ref({})
const rightClickMarketFormRules = ref({})
const rightClickMarketFormItemOptions = ref([])
// 行右键点击
const handleRowContextMenu = (row: any, column: any, event: any) => {
console.log('handleRowContextMenu', row, column, event)
event?.preventDefault()
currentRow.value = row
menuOptions.x = event?.clientX
menuOptions.y = event?.clientY
menuOptions.zIndex = 3000
menuVisible.value = true
}
/**
* @description 处理右键菜单
* */
const handleAction = (key: 'account' | 'acceptOrders' | 'transfer' | 'deposit' | 'withdraw') => {
console.log('handleAction', key)
switch (key) {
case 'account':
break
case 'acceptOrders':
break
case 'transfer':
break
case 'deposit':
break
case 'withdraw':
break
}
rightClickDialogType.value = key
rightClickDialogVisible.value = true
}
const handleRightClickDialogSubmit = () => { }
const handleRightClickDialogCancel = () => {
rightClickDialogVisible.value = false
}
const handleRightClickDialogClose = () => {
rightClickDialogVisible.value = false
}
/**
* @description 数据处理方法
* 映射表格数据
*/
const mapMarketTree = (data: any) => {
console.log('mapMarketTree', data)
return data.map((item: any) => ({
...item,
id: item.id, //id
agentRelation: item.path_name, //代理关系
accountName: item.username, //账户名
level: item.role_id * 1 === 5 && item.type * 1 === 2 ? 6 : item.role_id, //级别
basicAccount: item.wallet_capital.amount, //基本账户
margin: item.wallet_capital.bail, //保证金
stopOrderMargin: item.wallet_capital.bail_stop_order, //停止接单保证金
positionTransferMargin: item.wallet_capital.bail_change_order, //持仓转移保证金
positionRatio: (item.wallet_capital.toucun_percent * 1).toFixed(2), //头寸比率
pointSpreadCommission: item.wallet_capital.commission_point_diff, //点差返佣
commissionRatio: (item.wallet_capital.fee_handling_percent * 1).toFixed(2), //手续费比例
commissionReturn: item.wallet_capital.commission_fee_handling, //手续费返佣
floatProfitLoss: '后续添加', //浮动盈亏
closeProfitLoss: item.wallet_capital.closing_profit, //平仓盈亏
riskMargin: item.wallet_capital.risk, //风险保证金
serviceFee: item.wallet_capital.service, //服务费
flag: item.role_id * 1 === 5 ? (item.type! === 1 ? 1 : 2) : null,
//使用这个字段来判断是否是做市商
mobile: item.mobile || '--',
email: item.email || '--',
}))
}
/**
* @description 统一处理做市商表单联动逻辑
* @param type 联动类型level(级别变更) / parentAccountName(上级账户变更)
* @param value 选中的值(级别值/上级账户名)
* @returns {Promise<void>}
*/
const handleMarketFormLinkage = async (type: 'level' | 'parentAccountName', value: string) => {
try {
// 1. 级别变更:获取所属上级账户列表
if (type === 'level') {
// 清空关联数据,避免脏数据
marketForm.value.parentAccountName = ''
marketFormOptionsMap.value.parentAccountName = []
marketPointTree.value = []
if (!value) return // 未选择级别,直接返回
// 调用获取上级账户列表接口
let flag = null
if (
(selectedRow.value?.role_id * 1 === 5 || marketForm.value?.level * 1 === 5) &&
selectedRow.value?.type * 1 === 1
)
flag = 1
if (
(selectedRow.value?.role_id * 1 === 6 || marketForm.value?.level * 1 === 6) &&
selectedRow.value?.type * 1 === 2
)
flag = 2
const data = await getParAgentListApi({ role_id: value, flag: flag })
console.log('datasssssssssssss', data)
// 格式化下拉选项
marketFormOptionsMap.value.parentAccountName = data.map((item: any) => ({
label: item.username,
value: String(item.id), // 按实际接口返回字段调整
row: item,
}))
}
// 2. 上级账户变更:获取点差列表(仅添加模式)
if (type === 'parentAccountName' && dialogType.value === 'add') {
// 清空点差数据
marketPointTree.value = []
if (!value) return // 未选择上级账户,直接返回
// 新增根据选中的value匹配完整的上级账户对象
const rulesData = marketFormOptionsMap.value.parentAccountName.find(
(item) => item.value === value,
)
if (!rulesData) return // 未找到匹配项,直接返回
// 赋值最大头寸和手续费比例
marketForm.value.maxPositionRatio = rulesData.row.wallet_capital.toucun_percent
marketForm.value.maxCommissionRatio = rulesData.row.wallet_capital.fee_handling_percent
// 调用获取点差列表接口
const data = await getPointDiffConfigListApi({ user_id: value })
// 格式化点差表格数据(按实际需求调整)
marketPointTree.value = data.map((item: any) => ({
...item,
varietyName: item.name,
varietyCode: item.code,
point_diff: item.target_point_diff, // 添加时:可分配点差取 target_point_diff
pointDifference: 0, // 添加时已分配点差初始化为0
assignedPointDifference: '',
}))
}
} catch (error) {
console.error(`[${type}] 联动数据获取失败:`, error)
}
}
/**
* @description 统一数据获取
*/
//获取做市商列表
const getMarketList = async () => {
const params = {
page: currentPage.value,
page_size: pageSize.value,
user_name: marketSearchForm.value.accountName,
}
const data = await getMarketListApi(params)
// 表格数据
marketTree.value = mapMarketTree(data.data)
//分页数据
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
}
//获取风控模板选列表
const getRiskTemplateList = async () => {
const data = await getRiskTemplateListApi()
// 格式化下拉选项
marketFormOptionsMap.value.riskControlTemplate = data.map((item: any) => ({
label: item.name,
value: item.id, // 按实际接口返回字段调整
}))
}
/**
* @description 页面加载完成后需要获取的数据
*/
onMounted(() => {
getMarketList()
getRiskTemplateList()
})
/**
* @description 监听数据变化
*/
// 监听级别变化
watch(
() => marketForm.value.level,
(newLevel) => {
handleMarketFormLinkage('level', newLevel)
},
{ immediate: false }, // 初始不触发,选择后触发
)
// 监听上级账户变化
watch(
() => marketForm.value.parentAccountName,
(newParentAccountName) => {
handleMarketFormLinkage('parentAccountName', newParentAccountName)
},
{ immediate: false },
)
/**
* @description 定义表单初始值方法
*/
// 1. 定义表单初始值常量(方便复用)
const initMarketForm = {
email: '',
phone: '',
countryCode: '+86',
accountName: '',
level: '',
parentAccountName: '',
riskControlTemplate: '',
positionRatio: '',
commissionRatio: '',
maxPositionRatio: '',
maxCommissionRatio: '',
}
/**
* @description search方法
*/
// 搜索
const handleSearch = async () => {
await getMarketList()
}
// 重置
const handleReset = async () => {
marketSearchForm.value = {
accountName: '',
}
currentPage.value = 1
pageSize.value = 10
await getMarketList()
}
// 添加做市商
const handleAddMarket = async () => {
dialogVisible.value = true
dialogTitle.value = '添加'
dialogType.value = 'add'
// 2. 重置表单数据
marketForm.value = { ...initMarketForm }
}
// 编辑做市商
const handleEditMarket = async () => {
const row = selectedRow.value
console.log('rowrowrowrowrow', row)
// 解析上级ID
const getParentIdFromPath = (path: string, level: number) => {
const segments = path.split('/').filter(Boolean)
const levelNum = Number(level)
const parentIndex = levelNum - 2
if (levelNum <= 1 || segments.length <= parentIndex) return ''
return segments[parentIndex]
}
const parentId = getParentIdFromPath(row.path, row.level)
console.log('解析后的上级ID:', parentId)
// 先初始化表单数据
marketForm.value = {
...initMarketForm,
accountName: row.accountName || '',
level: String(row.level) || '',
parentAccountName: '',
riskControlTemplate: row.wallet_capital.risk_control_id || '',
positionRatio: row.positionRatio || '',
commissionRatio: row.commissionRatio || '',
maxPositionRatio: row.wallet_capital.toucun_percent || '',
maxCommissionRatio: row.wallet_capital.fee_handling_percent || '',
}
console.log(marketForm.value)
try {
// 先设置编辑模式,避免 watch 触发获取点差接口
dialogType.value = 'edit'
// 先加载上级选项
await handleMarketFormLinkage('level', marketForm.value.level)
if (parentId) {
marketForm.value.parentAccountName = parentId
}
// 再加载点差数据
if (row.id) {
const data = await getPointDiffConfigListApi({ user_id: row.id })
marketPointTree.value = data.map((item: any) => ({
...item,
varietyName: item.name,
varietyCode: item.code,
point_diff: item.point_diff, // 编辑时:可分配点差取 point_diff
pointDifference: item.target_point_diff,
assignedPointDifference: item.target_point_diff || '',
}))
}
// 所有数据加载完成后,再打开抽屉
dialogVisible.value = true
dialogTitle.value = '编辑'
} catch (error) {
console.error('编辑数据加载失败:', error)
}
}
/**
* @description 表格方法
*/
// 查看点差
const handleViewPointDiff = async (row: any) => {
try {
const data = await getPointDiffConfigListApi({ user_id: row.id })
marketPointTree.value = data.map((item: any) => ({
...item,
varietyName: item.name,
varietyCode: item.code,
pointDifference: item.point_diff,
assignedPointDifference: item.target_point_diff || '',
}))
// 数据加载完成后再打开抽屉
dialogVisible.value = true
dialogTitle.value = '查看点差'
dialogType.value = 'view'
} catch (error) {
console.error('加载点差数据失败:', error)
}
}
// 点击行事件
const handleRowClick = (row: any) => {
if (!row) {
selectedRow.value = null
return
}
selectedRow.value = row
}
/**
* @description 分页方法
*/
// 分页变更
const handlePaginationChange = async (page: { currentPage: number; pageSize: number }) => {
currentPage.value = page.currentPage
pageSize.value = page.pageSize
await getMarketList()
}
/**
* @description dialog方法
*/
// 提交之前构造数据:格式化点差为 { "1":"42", "2":"38" } 格式的对象
const formatPointDiffToObject = () => {
if (!marketPointTree.value || !marketPointTree.value.length) return {}
// 初始化空对象
const pointDiffObj: Record<string, string> = {}
console.log(marketPointTree.value)
// 遍历表格数据,构造 { 编号: 点差值 } 的对象
for (let i = 0; i < marketPointTree.value.length; i++) {
const item = marketPointTree.value[i]
if (item.pointDifference > item.point_diff) {
ElMessage.error('已分配差值不能大于可分配点差')
return false
}
pointDiffObj[String(item.id)] = item.pointDifference || '0'
}
return pointDiffObj // 返回纯对象,而非数组
}
//提交
const handleSubmit = async () => {
// console.log('提交', marketPointTree.value);
await marketFormRef.value?.validate()
const pointDiffObj = formatPointDiffToObject()
console.log('pointDiffObj', pointDiffObj)
if (!pointDiffObj) return
if (dialogType.value === 'add') {
const params = {
role_id: marketForm.value.level,
reg_type: activeTab.value === 'email' ? '1' : '2',
username: marketForm.value.accountName,
par_user_id: marketForm.value.parentAccountName,
mobile: activeTab.value === 'phone' ? marketForm.value.phone : '',
email: activeTab.value === 'email' ? marketForm.value.email : '',
password: '888888',
toucun_percent: marketForm.value.positionRatio,
fee_handling_percent: marketForm.value.commissionRatio,
point_diffs_json: JSON.stringify(pointDiffObj),
risk_control_id: marketForm.value.riskControlTemplate,
fee_setting_id: '',
}
if (marketForm.value.level === '5') params.flag = 1
if (marketForm.value.level === '6') {
params.flag = 2
params.role_id = '5'
}
await addMarketApi(params)
// 5. 刷新表格数据
await getMarketList()
dialogVisible.value = false
marketForm.value = { ...initMarketForm }
} else if (dialogType.value === 'edit') {
let flag = null
if (selectedRow.value.role_id * 1 === 5 && selectedRow.value.type * 1 === 1) flag = 1
if (selectedRow.value.role_id * 1 === 6 && selectedRow.value.type * 1 === 2) flag = 2
const params = {
user_id: selectedRow.value.id,
toucun_percent: marketForm.value.positionRatio,
point_diffs_json: JSON.stringify(pointDiffObj),
risk_control_id: marketForm.value.riskControlTemplate,
fee_handling_percent: marketForm.value.commissionRatio,
flag: flag,
fee_setting_id: '',
}
// 4. 调用编辑接口
await editMarketApi(params)
// 5. 刷新表格数据
await getMarketList()
dialogVisible.value = false
marketForm.value = { ...initMarketForm }
}
}
// 取消
const handleCancel = async () => {
dialogVisible.value = false
// 重置点差数据和drawer类型
}
</script>
<style scoped lang="scss">
:deep(.el-form-item__content) {
margin-left: 0 !important;
}
.market {
.search-container {
:deep(.el-form) {
.el-form-item {
flex: none;
}
}
}
}
.phone-email {
display: flex;
flex: 1;
.tab-btn {
display: flex;
margin-right: 10px;
.el-button {
height: 32px;
margin-left: 0;
}
}
.group {
display: flex;
flex: 1;
.el-form-item {
flex: 1;
}
.phone-item {
:deep(.el-form-item__content) {
flex-wrap: nowrap;
}
.el-select {
width: 120px;
}
}
}
}
.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>