This commit is contained in:
李远 2026-03-11 09:54:52 +08:00
parent a2afd0a787
commit 7e8d963095
4 changed files with 48 additions and 51 deletions

View File

@ -6,12 +6,11 @@ export interface MenuFormParams {
path: string; // 前端路由
api: string; // 后端接口路由
icon: string; // 菜单图标
parentId: number | string; // 上级菜单IDnumber类型
sort: number; // 排序number类型
type: number; // 类型1菜单2按钮number类型
status?: number; // 状态1启用2禁用可选编辑时用
id?: number; // 菜单ID仅编辑/删除时传)
parent_id?: string | null; // 上级菜单IDnumber类型
sort: number | null; // 排序number类型
type: number | null; // 类型1菜单2按钮number类型
status?: number | null; // 状态1启用2禁用可选编辑时用
id?: number | null; // 菜单ID仅编辑/删除时传)
}
/** 删除菜单参数类型 */

View File

@ -33,8 +33,8 @@
</MenuTable>
</div>
<!-- 菜单新增/编辑弹窗 -->
<MenuDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit" @cancel="handleCancel"
@close="handleClose">
<MenuDialog :visible="dialogVisible" :title="dialogTitle" :type="dialogType" @confirm="handleSubmit"
@cancel="handleCancel" @close="handleClose">
<MenuForm :form-data="menuForm" :form-rules="menuFormRules" :form-items="MenuFormItemOptions"
:options-map="menuFormOptionsMap" ref="menuFormRef" />
</MenuDialog>
@ -50,7 +50,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/menu'
// header
const menuTitle = ref('菜单管理')
const menuHeaderButtons = [
@ -69,21 +69,20 @@ const dialogType = ref('add')
//
const menuForm = ref({
name: '', //
nameValue: '', //
parentName: '', //
path: '', //
api: '', //
icon: '', //
sort: null, // 1
type: null //
type: null, //
status: null, //
})
const menuFormRef = ref<FormInstance>()
//
const menuFormRules = rules(menuForm.value, MenuiconOptions)
//
// icon
const menuFormOptionsMap = ref({
'parentName': [
{ label: '一级菜单', value: '1' },
],
'parentName': [] as any[],
'icon': MenuiconOptions,
'type': [
{ label: '菜单', value: 1 },
@ -113,7 +112,7 @@ const filterMenus = (menuList: any[]) => {
return enabledMenus
}
//
onMounted(async () => {
const fetchMenuList = async () => {
try {
const data = await getMenuApi()
menuTree.value = filterMenus(data)
@ -123,12 +122,17 @@ onMounted(async () => {
value: item.id ?? ''
}))
menuFormOptionsMap.value.parentName = [
{ label: '一级菜单', value: '1' },
{ label: '一级菜单', value: "0" },
...parentNameOptions
]
} catch (err) {
console.error('获取菜单列表失败')
console.error('获取菜单列表失败', err)
ElMessage.error('获取菜单列表失败,请稍后重试')
}
}
//
onMounted(async () => {
await fetchMenuList()
})
/**
@ -138,16 +142,23 @@ const handleEditMenu = (row: any) => {
dialogVisible.value = true
dialogTitle.value = '编辑菜单'
dialogType.value = 'edit'
menuForm.value = { ...row,
parentName: row.name
}
console.log('编辑菜单', row)
menuForm.value = {
...row,
id: row.id, // id
parentName: row.name,
status: row.status ?? 1, //
}
}
const handleDeleteMenu = (row: any) => {
console.log('删除菜单', row)
const handleDeleteMenu = async (row: any) => {
try {
await deleteMenuApi({id: row.id})
await fetchMenuList()
} catch (err) {
console.error('删除菜单失败')
}
}
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()
if (dialogType.value === 'add') {
const params = {
...menuForm.value,
parent_id: '0'
name: menuForm.value.name, //
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 fetchMenuList()
} else {
await editMenuApi(menuForm.value)
await fetchMenuList()
}
dialogVisible.value = false
//
// onMounted()
} catch (err) {
console.log('表单验证失败')
return

View File

@ -42,6 +42,6 @@ export const MenuFormItemOptions = [
{ prop: 'path', label: '路由路径', type: 'input' as const, placeholder: '请输入路由路径' },
{ 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: 'sort', label: '排序', type: 'number' as const, placeholder: '请输入排序,数字越小越靠前' },
{ prop: 'type', label: '菜单类型', type: 'radio' as const, placeholder: '请选择菜单类型' }
]

View File

@ -8,38 +8,20 @@ export const rules = (menuForm: any, iconOptions: any[]): FormRules => {
{ min: 2, max: 20, message: '菜单名称长度在 2 到 20 个字符之间', trigger: 'blur' }
],
// 2. 父级菜单必填默认0无需手动填
parentId: [{ required: true, message: '请选择父级菜单', trigger: 'change' }
parentName: [{ required: true, message: '请选择父级菜单', trigger: 'change' }
],
// 3. 路由路径:必填 + 格式校验(以/开头,仅包含字母/数字/下划线/斜杠)
path: [
{ required: true, message: '请输入路由路径', trigger: 'blur' },
{ pattern: /^\/[a-zA-Z0-9_\/]+$/, message: '路由路径必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' }
],
// 4. 后端路由:可选,但填了就必须符合格式
// 4. 后端路由:必填 + 格式校验(以/开头,仅包含字母/数字/下划线/斜杠)
api: [
{ required: true, pattern: /(^$)|(^\/[a-zA-Z0-9_\/]+$)/, message: '后端路由若填写,必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' }
{ required: true, pattern: /^\/[a-zA-Z0-9_\/]+$/, message: '后端路由必须以/开头,仅包含字母、数字、下划线和斜杠', trigger: 'blur' }
],
// 5. 图标名称:可选,但填了就必须是指定范围(匹配你提供的图标列表)
icon: [
{
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",
},
{required: true, message: '请选择图标名称', trigger: 'blur'}
],
// 6. 排序:必填 + 数字 + 最小值0
sort: [