From b82c43e513fabe3bd6f906ad42694e7b184c07b0 Mon Sep 17 00:00:00 2001 From: 18631081161 <2088094923@qq.com> Date: Sun, 19 Apr 2026 20:38:08 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9D=83=E9=99=90=E5=88=86=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Services/AdminUserService.cs | 9 ++- .../Services/PermissionService.cs | 63 ++++++++++++++++++- .../admin-web/src/router/index.ts | 24 ++++++- .../admin-web/src/utils/request.ts | 5 ++ .../admin-web/src/views/system/user/index.vue | 30 ++++++++- temp_menu29.txt | 1 + temp_menus.json | 1 + temp_test_menus.txt | 1 + 8 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 temp_menu29.txt create mode 100644 temp_menus.json create mode 100644 temp_test_menus.txt diff --git a/server/MiAssessment/src/MiAssessment.Admin/Services/AdminUserService.cs b/server/MiAssessment/src/MiAssessment.Admin/Services/AdminUserService.cs index 6c88de6..0a5a54a 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/Services/AdminUserService.cs +++ b/server/MiAssessment/src/MiAssessment.Admin/Services/AdminUserService.cs @@ -15,15 +15,17 @@ public class AdminUserService : IAdminUserService private readonly AdminDbContext _dbContext; private readonly ILogger _logger; private readonly IAuthService _authService; + private readonly IPermissionService _permissionService; // 超级管理员角色编码 private const string SuperAdminRoleCode = "super_admin"; - public AdminUserService(AdminDbContext dbContext, ILogger logger, IAuthService authService) + public AdminUserService(AdminDbContext dbContext, ILogger logger, IAuthService authService, IPermissionService permissionService) { _dbContext = dbContext; _logger = logger; _authService = authService; + _permissionService = permissionService; } /// @@ -310,6 +312,7 @@ public class AdminUserService : IAdminUserService } await _dbContext.SaveChangesAsync(); + _permissionService.InvalidateCache(userId); _logger.LogInformation("管理员 {UserId} 分配角色成功,角色数量: {Count}", userId, roleIds.Count); } @@ -353,6 +356,10 @@ public class AdminUserService : IAdminUserService } await _dbContext.SaveChangesAsync(); + + // 清除用户权限缓存,使新菜单权限立即生效 + _permissionService.InvalidateCache(userId); + _logger.LogInformation("管理员 {UserId} 分配用户专属菜单成功,菜单数量: {Count}", userId, menuIds.Count); } diff --git a/server/MiAssessment/src/MiAssessment.Admin/Services/PermissionService.cs b/server/MiAssessment/src/MiAssessment.Admin/Services/PermissionService.cs index 9dd2a03..e5d941e 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/Services/PermissionService.cs +++ b/server/MiAssessment/src/MiAssessment.Admin/Services/PermissionService.cs @@ -212,14 +212,71 @@ public class PermissionService : IPermissionService var roleIds = userRoles.Select(ur => ur.RoleId).ToList(); - // 获取角色关联的权限 - var permissions = await _dbContext.RolePermissions + // 1. 获取角色关联的权限(permissions 表) + var rolePermissions = await _dbContext.RolePermissions .Where(rp => roleIds.Contains(rp.RoleId)) .Include(rp => rp.Permission) .Select(rp => rp.Permission.Code) .Distinct() .ToListAsync(); - return permissions; + // 2. 收集角色关联的菜单 ID + var roleMenuIds = await _dbContext.RoleMenus + .Where(rm => roleIds.Contains(rm.RoleId)) + .Select(rm => rm.MenuId) + .Distinct() + .ToListAsync(); + + // 3. 收集用户专属菜单 ID + var userMenuIds = await _dbContext.AdminUserMenus + .Where(um => um.AdminUserId == adminUserId) + .Select(um => um.MenuId) + .Distinct() + .ToListAsync(); + + // 4. 合并所有菜单 ID,并递归查找所有子菜单 + var allMenuIds = roleMenuIds.Concat(userMenuIds).Distinct().ToHashSet(); + var allChildMenuIds = await GetAllChildMenuIdsAsync(allMenuIds); + allMenuIds.UnionWith(allChildMenuIds); + + // 5. 从所有菜单中提取权限标识 + var menuPermissions = await _dbContext.Set() + .Where(m => allMenuIds.Contains(m.Id) && m.Permission != null && m.Permission != "") + .Select(m => m.Permission!) + .Distinct() + .ToListAsync(); + + // 合并所有权限 + var allPermissions = rolePermissions + .Concat(menuPermissions) + .Distinct() + .ToList(); + + return allPermissions; + } + + /// + /// 递归获取所有子菜单 ID + /// + private async Task> GetAllChildMenuIdsAsync(HashSet parentIds) + { + var result = new HashSet(); + var currentParentIds = parentIds; + + while (currentParentIds.Count > 0) + { + var childIds = await _dbContext.Set() + .Where(m => currentParentIds.Contains(m.ParentId)) + .Select(m => m.Id) + .ToListAsync(); + + var newIds = childIds.Where(id => !result.Contains(id) && !parentIds.Contains(id)).ToHashSet(); + if (newIds.Count == 0) break; + + result.UnionWith(newIds); + currentParentIds = newIds; + } + + return result; } } diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/router/index.ts b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/router/index.ts index 78078a7..ab7f8ec 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/router/index.ts +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/router/index.ts @@ -10,7 +10,7 @@ export { businessRoutes, type RouteMeta, getBusinessPermissions, filterRoutesByP const constantRoutes: RouteRecordRaw[] = [ { path: '/', - redirect: '/dashboard' + redirect: '/dashboard' // 默认值,登录后会被动态路由覆盖 }, { path: '/login', @@ -60,6 +60,23 @@ const router = createRouter({ // 白名单路由 const whiteList = ['/login', '/404'] +/** + * 递归获取用户有权限的第一个可访问页面路径 + */ +function getFirstAccessiblePath(routes: RouteRecordRaw[], parentPath = ''): string | null { + for (const route of routes) { + const fullPath = parentPath ? `${parentPath}/${route.path}`.replace(/\/+/g, '/') : route.path + // 有 component 且不是 Layout 的就是叶子页面 + if (route.children && route.children.length > 0) { + const childPath = getFirstAccessiblePath(route.children, fullPath) + if (childPath) return childPath + } else if (route.component) { + return fullPath + } + } + return null +} + // 标记动态路由是否已加载 let dynamicRoutesLoaded = false @@ -94,6 +111,11 @@ router.beforeEach(async (to, _from, next) => { accessRoutes.forEach(route => { router.addRoute(route) }) + // 动态设置首页重定向到用户有权限的第一个页面 + const firstRoute = getFirstAccessiblePath(accessRoutes) + if (firstRoute) { + router.addRoute({ path: '/', redirect: firstRoute }) + } // 添加 404 兜底路由 router.addRoute({ path: '/:pathMatch(.*)*', redirect: '/404' }) // 标记动态路由已加载 diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/utils/request.ts b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/utils/request.ts index 70fba6b..82b609d 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/utils/request.ts +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/utils/request.ts @@ -123,6 +123,11 @@ async function handle401Error(error: any): Promise { // Don't retry if already retried or if it's a refresh request if (originalConfig._retry || isWhiteListUrl(originalConfig.url)) { + // 白名单接口(如登录)的 401 错误,直接显示后端返回的错误信息 + const msg = error.response?.data?.message + if (msg) { + ElMessage.error(msg) + } return Promise.reject(error) } diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue index c763294..2ddc4c0 100644 --- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue +++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/system/user/index.vue @@ -161,7 +161,6 @@ show-checkbox node-key="id" :default-checked-keys="checkedMenuIds" - :check-strictly="true" />