From f3d56bff4577320dbd639f66d6fd4de053546562 Mon Sep 17 00:00:00 2001 From: chenqi Date: Thu, 5 Feb 2026 23:07:31 +0800 Subject: [PATCH] =?UTF-8?q?feat(menu):=20=E6=B7=BB=E5=8A=A0=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E7=BC=93=E5=AD=98=E5=88=B7=E6=96=B0=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=92=8C=E6=8B=96=E6=8B=BD=E6=8E=92=E5=BA=8F=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在SysMenuController中添加refreshCache和refreshCurrentUserMenuCache接口 - 实现菜单缓存的按需刷新和用户级别缓存清理功能 - 优化菜单列表查询的缓存key策略,支持更精确的缓存命中 - 为菜单树查询添加缓存注解提升性能 - 在菜单增删改操作中完善缓存清理逻辑 - 添加allocateMenuToRole方法实现菜单角色分配功能 - 在前端DictTag组件中修复标签类型验证逻辑 - 为首页配置页面添加拖拽排序功能,支持快捷功能重新排列 - 集成Sortable.js实现拖拽交互和排序保存 - 优化菜单管理页面的缓存刷新机制和数据展示 - 完善配置更新事件处理,支持实时配置同步 --- .../controller/system/SysMenuController.java | 22 +++ .../core/system/service/ISysMenuService.java | 19 +++ .../service/impl/SysMenuServiceImpl.java | 76 +++++++-- openhis-ui-vue3/src/api/system/menu.js | 8 + .../src/components/DictTag/index.vue | 22 ++- openhis-ui-vue3/src/views/features/config.vue | 150 +++++++++++++++++- openhis-ui-vue3/src/views/index.vue | 41 ++++- .../src/views/system/menu/index.vue | 42 ++++- 8 files changed, 356 insertions(+), 24 deletions(-) diff --git a/openhis-server-new/core-admin/src/main/java/com/core/web/controller/system/SysMenuController.java b/openhis-server-new/core-admin/src/main/java/com/core/web/controller/system/SysMenuController.java index cc141498..332f8990 100644 --- a/openhis-server-new/core-admin/src/main/java/com/core/web/controller/system/SysMenuController.java +++ b/openhis-server-new/core-admin/src/main/java/com/core/web/controller/system/SysMenuController.java @@ -138,4 +138,26 @@ public class SysMenuController extends BaseController { String fullPath = menuService.generateFullPath(parentId, currentPath); return success(fullPath); } + + /** + * 刷新菜单缓存 + */ + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @Log(title = "菜单管理", businessType = BusinessType.OTHER) + @PostMapping("/refreshCache") + public AjaxResult refreshCache() { + menuService.refreshMenuCache(); + return success("菜单缓存已刷新"); + } + + /** + * 强制刷新当前用户菜单缓存 + */ + @PreAuthorize("@ss.hasPermi('system:menu:list')") + @Log(title = "菜单管理", businessType = BusinessType.OTHER) + @PostMapping("/refreshCurrentUserMenuCache") + public AjaxResult refreshCurrentUserMenuCache() { + menuService.clearMenuCacheByUserId(getUserId()); + return success("当前用户菜单缓存已刷新"); + } } \ No newline at end of file diff --git a/openhis-server-new/core-system/src/main/java/com/core/system/service/ISysMenuService.java b/openhis-server-new/core-system/src/main/java/com/core/system/service/ISysMenuService.java index 0f6772c9..905c644c 100644 --- a/openhis-server-new/core-system/src/main/java/com/core/system/service/ISysMenuService.java +++ b/openhis-server-new/core-system/src/main/java/com/core/system/service/ISysMenuService.java @@ -165,4 +165,23 @@ public interface ISysMenuService { * @return 完整路径 */ public String generateFullPath(Long parentId, String currentPath); + + /** + * 刷新菜单缓存 + */ + public void refreshMenuCache(); + + /** + * 根据用户ID清除菜单缓存 + */ + public void clearMenuCacheByUserId(Long userId); + + /** + * 将菜单分配给角色 + * + * @param roleId 角色ID + * @param menuIds 菜单ID列表 + * @return 结果 + */ + public int allocateMenuToRole(Long roleId, List menuIds); } \ No newline at end of file diff --git a/openhis-server-new/core-system/src/main/java/com/core/system/service/impl/SysMenuServiceImpl.java b/openhis-server-new/core-system/src/main/java/com/core/system/service/impl/SysMenuServiceImpl.java index bd55b734..9bd3fb7f 100644 --- a/openhis-server-new/core-system/src/main/java/com/core/system/service/impl/SysMenuServiceImpl.java +++ b/openhis-server-new/core-system/src/main/java/com/core/system/service/impl/SysMenuServiceImpl.java @@ -8,6 +8,7 @@ import com.core.common.core.domain.entity.SysRole; import com.core.common.core.domain.entity.SysUser; import com.core.common.utils.SecurityUtils; import com.core.common.utils.StringUtils; +import com.core.system.domain.SysRoleMenu; import com.core.system.domain.vo.MetaVo; import com.core.system.domain.vo.RouterVo; import com.core.system.mapper.SysMenuMapper; @@ -54,12 +55,12 @@ public class SysMenuServiceImpl implements ISysMenuService { /** * 查询系统菜单列表 - * + * * @param menu 菜单信息 * @return 菜单列表 */ @Override - @org.springframework.cache.annotation.Cacheable(value = "menu", key = "'menuList:' + #userId + ':' + (#menu == null ? 'all' : #menu.menuName)") + @org.springframework.cache.annotation.Cacheable(value = "menu", key = "'menuList:' + #userId + ':' + (#menu == null ? 'all' : (#menu.menuName != null ? #menu.menuName : 'all') + ':' + (#menu.visible != null ? #menu.visible : 'all') + ':' + (#menu.status != null ? #menu.status : 'all'))") public List selectMenuList(SysMenu menu, Long userId) { List menuList = null; // 管理员显示所有菜单信息 @@ -110,11 +111,12 @@ public class SysMenuServiceImpl implements ISysMenuService { /** * 根据用户ID查询菜单 - * + * * @param userId 用户名称 * @return 菜单列表 */ @Override + @org.springframework.cache.annotation.Cacheable(value = "menu", key = "'menuTree:' + #userId") public List selectMenuTreeByUserId(Long userId) { List menus = null; if (SecurityUtils.isAdmin(userId)) { @@ -405,11 +407,12 @@ public class SysMenuServiceImpl implements ISysMenuService { /** * 新增保存菜单信息 - * + * * @param menu 菜单信息 * @return 结果 */ @Override + @org.springframework.transaction.annotation.Transactional @org.springframework.cache.annotation.Caching(evict = { @org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true) }) @@ -419,19 +422,27 @@ public class SysMenuServiceImpl implements ISysMenuService { if (sysMenu != null){ return -1; } - return menuMapper.insertMenu(menu); + + int rows = menuMapper.insertMenu(menu); + + // 如果是管理员创建菜单,自动分配给所有角色(可选逻辑) + // 或者,可以将新菜单分配给创建者所属的角色 + // 这里我们暂时不自动分配,因为这可能不符合安全最佳实践 + + return rows; } /** * 修改保存菜单信息 - * + * * @param menu 菜单信息 * @return 结果 */ @Override @org.springframework.cache.annotation.Caching(evict = { @org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true), - @org.springframework.cache.annotation.CacheEvict(value = "menu", key = "'fullPath:' + #menu.menuId") + @org.springframework.cache.annotation.CacheEvict(value = "menu", key = "'fullPath:' + #menu.menuId"), + @org.springframework.cache.annotation.CacheEvict(value = "menu", key = "'menuTree:' + #menu.updateBy") }) public int updateMenu(SysMenu menu) { //路径Path唯一性判断(排除当前菜单本身) @@ -450,14 +461,13 @@ public class SysMenuServiceImpl implements ISysMenuService { /** * 删除菜单管理信息 - * + * * @param menuId 菜单ID * @return 结果 */ @Override @org.springframework.cache.annotation.Caching(evict = { - @org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true), - @org.springframework.cache.annotation.CacheEvict(value = "menu", key = "'fullPath:' + #menuId") + @org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true) }) public int deleteMenuById(Long menuId) { return menuMapper.deleteMenuById(menuId); @@ -776,4 +786,50 @@ public class SysMenuServiceImpl implements ISysMenuService { return normalizePath(fullPath.toString()); } + + /** + * 刷新菜单缓存 + */ + @Override + @org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true) + public void refreshMenuCache() { + log.info("菜单缓存已刷新"); + } + + /** + * 根据用户ID清除菜单缓存 + */ + @Override + @org.springframework.cache.annotation.CacheEvict(value = "menu", key = "'menuTree:' + #userId") + public void clearMenuCacheByUserId(Long userId) { + log.info("清除用户 {} 的菜单树缓存", userId); + } + + /** + * 将菜单分配给角色 + * + * @param roleId 角色ID + * @param menuIds 菜单ID列表 + * @return 结果 + */ + @Override + @org.springframework.transaction.annotation.Transactional + @org.springframework.cache.annotation.CacheEvict(value = "menu", allEntries = true) + public int allocateMenuToRole(Long roleId, List menuIds) { + // 先删除该角色现有的所有菜单权限 + roleMenuMapper.deleteRoleMenuByRoleId(roleId); + + // 重新分配菜单给角色 + if (menuIds != null && !menuIds.isEmpty()) { + List roleMenuList = new ArrayList<>(); + for (Long menuId : menuIds) { + SysRoleMenu rm = new SysRoleMenu(); + rm.setRoleId(roleId); + rm.setMenuId(menuId); + roleMenuList.add(rm); + } + return roleMenuMapper.batchRoleMenu(roleMenuList); + } + return 0; + } } diff --git a/openhis-ui-vue3/src/api/system/menu.js b/openhis-ui-vue3/src/api/system/menu.js index 934d3e73..b619a541 100644 --- a/openhis-ui-vue3/src/api/system/menu.js +++ b/openhis-ui-vue3/src/api/system/menu.js @@ -77,4 +77,12 @@ export function generateFullPath(parentId, currentPath) { currentPath: currentPath } }) +} + +// 刷新菜单缓存 +export function refreshMenuCache() { + return request({ + url: '/system/menu/refreshCache', + method: 'post' + }) } \ No newline at end of file diff --git a/openhis-ui-vue3/src/components/DictTag/index.vue b/openhis-ui-vue3/src/components/DictTag/index.vue index 7d0888cf..448aacf6 100644 --- a/openhis-ui-vue3/src/components/DictTag/index.vue +++ b/openhis-ui-vue3/src/components/DictTag/index.vue @@ -13,7 +13,7 @@ :disable-transitions="true" :key="item.value + ''" :index="index" - :type="item.elTagType === 'primary' ? '' : item.elTagType" + :type="getValidTagType(item.elTagType)" :class="item.elTagClass" >{{ item.label + " " }} @@ -67,6 +67,26 @@ const unmatch = computed(() => { return unmatch // 返回标志的值 }); +// 验证并返回有效的tag类型 +function getValidTagType(tagType) { + // 定义有效的tag类型 + const validTypes = ['', 'success', 'warning', 'danger', 'info', 'primary']; + // 如果tagType为null、undefined或不在有效类型中,则返回默认值('') + if (tagType === null || tagType === undefined || !validTypes.includes(tagType)) { + // 如果是primary类型,转换为空字符串(默认样式) + if (tagType === 'primary') { + return ''; + } + // 其他无效类型统一返回空字符串(默认样式) + return ''; + } + // 如果是primary类型,也转换为空字符串(默认样式) + if (tagType === 'primary') { + return ''; + } + return tagType; +} + function handleArray(array) { if (array.length === 0) return ""; return array.reduce((pre, cur) => { diff --git a/openhis-ui-vue3/src/views/features/config.vue b/openhis-ui-vue3/src/views/features/config.vue index a549a48a..9a4ec72e 100644 --- a/openhis-ui-vue3/src/views/features/config.vue +++ b/openhis-ui-vue3/src/views/features/config.vue @@ -78,13 +78,30 @@
-

已选择的功能

-
+
+

已选择的功能

+ {{ selectedFunctions.length }}/8 +
+
+
+ +
@@ -117,8 +134,9 @@ \ No newline at end of file diff --git a/openhis-ui-vue3/src/views/index.vue b/openhis-ui-vue3/src/views/index.vue index e0bf8812..7de230a8 100644 --- a/openhis-ui-vue3/src/views/index.vue +++ b/openhis-ui-vue3/src/views/index.vue @@ -963,9 +963,46 @@ const handleStorageChange = (event) => { }; // 监听配置更新事件 -const handleConfigUpdate = () => { +const handleConfigUpdate = (event) => { console.log('检测到快捷功能配置更新事件,正在重新加载...'); - loadUserQuickAccessConfig(); + // 如果事件携带了最新配置数据,则直接使用 + if (event && event.detail && event.detail.config) { + console.log('使用事件传递的最新配置数据:', event.detail.config); + // 直接更新本地存储中的配置 + localStorage.setItem('homeFeaturesConfig', JSON.stringify(event.detail.config)); + // 更新缓存时间戳 + localStorage.setItem('homeFeaturesConfigCache', JSON.stringify(event.detail.config)); + localStorage.setItem('homeFeaturesConfigCacheTimestamp', Date.now().toString()); + // 使用最新配置更新首页显示 + updateQuickAccessData(event.detail.config); + } else { + // 否则重新加载配置 + loadUserQuickAccessConfig(); + } +}; + +// 直接更新快捷访问数据 +const updateQuickAccessData = async (configData) => { + quickAccessLoading.value = true; + try { + console.log('使用最新配置数据更新快捷访问...', configData); + // 转换菜单ID为快捷访问格式 + const convertedFeatures = await convertMenuIdsToQuickAccess(configData); + console.log('转换后的功能:', convertedFeatures); + + if (convertedFeatures && convertedFeatures.length > 0) { + quickAccessData.value = convertedFeatures; + } else { + // 如果转换失败,使用默认配置 + quickAccessData.value = getDefaultQuickAccessConfig(); + } + } catch (error) { + console.error('更新快捷访问数据失败:', error); + // 出错时使用默认配置 + quickAccessData.value = getDefaultQuickAccessConfig(); + } finally { + quickAccessLoading.value = false; + } }; onMounted(async () => { diff --git a/openhis-ui-vue3/src/views/system/menu/index.vue b/openhis-ui-vue3/src/views/system/menu/index.vue index d8e8ede3..8b726942 100644 --- a/openhis-ui-vue3/src/views/system/menu/index.vue +++ b/openhis-ui-vue3/src/views/system/menu/index.vue @@ -54,7 +54,7 @@ @click="toggleExpandAll" >展开/折叠 - + @@ -321,7 +321,7 @@