路由权限及动态路由

This commit is contained in:
Songsl 2026-04-25 16:13:36 +08:00
parent c07b0128c8
commit 1aea365270
80 changed files with 885 additions and 2620 deletions

View File

@ -1,8 +1,8 @@
# 开发环境标识Vite 内置变量,无需修改)
NODE_ENV = development
# 开发环境接口基础地址(对应你配置的 http://192.168.11.183:8085/api
VITE_API_BASE_URL = http://192.168.11.183:8085/api
# 开发环境接口基础地址(对应你配置的 http://18.167.120.9:8085/api
VITE_API_BASE_URL = http://18.167.120.9:8085/api
# 请求超时时间(毫秒)
VITE_REQUEST_TIMEOUT = 10000

BIN
dist.zip Normal file

Binary file not shown.

View File

@ -7,16 +7,16 @@
</div>
</div>
<!-- 菜单区域 -->
<el-menu class="menu" :unique-opened="true" :default-active="activeMenu" router>
<template v-for="menu in menuList" :key="menu.id">
<!-- 无子菜单 -->
<el-menu-item v-if="!menu.children || menu.children.length === 0" :index="menu.path">
<el-icon>
<component :is="iconMap[menu.icon]" />
</el-icon>
<template #title>{{ menu.label }}</template>
</el-menu-item>
<!-- 菜单区域 -->
<el-menu class="menu" :unique-opened="true" :default-active="activeMenu" router>
<template v-for="menu in menuList" :key="menu.id">
<!-- 无子菜单 -->
<el-menu-item v-if="!menu.children || menu.children.length === 0" :index="menu.path">
<el-icon>
<component :is="iconMap[menu.icon]" />
</el-icon>
<template #title>{{ menu.label }}</template>
</el-menu-item>
<el-sub-menu v-else :index="menu.path">
<template #title>
@ -26,9 +26,9 @@
<span>{{ menu.label }}</span>
</template>
<el-menu-item v-for="subMenu in menu.children" :key="subMenu.id" :index="subMenu.path">
<el-icon v-if="subMenu.icon">
<component :is="iconMap[subMenu.icon]" />
</el-icon>
<!-- <el-icon v-if="subMenu.icon">-->
<!-- <component :is="iconMap[subMenu.icon]" />-->
<!-- </el-icon>-->
<template #title>{{ subMenu.label }}</template>
</el-menu-item>
</el-sub-menu>
@ -63,6 +63,7 @@ const activeMenu = computed(() => {
//
onMounted(async () => {
menuList.value = userStore.sideMenu || []
console.log('menuList.value', menuList.value)
})
//
@ -77,326 +78,7 @@ const iconMap: Record<string, any> = {
Tools,
}
//
import type { ElMenuItem } from '@/utils/menuFormat'
//
const staticMenuList = ref<ElMenuItem[]>([
//
{
id: 1,
label: '首页',
path: 'dashboard', // el-menu-item index="dashboard"
icon: 'HomeFilled', // <HomeFilled />
},
//
// {
// id: 2,
// label: '',
// path: '2', // el-sub-menu index="2"
// icon: 'List', // <List />
// children: [
// {
// id: 21,
// label: '',
// path: '2-1', // el-menu-item index="2-1"
// icon: ''
// //
// }
// ]
// },
//
{
id: 3,
label: '账户管理',
path: 'account', // el-sub-menu index="account"
icon: 'UserFilled', // <UserFilled />
children: [
{
id: 31,
label: '客户管理',
path: '/account/client', // index="client"
icon: '',
},
{
id: 32,
label: '代理管理',
path: '/account/agentList', // index="3-2"
icon: '',
},
{
id: 33,
label: '做市商管理',
path: '/account/marketMaker', // index="3-3"
icon: '',
},
{
id: 34,
label: '档案管理',
path: '/account/archives', // index="3-4"
icon: '',
},
{
id: 35,
label: '账号管理',
path: '/account/accountDetail', // index="3-5"
icon: '',
},
],
},
//
{
id: 4,
label: '交易管理',
path: '4', // index="4"
icon: 'Platform', // <Platform />
children: [
{
id: 41,
label: '品种',
path: '/trade/variety', // index="4-1"
icon: '',
},
{
id: 42,
label: '交易时间',
path: '/trade/group', // index="4-2"
icon: '',
},
{
id: 43,
label: '持仓统计',
path: '/trade/positionStat', // index="4-3"
icon: '',
},
{
id: 44,
label: '历史交易',
path: '/trade/historyTrade', // index="4-4"
icon: '',
},
{
id: 45,
label: '交易软件',
path: '/trade/tradeSoftware', // index="4-5"
icon: '',
},
{
id: 46,
label: '节假日时间',
path: '/trade/time', // index="4-3"
icon: '',
},
],
},
//
{
id: 5,
label: '公告管理',
path: '/notice', // index="5"
icon: 'BellFilled', // <BellFilled />
},
//
{
id: 6,
label: '资金管理',
path: '6', // index="6"
icon: 'GoodsFilled', // <GoodsFilled />
children: [
// {
// id: 61,
// label: '',
// path: '/funding/deposit', // index="6-1"
// icon: ''
// },
// {
// id: 62,
// label: '',
// path: '/funding/withdrawal', // index="6-2"
// icon: ''
// },
// {
// id: 63,
// label: '',
// path: '/funding/withdrawalDetail', // index="6-3"
// icon: ''
// },
// {
// id: 64,
// label: '',
// path: '/funding/depositDetail', // index="6-4"
// icon: ''
// },
{
id: 64,
label: '转账',
path: '/funding/transfer', // index="6-4"
icon: '',
},
{
id: 65,
label: '转账明细',
path: '/funding/transferDetail', // index="6-5"
icon: '',
},
{
id: 66,
label: '资金明细',
path: '/funding/fundDetail', // index="6-6"
icon: '',
},
// {
// id: 66,
// label: '',
// path: '/funding/flowDetail', // index="6-6"
// icon: ''
// },
// {
// id: 67,
// label: '',
// path: '/funding/receiverAccount', // index="6-7"
// icon: ''
// },
// {
// id: 68,
// label: '',
// path: '/funding/depositChannel', // index="6-8"
// icon: ''
// }
],
},
//
{
id: 7,
label: '系统管理',
path: 'system', // index="7"
icon: 'Tools', // <Tools />
children: [
{
id: 71,
label: '菜单管理',
path: '/system/menu', // index="6-1" index 6-1
icon: '',
},
{
id: 72,
label: '角色管理',
path: '/system/role', // index="6-2" index 6-2
icon: '',
},
{
id: 73,
label: '系统设置',
path: '/system/setting', // index="6-2" index 6-2
icon: '',
},
],
},
//
{
id: 8,
label: '风控管理',
path: '/risk', // index="8"
icon: 'WarningFilled', // <WarningFilled />
},
//
// {
// id: 9,
// label: '',
// path: 'funding', // index="funding"
// icon: 'GoodsFilled', // <GoodsFilled />
// children: [
// ///// /
// //
// ]
// },
//
// {
// id: 10,
// label: '',
// path: 'trade', // index="trade"
// icon: 'ChartBarFilled', // <ChartBarFilled />
// children: [
// //
// //
// //
// ]
// },
//
// {
// id: 11,
// label: '',
// path: 'account', // index="account"
// icon: 'UserFilled', // <UserFilled />
// children: [
// //
// //
// ]
// },
//
{
id: 12,
label: '个性化交易',
path: 'personalized', // index="personalized"
icon: 'SettingFilled', // <SettingFilled />
children: [
// app
//
{
id: 121,
label: '跟单记录',
path: '/personalized/followRecord', // index="12-1"
icon: '',
},
//
{
id: 122,
label: '带单记录',
path: '/personalized/leadRecord', // index="12-2"
icon: '',
},
],
},
//
{
id: 13,
label: '代理管理',
path: 'agent', // index="agent"
icon: 'UserFilled', // <UserFilled />
children: [
//
{
id: 132,
label: '客户列表',
path: '/agent/customerList', // index="13-2"
icon: '',
},
//
{
id: 133,
label: '客户交易订单',
path: '/agent/customerTradeOrder', // index="13-3"
icon: '',
},
//
{
id: 135,
label: '客户资金流水',
path: '/agent/customerCapitalFlow', // index="13-5"
icon: '',
},
],
},
//
{
id: 14,
label: '通知管理',
path: 'notification', // index="notification"
icon: 'NotificationFilled', // <NotificationFilled />
},
])
</script>
<style scoped lang="scss">

View File

@ -0,0 +1,23 @@
const viewModules = import.meta.glob('@/views/**/index.vue')
export const handleRouter = (list: any[]):any[] => {
let routerList:any[] = [];
list.forEach(item => {
if (item.son && item.son.length > 0) {
routerList.push(...handleRouter(item.son))
} else {
const componentPath = `/src/views${item.path}/index.vue`
routerList.push({
path: item.path,
name: item.name,
component: viewModules[componentPath] || (() => import('@/views/404/index.vue')),
meta: {
title: item.name, // 左侧菜单显示的标题
},
})
}
});
return routerList;
};

View File

@ -1,5 +1,5 @@
import { createRouter, createWebHistory } from 'vue-router';
import { useUserStore } from '@/stores/modules/user';
import { createRouter, createWebHistory } from 'vue-router'
import { useUserStore } from '@/stores/modules/user'
import Layout from '@/components/layout/Layout.vue'
const routes = [
@ -51,351 +51,20 @@ const routes = [
// 带侧边栏布局的核心业务页面
{
path: '/',
name: 'Layout',
component: Layout, // 全局布局Sidebar + Header + MainContent
redirect: '/dashboard',
meta: { requiresAuth: true }, // 需要登录
children: [
// 首页
{
path: 'dashboard',
path: '/dashboard',
name: 'Dashboard',
component: () => import('@/views/dashboard/index.vue'),
meta: {
title: '首页', // 左侧菜单显示的标题
},
},
// 系统管理
{
path: 'system',
name: 'System',
meta: {
title: '系统管理', // 左侧菜单显示的标题
},
children: [
{
path: 'menu',
name: 'Menu',
component: () => import('@/views/system/menu/index.vue'),
meta: {
title: '菜单管理', // 左侧菜单显示的标题
},
},
{
path: 'role',
name: 'Role',
component: () => import('@/views/system/role/index.vue'),
meta: {
title: '角色管理', // 左侧菜单显示的标题
},
},
{
path: 'setting',
name: 'Setting',
component: () => import('@/views/system/setting/index.vue'),
meta: {
title: '系统设置', // 左侧菜单显示的标题
},
},
],
},
//模板配置
{
path: 'template',
name: 'Template',
meta: {
title: '模板配置', // 左侧菜单显示的标题
},
children: [],
},
//账户管理
{
path: 'account',
name: 'Account',
meta: {
title: '账户管理', // 左侧菜单显示的标题
},
children: [
{
path: 'client',
name: 'Client',
component: () => import('@/views/account/client/index.vue'),
meta: {
title: '客户管理', // 左侧菜单显示的标题
},
},
{
path: 'agentList',
name: 'AgentList',
component: () => import('@/views/account/agentList/index.vue'),
meta: {
title: '代理管理', // 左侧菜单显示的标题
},
},
{
path: 'marketMaker',
name: 'MarketMaker',
component: () => import('@/views/account/marketMaker/index.vue'),
meta: {
title: '做市商管理', // 左侧菜单显示的标题
},
},
{
path: 'archives',
name: 'Archives',
component: () => import('@/views/account/archives/index.vue'),
meta: {
title: '档案管理', // 左侧菜单显示的标题
},
},
{
path: 'accountDetail',
name: 'AccountDetail',
component: () => import('@/views/account/accountDetail/index.vue'),
meta: {
title: '账号管理', // 左侧菜单显示的标题
},
},
],
},
//交易管理
{
path: 'trade',
name: 'Trade',
meta: {
title: '交易管理', // 左侧菜单显示的标题
},
children: [
{
path: 'variety',
name: 'Variety',
component: () => import('@/views/trade/variety/index.vue'),
meta: {
title: '品种', // 左侧菜单显示的标题
},
},
{
path: 'group',
name: 'Group',
component: () => import('@/views/trade/group/index.vue'),
meta: {
title: '交易时间', // 左侧菜单显示的标题
},
},
{
path: 'time',
name: 'Time',
component: () => import('@/views/trade/time/index.vue'),
meta: {
title: '节假日时间', // 左侧菜单显示的标题
},
},
{
path: 'positionStat',
name: 'PositionStat',
component: () => import('@/views/trade/positionStat/index.vue'),
meta: {
title: '持仓统计', // 左侧菜单显示的标题
},
},
{
path: 'historyTrade',
name: 'HistoryTrade',
component: () => import('@/views/trade/historyTrade/index.vue'),
meta: {
title: '历史交易', // 左侧菜单显示的标题
},
},
{
path: 'tradeSoftware',
name: 'TradeSoftware',
component: () => import('@/views/trade/tradeSoftware/index.vue'),
meta: {
title: '交易软件', // 左侧菜单显示的标题
},
},
],
},
//公告管理
{
path: 'notice',
name: 'Notice',
meta: {
title: '公告管理', // 左侧菜单显示的标题
},
component: () => import('@/views/notice/index.vue'),
},
//资金管理
{
path: 'funding',
name: 'Funding',
meta: {
title: '资金管理', // 左侧菜单显示的标题
},
children: [
{
path: 'deposit',
name: 'Deposit',
component: () => import('@/views/funding/deposit/index.vue'),
meta: {
title: '存款审核', // 左侧菜单显示的标题
},
},
{
path: 'withdrawal',
name: 'Withdrawal',
component: () => import('@/views/funding/withdrawl/index.vue'),
meta: {
title: '取款审核', // 左侧菜单显示的标题
},
},
{
path: 'withdrawalDetail',
name: 'WithdrawalDetail',
component: () => import('@/views/funding/withdrawalDetail/index.vue'),
meta: {
title: '取款明细', // 左侧菜单显示的标题
},
},
{
path: 'depositDetail',
name: 'DepositDetail',
component: () => import('@/views/funding/depositDetail/index.vue'),
meta: {
title: '存款明细', // 左侧菜单显示的标题
},
},
{
path: 'transfer',
name: 'Transfer',
component: () => import('@/views/funding/transfer/index.vue'),
meta: {
title: '转账', // 左侧菜单显示的标题
},
},
{
path: 'transferDetail',
name: 'TransferDetail',
component: () => import('@/views/funding/transferDetail/index.vue'),
meta: {
title: '转账明细', // 左侧菜单显示的标题
},
},
{
path: 'capitalFlow',
name: 'CapitalFlow',
component: () => import('@/views/funding/capitalFlow/index.vue'),
meta: {
title: '资金流水', // 左侧菜单显示的标题
},
},
{
path: 'receiverAccount',
name: 'ReceiverAccount',
component: () => import('@/views/funding/receiverAccount/index.vue'),
meta: {
title: '收款账户列表', // 左侧菜单显示的标题
},
},
{
path: 'depositChannel',
name: 'DepositChannel',
component: () => import('@/views/funding/depositChannel/index.vue'),
meta: {
title: '存款渠道列表', // 左侧菜单显示的标题
},
},
{
path: 'fundDetail',
name: 'FundDetail',
component: () => import('@/views/funding/fundDetail/index.vue'),
meta: {
title: '资金明细', // 左侧菜单显示的标题
},
},
],
},
{
path: 'risk',
name: 'Risk',
meta: {
title: '风控管理', // 左侧菜单显示的标题
},
component: () => import('@/views/risk/index.vue'),
},
// 个性化交易
{
path: 'personalized',
name: 'Personalized',
meta: {
title: '个性化交易', // 左侧菜单显示的标题
},
children: [
// 跟单记录
{
path: 'followRecord',
name: 'FollowRecord',
component: () => import('@/views/personalized/followRecord/index.vue'),
meta: {
title: '跟单记录', // 左侧菜单显示的标题
},
},
// 带单记录
{
path: 'leadRecord',
name: 'LeadRecord',
component: () => import('@/views/personalized/leadRecord/index.vue'),
meta: {
title: '带单记录', // 左侧菜单显示的标题
},
},
],
},
//代理管理
{
path: 'agent',
name: 'Agent',
meta: {
title: '代理管理', // 左侧菜单显示的标题
},
children: [
//客户列表
{
path: 'customerList',
name: 'CustomerList',
component: () => import('@/views/agent/customerList/index.vue'),
meta: {
title: '客户列表', // 左侧菜单显示的标题
},
},
//客户详情
{
path: 'customerDetail/:id',
name: 'CustomerDetail',
component: () => import('@/views/agent/customerDetail/index.vue'),
meta: {
title: '客户详情', // 左侧菜单显示的标题
},
},
//客户交易订单
{
path: 'customerTradeOrder',
name: 'CustomerTradeOrder',
component: () => import('@/views/agent/customerTradeOrder/index.vue'),
meta: {
title: '客户交易订单', // 左侧菜单显示的标题
},
},
//客户资金流水
{
path: 'customerCapitalFlow',
name: 'CustomerCapitalFlow',
component: () => import('@/views/agent/customerCapitalFlow/index.vue'),
meta: {
title: '客户资金流水', // 左侧菜单显示的标题
},
},
],
},
],
},
{
@ -407,34 +76,37 @@ const routes = [
const router = createRouter({
history: createWebHistory(),
routes,
});
})
// 全局前置守卫
router.beforeEach(async (to, from) => {
const userStore = useUserStore();
const isLoggedIn = !!userStore.token;
const userStore = useUserStore()
const isLoggedIn = !!userStore.token
// 1. 已登录用户访问授权页 → 重定向到首页
if (isLoggedIn && to.meta.isAuthPage) {
if (to.path === '/') return true;
return '/';
}
console.log('全局前置守卫', isLoggedIn, to.meta.requiresAuth)
// 2. 未登录用户访问需要登录的页面(包括首页)→ 重定向到登录页
// 1. 未登录用户访问需要登录的页面(包括首页)→ 重定向到登录页
if (!isLoggedIn && to.meta.requiresAuth) {
if (to.path === '/user/login') return true;
return '/user/login';
if (to.path === '/user/login') return true
console.log('全局前置守卫', isLoggedIn, to.meta.requiresAuth)
return '/user/login'
}
// 2. 已登录用户访问授权页 → 重定向到首页
// if (isLoggedIn) {
// if (to.path === '/' && to.meta.isAuthPage) return true
// return '/'
// }
// 3. 其他情况正常放行
return true;
});
return true
})
// 可选:设置页面标题
router.afterEach((to) => {
if (to.meta.title) {
document.title = to.meta.title as string;
document.title = to.meta.title as string
}
});
})
export default router;
export default router

View File

@ -3,6 +3,7 @@ import { loginApi, registerApi, logoutApi, forgotPasswordApi, changePasswordApi,
import type { LoginParams, RegisterParams, ForgotPasswordParams, ChangePasswordParams, SendCodeParams } from '@/api/types/user';
import type { UserInfo, MenuItem, PermissionItem } from '@/stores/types/user';
import { formatMenuToElMenu, type ElMenuItem } from '@/utils/menuFormat';
import { handleRouter } from '@/router/handleRouter.ts'
// 定义用户 Store
export const useUserStore = defineStore('user', () => {
@ -36,7 +37,19 @@ const setUserInfo = (set: any) => {
setStorage('menusInfo', set.menus_info);
setStorage('permissionsInfo', set.permissions_info);
console.log('用户信息保存成功');
const userStore = useUserStore()
if (userStore.token && userStore.menusInfo.length > 0) {
const routeList = handleRouter(userStore.menusInfo).filter(
(item) => item.path && item.path !== '/dashboard',
)
routeList.forEach((route) => router.addRoute('Layout', route))
}
router.getRoutes().forEach((route) => {
console.log(`路径: ${route.path}, 名称: ${route.name || '无'}`)
})
console.log('用户信息保存成功', handleRouter(set.menus_info), router);
};
/**

View File

@ -38,11 +38,7 @@ export const formatMenuToElMenu = (rawMenuList: any[]): ElMenuItem[] => {
}
// 2. 处理图标:仅一级菜单基于 path 映射,二级菜单无图标
let icon: string = '';
if (isFirstLevel) {
// 一级菜单:根据后端 path 字段映射图标
icon = pathToIconMap[menu.path] || pathToIconMap['default'] || '';
}
let icon: string = menu.icon as string;
// 二级菜单:不赋值 icon → 自动无图标
// 3. 基础菜单项
@ -63,4 +59,4 @@ export const formatMenuToElMenu = (rawMenuList: any[]): ElMenuItem[] => {
// 遍历一级菜单,标记 isFirstLevel = true
return rawMenuList.map(menu => formatSubMenu(menu, true));
};
};

View File

@ -1,194 +0,0 @@
<template>
<div class="account-detail">
<h4>账户信息</h4>
<el-card class="account-info">
<el-icon>
<UserFilled />
</el-icon>
<div class="item">
<div class="item-content">
<div class="item-label" v-if="!isChangeName">
<span>账号名称:</span>
<span>{{ accountName }}</span>
</div>
<div class="item-changeName" v-else>
<span>账号名称:</span>
<el-input v-model="newAccountName" placeholder="请输入新账号名称" size="small" ref="inputRef" @blur="handleSave" @keyup.enter="handleSave"/>
</div>
<el-button type="default" @click="changeName" size="small">修改名称</el-button>
</div>
<span>用户最后登录时间:2026.02.02 07:00:00</span>
</div>
</el-card>
<div class="security">
<h4>安全信息</h4>
<div class="card-group">
<el-card>
<span>邮箱</span>
<span>已绑定邮箱:{{ userInfo?.email || '未绑定' }}</span>
<el-button :type="accountStatus === true ? 'primary' : 'danger'" @click="handleEmail">{{ accountStatus === true ? '已验证' :
'未验证' }}</el-button>
</el-card>
<el-card>
<span>手机号</span>
<span>已绑定手机号:{{ userInfo?.phone || '未绑定' }}</span>
<el-button :type="phoneStatus === true ? 'primary' : 'danger'" @click="handlePhone">{{ phoneStatus === true ? '已绑定' :
'未绑定' }}</el-button>
</el-card>
<el-card>
<span>密码</span>
<span>************</span>
<el-button type="primary" @click="changePassword">修改</el-button>
</el-card>
<el-card>
<span>默认语言</span>
<span>选择您的语言偏好</span>
<LanguageSelect v-model="languageStore.lang" @change="languageStore.changeLang" />
</el-card>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { UserFilled } from '@element-plus/icons-vue'
//
import LanguageSelect from '@/components/LanguageSelect/index.vue'
// Pinia
import { useLanguageStore } from '@/stores/modules/language'
import { getStorage, setStorage } from '@/utils/storage'
//
import { editAgentNameApi } from '@/api/modules/agent'
//
const router = useRouter()
const languageStore = useLanguageStore()
//
const accountStatus = ref(true)
//
const phoneStatus = ref(true)
/**
* @description 账户信息数据
*/
//
const userInfo = getStorage<any>('userInfo')
const accountName = ref(userInfo?.username || '')
const newAccountName = ref(accountName.value)
//
const isChangeName = ref(false)
//
const inputRef = ref<HTMLInputElement>()
/**
* @description 绑定邮箱
*/
const handleEmail = ()=>{
accountStatus.value === false ? router.push('/user/bind-email') : ''
}
/**
* @description 绑定手机号
*/
const handlePhone = ()=>{
phoneStatus.value === false ? router.push('/user/bind-phone') : ''
}
/**
* @description 修改密码
*/
const changePassword = () => {
router.push('/user/change-password')
}
//
const changeName = async () => {
isChangeName.value = true
// DOM
await nextTick()
inputRef.value?.focus()
}
//
const handleSave = async () => {
if (newAccountName.value.trim()) {
await editAgentNameApi({
account_id: userInfo?.id || '',
account_name: newAccountName.value
})
accountName.value = newAccountName.value.trim()
setStorage('userInfo', {
...userInfo,
username: accountName.value
})
} else {
newAccountName.value = accountName.value //
}
isChangeName.value = false
}
</script>
<style scoped lang="scss">
.account-detail {
h4 {
margin-bottom: 10px;
font-weight: 400;
}
.account-info {
box-shadow: none;
:deep(.el-card__body) {
display: flex;
padding: 10px;
.el-icon {
font-size: 48px;
}
}
.item {
.item-content {
display: flex;
align-items: center;
.item-changeName {
display: flex;
span {
width: 70px;
}
.el-input {
margin-right: 10px;
width: 140px;
}
}
}
}
}
.security {
margin-top: 20px;
.card-group {
display: flex;
flex-direction: column;
gap: 20px;
.el-card {
:deep(.el-card__body) {
padding: 20px 10px;
display: flex;
justify-content: space-between;
align-items: center;
}
}
}
}
}
</style>

View File

@ -1,375 +0,0 @@
<template>
<div class="agent">
<!-- 搜索 -->
<AgentSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
@reset="handleReset" />
<!-- 表格 -->
<AgentTable :table-data="agentTree" :columns="agentTableColumnsOptions" row-key="id">
<template #status="{ row }">
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" />
</template>
<template #pointDifference="{ row }">
<el-button type="primary" size="small" @click="handleViewPointDiff(row)">
查看
</el-button>
</template>
<template #edit="{ row }">
<el-button type="primary" size="small" @click="handleEdit(row)">
编辑
</el-button>
</template>
</AgentTable>
<!-- 分页 -->
<AgentPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
<!-- 弹窗 -->
<AgentDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :fullscreen="dialogFullscreen"
:show-footer="dialogType !== 'view'" @confirm="handleSubmit" @cancel="handleCancel" @close="handleClose">
<!-- 编辑表单 -->
<AgentForm :form-data="agentForm" :form-rules="agentFormRules" :form-items="agentFormItemOptions"
:options-map="agentFormOptionsMap" ref="agentFormRef" v-if="dialogType !== 'view'"></AgentForm>
<!-- 点差表格 -->
<AgentTable :table-data="agentPointTree" :columns="agentPointTableColumnsOptions" row-key="id">
<template #assignedPointDiff="{ row }">
<el-form :model="row" :rules="rules(agentForm,row)">
<el-form-item prop="assignedPointDifference">
<el-input v-model="row.assignedPointDifference" size="small" placeholder="请输入点差" />
</el-form-item>
</el-form>
</template>
</AgentTable>
</AgentDialog>
<!-- 编辑/查看表单 -->
</template>
<script lang="ts" setup>
//
import AgentSearch from '@/components/common/Search.vue'
import AgentTable from '@/components/common/Table.vue'
import AgentPagination from '@/components/common/Pagination.vue'
import AgentDialog from '@/components/common/Dialog.vue'
import AgentForm from '@/components/common/Form.vue'
//
import { agentSearch, agentTableColumns, viewAgentPointTableColumns, agentPointTableColumns, agentFormItem } from './options'
import { rules } from './rules'
//
import { getAgentListApi, getPointDiffConfigApi, getRiskTemplateOptionsApi, getFeeTemplateOptionsApi, editPointDiffApi } from '@/api/modules/account/agent'
//
import { getStorage } from '@/utils/storage';
//id
const useInfo = ref<any>(getStorage('userInfo'))
/**
* @description 定义搜索数据
*/
const searchForm = ref({
username: '',
status: '正常',
startDateTime: '',
endDateTime: '',
})
const searchOptions = ref(agentSearch)
/**
* @description 定义表格数据
*/
const agentTree = ref([])
const agentTableColumnsOptions = ref(agentTableColumns)
/**
* @description 定义分页数据
*/
const total = ref(0)
const pageSize = ref(10)
const currentPage = ref(1)
/**
* @description 定义弹窗数据
*/
const dialogVisible = ref<boolean>(false)
const dialogTitle = ref<string>('')
const dialogType = ref<string>('edit')
const dialogFullscreen = ref(false)
/**
* @description 定义弹窗form数据
*/
const agentForm = ref({
username: '',
parentUsername: '',
riskTemplate: '',
feeTemplate: '',
positionRatio: '',
feeRate: '',
maxPositionRatio: '',
maxFeeRate: '',
//id
id: '',
})
const agentFormRules = rules(agentForm.value)
const agentFormRef = ref()
const agentFormItemOptions = ref(agentFormItem)
//使
const optionsFetched = ref(false)
interface SelectOption {
label: string
value: string | number
}
const agentFormOptionsMap = ref({
riskTemplate: [] as SelectOption[],
feeTemplate: [] as SelectOption[],
})
/**
* @description 定义弹窗表格数据
*/
const agentPointTree = ref([])
const agentPointTableColumnsOptions = computed(() => {
return dialogType.value !== 'view' ? agentPointTableColumns : viewAgentPointTableColumns;
});
/**
* @description 定义通用数据处理方法
*/
// + +
function sumStrNumsWith5Decimals(strNums: string[]) {
// 1.
let total = 0;
// 2.
strNums.forEach(strNum => {
// /0
const num = parseFloat(strNum) || 0;
total += num;
});
// 3.
const result = parseFloat(total.toFixed(5));
return result;
}
//
const mapObjectToOptions = (obj: Record<string, string>) => {
return Object.entries(obj).map(([label, value]) => ({
label, // /
value // /ID
}))
}
/**
* @description 定义通用数据获取方法
*/
//
const getAgentList = async () => {
const params = {
user_id: useInfo.value.id,
page: currentPage.value,
page_size: pageSize.value,
user_name: searchForm.value.username,
status: searchForm.value.status,
reg_start_time: searchForm.value.startDateTime,
reg_end_time: searchForm.value.endDateTime,
}
const data = await getAgentListApi(params)
//
agentTree.value = data.data.map((item: any) => {
//item
const walletNums = [
// undefined
item.wallet_capital?.commission_fee_handling || '0.00000',
item.wallet_capital?.commission_point_diff || '0.00000'
];
//
const rebateTotalAmount = sumStrNumsWith5Decimals(walletNums);
return {
...item,
createTime: item.created_at || '',
feeRate: item.wallet_capital.fee_handling_percent,
rebateRate: item.wallet_capital.commission_fee_handling,
pointDifference: item.wallet_capital.commission_point_diff,
rebateTotalAmount: rebateTotalAmount,
clientCount: item.son_user.length,
level: item.role.title,
parentUsername: item.par_uid === item.parent.id ? item.parent.username : '',
}
})
//
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
}
//
const getPointDiffConfig = async (row: any) => {
const params = {
user_id: row.id
}
const data = await getPointDiffConfigApi(params)
//
agentPointTree.value = data.map((item: any) => ({
...item,
varietyName: item.name,
varietyCode: item.code,
pointDifference: item.point_diff,
assignedPointDifference: item.target_point_diff
}));
}
//
const getOptions = async () => {
//
if (optionsFetched.value) return
try {
const [riskTemplateRes, feeTemplateRes] = await Promise.all([
//
getRiskTemplateOptionsApi(),
//
getFeeTemplateOptionsApi()
])
// label=value=ID
agentFormOptionsMap.value.riskTemplate = riskTemplateRes.map((item: any) => ({
label: item.name,
value: item.id
}))
// label=value=ID
agentFormOptionsMap.value.feeTemplate = mapObjectToOptions(feeTemplateRes as Record<string, string>)
optionsFetched.value = true //
} catch (error) {
console.error('获取表单选项失败:', error)
}
}
/**
* @description 页面加载完成需要调用的方法
*/
onMounted(() => {
getAgentList()
})
/**
* @description 定义搜索组件的全部方法
*/
const handleSearch = () => {
getAgentList()
}
const handleReset = () => {
searchForm.value = {
username: '',
status: '正常',
startDateTime: '',
endDateTime: '',
}
getAgentList()
}
/**
* @description 定义表格方法
*/
//
const handleViewPointDiff = (row: any) => {
getPointDiffConfig(row)
dialogVisible.value = true
dialogTitle.value = '查看点差'
dialogType.value = 'view'
dialogFullscreen.value = true
}
//
const handleEdit = async (row: any) => {
console.log('row', row)
//
await Promise.all([
// getPointDiffConfig(row),
getOptions()
])
//
agentForm.value = {
username: row.username || '',
parentUsername: row.parentUsername || '',
riskTemplate: row.wallet_capital.risk_control_id || '',
feeTemplate: row.wallet_capital.fee_setting_id || '',
positionRatio: row.wallet_capital.toucun_percent || '',
feeRate: row.feeRate || '',
id: row.id || '',
maxPositionRatio: row.wallet_capital.toucun_percent || '',
maxFeeRate: row.wallet_capital.fee_handling_percent || '',
}
dialogVisible.value = true
dialogTitle.value = '编辑'
dialogType.value = 'edit'
dialogFullscreen.value = false
}
/**
* @description 定义分页方法
*/
//
const handlePaginationChange = async (page: { currentPage: number, pageSize: number }) => {
currentPage.value = page.currentPage
pageSize.value = page.pageSize
await getAgentList()
}
/**
* @description 定义dialog方法
*/
/**
* @description 提交之前构造数据
*/
// { "1":"42", "2":"38" }
const formatPointDiffToObject = () => {
if (!agentPointTree.value || !agentPointTree.value.length) return {};
//
const pointDiffObj: Record<string, string> = {};
// { : }
agentPointTree.value.forEach((item: { id: string | number; assignedPointDifference: string }) => {
//
pointDiffObj[String(item.id)] = item.assignedPointDifference || '0';
});
return pointDiffObj; //
};
//
const handleSubmit = async () => {
await agentFormRef.value.validate()
const pointDiffObj = formatPointDiffToObject()
const params = {
user_id: agentForm.value.id || '',
toucun_percent: agentForm.value.positionRatio || '',
point_diffs_json: JSON.stringify(pointDiffObj),
fee_handling_percent: agentForm.value.feeRate || '',
fee_setting_id: agentForm.value.feeTemplate || '',
risk_control_id: agentForm.value.riskTemplate || '',
}
await editPointDiffApi(params)
//
await getAgentList()
dialogVisible.value = false
}
//
const handleCancel = () => {
dialogVisible.value = false
}
//
const handleClose = () => {
dialogVisible.value = false
}
</script>
<style scoped lang="scss">
.table-container{
max-width: 100%;
}
</style>

View File

@ -1,76 +0,0 @@
// 获取当前年份
const currentYear = new Date().getFullYear()
// 定义禁用日期函数:只能选择今年及以后的日期
const disabledDate = (time: Date) => {
// 时间早于今年1月1日则禁用
return time.getTime() < new Date(currentYear, 0, 1).getTime()
}
//定义客户搜索表单配置
export const agentSearch = [
{ prop: 'username', label: '用户名', type: 'input' as const, placeholder: '用户名', width: '120px' },
{
prop: "startDateTime", label: '注册开始时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD',
width: '180px',
disabledDate
},
{
prop: "endDateTime", label: '注册结束时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD',
width: '180px',
disabledDate
},
]
// 定义表格列配置
export const agentTableColumns = [
{ prop: 'id', label: 'ID', align: 'center' as const, width: '80px' },
{ prop: 'username', label: '代理名称', align: 'center' as const },
{ prop: 'createTime', label: '注册时间', align: 'center' as const },
{ slotName: 'status', label: '状态', align: 'center' as const },
{ prop: 'feeRate', label: '手续费', align: 'center' as const },
{ prop: 'rebateRate', label: '手续费返佣', align: 'center' as const },
{ prop: 'pointDifference', label: '点差返佣', align: 'center' as const },
{ slotName: 'pointDifference', label: '点差', align: 'center' as const },
{ prop: 'rebateTotalAmount', label: '返佣总金额', align: 'center' as const },
{ prop: 'clientCount', label: '客户数量', align: 'center' as const },
{ prop: 'level', label: '级别', align: 'center' as const },
{ prop: 'parentUsername', label: '所属上级', align: 'center' as const },
{ slotName: 'edit', label: '编辑', align: 'center' as const },
]
// 查看代理点差表格列配置
export const viewAgentPointTableColumns = [
{ prop: 'varietyName', label: '品种名称', align: 'center' as const },
{ prop: 'varietyCode', label: '品种编号', align: 'center' as const },
{ prop: 'pointDifference', label: '可用点差', align: 'center' as const },
{ prop: 'assignedPointDifference', label: '已分配点差', align: 'center' as const },
]
//编辑代理表格列配置
export const agentPointTableColumns = ([
{ prop: 'varietyName', label: '名称', align: 'center' as const },
{ prop: 'varietyCode', label: '编号', align: 'center' as const },
{ prop: 'pointDifference', label: '可分配点差', align: 'center' as const },
{ prop: 'assignedPointDifference', label: '已分配点差', align: 'center' as const, slotName: 'assignedPointDiff' },
])
//编辑代理form配置
export const agentFormItem = [
//代理名称
{ prop: 'username', label: '代理名称', type: 'input' as const, placeholder: '代理名称', width: '120px' },
//所属上级
{ prop: 'parentUsername', label: '所属上级', type: 'input' as const, placeholder: '所属上级', width: '120px' },
//风控模板
{ prop: 'riskTemplate', label: '风控模板', type: 'select' as const, placeholder: '风控模板', width: '120px' },
//手续费模板
{ prop: 'feeTemplate', label: '手续费模板', type: 'select' as const, placeholder: '手续费模板', width: '120px' },
//头寸
{ prop: 'positionRatio', label: '头寸', type: 'input' as const, placeholder: '请输入头寸', width: '120px' },
//手续费
{ prop: 'feeRate', label: '手续费', type: 'input' as const, placeholder: '手续费', width: '120px' },
]

View File

@ -1,122 +0,0 @@
import type { FormRules } from 'element-plus'
export const rules = (form: any, row?: any): FormRules => {
return {
username: [
{ required: true, message: '请输入名称', trigger: ['blur'] }
],
parentUsername: [
{ required: true, message: '请输入所属上级', trigger: ['blur'] }
],
riskTemplate: [
{ required: true, message: '请选择风险模板', trigger: ['change'] }
],
feeTemplate: [
{ required: true, message: '请选择手续费模板', trigger: ['change'] }
],
positionRatio: [
{ required: true, message: '请输入头寸', trigger: ['blur'] },
{
validator: (rule, value, callback) => {
// 空值已经由 required 校验处理
if (value === '' || value === undefined || value === null) {
return callback();
}
// 尝试转换为数字
const numValue = Number(value);
// 转换失败 → 不是有效数字
if (isNaN(numValue)) {
return callback(new Error('头寸必须为数字'));
}
// 范围校验0-100
if (numValue < 0 || numValue > 100) {
return callback(new Error('头寸需在0-100之间'));
}
// 精度校验最多2位小数
if (!/^\d+(\.\d{1,2})?$/.test(value.toString())) {
return callback(new Error('头寸最多保留2位小数'));
}
// 不超过上级最大头寸校验
if (form.maxPositionRatio && numValue > Number(form.maxPositionRatio)) {
return callback(new Error(`头寸不能超过上级账户的最大头寸(${form.maxPositionRatio})`));
}
callback();
},
trigger: ['blur', 'change']
}
],
feeRate: [
{ required: true, message: '请输入手续费率', trigger: ['blur'] },
{
validator: (rule, value, callback) => {
// 空值已经由 required 校验处理
if (value === '' || value === undefined || value === null) {
return callback();
}
// 尝试转换为数字
const numValue = Number(value);
// 转换失败 → 不是有效数字
if (isNaN(numValue)) {
return callback(new Error('头寸必须为数字'));
}
// 范围校验0-100
if (numValue < 0 || numValue > 100) {
return callback(new Error('头寸需在0-100之间'));
}
// 精度校验最多5位小数
if (!/^\d+(\.\d{1,5})?$/.test(value.toString())) {
return callback(new Error('手续费率最多保留5位小数'));
}
// 不超过上级最大头寸校验
if (form.maxFeeRate && numValue > Number(form.maxFeeRate)) {
return callback(new Error(`头寸不能超过上级账户的最大头寸(${form.maxFeeRate})`));
}
callback();
},
trigger: ['blur', 'change']
}
],
assignedPointDifference: [
{ required: true, message: '请输入点差', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value === '' || value === undefined || value === null) {
return callback()
}
const numValue = Number(value)
// 1. 基础数字校验
if (isNaN(numValue)) {
return callback(new Error('点差必须为数字'))
}
if (numValue < 0) {
return callback(new Error('点差不能为负数'))
}
if (!/^\d+(\.\d{1,5})?$/.test(value.toString())) {
return callback(new Error('点差最多保留5位小数'))
}
// 2. 核心校验:已分配点差 ≤ 可分配点差
if (row && row.pointDifference !== undefined) {
const maxPointDiff = Number(row.pointDifference)
if (numValue > maxPointDiff) {
return callback(new Error(`可分配点差为(${maxPointDiff})`))
}
}
callback()
},
trigger: ['blur', 'change']
}
],
}
}

View File

@ -1,114 +0,0 @@
<template>
<div>
<!-- 搜索 -->
<CustomerCapitalFlowSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
@reset="handleReset">
</CustomerCapitalFlowSearch>
<!-- 表格 -->
<CustomerCapitalFlowTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="id" />
<!-- 分页 -->
<CustomerCapitalFlowPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
</template>
<script setup lang="ts">
//
import CustomerCapitalFlowSearch from '@/components/common/Search.vue'
import CustomerCapitalFlowTable from '@/components/common/Table.vue'
import CustomerCapitalFlowPagination from '@/components/common/Pagination.vue'
//
import { search, tableColumns } from './options'
//
import { getCustomCapitalFlowListApi } from '@/api/modules/agent/customCapitalFlow'
//
/**
* @description: 定义搜索数据
*/
const searchForm = ref({
accountName: '',
userId: '',
startTime: '',
endTime: '',
})
const searchOptions = ref(search)
/**
* @description: 定义表格数据
*/
const tableTree = ref([])
const tableColumnsOptions = ref(tableColumns)
/**
* @description: 定义分页数据
*/
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
/**
* @description: 定义公共请求数据方法
*/
const getCustomerCapitalFlowList = async () => {
const params = {
page: currentPage.value,
page_size: pageSize.value,
start_time: searchForm.value.startTime.length > 0 ? `${searchForm.value.startTime} 0:00:00` : '2026-01-01 0:00:00',
end_time: searchForm.value.endTime.length > 0 ? `${searchForm.value.endTime} 23:59:59` : '2026-03-15 23:59:59',
user_name: searchForm.value.accountName,
account_id: '',
}
const data: any = await getCustomCapitalFlowListApi(params)
//
// table
tableTree.value = data.data.map((item: any) => ({
id:item.account_id,
flowNo:item.trade_id,
accountName:item.user.username,
amount:item.amount,
fundType:item.wallet_type === "1" ? '主账户' : '子账户',
// status:'',
operationType:item.enum_dic.value,
createTime:item.created_at,
}))
//
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getCustomerCapitalFlowList()
})
/**
* @description: 定义搜索方法
*/
const handleSearch = () => {
getCustomerCapitalFlowList()
}
const handleReset = () => {
searchForm.value = {
accountName: '',
userId: '',
startTime: '',
endTime: '',
}
getCustomerCapitalFlowList()
}
/**
* @description: 定义分页方法
*/
const handlePaginationChange = (page: { currentPage: number, size: number }) => {
currentPage.value = page.currentPage
getCustomerCapitalFlowList()
}
</script>

View File

@ -1,35 +0,0 @@
export const search = [
// 账号名称
{ prop: 'accountName', label: '账号名称', type: 'input' as const, placeholder: '请输入账号名称', width: '150px' },
// 开始时间
{
prop: "startTime", label: '注册开始时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD',
},
// 结束时间
{
prop: "endTime", label: '注册结束时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD',
},
// 选择账号
{ prop: 'userId', label: '账号', type: 'select' as const, placeholder: '请选择账号' },
]
export const tableColumns = [
// 流水id
{ prop: 'id', label: '流水id' },
// 流水单号
{ prop: 'flowNo', label: '流水单号' },
// 账户名称
{ prop: 'accountName', label: '账户名称' },
// 金额
{ prop: 'amount', label: '金额' },
// 资金类别
{ prop: 'fundType', label: '资金类别' },
// 状态
// { prop: 'status', label: '状态' },
// 操作方式
{ prop: 'operationType', label: '操作方式' },
// 时间
{ prop: 'createTime', label: '时间' },
]

View File

@ -1,212 +0,0 @@
<template>
<div class="customer-detail">
<div class="back">
<div class="title" @click="handleBack">
<el-icon>
<ArrowLeft />
</el-icon>
返回上层
</div>
</div>
<div class="detail">
<el-card class="user-card">
<el-icon>
<UserFilled />
</el-icon>
<div class="user-item">
<span>账户名称:{{customerDetail.accountName}}</span>
<span>账户id:{{customerDetail.userId}}</span>
<span>账户角色:{{customerDetail.userRole}}</span>
</div>
</el-card>
<el-card class="information-card">
<div class="information-item">
<div class="group">
<p>手机号</p>
<p>{{customerDetail.phone}}</p>
</div>
<div class="group">
<p>邮箱</p>
<p>{{customerDetail.email}}</p>
</div>
</div>
<div class="information-item">
<div class="group">
<p>存款金额</p>
<p>{{customerDetail.depositAmount}}</p>
</div>
<div class="group">
<p>账户余额</p>
<p>{{customerDetail.balance}}</p>
</div>
</div>
<div class="information-item">
<div class="group">
<p>注册时间</p>
<p>{{customerDetail.registerTime}}</p>
</div>
<div class="group">
<p>历史交易金额</p>
<p>{{customerDetail.historyTradeAmount}}</p>
</div>
</div>
</el-card>
</div>
<CustomerDetailTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="id" :default-sort="{ prop: 'balance', order: 'descending' }"/>
</div>
</template>
<script setup lang="ts">
import { ArrowLeft, UserFilled } from '@element-plus/icons-vue'
//
import CustomerDetailTable from '@/components/common/Table.vue'
//
import { tableColumns } from './options'
import { getCustomerDetailApi } from '@/api/modules/agent/customerDetail'
const router = useRouter()
const route = useRoute()
/**
* @description: 定义客户详情数据
*/
const customerDetail = ref({
//
accountName: '',
// id
userId: '',
//
userRole: '',
//
phone: '',
//
email: '',
//
depositAmount: '',
//
balance: '',
//
registerTime: '',
//
historyTradeAmount: '',
})
//
const roleMap: any = {
1: '平台',
2: '承包商',
3: '特殊做市商',
4: '普通做市商',
5: '代理',
6: '用户'
}
/**
* @description: 定义表格数据
*/
const tableTree = ref([])
const tableColumnsOptions = ref(tableColumns)
/**
* @description: 获取客户详情
*/
const getCustomerDetail = async () => {
const id = route.params.id
const data: any = await getCustomerDetailApi({
user_id: id,
})
//
customerDetail.value = {
//
accountName: data.user.username,
// id
userId: data.user.id,
//
userRole: roleMap[data.user.role_id],
//
phone: data.user.phone,
//
email: data.user.email,
//
depositAmount: data.user.total_recharge_amount,
//
balance: data.user.total_recharge_amount,
//
registerTime: data.user.created_at,
//
historyTradeAmount: data.user.history_transfer_amount,
}
//
tableTree.value = data.accounts.map((item: any) => ({
tradeAccount:item.wallets_trade.account_id,
accountName:item.account_name,
balance:item.wallets_trade.amount,
leverage:item.wallets_trade.lever,
historyTradeVolume: item.collect_data !== null ? item.collect_data.total_order: '',
createTime:item.created_at,
}))
console.log(data);
}
/**
* @description: 页面加载完成需要加载的数据
*/
onMounted(() => {
getCustomerDetail()
})
/**
* @description: 定义返回上层方法
*/
const handleBack = () => {
router.back()
}
</script>
<style scoped lang="scss">
.back {
cursor: pointer;
margin-bottom: 10px;
}
.detail {
display: flex;
gap: 20px;
margin-bottom: 10px;
.el-card {
:deep(.el-card__body) {
padding: 20px 10px;
display: flex;
}
}
.user-card {
max-width: 300px;
.el-icon {
font-size: 74px;
}
.user-item {
display: flex;
flex-direction: column;
}
}
.information-card {
width: calc(100% - 350px);
:deep(.el-card__body) {
justify-content: space-between;
}
.information-item {
display: flex;
flex-direction: column;
gap: 5px;
}
}
}
</style>

View File

@ -1,14 +0,0 @@
export const tableColumns = [
//交易账号
{label: '交易账号', prop: 'tradeAccount'},
//账户名称
{label: '账户名称', prop: 'accountName'},
//余额
{label: '余额', prop: 'balance', sortable: true},
//交易杠杆
{label: '交易杠杆', prop: 'leverage'},
//历史交易量
{label: '历史交易量', prop: 'historyTradeVolume'},
//添加时间
{label: '添加时间', prop: 'createTime'},
]

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>存款</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>存款审核</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>取款</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>取款渠道</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>取款审核</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -19,7 +19,7 @@
import { UserFilled } from '@element-plus/icons-vue'
import { getStorage } from '@/utils/storage'
import Agent from '@/views/agent/index.vue'
import Agent from './components/view/index.vue'
//id
const userId = getStorage('userInfo')?.id
@ -77,4 +77,4 @@ const cardData = [
font-weight: 600;
}
}
</style>
</style>

View File

@ -1,3 +0,0 @@
<template>
<h1>资金流水</h1>
</template>

View File

@ -1,3 +0,0 @@
<template>
<h1>存款审核</h1>
</template>

View File

@ -1,39 +0,0 @@
/**
* @description:
*/
export const search = [
//存款账户
{
label: '存款账户',
prop: 'depositAccount',
type: 'input',
placeholder: '请输入存款账户'
},
//级别
{
label: '级别',
prop: 'level',
type: 'select',
placeholder: '请选择级别',
}
]
/**
* @description:
*/
export const tableColumns = [
{label: '序号', prop: 'index'},
{label: '账户', prop: 'depositAccount'},
{label: '所属代理', prop: 'agentName'},
{label: '流水编号', prop: 'serialNumber'},
{label: '钱包地址', prop: 'walletAddress'},
{label: '钱包类型', prop: 'walletType'},
{label: '账户持有人姓名', prop: 'accountHolderName'},
{label: '银行名称', prop: 'bankName'},
{label: '银行识别代码', prop: 'bankCode'},
]
/**
* @description: dialog表格配置
*/
export const dialogTableColumns = {}

View File

@ -1,3 +0,0 @@
<template>
<h1>存款明细</h1>
</template>

View File

@ -1,37 +0,0 @@
<template>
<div class="receiver-account">
<div class="bar">
<el-button type="primary" @click="handleAdd">添加</el-button>
<el-button type="primary" @click="handleModify">编辑</el-button>
<el-button type="primary" @click="handleEnable">启用</el-button>
<el-button type="danger" @click="handleDisable">禁用</el-button>
<el-button type="primary" @click="handleExport">导出</el-button>
</div>
<ReceiverAccountTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="id" ref="tableRef"></ReceiverAccountTable>
</div>
</template>
<script setup lang="ts">
//
import ReceiverAccountTable from '@/components/common/Table.vue'
import { tableColumns } from './options'
//
const tableRef = ref()
const tableColumnsOptions = ref(tableColumns)
const tableTree = ref([])
</script>
<style scoped>
.receiver-account {
.bar {
margin-bottom: 10px;
display: flex;
align-items: center;
span {
margin-right: 10px;
}
}
}
</style>

View File

@ -1,17 +0,0 @@
export const tableColumns = [
//序号
{ label: '序号', prop: 'index' },
//账户持有人姓名
{ label: '账户持有人姓名', prop: 'accountHolderName' },
//银行名称
{ label: '银行名称', prop: 'bankName' },
//银行卡号
{ label: '银行卡号', prop: 'bankAccount' },
//银行识别代码
{ label: '银行识别代码', prop: 'bankCode' },
//银行地址
{ label: '银行地址', prop: 'bankAddress' },
//状态
{ label: '状态', slotName: 'status' },
]

View File

@ -1,3 +0,0 @@
<template>
<h1>取款明细</h1>
</template>

View File

@ -1,3 +0,0 @@
<template>
<h1>取款审核</h1>
</template>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>手续费模板</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>跟单订单</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>充值订单</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>带单订单</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -0,0 +1,7 @@
<script setup lang="ts"></script>
<template>
<h1>提现</h1>
</template>
<style scoped lang="scss"></style>

View File

@ -1,120 +0,0 @@
<template>
<div class="follow-record">
<!-- 搜索 -->
<FollowRecordSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
@reset="handleReset">
<template #button="{ form }">
<el-button type="primary" @click="handleExport()">
导出
</el-button>
</template>
</FollowRecordSearch>
<!-- 表格 -->
<FollowRecordTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="id" />
<!-- 分页 -->
<FollowRecordPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
</template>
<script setup lang="ts">
//
import FollowRecordSearch from '@/components/common/Search.vue'
import FollowRecordTable from '@/components/common/Table.vue'
import FollowRecordPagination from '@/components/common/Pagination.vue'
//
import { search, tableColumns } from './options'
//
import { getFollowListApi } from '@/api/modules/personalized/followRecord'
//
import { exportToExcel } from '@/utils/exportToExcel'
/**
* @description: 定义搜索数据
*/
const searchOptions = ref(search)
const searchForm = ref({
orderNo: '',
startTime: '',
endTime: '',
})
/**
* @description: 定义表格数据
*/
const tableTree = ref([])
const tableColumnsOptions = ref(tableColumns)
/**
* @description: 定义分页数据
*/
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
/**
* @description: 定义公共请求数据方法
*/
const getFollowList = async () => {
const params = {
page: currentPage.value,
page_size: pageSize.value,
start_time: searchForm.value.startTime.length > 0 ? `${searchForm.value.startTime} 0:00:00` : '2026-01-01 0:00:00',
end_time: searchForm.value.endTime.length > 0 ? `${searchForm.value.endTime} 23:59:59` : '2026-03-01 23:59:59',
order_no: searchForm.value.orderNo,
}
const data: any = await getFollowListApi(params)
// table
tableTree.value = data.data.map((item: any) => ({
orderNo: item.order_id,
followAccount: item.user.username,
symbol: item.variety_id,
tradeQty: item.num,
tradeTime: item.created_at,
closeProfit: '后续添加'
}))
//
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getFollowList()
})
/**
* @description: 定义搜索方法
*/
const handleSearch = async () => {
console.log("搜索");
getFollowList()
}
const handleReset = () => {
console.log('重置搜索')
searchForm.value = {
orderNo: '',
startTime: '',
endTime: '',
}
getFollowList()
}
const handleExport = () => {
exportToExcel({
tableData: tableTree.value,
columns: tableColumnsOptions.value,
title: '导出跟单记录',
defaultFileNamePrefix: '跟单记录',
})
}
/**
* @description: 定义分页方法
*/
const handlePaginationChange = (page: { currentPage: number, size: number }) => {
currentPage.value = page.currentPage
getFollowList()
}
</script>

View File

@ -1,29 +0,0 @@
export const search = [
// 订单号
{ prop: 'orderNo', label: '订单号', type: 'input' as const, placeholder: '请输入订单号',width: '200px' },
//开始时间
{
prop: 'startTime', label: '开始时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD', placeholder: '请选择开始时间', width: '150px'
},
//结束时间
{
prop: 'endTime', label: '结束时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD', placeholder: '请选择结束时间', width: '150px'
},
]
export const tableColumns = [
// 订单号
{ prop: 'orderNo', label: '订单号' },
// 跟单账户
{ prop: 'followAccount', label: '跟单账户' },
// 交易品种
{ prop: 'symbol', label: '交易品种' },
// 交易数量
{ prop: 'tradeQty', label: '交易数量' },
// 交易时间
{ prop: 'tradeTime', label: '交易时间' },
// 平仓盈亏
{ prop: 'closeProfit', label: '平仓盈亏' },
]

View File

@ -1,120 +0,0 @@
<template>
<div class="follow-record">
<!-- 搜索 -->
<LeadRecordSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
@reset="handleReset">
<template #button="{ form }">
<el-button type="primary" @click="handleExport()">
导出
</el-button>
</template>
</LeadRecordSearch>
<!-- 表格 -->
<LeadRecordTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="id" />
<!-- 分页 -->
<LeadRecordPagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
</template>
<script setup lang="ts">
//
import LeadRecordSearch from '@/components/common/Search.vue'
import LeadRecordTable from '@/components/common/Table.vue'
import LeadRecordPagination from '@/components/common/Pagination.vue'
//
import { search, tableColumns } from './options'
//
import { getFollowListApi } from '@/api/modules/personalized/followRecord'
//
import { exportToExcel } from '@/utils/exportToExcel'
/**
* @description: 定义搜索数据
*/
const searchOptions = ref(search)
const searchForm = ref({
orderNo: '',
startTime: '',
endTime: '',
})
/**
* @description: 定义表格数据
*/
const tableTree = ref([])
const tableColumnsOptions = ref(tableColumns)
/**
* @description: 定义分页数据
*/
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
/**
* @description: 定义公共请求数据方法
*/
const getLeadList = async () => {
const params = {
page: currentPage.value,
page_size: pageSize.value,
start_time: searchForm.value.startTime.length > 0 ? `${searchForm.value.startTime} 0:00:00` : '2026-01-01 0:00:00',
end_time: searchForm.value.endTime.length > 0 ? `${searchForm.value.endTime} 23:59:59` : '2026-03-01 23:59:59',
order_no: searchForm.value.orderNo,
}
const data: any = await getFollowListApi(params)
// table
tableTree.value = data.data.map((item: any) => ({
orderNo: item.order_id,
followAccount: item.user.username,
symbol: item.variety_id,
tradeQty: item.num,
tradeTime: item.created_at,
closeProfit: '后续添加'
}))
//
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getLeadList()
})
/**
* @description: 定义搜索方法
*/
const handleSearch = async () => {
console.log("搜索");
getLeadList()
}
const handleReset = () => {
console.log('重置搜索')
searchForm.value = {
orderNo: '',
startTime: '',
endTime: '',
}
getLeadList()
}
const handleExport = () => {
exportToExcel({
tableData: tableTree.value,
columns: tableColumnsOptions.value,
title: '导出带单记录',
defaultFileNamePrefix: '带单记录',
})
}
/**
* @description: 定义分页方法
*/
const handlePaginationChange = (page: { currentPage: number, size: number }) => {
currentPage.value = page.currentPage
getLeadList()
}
</script>

View File

@ -1,29 +0,0 @@
export const search = [
// 订单号
{ prop: 'orderNo', label: '订单号', type: 'input' as const, placeholder: '请输入订单号',width: '200px' },
//开始时间
{
prop: 'startTime', label: '开始时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD', placeholder: '请选择开始时间', width: '150px'
},
//结束时间
{
prop: 'endTime', label: '结束时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD', placeholder: '请选择结束时间', width: '150px'
},
]
export const tableColumns = [
// 订单号
{ prop: 'orderNo', label: '订单号' },
// 跟单账户
{ prop: 'followAccount', label: '跟单账户' },
// 交易品种
{ prop: 'symbol', label: '交易品种' },
// 交易数量
{ prop: 'tradeQty', label: '交易数量' },
// 交易时间
{ prop: 'tradeTime', label: '交易时间' },
// 平仓盈亏
{ prop: 'closeProfit', label: '平仓盈亏' },
]

View File

@ -0,0 +1,387 @@
<template>
<div>
<!-- 搜索 -->
<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">
<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="handleDetail(row)" v-if="row.userId">
查看详情
</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'" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<CustomerListTable :table-data="marginRatioTree" :columns="marginRatioTableColumnsOptions" row-key="id"
v-if="dialogType === 'editMarginRatio'">
<template #assignedPointDiff="{ row }">
<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-form-item>
</el-form>
</template>
</CustomerListTable>
<CustomerListForm :form-data="commissionRatioForm" :form-rules="commissionRatioFormRules"
:form-items="commissionRatioFormItemOptions" ref="commissionRatioFormRef"
v-if="dialogType === 'editCommissionRatio'"></CustomerListForm>
</CustomerListDialog>
<!-- dialog表格 -->
</div>
</template>
<script setup lang="ts">
//
import CustomerListSearch from '@/components/common/Search.vue'
import CustomerListTable from '@/components/common/Table.vue'
import CustomerListPagination from '@/components/common/Pagination.vue'
import CustomerListDialog from '@/components/common/Dialog.vue'
import CustomerListForm from '@/components/common/Form.vue'
//
import { search, tableColumns, marginRatioTableColumns, commissionRatioFormItem } from './options'
//
import { getCustomerListApi, getSelfVarietyPointDiffConfigApi, editCustomerApi } from '@/api/modules/agent/customerList'
//
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)
/**
* @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)
/**
* @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 roleMap: any = {
1: '平台',
2: '承包商',
3: '特殊做市商',
4: '普通做市商',
5: '代理',
6: '用户'
}
/**
* @description: 定义公共请求数据方法
*/
//
const getCustomerList = async (id: string) => {
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,
}))
//
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
}
//
const getSelfVarietyPointDiffConfig = async (id: string) => {
const params = {
user_id: id,
}
const data: any = await getSelfVarietyPointDiffConfigApi(params)
marginRatioTree.value = data.data.map((item: any) => ({
id: item.id,
varietyName: item.name,
varietyCode: item.code,
pointDifference: item.point_diff,
assignedPointDifference: item.target_point_diff
}))
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getCustomerList(userId.value)
})
/**
* @description: 定义搜索方法
*/
const handleSearch = async () => {
getCustomerList(userId.value)
}
const handleReset = () => {
searchForm.value = {
accountName: '',
startTime: '',
endTime: '',
status: '',
}
getCustomerList(userId.value)
}
/**
* @description: 定义表格方法
*/
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) => {
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,
}))
}
/**
* @description: 定义分页方法
*/
const handlePaginationChange = (page: { currentPage: number, size: number }) => {
currentPage.value = page.currentPage
getCustomerList(userId.value)
}
/**
* @description: 定义弹窗方法
*/
//
const handleSubmit = async () => {
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) })
dialogVisible.value = false
} catch (err) {
console.log('提交失败:', err)
}
}
if (dialogType.value === 'editCommissionRatio') {
await commissionRatioFormRef.value.validate()
try {
await editCustomerApi({ ...params, fee_handling_percent: commissionRatioForm.value.commissionRatio })
dialogVisible.value = false
} catch (err) {
console.log('提交失败:', err)
}
}
}
//
const handleCancel = () => {
dialogVisible.value = false
}
//
const handleClose = () => {
dialogVisible.value = false
}
/**
* @description: 定义弹窗表格方法
*/
</script>
<style scoped lang="scss">
:deep(.placeholder-row) {
text-align: center;
color: #909399;
/* 灰色和Element空状态颜色一致 */
}
</style>

View File

@ -0,0 +1,55 @@
export const search = [
// 账号名称
{ prop: 'accountName', label: '账号名称', type: 'input' as const, placeholder: '请输入账号名称', width: '150px' },
// 开始时间
{
prop: "startTime", label: '注册开始时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD',
},
// 结束时间
{
prop: "endTime", label: '注册结束时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD',
},
// 状态
{ prop: 'status', label: '状态', type: 'select' as const, placeholder: '请选择状态' },
]
export const tableColumns = [
// 账号名称
{ label: '账号名称', prop: 'accountName'},
// 账号id
{ prop: 'userId', label: '账号id' },
// 状态
{ prop: 'status', label: '状态' },
// 账号角色
{ prop: 'role', label: '账号角色' },
// 账号数量
{ prop: 'accountQty', label: '账号数量' },
// 点差分配比例
{ prop: 'marginRatio', label: '点差分配比例', slotName: 'marginRatio' },
// 手续费分成比例
{ prop: 'commissionRatio', label: '手续费分成比例', slotName: 'commissionRatio' },
// 注册时间
{ prop: 'createTime', label: '注册时间', sortable: true },
// 操作
{ label: '操作', slotName: 'action' }
]
// 点差表格配置
export const marginRatioTableColumns = [
// 品种名称
{ prop: 'varietyName', label: '名称', align: 'center' as const },
// 品种编号
{ prop: 'varietyCode', label: '编号', align: 'center' as const },
// 可分配点差
{ prop: 'pointDifference', label: '可分配点差', align: 'center' as const },
// 已分配点差
{ prop: 'assignedPointDifference', label: '已分配点差', align: 'center' as const, slotName: 'assignedPointDiff' },
]
// 手续费分成比例form配置
export const commissionRatioFormItem = [
// 手续费分成比例
{ prop: 'commissionRatio', label: '手续费分成比例', type: 'input' as const, placeholder: '请输入手续费分成比例',labelWidth: '140px'},
]

View File

@ -1,58 +1,80 @@
<template>
<div class="system">
<!-- 角色列表头 -->
<div class="header">
<h4>角色列表</h4>
<el-button type="primary" @click="handleAddRole">新增角色</el-button>
</div>
<!-- 角色列表表格 -->
<RoleTable :table-data="roleTree" :columns="roleTableColumns" row-key="id">
<template #status="{ row }">
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" active-text="启用"
inactive-text="禁用"/>
</template>
<template #createTime="{ row }">
{{ row.created_at }}
</template>
<template #action="{ row }">
<el-button link type="primary" size="small" @click="handleEditRole(row)">
编辑
</el-button>
<el-button link type="primary" size="small" @click="handlePermission(row)">
设置权限
</el-button>
<el-button link type="danger" size="small" @click="handleDeleteRole(row)">
删除
</el-button>
</template>
</RoleTable>
<div class="system">
<!-- 角色列表头 -->
<div class="header">
<h4>角色列表</h4>
<el-button type="primary" @click="handleAddRole">新增角色</el-button>
</div>
<!-- 角色新增/编辑弹窗 -->
<RoleDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose" :fullscreen="fullScreen">
<RoleForm :form-data="roleForm" :form-rules="roleFormRules" :form-items="roleFormItemOptions" ref="roleFormRef"
:options-map="roleFormOptionsMap" :dialog-type="dialogType">
<template #permissions="{ formData }">
<div class="permission-group">
<div v-for="(group, module) in groupedPermissions" :key="module" class="permission-module">
<!-- 点击事件的 checked 参数 -->
<el-checkbox :model-value="getModuleCheckedState(module)"
:indeterminate="getModuleIndeterminateState(module)"
@change="(checked: boolean | string | number) => handleModuleCheckChange(module, !!checked)"
class="module-title">
{{ module }}
</el-checkbox>
<!-- 添加子权限 change 事件监听 -->
<el-checkbox-group v-model="formData.permissions" @change="handlePermissionChange">
<el-checkbox v-for="perm in group" :key="perm.id" :value="perm.id">
{{ perm.title }} ({{ perm.name }})
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</template>
</RoleForm>
</RoleDialog>
<!-- 角色列表表格 -->
<RoleTable :table-data="roleTree" :columns="roleTableColumns" row-key="id">
<template #status="{ row }">
<el-switch
:model-value="row.status === 1"
:active-value="true"
:inactive-value="false"
active-text="启用"
inactive-text="禁用"
/>
</template>
<template #createTime="{ row }">
{{ row.created_at }}
</template>
<template #action="{ row }">
<el-button link type="primary" size="small" @click="handleEditRole(row)"> 编辑 </el-button>
<el-button link type="primary" size="small" @click="handlePermission(row)">
设置权限
</el-button>
<el-button link type="danger" size="small" @click="handleDeleteRole(row)"> 删除 </el-button>
</template>
</RoleTable>
</div>
<!-- 角色新增/编辑弹窗 -->
<RoleDialog
:visible="dialogVisible"
:title="dialogTitle"
:type="dialogType"
@confirm="handleSubmit"
@cancel="handleCancel"
@close="handleClose"
:fullscreen="fullScreen"
>
<RoleForm
:form-data="roleForm"
:form-rules="roleFormRules"
:form-items="roleFormItemOptions"
ref="roleFormRef"
:options-map="roleFormOptionsMap"
:dialog-type="dialogType"
>
<template #permissions="{ formData }">
<div class="permission-group">
<div
v-for="(group, module) in groupedPermissions"
:key="module"
class="permission-module"
>
<!-- 点击事件的 checked 参数 -->
<el-checkbox
:model-value="getModuleCheckedState(module)"
:indeterminate="getModuleIndeterminateState(module)"
@change="
(checked: boolean | string | number) => handleModuleCheckChange(module, !!checked)
"
class="module-title"
>
{{ module }}
</el-checkbox>
<!-- 添加子权限 change 事件监听 -->
<el-checkbox-group v-model="formData.permissions" @change="handlePermissionChange">
<el-checkbox v-for="perm in group" :key="perm.id" :value="perm.id">
{{ perm.title }} ({{ perm.name }})
</el-checkbox>
</el-checkbox-group>
</div>
</div>
</template>
</RoleForm>
</RoleDialog>
</template>
<script lang="ts" setup>
@ -67,12 +89,19 @@ import RoleForm from '@/components/common/Form.vue'
import { roleTableColumns, roleFormItem, permissionsFormItem } from './options'
import { rules } from './rules'
import { getRoleApi, createRoleApi, editRoleApi, deleteRoleApi, getRolePermissionApi, setRolePermissionApi } from '@/api/modules/system/role'
import {
getRoleApi,
createRoleApi,
editRoleApi,
deleteRoleApi,
getRolePermissionApi,
setRolePermissionApi,
} from '@/api/modules/system/role'
// header
const roleTitle = ref('角色管理')
const roleHeaderButtons = [
{ label: '新增角色', type: 'primary' as const, icon: Plus, key: 'addRole' },
{ label: '新增角色', type: 'primary' as const, icon: Plus, key: 'addRole' },
]
//
@ -86,9 +115,9 @@ const fullScreen = ref(false)
//
const roleForm = ref({
name: '', //
title: '', //
permissions: [] as number[], //
name: '', //
title: '', //
permissions: [] as number[], //
})
const roleFormRef = ref<FormInstance>()
//
@ -97,7 +126,7 @@ const roleFormRules = rules()
const roleFormItemOptions = ref(roleFormItem)
//
const roleFormOptionsMap = ref({
'permissions': [] as any[],
permissions: [] as any[],
})
//id
@ -107,232 +136,258 @@ const groupedPermissions = ref<Record<string, any[]>>({})
//
const allPermissions = ref<any[]>([])
/**
/**
@description: 新增角色
**/
const handleAddRole = () => {
console.log('新增角色', dialogType.value)
dialogVisible.value = true
dialogTitle.value = '新增角色'
handleDialogType(dialogType.value)
console.log('新增角色', dialogType.value)
dialogVisible.value = true
dialogTitle.value = '新增角色'
handleDialogType(dialogType.value)
}
/**
/**
@description: 角色数据处理
**/
// status=1
const filterRoles = (roleList: any[]) => {
const enabledRoles = roleList.filter(role => role.status === 1)
return enabledRoles
const enabledRoles = roleList.filter((role) => role.status === 1)
return enabledRoles
}
//
const fetchRoleList = async () => {
try {
const data = await getRoleApi()
roleTree.value = data
} catch (err) {
console.error('获取角色列表失败', err)
}
try {
const data = await getRoleApi()
roleTree.value = data
} catch (err) {
console.error('获取角色列表失败', err)
}
}
//
onMounted(async () => {
await fetchRoleList()
await fetchRoleList()
})
/**
/**
@description: 处理菜单编辑/删除/状态切换
**/
//
const handleEditRole = (row: any) => {
dialogVisible.value = true
dialogTitle.value = '编辑角色'
dialogType.value = 'edit'
handleDialogType(dialogType.value)
roleForm.value = {
...row,
id: row.id, // id
}
console.log('编辑角色', dialogType.value)
dialogVisible.value = true
dialogTitle.value = '编辑角色'
dialogType.value = 'edit'
handleDialogType(dialogType.value)
roleForm.value = {
...row,
id: row.id, // id
}
console.log('编辑角色', dialogType.value)
}
//
const handleDeleteRole = async (row: any) => {
try {
await deleteRoleApi({ id: row.id })
await fetchRoleList()
} catch (err) {
console.error('删除角色失败')
}
try {
await deleteRoleApi({ id: row.id })
await fetchRoleList()
} catch (err) {
console.error('删除角色失败')
}
}
//
const handleDialogType = (type: string) => {
if (type === 'permissions') {
roleFormItemOptions.value = permissionsFormItem as any;
fullScreen.value = true
console.log('权限弹窗', fullScreen.value)
} else {
roleFormItemOptions.value = roleFormItem as any;
fullScreen.value = false
}
if (type === 'permissions') {
roleFormItemOptions.value = permissionsFormItem as any
fullScreen.value = true
console.log('权限弹窗', fullScreen.value)
} else {
roleFormItemOptions.value = roleFormItem as any
fullScreen.value = false
}
}
//
const getModuleCheckedState = (module: string) => {
const group = groupedPermissions.value[module]
if (!group) return false
const groupIds = group.map(perm => perm.id)
return groupIds.every(id => roleForm.value.permissions.includes(id))
const group = groupedPermissions.value[module]
if (!group) return false
const groupIds = group.map((perm) => perm.id)
return groupIds.every((id) => roleForm.value.permissions.includes(id))
}
//
const getModuleIndeterminateState = (module: string) => {
const group = groupedPermissions.value[module]
if (!group) return false
const groupIds = group.map(perm => perm.id)
const selectedCount = groupIds.filter(id => roleForm.value.permissions.includes(id)).length
return selectedCount > 0 && selectedCount < group.length
const group = groupedPermissions.value[module]
if (!group) return false
const groupIds = group.map((perm) => perm.id)
const selectedCount = groupIds.filter((id) => roleForm.value.permissions.includes(id)).length
return selectedCount > 0 && selectedCount < group.length
}
// /
const handleModuleCheckChange = (module: string, checked: boolean) => {
const group = groupedPermissions.value[module]
if (!group) return
const groupIds = group.map(perm => perm.id)
const group = groupedPermissions.value[module]
if (!group) return
const groupIds = group.map((perm) => perm.id)
if (checked) {
roleForm.value.permissions = [...new Set([...roleForm.value.permissions, ...groupIds])]
} else {
roleForm.value.permissions = roleForm.value.permissions.filter(id => !groupIds.includes(id))
}
if (checked) {
roleForm.value.permissions = [...new Set([...roleForm.value.permissions, ...groupIds])]
} else {
roleForm.value.permissions = roleForm.value.permissions.filter((id) => !groupIds.includes(id))
}
}
//
const handlePermissionChange = () => {
roleForm.value = { ...roleForm.value }
roleForm.value = { ...roleForm.value }
}
// module
const groupPermissionsByModule = (permissions: any[]) => {
const grouped: Record<string, any[]> = {}
permissions.forEach(perm => {
grouped[perm.module] = grouped[perm.module] || []
grouped[perm.module]!.push(perm)
})
Object.values(grouped).forEach(group => group.sort((a, b) => a.id - b.id))
return grouped
const grouped: Record<string, any[]> = {}
permissions.forEach((perm) => {
grouped[perm.module] = grouped[perm.module] || []
const item = {
...perm,
pId: permissions.find((p) => p.title === perm.module).id,
}
grouped[perm.module]!.push(item)
})
for (const key in grouped) {
console.log(key, grouped[key])
grouped[key] = grouped[key].filter((item) => item.pId !== item.id)
}
Object.values(grouped).forEach((group) => group.sort((a, b) => a.id - b.id))
return grouped
}
//
const handlePermission = async (row: any) => {
dialogVisible.value = true
dialogTitle.value = '设置角色权限'
dialogType.value = 'permissions'
handleDialogType(dialogType.value)
permissionIds.value = row.id
try {
const data = await getRolePermissionApi({ id: row.id })
allPermissions.value = data.all_permission_info
groupedPermissions.value = groupPermissionsByModule(data.all_permission_info)
roleForm.value.permissions = data.permission_ids || []
console.log('分组后的权限', groupedPermissions.value)
} catch (err) {
console.error('获取角色权限失败', err)
}
dialogVisible.value = true
dialogTitle.value = '设置角色权限'
dialogType.value = 'permissions'
handleDialogType(dialogType.value)
permissionIds.value = row.id
try {
const data = await getRolePermissionApi({ id: row.id })
allPermissions.value = data.all_permission_info.map((item) => ({
...item,
pId: data.all_permission_info.find((p) => p.title === item.module)?.id,
}))
groupedPermissions.value = groupPermissionsByModule(data.all_permission_info)
roleForm.value.permissions = data.permission_ids || []
console.log('分组后的权限', groupedPermissions.value)
} catch (err) {
console.error('获取角色权限失败', err)
}
}
/**
/**
@description: 处理弹窗提交/取消/关闭事件
**/
const handleSubmit = async () => {
console.log('提交菜单表单', roleForm.value.permissions) // ID便
try {
await roleFormRef.value?.validate()
if (dialogType.value === 'add') {
const params = {
name: roleForm.value.name,
title: roleForm.value.title,
}
await createRoleApi(params)
await fetchRoleList()
} else if (dialogType.value === 'edit') {
await editRoleApi(roleForm.value)
await fetchRoleList()
} else {
await setRolePermissionApi({
id: permissionIds.value,
permission_ids: roleForm.value.permissions.join(',')
})
await fetchRoleList()
console.log(
'提交菜单表单',
roleForm.value.permissions,
groupedPermissions.value,
allPermissions.value,
) // ID便
try {
await roleFormRef.value?.validate()
if (dialogType.value === 'add') {
const params = {
name: roleForm.value.name,
title: roleForm.value.title,
}
await createRoleApi(params)
await fetchRoleList()
} else if (dialogType.value === 'edit') {
await editRoleApi(roleForm.value)
await fetchRoleList()
} else {
let permissionIdList: any[] = [...roleForm.value.permissions]
for (let i = 0; i < allPermissions.value.length; i++) {
const flag = permissionIdList.includes(allPermissions.value[i]!.id)
if (flag) {
if (!permissionIdList.includes(allPermissions.value[i]!.pId))
permissionIdList.push(allPermissions.value[i]!.pId)
}
dialogVisible.value = false
} catch (err) {
console.log('表单验证失败')
return
}
console.log('设置角色权限', permissionIdList.join(','))
await setRolePermissionApi({
id: permissionIds.value,
permission_ids: permissionIdList.join(','),
})
await fetchRoleList()
}
dialogVisible.value = false
} catch (err) {
console.log('表单验证失败')
return
}
}
//
const handleCancel = () => {
dialogVisible.value = false
dialogType.value = 'add'
roleForm.value = {
name: '',
title: '',
permissions: [] as number[],
}
dialogVisible.value = false
dialogType.value = 'add'
roleForm.value = {
name: '',
title: '',
permissions: [] as number[],
}
}
//
const handleClose = () => {
dialogVisible.value = false
dialogType.value = 'add'
roleForm.value = {
name: '',
title: '',
permissions: [] as number[],
}
dialogVisible.value = false
dialogType.value = 'add'
roleForm.value = {
name: '',
title: '',
permissions: [] as number[],
}
}
</script>
<style scoped lang="scss">
.permission-group{
width: 100%;
.permission-group {
width: 100%;
}
:deep(.el-form-item__content) {
margin-left: 0 !important;
margin-left: 0 !important;
}
.permission-module {
margin-bottom: 20px;
border: 1px solid #e5e7eb;
border-radius: 6px;
padding: 15px;
margin-bottom: 20px;
border: 1px solid #e5e7eb;
border-radius: 6px;
padding: 15px;
&:last-child {
margin-bottom: 0;
}
&:last-child {
margin-bottom: 0;
}
}
.module-title {
margin: 0 0 10px 0;
font-size: 16px;
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 8px;
display: flex; // flex block
margin: 0 0 10px 0;
font-size: 16px;
font-weight: 600;
border-bottom: 1px solid #e5e7eb;
padding-bottom: 8px;
display: flex; // flex block
}
.permission-list {
display: flex;
flex-wrap: wrap;
gap: 15px;
display: flex;
flex-wrap: wrap;
gap: 15px;
}
.el-checkbox-group {
label {
margin-right: 15px;
}
label {
margin-right: 15px;
}
}
</style>
</style>

View File

@ -1,131 +0,0 @@
<template>
<div class="history-trade">
<HistoryTradeSearch :search-form="searchForm" :search-options="searchOptions" @search="handleSearch"
@reset="handleReset">
<template #button="{ form }">
<el-button type="primary" @click="handleExport()">
导出
</el-button>
</template>
</HistoryTradeSearch>
<!-- 表格 -->
<HistoryTradeTable :table-data="tableTree" :columns="tableColumnsOptions" row-key="id" />
<!-- 分页 -->
<HistoryTradePagination v-model="currentPage" v-model:pageSizeValue="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
</template>
<script setup lang="ts">
//
import HistoryTradeSearch from '@/components/common/Search.vue'
import HistoryTradeTable from '@/components/common/Table.vue'
import HistoryTradePagination from '@/components/common/Pagination.vue'
//
import { search, tableColumns } from './options'
//
import { getHistoryTradeListApi } from '@/api/modules/trade/historyTrade'
//
//
import { exportToExcel } from '@/utils/exportToExcel'
/**
* @description: 定义搜索数据
*/
const searchOptions = ref(search)
const searchForm = ref({
username: '',
orderNo: '',
subAccountId: '',
tradeType: '',
startTime: '',
endTime: '',
})
/**
* @description: 定义表格数据
*/
const tableTree = ref([])
const tableColumnsOptions = ref(tableColumns)
/**
* @description: 定义分页数据
*/
const currentPage = ref(1)
const pageSize = ref(10)
const total = ref(0)
/**
* @description: 定义公共请求数据方法
*/
const getHistoryTradeList = async () => {
const params = {
page: currentPage.value,
page_size: pageSize.value,
user_name: searchForm.value.username,
account_id: searchForm.value.subAccountId,
order_no: searchForm.value.orderNo,
type: searchForm.value.tradeType,
start_time: searchForm.value.startTime.length > 0 ? `${searchForm.value.startTime} 0:00:00` : '2026-01-01 0:00:00',
end_time: searchForm.value.endTime.length > 0 ? `${searchForm.value.endTime} 23:59:59` : '2026-03-01 23:59:59',
}
const data: any = await getHistoryTradeListApi(params)
tableTree.value = data.data.map((item: any) => ({
orderNo: item.order_id,
tradeType: item.type === '1' ? '买入' : '卖出',
amount: item.amount,
tradeQty: item.num,
tradeTime: item.created_at,
floatProfit: '后续添加'
}))
//
total.value = data.total
currentPage.value = data.current_page
pageSize.value = Number(data.per_page)
}
/**
* @description: 页面加载完成需要请求的数据
*/
onMounted(() => {
getHistoryTradeList()
})
/**
* @description: 定义搜索方法
*/
const handleSearch = () => {
getHistoryTradeList()
}
/**
* @description: 定义重置方法
*/
const handleReset = () => {
searchForm.value = {
username: '',
orderNo: '',
subAccountId: '',
tradeType: '',
startTime: '',
endTime: '',
}
getHistoryTradeList()
}
/**
* @description: 定义导出方法
*/
const handleExport = () => {
exportToExcel({
tableData: tableTree.value,
columns: tableColumnsOptions.value,
title: '导出交易记录',
defaultFileNamePrefix: '交易记录',
})
}
/**
* @description: 定义分页方法
*/
const handlePaginationChange = (page: any) => {
currentPage.value = page.currentPage
getHistoryTradeList()
}
</script>

View File

@ -1,38 +0,0 @@
export const search = [
// 用户名
{ prop: 'username', label: '用户名', type: 'input' as const, placeholder: '请输入用户名',width: '150px' },
// 订单号
{ prop: 'orderNo', label: '订单号', type: 'input' as const, placeholder: '请输入订单号',width: '150px' },
// 交易账户ID
{ prop: 'subAccountId', label: '交易账户ID', type: 'input' as const, placeholder: '请输入交易账户ID',width: '150px' },
// 交易类型
{ prop: 'tradeType', label: '交易类型', type: 'select' as const, placeholder: '请选择交易类型',width: '140px', options: [
{ label: '买入', value: '1' },
{ label: '卖出', value: '2' },
] },
//开始时间
{
prop: 'startTime', label: '开始时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD', placeholder: '请选择开始时间', width: '150px'
},
//结束时间
{
prop: 'endTime', label: '结束时间', type: 'date' as const, dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD', placeholder: '请选择结束时间', width: '150px'
},
]
export const tableColumns = [
// 订单号
{ prop: 'orderNo', label: '订单号' },
// 操作类型
{ prop: 'tradeType', label: '操作类型' },
// 金额
{ prop: 'amount', label: '金额' },
// 交易数量
{ prop: 'tradeQty', label: '交易数量' },
// 交易时间
{ prop: 'tradeTime', label: '交易时间' },
// 浮动盈亏
{ prop: 'floatProfit', label: '浮动盈亏' },
]

View File

@ -20,10 +20,12 @@
<tbody>
<tr v-for="(item, index) in dataList" :key="`${item.id}-${index}`">
<td>
<el-input v-model="item.name" style="width: 80px" />
<el-input v-if="item.editStatus" v-model="item.name" style="width: 80px" />
<span v-else>{{ item.name }}</span>
</td>
<td>
<el-date-picker
v-if="item.editStatus"
v-model="item.datePicker"
type="datetimerange"
start-placeholder="休市开始时间"
@ -33,6 +35,7 @@
time-format="YYYY-MM-DD HH:mm:ss"
value-format="YYYY-MM-DD HH:mm:ss"
/>
<span v-else>{{ item.datePicker[0] }} - {{ item.datePicker[1] }}</span>
</td>
<td>
<el-select
@ -40,6 +43,7 @@
placeholder="请选择休市市场"
style="width: 240px"
v-model="item.groupId"
v-if="item.editStatus"
>
<el-option
v-for="item in marketList"
@ -48,13 +52,30 @@
:value="item.value"
/>
</el-select>
<span>{{
(item?.groupId || []).map(
(item: any) =>
(marketList || []).find((item2: any) => item2.value === item)?.label || '',
).join(',')
}}</span>
</td>
<td><el-input style="width: 250px" v-model="item.description" /></td>
<td>
<el-button type="primary" v-if="!item.editStatus" @click="item.editStatus = true">编辑</el-button>
<el-button type="danger" v-if="!item.editStatus" @click="handleDel(item.id)">删除</el-button>
<el-button type="primary" v-if="item.editStatus" @click="submitItem(index)">确认</el-button>
<el-button type="danger" v-if="item.editStatus" @click="item.editStatus = false">取消</el-button>
<el-input v-if="item.editStatus" style="width: 250px" v-model="item.description" />
<span v-else>{{ item.description }}</span>
</td>
<td>
<el-button type="primary" v-if="!item.editStatus" @click="item.editStatus = true"
>编辑</el-button
>
<el-button type="danger" v-if="!item.editStatus" @click="handleDel(item.id, index)"
>删除</el-button
>
<el-button type="primary" v-if="item.editStatus" @click="submitItem(index)"
>确认</el-button
>
<el-button type="danger" v-if="item.editStatus" @click="item.editStatus = false"
>取消</el-button
>
</td>
</tr>
</tbody>
@ -130,7 +151,7 @@ const submitItem = (idx: number) => {
description: dataList.value[idx]!.description,
}
if (dataList.value[idx]!.id) {
editHoliday({id: dataList.value[idx]!.id, ...submitObj}).then(() => {
editHoliday({ id: dataList.value[idx]!.id, ...submitObj }).then(() => {
initDate()
})
} else {
@ -140,12 +161,16 @@ const submitItem = (idx: number) => {
}
}
const handleDel = (id: number | string) => {
const handleDel = (id: number | string, idx: number) => {
if (!id) {
dataList.value.splice(idx, 1)
return
}
ElMessageBox.confirm('确认删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
}).then(() => {
delHoliday({id}).then(() => {
delHoliday({ id }).then(() => {
initDate()
})
})

View File

@ -63,7 +63,7 @@
<el-button type="primary" v-if="!item.editStatus" @click="item.editStatus = true"
>编辑</el-button
>
<el-button type="danger" v-if="!item.editStatus" @click="deleteGroup(item.id)"
<el-button type="danger" v-if="!item.editStatus" @click="deleteGroup(item.id, index)"
>删除</el-button
>
<el-button type="primary" v-if="item.editStatus" @click="submitItem(index)"
@ -205,7 +205,11 @@ const submitItem = (idx: number) => {
}
}
const deleteGroup = (id: number) => {
const deleteGroup = (id: number, idx: number) => {
if (!id) {
tradeGroupTree.value.splice(idx, 1)
return
}
ElMessageBox.confirm('确认删除吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',