This commit is contained in:
parent
36b08997bd
commit
b82c43e513
|
|
@ -15,15 +15,17 @@ public class AdminUserService : IAdminUserService
|
|||
private readonly AdminDbContext _dbContext;
|
||||
private readonly ILogger<AdminUserService> _logger;
|
||||
private readonly IAuthService _authService;
|
||||
private readonly IPermissionService _permissionService;
|
||||
|
||||
// 超级管理员角色编码
|
||||
private const string SuperAdminRoleCode = "super_admin";
|
||||
|
||||
public AdminUserService(AdminDbContext dbContext, ILogger<AdminUserService> logger, IAuthService authService)
|
||||
public AdminUserService(AdminDbContext dbContext, ILogger<AdminUserService> logger, IAuthService authService, IPermissionService permissionService)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
_authService = authService;
|
||||
_permissionService = permissionService;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<Entities.Menu>()
|
||||
.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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 递归获取所有子菜单 ID
|
||||
/// </summary>
|
||||
private async Task<HashSet<long>> GetAllChildMenuIdsAsync(HashSet<long> parentIds)
|
||||
{
|
||||
var result = new HashSet<long>();
|
||||
var currentParentIds = parentIds;
|
||||
|
||||
while (currentParentIds.Count > 0)
|
||||
{
|
||||
var childIds = await _dbContext.Set<Entities.Menu>()
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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' })
|
||||
// 标记动态路由已加载
|
||||
|
|
|
|||
|
|
@ -123,6 +123,11 @@ async function handle401Error(error: any): Promise<any> {
|
|||
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -161,7 +161,6 @@
|
|||
show-checkbox
|
||||
node-key="id"
|
||||
:default-checked-keys="checkedMenuIds"
|
||||
:check-strictly="true"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button @click="menuDialogVisible = false">取消</el-button>
|
||||
|
|
@ -304,6 +303,25 @@ const passwordRules: FormRules = {
|
|||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤出叶子节点 ID,用于树组件回显(非 check-strictly 模式)
|
||||
*/
|
||||
function filterLeafMenuIds(tree: MenuTree[], checkedIds: number[]): number[] {
|
||||
const result: number[] = []
|
||||
const idSet = new Set(checkedIds)
|
||||
function walk(nodes: MenuTree[]) {
|
||||
for (const node of nodes) {
|
||||
if (node.children && node.children.length > 0) {
|
||||
walk(node.children)
|
||||
} else if (idSet.has(node.id)) {
|
||||
result.push(node.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(tree)
|
||||
return result
|
||||
}
|
||||
|
||||
const fetchData = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
|
|
@ -453,14 +471,20 @@ const handleSubmitDepartment = async () => {
|
|||
const handleAssignMenu = async (row: AdminUser) => {
|
||||
currentUserId.value = row.id
|
||||
const res = await getUserMenus(row.id)
|
||||
checkedMenuIds.value = res.data
|
||||
// 去掉 check-strictly 后,回显时只设置叶子节点 ID,避免父节点导致子节点全选
|
||||
const allIds: number[] = res.data
|
||||
const leafIds = filterLeafMenuIds(menuTree.value, allIds)
|
||||
checkedMenuIds.value = leafIds
|
||||
menuDialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleSubmitMenus = async () => {
|
||||
menuSubmitLoading.value = true
|
||||
try {
|
||||
const menuIds = menuTreeRef.value?.getCheckedKeys(false) as number[]
|
||||
const menuIds = [
|
||||
...(menuTreeRef.value?.getCheckedKeys(false) as number[]),
|
||||
...(menuTreeRef.value?.getHalfCheckedKeys() as number[])
|
||||
]
|
||||
await assignUserMenus({ userId: currentUserId.value, menuIds })
|
||||
ElMessage.success('分配成功')
|
||||
menuDialogVisible.value = false
|
||||
|
|
|
|||
1
temp_menu29.txt
Normal file
1
temp_menu29.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"code":0,"message":"success","data":{"id":29,"parentId":28,"name":"用户列表","path":"/user/list","component":"business/user/index","icon":"peoples","menuType":2,"permission":"user:view","sortOrder":1,"status":1,"isExternal":false,"isCache":true,"createdAt":"2026-02-03T17:32:15.877","updatedAt":"2026-04-19T20:22:50.107"}}
|
||||
1
temp_menus.json
Normal file
1
temp_menus.json
Normal file
File diff suppressed because one or more lines are too long
1
temp_test_menus.txt
Normal file
1
temp_test_menus.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"code":0,"message":"success","data":[28,29,30,31,32,34,35,36,37]}
|
||||
Loading…
Reference in New Issue
Block a user