menu
This commit is contained in:
parent
a2afd0a787
commit
7e8d963095
|
|
@ -6,12 +6,11 @@ export interface MenuFormParams {
|
||||||
path: string; // 前端路由
|
path: string; // 前端路由
|
||||||
api: string; // 后端接口路由
|
api: string; // 后端接口路由
|
||||||
icon: string; // 菜单图标
|
icon: string; // 菜单图标
|
||||||
parentId: number | string; // 上级菜单ID(number类型)
|
|
||||||
sort: number; // 排序(number类型)
|
|
||||||
type: number; // 类型:1菜单2按钮(number类型)
|
|
||||||
status?: number; // 状态:1启用2禁用(可选,编辑时用)
|
|
||||||
id?: number; // 菜单ID(仅编辑/删除时传)
|
|
||||||
parent_id?: string | null; // 上级菜单ID(number类型)
|
parent_id?: string | null; // 上级菜单ID(number类型)
|
||||||
|
sort: number | null; // 排序(number类型)
|
||||||
|
type: number | null; // 类型:1菜单2按钮(number类型)
|
||||||
|
status?: number | null; // 状态:1启用2禁用(可选,编辑时用)
|
||||||
|
id?: number | null; // 菜单ID(仅编辑/删除时传)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 删除菜单参数类型 */
|
/** 删除菜单参数类型 */
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,8 @@
|
||||||
</MenuTable>
|
</MenuTable>
|
||||||
</div>
|
</div>
|
||||||
<!-- 菜单新增/编辑弹窗 -->
|
<!-- 菜单新增/编辑弹窗 -->
|
||||||
<MenuDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit" @cancel="handleCancel"
|
<MenuDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
|
||||||
@close="handleClose">
|
@cancel="handleCancel" @close="handleClose">
|
||||||
<MenuForm :form-data="menuForm" :form-rules="menuFormRules" :form-items="MenuFormItemOptions"
|
<MenuForm :form-data="menuForm" :form-rules="menuFormRules" :form-items="MenuFormItemOptions"
|
||||||
:options-map="menuFormOptionsMap" ref="menuFormRef" />
|
:options-map="menuFormOptionsMap" ref="menuFormRef" />
|
||||||
</MenuDialog>
|
</MenuDialog>
|
||||||
|
|
@ -50,7 +50,7 @@ import MenuForm from '@/components/common/Form.vue'
|
||||||
import { MenuTableColumns, MenuiconOptions, menuiconMap, MenuFormItemOptions } from './options'
|
import { MenuTableColumns, MenuiconOptions, menuiconMap, MenuFormItemOptions } from './options'
|
||||||
import { rules } from './rules'
|
import { rules } from './rules'
|
||||||
|
|
||||||
import { getMenuApi,createMenuApi,editMenuApi,deleteMenuApi } from '@/api/modules/menu'
|
import { getMenuApi, createMenuApi, editMenuApi, deleteMenuApi } from '@/api/modules/menu'
|
||||||
// 定义header配置
|
// 定义header配置
|
||||||
const menuTitle = ref('菜单管理')
|
const menuTitle = ref('菜单管理')
|
||||||
const menuHeaderButtons = [
|
const menuHeaderButtons = [
|
||||||
|
|
@ -69,21 +69,20 @@ const dialogType = ref('add')
|
||||||
// 定义菜单表单数据
|
// 定义菜单表单数据
|
||||||
const menuForm = ref({
|
const menuForm = ref({
|
||||||
name: '', // 菜单名称
|
name: '', // 菜单名称
|
||||||
nameValue: '', // 默认一级菜单
|
parentName: '', // 上级菜单名称
|
||||||
path: '', // 路由路径
|
path: '', // 路由路径
|
||||||
api: '', // 后端路由
|
api: '', // 后端路由
|
||||||
icon: '', // 图标名称
|
icon: '', // 图标名称
|
||||||
sort: null, // 排序(默认1)
|
sort: null, // 排序(默认1)
|
||||||
type: null // 默认是菜单类型
|
type: null, // 默认是菜单类型
|
||||||
|
status: null, // 默认是启用状态
|
||||||
})
|
})
|
||||||
const menuFormRef = ref<FormInstance>()
|
const menuFormRef = ref<FormInstance>()
|
||||||
// 表单验证规则
|
// 表单验证规则
|
||||||
const menuFormRules = rules(menuForm.value, MenuiconOptions)
|
const menuFormRules = rules(menuForm.value, MenuiconOptions)
|
||||||
// 菜单表单选项映射
|
// icon和菜单表单选项映射
|
||||||
const menuFormOptionsMap = ref({
|
const menuFormOptionsMap = ref({
|
||||||
'parentName': [
|
'parentName': [] as any[],
|
||||||
{ label: '一级菜单', value: '1' },
|
|
||||||
],
|
|
||||||
'icon': MenuiconOptions,
|
'icon': MenuiconOptions,
|
||||||
'type': [
|
'type': [
|
||||||
{ label: '菜单', value: 1 },
|
{ label: '菜单', value: 1 },
|
||||||
|
|
@ -113,7 +112,7 @@ const filterMenus = (menuList: any[]) => {
|
||||||
return enabledMenus
|
return enabledMenus
|
||||||
}
|
}
|
||||||
// 获取菜单列表
|
// 获取菜单列表
|
||||||
onMounted(async () => {
|
const fetchMenuList = async () => {
|
||||||
try {
|
try {
|
||||||
const data = await getMenuApi()
|
const data = await getMenuApi()
|
||||||
menuTree.value = filterMenus(data)
|
menuTree.value = filterMenus(data)
|
||||||
|
|
@ -123,12 +122,17 @@ onMounted(async () => {
|
||||||
value: item.id ?? ''
|
value: item.id ?? ''
|
||||||
}))
|
}))
|
||||||
menuFormOptionsMap.value.parentName = [
|
menuFormOptionsMap.value.parentName = [
|
||||||
{ label: '一级菜单', value: '1' },
|
{ label: '一级菜单', value: "0" },
|
||||||
...parentNameOptions
|
...parentNameOptions
|
||||||
]
|
]
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('获取菜单列表失败')
|
console.error('获取菜单列表失败', err)
|
||||||
|
ElMessage.error('获取菜单列表失败,请稍后重试')
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
// 页面加载时获取菜单列表
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchMenuList()
|
||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -138,16 +142,23 @@ const handleEditMenu = (row: any) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
dialogTitle.value = '编辑菜单'
|
dialogTitle.value = '编辑菜单'
|
||||||
dialogType.value = 'edit'
|
dialogType.value = 'edit'
|
||||||
menuForm.value = { ...row,
|
menuForm.value = {
|
||||||
parentName: row.name
|
...row,
|
||||||
}
|
id: row.id, // 菜单id
|
||||||
console.log('编辑菜单', row)
|
parentName: row.name,
|
||||||
|
status: row.status ?? 1, // 默认是启用状态
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const handleDeleteMenu = (row: any) => {
|
const handleDeleteMenu = async (row: any) => {
|
||||||
console.log('删除菜单', row)
|
try {
|
||||||
|
await deleteMenuApi({id: row.id})
|
||||||
|
await fetchMenuList()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('删除菜单失败')
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const handleToggleStatus = (row: any) => {
|
const handleToggleStatus = (row: any) => {
|
||||||
console.log('切换菜单状态', row)
|
row.status = row.status === 1 ? 2 : 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -159,16 +170,21 @@ const handleSubmit = async () => {
|
||||||
await menuFormRef.value?.validate()
|
await menuFormRef.value?.validate()
|
||||||
if (dialogType.value === 'add') {
|
if (dialogType.value === 'add') {
|
||||||
const params = {
|
const params = {
|
||||||
...menuForm.value,
|
name: menuForm.value.name, // 菜单名称
|
||||||
parent_id: '0'
|
parent_id: menuForm.value.parentName, // 上级菜单id
|
||||||
|
path: menuForm.value.path, // 路由路径
|
||||||
|
api: menuForm.value.api, // 后端路由
|
||||||
|
icon: menuForm.value.icon, // 图标名称
|
||||||
|
sort: menuForm.value.sort ?? 1, // 排序(默认1)
|
||||||
|
type: menuForm.value.type ?? 1, // 默认是菜单类型
|
||||||
}
|
}
|
||||||
await createMenuApi(params)
|
await createMenuApi(params)
|
||||||
|
await fetchMenuList()
|
||||||
} else {
|
} else {
|
||||||
await editMenuApi(menuForm.value)
|
await editMenuApi(menuForm.value)
|
||||||
|
await fetchMenuList()
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
// 刷新菜单列表
|
|
||||||
// onMounted()
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('表单验证失败')
|
console.log('表单验证失败')
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,6 @@ export const MenuFormItemOptions = [
|
||||||
{ prop: 'path', label: '路由路径', type: 'input' as const, placeholder: '请输入路由路径' },
|
{ prop: 'path', label: '路由路径', type: 'input' as const, placeholder: '请输入路由路径' },
|
||||||
{ prop: 'api', label: '后端路由', type: 'input' as const, placeholder: '请输入后端路由' },
|
{ prop: 'api', label: '后端路由', type: 'input' as const, placeholder: '请输入后端路由' },
|
||||||
{ prop: 'icon', label: '图标名称', type: 'select' as const, placeholder: '请选择图标名称' },
|
{ prop: 'icon', label: '图标名称', type: 'select' as const, placeholder: '请选择图标名称' },
|
||||||
{ prop: 'sort', label: '排序', type: 'number' 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: '请选择菜单类型' }
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -8,38 +8,20 @@ export const rules = (menuForm: any, iconOptions: any[]): FormRules => {
|
||||||
{ min: 2, max: 20, message: '菜单名称长度在 2 到 20 个字符之间', trigger: 'blur' }
|
{ min: 2, max: 20, message: '菜单名称长度在 2 到 20 个字符之间', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
// 2. 父级菜单:必填(默认0,无需手动填)
|
// 2. 父级菜单:必填(默认0,无需手动填)
|
||||||
parentId: [{ required: true, message: '请选择父级菜单', trigger: 'change' }
|
parentName: [{ required: true, message: '请选择父级菜单', trigger: 'change' }
|
||||||
],
|
],
|
||||||
// 3. 路由路径:必填 + 格式校验(以/开头,仅包含字母/数字/下划线/斜杠)
|
// 3. 路由路径:必填 + 格式校验(以/开头,仅包含字母/数字/下划线/斜杠)
|
||||||
path: [
|
path: [
|
||||||
{ required: true, message: '请输入路由路径', trigger: 'blur' },
|
{ required: true, message: '请输入路由路径', trigger: 'blur' },
|
||||||
{ pattern: /^\/[a-zA-Z0-9_\/]+$/, message: '路由路径必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' }
|
{ pattern: /^\/[a-zA-Z0-9_\/]+$/, message: '路由路径必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
// 4. 后端路由:可选,但填了就必须符合格式
|
// 4. 后端路由:必填 + 格式校验(以/开头,仅包含字母/数字/下划线/斜杠)
|
||||||
api: [
|
api: [
|
||||||
{ required: true, pattern: /(^$)|(^\/[a-zA-Z0-9_\/]+$)/, message: '后端路由若填写,必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' }
|
{ required: true, pattern: /^\/[a-zA-Z0-9_\/]+$/, message: '后端路由必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
// 5. 图标名称:可选,但填了就必须是指定范围(匹配你提供的图标列表)
|
// 5. 图标名称:可选,但填了就必须是指定范围(匹配你提供的图标列表)
|
||||||
icon: [
|
icon: [
|
||||||
{
|
{required: true, message: '请选择图标名称', trigger: 'blur'}
|
||||||
validator: (rule, value, callback) => {
|
|
||||||
// 一级菜单必须选图标
|
|
||||||
if (menuForm.parentId === "0" && !value) {
|
|
||||||
callback(new Error("一级菜单必须选择图标"))
|
|
||||||
}
|
|
||||||
// 若填写了图标,必须是指定范围内的
|
|
||||||
else if (
|
|
||||||
value &&
|
|
||||||
!iconOptions.some(item => item.value === value)
|
|
||||||
) {
|
|
||||||
callback(new Error("请选择预设图标"))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
trigger: "change",
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
// 6. 排序:必填 + 数字 + 最小值0
|
// 6. 排序:必填 + 数字 + 最小值0
|
||||||
sort: [
|
sort: [
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue