This commit is contained in:
李远 2026-03-12 18:04:16 +08:00
parent e26deae5b5
commit ba89a965c7
17 changed files with 607 additions and 149 deletions

View File

@ -0,0 +1,32 @@
import { request } from '@/api';
import type { accountSearchParams, accountListResponse, accountCreateParams } from '@/api/types/account'
// 获取客户列表
export async function getClientListApi(params: accountSearchParams): Promise<accountListResponse> {
return request.get<accountListResponse>('/user/getUserList', { ...params })
}
//获取代理列表
export async function getAgentOptionsApi(){
return request.get('/user/getParOptions')
}
//获取手续费模板列表
export async function getFeeTemplateOptionsApi(){
return request.get('/user/getFeeSettingOptions')
}
//获取风控模板列表
export async function getRiskTemplateOptionsApi() {
return request.get('/user/getRiskControlOptions')
}
// 添加客户
export async function createClientApi(params: accountCreateParams): Promise<void> {
return request.post<void>('/user/createUser', params)
}
// 更新客户
export async function editClientApi(params: accountCreateParams): Promise<void> {
return request.post<void>('/user/editUser', params)
}

55
src/api/types/account.d.ts vendored Normal file
View File

@ -0,0 +1,55 @@
/**
* @description
* @param {accountSearchParams} params -
* @response {accountListResponse}
*
*/
// 客户列表查询参数
export interface accountSearchParams {
page?: number
page_size?: number
username?: string
status?: string | number
reg_start_time?: string
reg_end_time?: string
}
// 定义列表项的类型
interface UserItem {
id: number;
username: string;
par_uid: number;
created_at: string; // 时间字符串类型
status: number;
parent: ParentUser; // 嵌套的父级用户对象
}
// 定义完整响应数据的类型
interface UserResponse {
data: UserItem[]; // 数据是 UserItem 类型的数组
}
export interface accountListResponse {
data: UserItem[];
total: number;
per_page: number;
}
/**
* @description
* @param {accountCreateParams} params -
* @response {void}
*/
// 客户创建类型
export interface accountCreateParams {
reg_type?: number
email?: string
mobile?: string
parent_id?: number | string
fee_setting_id?: number | string
risk_control_id?: number | string
user_id?: string;
username?: string;
is_levelup?: number;
status?: number;
}

View File

@ -1,4 +1,3 @@
// src/utils/request.types.ts
import type {
AxiosRequestConfig,
AxiosResponse,

View File

@ -27,7 +27,7 @@ export interface MenuItem {
parent_id: string; // 上级菜单IDstring类型匹配后端
sort: string; // 排序string类型
type: string; // 类型1菜单2按钮string类型
status?: string; // 状态1启用2禁用可选编辑时用
status?: string | null; // 状态1启用2禁用可选编辑时用
id?: string; // 菜单ID仅编辑/删除时传)
}

View File

@ -1,7 +1,6 @@
<!-- src/components/CommonDialog.vue -->
<template>
<el-dialog :model-value="visible" :title="title" :width="width" :destroy-on-close="destroyOnClose" @close="handleClose"
v-bind="$attrs">
<el-dialog :model-value="visible" :title="title" :width="width" :destroy-on-close="destroyOnClose"
@close="handleClose" v-bind="$attrs">
<!-- 对话框内容插槽 -->
<slot />
@ -50,4 +49,7 @@ const handleCancel = () => {
const handleClose = () => {
emit('close')
}
</script>
</script>
<style scoped lang="scss">
</style>

View File

@ -2,7 +2,7 @@
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-bind="$attrs">
<!-- 动态表单项 -->
<template v-for="(item, index) in formItems" :key="index">
<el-form-item :label="item.label" :prop="item.prop" :required="item.required">
<el-form-item :label="item.label" :prop="item.prop" :required="item.required" label-position="left" :label-width="item.labelWidth || '100px'">
<!-- 输入框 -->
<el-input v-if="item.type === 'input'" v-model="formData[item.prop]" :placeholder="item.placeholder"
:disabled="item.disabled" clearable />
@ -63,6 +63,8 @@ interface FormItem {
min?: number
max?: number
slotName?: string //
labelPosition?: 'left' | 'right'
labelWidth?: string
}
interface Props {

View File

@ -24,7 +24,7 @@
:start-placeholder="item.startPlaceholder || '开始日期'"
:end-placeholder="item.endPlaceholder || '结束日期'"
:value-format="item.valueFormat || 'YYYY-MM-DD'" :clearable="item.clearable ?? true"
:disabled="item.disabled" :locale="'zhCn'" :picker-options="item.pickerOptions"
:disabled-date="item.disabledDate" :locale="'zhCn'"
/>
<!-- 单选框组 -->
@ -89,10 +89,7 @@ interface SearchOption {
startPlaceholder?: string; //
endPlaceholder?: string; //
valueFormat?: string; //
// /
pickerOptions?: {
disabledDate?: (date: Date) => boolean;
};
disabledDate?: (date: Date) => boolean; //
width?: string; //
minWidth?: string; //
}

View File

@ -1,88 +1,384 @@
<template>
<div class="client">
<!-- 搜索组件 -->
<ClientSearch :search-form="ClientSearchForm" :search-options="clientSearchOptions" @search="handleSearch"
<ClientSearch :search-form="clientSearchForm" :search-options="clientSearchOptions" @search="handleSearch"
@reset="handleReset">
<!-- 这里可以插入自定义插槽如果需要 -->
<!-- button 插槽自定义额外按钮 -->
<template #button="{ form }">
<!-- form 就是子组件的 searchForm可以直接使用 -->
<el-button type="primary" @click="handleAdd()">
添加客户
</el-button>
</template>
</ClientSearch>
<!-- table组件 -->
<!-- <ClientTable :table-data="ClientTableTree" :columns="clientTableColumns">
<ClientTable :table-data="clientTree" :columns="clientTableColumns" row-key="id" :tree-props="treeProps">
<template #status="{ row }">
<el-switch v-model="row.status" :active-value="1" :inactive-value="2" active-text="启用"
inactive-text="禁用" @change="handleStatusChange(row)" />
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" active-text="启用"
inactive-text="禁用" />
</template>
<template #action="{ row }">
<el-button type="primary" size="small" @click="handleChange(row)">
修改
</el-button>
</template>
</ClientTable>
<!-- 分页组件 -->
<!-- <ClientPagination :pagination-data="ClientPaginationData" @page-change="handlePageChange" /> -->
<ClientPagination v-model="currentPage" v-model:page-size-value="pageSize" :total="total"
@pagination-change="handlePaginationChange" />
</div>
<!-- dialog -->
<!-- <ClientDialog :dialog-visible="dialogVisible" :dialog-title="dialogTitle" :dialog-type="dialogType" :form-options="clientAddFormOptions" @close="dialogVisible = false" @submit="handleSubmit"/> --> -->
<!-- 客户弹窗 -->
<ClientDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<ClientForm :form-data="clientForm" :form-rules="clientFormRules" :form-items="clientFormOptions"
:options-map="clientFormOptionsMap" ref="clientFormRef">
<template #phoneOrEmail="{ formData }">
<div class="phone-email">
<div class="tab-btn">
<el-button type="primary" size="small" :class="{ 'active': activeTab === 'email' }"
@click="activeTab = 'email'">
邮箱
</el-button>
<el-button type="primary" class="phone" size="small"
:class="{ 'active': activeTab === 'phone' }" @click="activeTab = 'phone'">
手机号
</el-button>
</div>
<el-form-item prop="email" v-if="activeTab === 'email'" class="email-item">
<el-input v-model="formData.email" placeholder="请输入邮箱"></el-input>
</el-form-item>
<el-form-item prop="phone" v-else class="phone-item">
<el-select v-model="formData.countryCode" :teleported="false" value-key="code" placeholder="国家"
class="country-select">
<el-option v-for="item in countryOptions" :key="item.code" :label="item.code"
:value="item.code">
<div class="country-option">
<span class="country-name">{{ item.name }}</span>
<span class="country-code">{{ item.code }}</span>
</div>
</el-option>
</el-select>
<el-input v-model="formData.phone" placeholder="请输入手机号" clearable />
</el-form-item>
</div>
</template>
</ClientForm>
</ClientDialog>
</template>
<script lang="ts" setup>
//
//
import ClientSearch from '@/components/common/Search.vue'
import ClientTable from '@/components/common/Table.vue'
import ClientPagination from '@/components/common/Pagination.vue'
import ClientDialog from '@/components/common/Dialog.vue'
import ClientForm from '@/components/common/Form.vue'
//
import { getClientListApi, getFeeTemplateOptionsApi, getRiskTemplateOptionsApi, getAgentOptionsApi, createClientApi, editClientApi } from '@/api/modules/account/client'
//
import { clientSearch, clientAddFormItem, clientChangeFormItem, countryOptions, clientTableColumns } from './options'
import { rules } from './rules'
//
import { clientSearchOptions, clientTableColumns,clientAddFormOptions,clientChangeFormOptions } from './options'
//
const ClientSearchForm = ref({
//
const clientSearchOptions = ref(clientSearch)
const clientSearchForm = ref({
page: 1,
pageSize: 10,
username: '',
status: '',
startDate: '',
endDate: ''
status: '正常',
startDateTime: '',
endDateTime: '',
})
//table
const ClientTableTree = ref([])
//
const ClientPaginationData = ref({
total: 0,
currentPage: 1,
pageSize: 10
//
const clientTree = ref<any[]>([])
const treeProps = ref({
children: 'parent',
})
//dialog
const dialogVisible = ref(false)
//
const total = ref(0)
const pageSize = ref(1)
const currentPage = ref(1)
// dialog
const dialogTitle = ref('')
const dialogType = ref('')
const dialogVisible = ref(false)
const dialogType = ref('add')
const clientForm = ref({
//
feeTemplate: '',
//
riskTemplate: '',
//
agentUsername: '',
email: '',
phone: '',
countryCode: '+86',
//
isUpgrade: 1,
//
userName: '',
status: 1,
id: '',
})
/**
* @description: 搜索方法
* @param {any} formData 搜索表单数据
* @return {void}
*/
//
const handleSearch = () => {
console.log('搜索表单数据:', ClientSearchForm.value)
//
const clientFormRef = ref()
const clientFormRules = ref(rules())
const clientFormOptions = ref<any[]>(clientAddFormItem)
//
const activeTab = ref('email')
//
interface FormOption {
label: string;
value: string | number;
}
interface ClientFormOptionsMap {
feeTemplate: FormOption[];
riskTemplate: FormOption[];
agentUsername: FormOption[];
isUpgrade: FormOption[];
}
const clientFormOptionsMap = ref<ClientFormOptionsMap>({
feeTemplate: [],
riskTemplate: [],
agentUsername: [],
isUpgrade: [
{ label: '是', value: 1 },
{ label: '否', value: 2 }
],
})
//
const handleReset = () => {
ClientSearchForm.value = {
username: '',
status: '',
startDate: '',
endDate: ''
//
const fetchClientList = async () => {
const params = {
page: clientSearchForm.value.page,
page_size: clientSearchForm.value.pageSize,
username: clientSearchForm.value.username,
status: clientSearchForm.value.status === '正常' ? 1 : 2,
reg_start_time: clientSearchForm.value.startDateTime.length > 0 ? `${clientSearchForm.value.startDateTime} 0:00:00` : '',
reg_end_time: clientSearchForm.value.endDateTime.length > 0 ? `${clientSearchForm.value.endDateTime} 23:59:59` : '',
}
const data = await getClientListApi(params)
clientTree.value = data.data.map(item => ({
...item,
//
agentUsername: item.par_uid === item.parent?.id ? item.parent?.username : '',
}));
//
total.value = data.total; //
pageSize.value = Number(data.per_page); //
}
onMounted(async () => {
console.log('客户列表', clientTree.value)
await fetchClientList()
})
/**
* @description: 搜索事件
*/
//
const handleSearch = async () => {
try {
await fetchClientList()
console.log('客户列表', clientSearchForm.value)
} catch (err) {
console.error('获取客户列表失败', err)
}
}
//
const handleAdd = () => {
console.log('添加客户')
//
const handleReset = () => {
clientSearchForm.value = {
page: 1,
pageSize: 10,
username: '',
status: '正常',
startDateTime: '',
endDateTime: '',
}
}
</script>
//
const handleDialogType = (type: string) => {
clientFormOptions.value = type === 'add' ? clientAddFormItem : clientChangeFormItem
}
//
const getOptions = async () => {
//
const agentOptions = await getAgentOptionsApi()
//
const feeTemplateOptions = await getFeeTemplateOptionsApi()
//
const riskTemplateOptions = await getRiskTemplateOptionsApi()
clientFormOptionsMap.value.feeTemplate = mapObjectToOptions(feeTemplateOptions as Record<string, string>)
clientFormOptionsMap.value.riskTemplate = mapObjectToOptions(riskTemplateOptions as Record<string, string>)
clientFormOptionsMap.value.agentUsername = mapObjectToOptions(agentOptions as Record<string, string>)
}
/**
* @description: 表格修改事件
*/
const handleChange = async (row: any) => {
dialogVisible.value = true
dialogTitle.value = '修改客户'
dialogType.value = 'edit'
handleDialogType(dialogType.value)
//
await getOptions()
// id
clientForm.value = {
id: row.id, // ID
feeTemplate: row.feeTemplate || '', //
riskTemplate: row.riskTemplate || '', //
agentUsername: row.agentUsername || '', //
email: row.email || '', //
phone: row.phone || '', //
countryCode: row.countryCode || '+86', //
isUpgrade: row.isUpgrade || 1, //
userName: row.username || '', //
status: row.status || 1, //
}
}
//
const handlePaginationChange = (page: number) => {
clientSearchForm.value.page = page
handleSearch()
}
/**
* @description: dialog事件
*/
//
const mapObjectToOptions = (obj: Record<string, string>) => {
return Object.entries(obj).map(([label, value]) => ({
label: label, //
value: value // ID
}));
}
//
const handleAdd = async () => {
dialogVisible.value = true
dialogType.value = 'add'
dialogTitle.value = '添加客户'
handleDialogType(dialogType.value)
//
await getOptions()
}
const handleSubmit = async () => {
try {
await clientFormRef.value?.validate()
if (dialogType.value === 'add') {
const params = {
reg_type: activeTab.value === 'email' ? 1 : 2,
email: clientForm.value.email,
mobile: clientForm.value.phone,
parent_id: clientForm.value.agentUsername,
risk_control_id: clientForm.value.riskTemplate,
fee_setting_id: clientForm.value.feeTemplate,
}
await createClientApi(params)
await fetchClientList()
dialogVisible.value = false
} else {
console.log('编辑客户', clientForm.value)
const params = {
user_id: clientForm.value.id,
risk_control_id: clientForm.value.riskTemplate,
fee_setting_id: clientForm.value.feeTemplate,
username: clientForm.value.userName,
is_levelup: clientForm.value.isUpgrade,
status: clientForm.value.status,
}
await editClientApi(params)
await fetchClientList()
dialogVisible.value = false
}
} catch (err) {
console.error('客户表单验证失败', err)
}
}
//
const handleCancel = () => {
dialogVisible.value = false
dialogType.value = 'add'
clientForm.value = {
//
feeTemplate: '',
//
riskTemplate: '',
//
agentUsername: '',
email: '',
phone: '',
countryCode: '+86',
}
}
//
const handleClose = () => {
dialogVisible.value = false
dialogType.value = 'add'
clientForm.value = {
//
feeTemplate: '',
//
riskTemplate: '',
//
agentUsername: '',
email: '',
phone: '',
countryCode: '+86',
}
}
</script>
<style scoped>
.phone-email {
display: flex;
:deep(.el-form-item__content) {
flex-wrap: nowrap;
}
.tab-btn {
display: flex;
margin-right: 10px;
.el-button {
margin-left: 0;
background-color: transparent;
border: 1px solid #DCDFE6;
color: #303133;
height: 32px;
}
.active {
background-color: #409EFF;
color: #fff;
}
}
.email-item {
.el-input {
width: 100%;
}
}
.phone-item {
.el-select {
width: 120px;
margin-right: 10px;
}
}
}
</style>

View File

@ -1,5 +1,13 @@
// 获取当前年份
const currentYear = new Date().getFullYear()
// 定义禁用日期函数:只能选择今年及以后的日期
const disabledDate = (time: Date) => {
// 时间早于今年1月1日则禁用
return time.getTime() < new Date(currentYear, 0, 1).getTime()
}
//定义客户搜索表单配置
export const clientSearchOptions = [
export const clientSearch = [
{
prop: 'username',
label: '用户名',
@ -8,22 +16,24 @@ export const clientSearchOptions = [
width: '120px'
},
{
prop: "startDate",
prop: "startDateTime",
label: '注册开始时间',
type: 'date' as const,
dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD',
// minWidth: '200px'
width: '180px'
width: '180px',
disabledDate
},
{
prop: "endDate",
prop: "endDateTime",
label: '注册结束时间',
type: 'date' as const,
dateType: 'date' as const, // 时间范围选择器
valueFormat: 'YYYY-MM-DD',
// minWidth: '200px'
width: '180px'
width: '180px',
disabledDate
},
{
prop: 'status',
@ -32,37 +42,52 @@ export const clientSearchOptions = [
placeholder: '选择状态',
width: '80px',
options: [
{ label: '全部', value: '' },
{ label: '开启', value: 1 },
{ label: '关闭', value: 0 }
{ label: '正常', value: 1 },
{ label: '封禁', value: 2 }
],
}
]
//定义客户table列配置
export const clientTableColumns = [
{ prop: 'id', label: 'ID' },
{ prop: 'username', label: '用户名' },
{ prop: 'registerTime', label: '注册时间' },
{ prop: 'agentUsername', label: '所属代理' },
{ prop: 'status', label: '状态' },
{ prop: 'action', label: '操作' },
{ prop: 'id', label: 'ID', align: 'center' as const },
{ prop: 'username', label: '用户名', align: 'center' as const },
{ prop: 'created_at', label: '注册时间', align: 'center' as const },
{ prop: 'agentUsername', label: '所属代理', align: 'center' as const },
{ label: '状态', slotName: 'status', align: 'center' as const },
{ label: '操作', slotName: 'action', align: 'center' as const }
]
//定义添加客户form配置
export const clientAddFormOptions = [
{ prop: 'clientName', label: '客户名称', type: 'input' as const, placeholder: '客户名称' },
export const clientAddFormItem = [
// { prop: 'clientName', label: '客户名称', type: 'input' as const, placeholder: '客户名称' },
// { prop: 'isUpgrade', label: '是否升级为代理', type: 'radio' as const, placeholder: '是否升级为代理' },
{ prop: 'feeTemplate', label: '手续费模板', type: 'select' as const, placeholder: '选择手续费模板' },
{ prop: 'isUpgrade', label: '是否升级为代理', type: 'radio' as const, placeholder: '是否升级为代理' },
{ prop: 'riskTemplate', label: '风控模板', type: 'select' as const, placeholder: '选择风控模板' },
{ prop: 'parentUsername', label: '所属上级', type: 'input' as const, placeholder: '所属上级' },
{ prop: 'contactPhoneOrEmail', label: '手机号或邮箱', type: 'input' as const, placeholder: '手机号或邮箱' },
{ prop: 'agentUsername', label: '所属上级', type: 'select' as const, placeholder: '所属上级' },
{ prop: 'phoneOrEmail', label: '', type: 'slot' as const, slotName: 'phoneOrEmail'},
]
//定义修改客户form配置
export const clientChangeFormOptions = [
export const clientChangeFormItem = [
{ prop: 'feeTemplate', label: '手续费模板', type: 'select' as const, placeholder: '选择手续费模板' },
{ prop: 'riskTemplate', label: '风控模板', type: 'select' as const, placeholder: '选择风控模板' },
{ prop: 'isUpgrade', label: '是否升级为代理', type: 'radio' as const, placeholder: '是否升级为代理' },
{ prop: 'changeName', label: '客户名称', type: 'input' as const, placeholder: '客户名称' },
{ prop: 'isUpgrade', label: '是否升级为代理', type: 'radio' as const, placeholder: '是否升级为代理', labelWidth: '140px' },
]
]
// 国家码选项
interface CountryItem {
name: string
code: string
}
export const countryOptions = ref<CountryItem[]>([
{ name: '中国', code: '+86' },
{ name: '中国台湾', code: '+886' },
{ name: '中国香港', code: '+852' },
{ name: '中国澳门', code: '+853' },
{ name: '韩国', code: '+82' },
{ name: '美国', code: '+1' },
{ name: '新加坡', code: '+65' }
])

View File

@ -0,0 +1,35 @@
import type { FormRules } from 'element-plus'
export const rules = (): FormRules => {
return {
//手续费模板
feeTemplate: [
{ required: true, message: '请选择手续费模板', trigger: ['blur'] },
],
//风控模板
riskTemplate: [
{ required: true, message: '请选择风控模板', trigger: ['blur'] },
],
//所属上级
agentUsername: [
{ required: true, message: '请选择所属上级', trigger: ['blur'] },
],
//手机号
phone: [
{ required: true, message: '请输入手机号', trigger: ['blur'] },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: ['blur'] },
],
//邮箱
email: [
{ required: true, message: '请输入邮箱地址', trigger: ['blur'] },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: ['blur'] }
],
//是否升级为代理
isUpgrade: [
{ required: true, message: '请选择是否升级为代理', trigger: ['blur'] },
],
//客户名称
changeName: [
{ required: true, message: '请输入客户名称', trigger: ['blur'] },
],
}
}

View File

@ -0,0 +1,47 @@
// 建议单独创建 types.ts 文件管理所有类型
// types.ts
export interface ClientSearchForm {
page: number;
pageSize: number;
username: string;
status: '正常' | '禁用';
startDateTime: string;
endDateTime: string;
}
export interface ClientItem {
id: string | number;
username: string;
status: 1 | 2;
email?: string;
phone?: string;
countryCode?: string;
feeTemplate?: string | number;
riskTemplate?: string | number;
agentUsername?: string;
isUpgrade?: 1 | 2;
par_uid?: string | number;
parent?: {
id: string | number;
username: string;
};
}
export interface ClientForm {
id: string;
feeTemplate: string | number;
riskTemplate: string | number;
agentUsername: string | number;
email: string;
phone: string;
countryCode: string;
isUpgrade: 1 | 2;
userName: string;
status: 1 | 2;
}
export interface ClientListResponse {
data: ClientItem[];
total: number;
per_page: number | string;
}

View File

@ -14,8 +14,8 @@
{{ row.type === 1 ? '目录' : '按钮' }}
</template>
<template #status="{ row }">
<el-switch v-model="row.status" :active-value="1" :inactive-value="2" active-text="启用"
inactive-text="禁用" @change="handleStatusChange(row)" />
<el-switch :model-value="row.status === 1" :active-value="true" :inactive-value="false" active-text="启用"
inactive-text="禁用"/>
</template>
<template #action="{ row }">
<el-button link type="primary" size="small" @click="handleEditMenu(row)">
@ -24,10 +24,6 @@
<el-button link type="danger" size="small" @click="handleDeleteMenu(row)">
删除
</el-button>
<!-- <el-button link size="small" :type="row.status === 1 ? 'warning' : 'success'"
@click="handleToggleStatus(row)">
{{ row.status === 1 ? '禁用' : '启用' }}
</el-button> -->
</template>
</MenuTable>
</div>
@ -49,7 +45,7 @@ import MenuForm from '@/components/common/Form.vue'
import { menuTableColumns, menuiconOptions, menuiconMap, menuFormItemOptions } from './options'
import { rules } from './rules'
import { getMenuApi, createMenuApi, editMenuApi, deleteMenuApi } from '@/api/modules/menu'
import { getMenuApi, createMenuApi, editMenuApi, deleteMenuApi } from '@/api/modules/system/menu'
// header
const menuTitle = ref('菜单管理')
const menuHeaderButtons = [
@ -86,6 +82,10 @@ const menuFormOptionsMap = ref({
'type': [
{ label: '菜单', value: 1 },
{ label: '按钮', value: 2 }
],
'status': [
{ label: '启用', value: 1 },
{ label: '禁用', value: 2 }
]
})
@ -95,26 +95,18 @@ const menuFormOptionsMap = ref({
const handleAddMenu = () => {
dialogVisible.value = true
dialogTitle.value = '新增菜单'
menuForm.value.status = 1 as any //
}
/**
@description: 菜单数据处理
**/
// status=1
const filterMenus = (menuList: any[]) => {
const enabledMenus = menuList.filter(menu => menu.status === 1)
enabledMenus.forEach(menu => {
if (menu.son && menu.son.length) {
menu.son = filterMenus(menu.son)
}
})
return enabledMenus
}
//
const fetchMenuList = async () => {
try {
const data = await getMenuApi()
menuTree.value = filterMenus(data)
menuTree.value = data
// parentName
const parentNameOptions = data.map(item => ({
label: item.name,
@ -156,25 +148,6 @@ const handleDeleteMenu = async (row: any) => {
console.error('删除菜单失败')
}
}
const handleStatusChange = async (row: any) => {
const oldStatus = row.status === 1 ? 2 : 1
try {
//
// await updateMenuStatusApi({
// id: row.id,
// status: row.status
// })
// ElMessage.success(`${row.status === 1 ? '' : ''}`)
//
// await fetchMenuList()
} catch (err) {
//
row.status = oldStatus
ElMessage.error('状态修改失败')
console.error(err)
}
}
/**
@description: 处理弹窗提交/取消/关闭事件
@ -197,6 +170,7 @@ const handleSubmit = async () => {
await fetchMenuList()
} else {
await editMenuApi(menuForm.value)
console.log('编辑菜单成功', menuForm.value)
await fetchMenuList()
}
dialogVisible.value = false

View File

@ -43,5 +43,6 @@ export const menuFormItemOptions = [
{ prop: 'api', label: '后端路由', type: 'input' as const, placeholder: '请输入后端路由' },
{ prop: 'icon', label: '图标名称', type: 'select' as const, placeholder: '请选择图标名称' },
{ prop: 'sort', label: '排序', type: 'number' as const, placeholder: '请输入排序,数字越小越靠前' },
{ prop: 'type', label: '菜单类型', type: 'radio' as const, placeholder: '请选择菜单类型' }
{ prop: 'type', label: '菜单类型', type: 'radio' as const, placeholder: '请选择菜单类型' },
{ prop: 'status', label: '状态', type: 'radio' as const, placeholder: '请选择状态' },
]

View File

@ -32,6 +32,12 @@ export const rules = (menuForm: any, iconOptions: any[]): FormRules => {
type: [
{ required: true, message: '请选择菜单类型', trigger: 'change' },
{ type: 'number', enum: [1, 2], message: '菜单类型只能是1菜单或2按钮', trigger: 'change' }
],
// 8. 状态必填单选框默认1无需手动选
status: [
{ required: true, message: '请选择状态', trigger: 'change' },
{ type: 'number', enum: [1, 2], message: '状态只能是1启用或2禁用', trigger: 'change' }
]
}
}

View File

@ -5,8 +5,8 @@
<!-- 角色列表表格 -->
<RoleTable :table-data="roleTree" :columns="roleTableColumns" row-key="id">
<template #status="{ row }">
<el-switch v-model="row.status" :active-value="1" :inactive-value="2" active-text="启用"
inactive-text="禁用" @change="handleStatusChange(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 }}
@ -25,7 +25,7 @@
</RoleTable>
</div>
<!-- 角色新增/编辑弹窗 -->
<RoleDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
<RoleDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" :width="dialogWidth" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<RoleForm :form-data="roleForm" :form-rules="roleFormRules" :form-items="roleFormItemOptions" ref="roleFormRef"
:options-map="roleFormOptionsMap" :dialog-type="dialogType">
@ -64,7 +64,7 @@ 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/role'
import { getRoleApi, createRoleApi, editRoleApi, deleteRoleApi, getRolePermissionApi, setRolePermissionApi } from '@/api/modules/system/role'
// header
const roleTitle = ref('角色管理')
@ -79,6 +79,7 @@ const roleTree = ref<any[]>([])
const dialogVisible = ref(false)
const dialogTitle = ref('')
const dialogType = ref('add')
const dialogWidth = ref('600px')
//
const roleForm = ref({
@ -121,11 +122,12 @@ const filterRoles = (roleList: any[]) => {
const enabledRoles = roleList.filter(role => role.status === 1)
return enabledRoles
}
//
const fetchRoleList = async () => {
try {
const data = await getRoleApi()
roleTree.value = filterRoles(data)
roleTree.value = data
} catch (err) {
console.error('获取角色列表失败', err)
}
@ -160,33 +162,15 @@ const handleDeleteRole = async (row: any) => {
console.error('删除角色失败')
}
}
//
const handleStatusChange = async (row: any) => {
const oldStatus = row.status === 1 ? 2 : 1
try {
// //
// await updateMenuStatusApi({
// id: row.id,
// status: row.status
// })
// ElMessage.success(`${row.status === 1 ? '' : ''}`)
// //
// await fetchMenuList()
} catch (err) {
//
row.status = oldStatus
ElMessage.error('状态修改失败')
console.error(err)
}
}
//
const handleDialogType = (type: string) => {
if (type === 'permissions') {
roleFormItemOptions.value = permissionsFormItem as any;
dialogWidth.value = '900px'
} else {
roleFormItemOptions.value = roleFormItem as any;
dialogWidth.value = '600px'
}
}
@ -309,6 +293,9 @@ const handleClose = () => {
</script>
<style scoped lang="scss">
.permission-group{
width: 100%;
}
:deep(.el-form-item__content) {
margin-left: 0 !important;
}