diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts index 804082d7..04dbc258 100644 --- a/src/api/system/menu/index.ts +++ b/src/api/system/menu/index.ts @@ -37,12 +37,12 @@ export const getMenu = (id: number) => { } // 新增菜单 -export const createMenu = (data: MenuVO) => { +export const createMenu = (data: Partial) => { return request.post({ url: '/system/menu/create', data }) } // 修改菜单 -export const updateMenu = (data: MenuVO) => { +export const updateMenu = (data: Partial) => { return request.put({ url: '/system/menu/update', data }) } diff --git a/src/api/system/permission/index.ts b/src/api/system/permission/index.ts index b3c7696b..b6369288 100644 --- a/src/api/system/permission/index.ts +++ b/src/api/system/permission/index.ts @@ -17,8 +17,11 @@ export interface PermissionAssignRoleDataScopeReqVO { } // 查询角色拥有的菜单权限 -export const getRoleMenuList = async (roleId: number) => { - return await request.get({ url: '/system/permission/list-role-menus?roleId=' + roleId }) +export const getRoleMenuList = async (roleId: number, clientType?: number) => { + return await request.get({ + url: '/system/permission/list-role-menus', + params: { roleId, clientType } + }) } // 赋予角色菜单权限 diff --git a/src/views/system/menu/MenuForm.vue b/src/views/system/menu/MenuForm.vue index acba7cb4..5ebfbf57 100644 --- a/src/views/system/menu/MenuForm.vue +++ b/src/views/system/menu/MenuForm.vue @@ -66,16 +66,6 @@ - - - {{ t('SystemManagement.Menu.terminalTypeMobile') }} - {{ t('SystemManagement.Menu.terminalTypeScanner') }} - - ({ name: [{ required: true, message: t('SystemManagement.Menu.nameRequired'), trigger: 'blur' }], sort: [{ required: true, message: t('SystemManagement.Menu.sortRequired'), trigger: 'blur' }], path: [{ required: clientType.value === 1, message: t('SystemManagement.Menu.pathRequired'), trigger: 'blur' }], - status: [{ required: true, message: t('SystemManagement.Menu.statusRequired'), trigger: 'blur' }], - terminalType: [{ required: clientType.value === 2 && formData.value.type === SystemMenuTypeEnum.DIR, message: t('SystemManagement.Menu.terminalTypeRequired'), trigger: 'change' }] + status: [{ required: true, message: t('SystemManagement.Menu.statusRequired'), trigger: 'blur' }] })) const formRef = ref() // 表单 Ref @@ -222,16 +210,13 @@ const submitForm = async () => { } } } - const data = formData.value as unknown as MenuApi.MenuVO & { terminalType?: number } + const { terminalType: _terminalType, ...data } = formData.value as typeof formData.value & { terminalType?: number } data.clientType = clientType.value - data.terminalType = clientType.value === 2 && formData.value.type === SystemMenuTypeEnum.DIR - ? formData.value.terminalType - : undefined if (formType.value === 'create') { - await MenuApi.createMenu(data) + await MenuApi.createMenu(data as unknown as MenuApi.MenuVO) message.success(t('common.createSuccess')) } else { - await MenuApi.updateMenu(data) + await MenuApi.updateMenu(data as unknown as MenuApi.MenuVO) message.success(t('common.updateSuccess')) } dialogVisible.value = false @@ -272,8 +257,7 @@ const resetForm = () => { visible: true, keepAlive: true, alwaysShow: true, - clientType: 1, - terminalType: 1 + clientType: 1 } formRef.value?.resetFields() } diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index b157633d..681319da 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -88,16 +88,6 @@ - - - @@ -197,11 +187,6 @@ const handleClientTypeChange = (val: number | string) => { getList() } -const getTerminalTypeLabel = (value?: number) => { - if (Number(value) === 2) return t('SystemManagement.Menu.terminalTypeScanner') - return t('SystemManagement.Menu.terminalTypeMobile') -} - /** 重置按钮操作 */ const resetQuery = () => { queryFormRef.value.resetFields() diff --git a/src/views/system/role/RoleAssignMenuForm.vue b/src/views/system/role/RoleAssignMenuForm.vue index e5812672..a69d847f 100644 --- a/src/views/system/role/RoleAssignMenuForm.vue +++ b/src/views/system/role/RoleAssignMenuForm.vue @@ -16,6 +16,12 @@ ({ id: undefined, name: '', code: '', @@ -74,30 +89,80 @@ const menuOptions = ref([]) // 菜单树形结构 const menuExpand = ref(false) // 展开/折叠 const treeRef = ref() // 菜单树组件 Ref const treeNodeAll = ref(false) // 全选/全不选 +const activeClientType = ref(1) // 客户端类型:1=Web,2=APP +const currentTreeClientType = ref(1) // 当前树实际对应的客户端类型 +const roleMenuIdsByClientType = reactive>({ + 1: [], + 2: [] +}) +const loadedClientTypes = reactive>({ + 1: false, + 2: false +}) + +const getCurrentTreeMenuIds = () => { + if (!treeRef.value) return [] + return Array.from(new Set([ + ...(treeRef.value.getCheckedKeys(false) as Array), + ...(treeRef.value.getHalfCheckedKeys() as Array) + ])) +} + +const saveCurrentClientMenuIds = () => { + if (!dialogVisible.value) return + roleMenuIdsByClientType[currentTreeClientType.value] = getCurrentTreeMenuIds() +} + +const ensureRoleMenuIdsLoaded = async (roleId: number, clientType: number) => { + if (loadedClientTypes[clientType]) return + roleMenuIdsByClientType[clientType] = await PermissionApi.getRoleMenuList(roleId, clientType) + loadedClientTypes[clientType] = true +} + +const loadRoleMenus = async (roleId: number) => { + currentTreeClientType.value = activeClientType.value + treeNodeAll.value = false + menuExpand.value = false + treeRef.value?.setCheckedNodes([]) + menuOptions.value = handleTree(await MenuApi.getSimpleMenusList(currentTreeClientType.value)) + await ensureRoleMenuIdsLoaded(roleId, currentTreeClientType.value) + await nextTick() + formData.menuIds = roleMenuIdsByClientType[currentTreeClientType.value] ?? [] + formData.menuIds.forEach((menuId: number) => { + treeRef.value.setChecked(menuId, true, false) + }) +} /** 打开弹窗 */ const open = async (row: RoleApi.RoleVO) => { dialogVisible.value = true resetForm() - // 加载 Menu 列表。注意,必须放在前面,不然下面 setChecked 没数据节点 - menuOptions.value = handleTree(await MenuApi.getSimpleMenusList()) // 设置数据 formData.id = row.id formData.name = row.name formData.code = row.code formLoading.value = true try { - formData.menuIds = await PermissionApi.getRoleMenuList(row.id) - // 设置选中 - formData.menuIds.forEach((menuId: number) => { - treeRef.value.setChecked(menuId, true, false) - }) + await loadRoleMenus(row.id) } finally { formLoading.value = false } } defineExpose({ open }) // 提供 open 方法,用于打开弹窗 +/** 切换客户端类型 */ +const handleClientTypeChange = async (val: number | string) => { + saveCurrentClientMenuIds() + activeClientType.value = Number(val) + if (!dialogVisible.value || !formData.id) return + formLoading.value = true + try { + await loadRoleMenus(formData.id) + } finally { + formLoading.value = false + } +} + /** 提交表单 */ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调 const submitForm = async () => { @@ -105,15 +170,18 @@ const submitForm = async () => { if (!formRef) return const valid = await formRef.value.validate() if (!valid) return + if (formData.id === undefined) return + saveCurrentClientMenuIds() // 提交请求 formLoading.value = true try { + await Promise.all([1, 2].map((clientType) => ensureRoleMenuIdsLoaded(formData.id as number, clientType))) const data = { roleId: formData.id, - menuIds: [ - ...(treeRef.value.getCheckedKeys(false) as unknown as Array), // 获得当前选中节点 - ...(treeRef.value.getHalfCheckedKeys() as unknown as Array) // 获得半选中的父节点 - ] + menuIds: Array.from(new Set([ + ...roleMenuIdsByClientType[1], + ...roleMenuIdsByClientType[2] + ])) } await PermissionApi.assignRoleMenu(data) message.success(t('common.updateSuccess')) @@ -128,6 +196,8 @@ const submitForm = async () => { /** 重置表单 */ const resetForm = () => { // 重置选项 + activeClientType.value = 1 + currentTreeClientType.value = 1 treeNodeAll.value = false menuExpand.value = false // 重置表单 @@ -135,6 +205,10 @@ const resetForm = () => { formData.name = '' formData.code = '' formData.menuIds = [] + roleMenuIdsByClientType[1] = [] + roleMenuIdsByClientType[2] = [] + loadedClientTypes[1] = false + loadedClientTypes[2] = false treeRef.value?.setCheckedNodes([]) formRef.value?.resetFields() } @@ -160,4 +234,22 @@ const handleCheckedTreeExpand = () => { .role-assign-menu-form :deep(.el-form-item__label) { min-width: 80px; } + +.role-assign-menu-form__header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; +} + +.role-assign-menu-form__header :deep(.el-tabs__header) { + margin: 0; +} + +.role-assign-menu-form__actions { + display: flex; + align-items: center; + gap: 8px; + flex-wrap: wrap; +}