Browse Source

feat: 完善路由配置映射,支持场景绑定页面

- 在 routeConfig.ts 中添加 scenarios.binding 路由映射
- 配置场景绑定页面组件懒加载
- 设置路由路径为 'binding'
- 确保动态路由系统支持场景绑定功能
- 与现有路由配置保持一致的命名规范
refactor/permission-config
root 4 months ago
parent
commit
f44b28c8e9
  1. 14
      src/X1.Application/Features/Auth/Commands/BaseLoginCommandHandler.cs
  2. 14
      src/X1.Application/Features/Auth/Commands/RefreshToken/RefreshTokenCommandHandler.cs
  3. 6
      src/X1.Application/Features/Auth/Models/UserInfo.cs
  4. 39
      src/X1.Application/Features/Permissions/Commands/BatchCreatePermissions/BatchCreatePermissionsCommand.cs
  5. 87
      src/X1.Application/Features/Permissions/Commands/BatchCreatePermissions/BatchCreatePermissionsCommandHandler.cs
  6. 5
      src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommand.cs
  7. 37
      src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs
  8. 7
      src/X1.Application/Features/Permissions/Commands/UpdatePermission/UpdatePermissionCommand.cs
  9. 8
      src/X1.Application/Features/Permissions/Commands/UpdatePermission/UpdatePermissionCommandHandler.cs
  10. 11
      src/X1.Application/Features/Permissions/Queries/GetAllPermissionsQuery.cs
  11. 74
      src/X1.Application/Features/Permissions/Queries/GetAllPermissionsQueryHandler.cs
  12. 14
      src/X1.Application/Features/Permissions/Queries/GetPermissionTree/GetPermissionTreeQueryHandler.cs
  13. 89
      src/X1.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsCommandHandler.cs
  14. 20
      src/X1.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsResponse.cs
  15. 9
      src/X1.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsCommandHandler.cs
  16. 12
      src/X1.Application/Features/Users/Queries/Dtos/UserDto.cs
  17. 32
      src/X1.Application/Features/Users/Queries/GetAllUsers/GetAllUsersQueryHandler.cs
  18. 11
      src/X1.Application/Features/Users/Queries/GetCurrentUser/GetCurrentUserQuery.cs
  19. 128
      src/X1.Application/Features/Users/Queries/GetCurrentUser/GetCurrentUserQueryHandler.cs
  20. 28
      src/X1.Application/Features/Users/Queries/GetUserById/GetUserByIdQueryHandler.cs
  21. 26
      src/X1.Domain/Entities/Permission.cs
  22. 5
      src/X1.Domain/Repositories/Identity/INavigationMenuRepository.cs
  23. 8
      src/X1.Domain/Repositories/Identity/IRolePermissionRepository.cs
  24. 15
      src/X1.Infrastructure/Repositories/Identity/NavigationMenuRepository.cs
  25. 13
      src/X1.Infrastructure/Repositories/Identity/RolePermissionRepository.cs
  26. 18
      src/X1.Presentation/Controllers/UsersController.cs
  27. 487
      src/X1.WebUI/src/components/permissions/PermissionAssignmentDialog.tsx
  28. 189
      src/X1.WebUI/src/components/permissions/README.md
  29. 46
      src/X1.WebUI/src/components/ui/scroll-area.tsx
  30. 7
      src/X1.WebUI/src/constants/auth.ts
  31. 100
      src/X1.WebUI/src/constants/defaultPermissions.ts
  32. 345
      src/X1.WebUI/src/constants/menuConfig.backup.ts
  33. 499
      src/X1.WebUI/src/constants/menuConfig.ts
  34. 81
      src/X1.WebUI/src/contexts/AuthContext.tsx
  35. 2
      src/X1.WebUI/src/hooks/useAuthSync.ts
  36. 2
      src/X1.WebUI/src/hooks/usePermissions.ts
  37. 318
      src/X1.WebUI/src/modify.md
  38. 6
      src/X1.WebUI/src/pages/auth/ForbiddenPage.tsx
  39. 2
      src/X1.WebUI/src/pages/button-permissions/ButtonPermissionsTable.tsx
  40. 2
      src/X1.WebUI/src/pages/button-permissions/ButtonPermissionsView.tsx
  41. 61
      src/X1.WebUI/src/pages/navigation-menus/NavigationMenuForm.tsx
  42. 5
      src/X1.WebUI/src/pages/navigation-menus/NavigationMenuFormEnhanced.tsx
  43. 109
      src/X1.WebUI/src/pages/navigation-menus/NavigationMenuTable.tsx
  44. 11
      src/X1.WebUI/src/pages/navigation-menus/NavigationMenusView.tsx
  45. 1055
      src/X1.WebUI/src/pages/permissions/PermissionForm.tsx
  46. 292
      src/X1.WebUI/src/pages/permissions/PermissionTable.tsx
  47. 4
      src/X1.WebUI/src/pages/permissions/PermissionsView.tsx
  48. 68
      src/X1.WebUI/src/pages/roles/RoleTable.tsx
  49. 8
      src/X1.WebUI/src/pages/roles/RolesView.tsx
  50. 401
      src/X1.WebUI/src/routes/AppRouter.tsx
  51. 401
      src/X1.WebUI/src/routes/AppRouter.tsx.backup
  52. 94
      src/X1.WebUI/src/routes/DynamicRouteGenerator.tsx
  53. 123
      src/X1.WebUI/src/routes/DynamicRoutes.tsx
  54. 142
      src/X1.WebUI/src/routes/routeConfig.ts
  55. 109
      src/X1.WebUI/src/routes/test-dynamic-routes.ts
  56. 1
      src/X1.WebUI/src/services/authService.ts
  57. 4
      src/X1.WebUI/src/services/navigationMenuService.ts
  58. 9
      src/X1.WebUI/src/services/permissionService.ts
  59. 3
      src/X1.WebUI/src/services/rolePermissionService.ts
  60. 8
      src/X1.WebUI/src/types/auth.ts
  61. 22
      src/X1.WebUI/src/types/navigation.ts
  62. 3
      src/X1.WebUI/src/utils/iconMapper.ts
  63. 142
      src/X1.WebUI/src/utils/iconUtils.ts
  64. 1206
      src/modify.md

14
src/X1.Application/Features/Auth/Commands/BaseLoginCommandHandler.cs

@ -232,15 +232,15 @@ public abstract class BaseLoginCommandHandler<TCommand, TResponse> : IRequestHan
claims.AddRange(roles.Select(role => new Claim(ClaimTypes.Role, role)));
// 获取所有角色的权限
var permissions = new Dictionary<string, bool>();
foreach (var role in roles)
var permissionCodes = new HashSet<string>();
if (roles.Any())
{
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync(role, cancellationToken);
foreach (var rolePermission in rolePermissions)
var allRolePermissions = await _rolePermissionRepository.GetRolePermissionsByRolesAsync(roles, cancellationToken);
foreach (var rolePermission in allRolePermissions)
{
if (!permissions.ContainsKey(rolePermission.Permission.Code))
if (rolePermission.Permission != null)
{
permissions[rolePermission.Permission.Code] = true;
permissionCodes.Add(rolePermission.Permission.Code);
}
}
}
@ -286,7 +286,7 @@ public abstract class BaseLoginCommandHandler<TCommand, TResponse> : IRequestHan
user.Email!,
user.PhoneNumber,
roles.ToList().AsReadOnly(),
permissions.ToDictionary(kvp => kvp.Key, kvp => kvp.Value));
permissionCodes.ToList().AsReadOnly());
// 记录成功的登录日志
loginLog = LoginLog.Create(

14
src/X1.Application/Features/Auth/Commands/RefreshToken/RefreshTokenCommandHandler.cs

@ -101,15 +101,15 @@ public sealed class RefreshTokenCommandHandler : IRequestHandler<RefreshTokenCom
var roles = await _userRoleRepository.GetUserRolesAsync(userId, cancellationToken);
// 获取所有角色的权限
var permissions = new Dictionary<string, bool>();
foreach (var role in roles)
var permissionCodes = new HashSet<string>();
if (roles.Any())
{
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync(role, cancellationToken);
foreach (var rolePermission in rolePermissions)
var allRolePermissions = await _rolePermissionRepository.GetRolePermissionsByRolesAsync(roles, cancellationToken);
foreach (var rolePermission in allRolePermissions)
{
if (!permissions.ContainsKey(rolePermission.Permission.Code))
if (rolePermission.Permission != null)
{
permissions[rolePermission.Permission.Code] = true;
permissionCodes.Add(rolePermission.Permission.Code);
}
}
}
@ -134,7 +134,7 @@ public sealed class RefreshTokenCommandHandler : IRequestHandler<RefreshTokenCom
claims.FirstOrDefault(c => c.Type == ClaimTypes.Email)?.Value ?? string.Empty,
claims.FirstOrDefault(c => c.Type == ClaimTypes.MobilePhone)?.Value,
roles.ToList().AsReadOnly(),
permissions);
permissionCodes.ToList().AsReadOnly());
_logger.LogInformation("刷新令牌成功");
return OperationResult<AuthenticateUserResponse>.CreateSuccess(

6
src/X1.Application/Features/Auth/Models/UserInfo.cs

@ -38,9 +38,9 @@ public class UserInfo
public IReadOnlyList<string> Roles { get; }
/// <summary>
/// 权限字典
/// 权限列表
/// </summary>
public IReadOnlyDictionary<string, bool> Permissions { get; }
public IReadOnlyList<string> Permissions { get; }
/// <summary>
/// 初始化用户信息
@ -52,7 +52,7 @@ public class UserInfo
string email,
string? phoneNumber,
IReadOnlyList<string> roles,
IReadOnlyDictionary<string, bool> permissions)
IReadOnlyList<string> permissions)
{
Id = id;
UserName = userName;

39
src/X1.Application/Features/Permissions/Commands/BatchCreatePermissions/BatchCreatePermissionsCommand.cs

@ -12,14 +12,10 @@ namespace X1.Application.Features.Permissions.Commands.BatchCreatePermissions;
/// "permissions": [
/// {
/// "name": "查看用户",
/// "code": "users.view",
/// "type": 1,
/// "level": 1,
/// "resourceType": 2,
/// "actionType": 1,
/// "navigationMenuId": "menu-id-123",
/// "description": "查看用户列表",
/// "isSystem": true,
/// "sortOrder": 1
/// "isEnabled": true
/// }
/// ]
/// }
@ -40,29 +36,9 @@ public sealed record CreatePermissionDto(
string Name,
/// <summary>
/// 权限代码
/// 导航菜单ID,用于获取权限代码
/// </summary>
string Code,
/// <summary>
/// 权限类型
/// </summary>
int Type,
/// <summary>
/// 权限级别
/// </summary>
int Level,
/// <summary>
/// 资源类型
/// </summary>
int? ResourceType,
/// <summary>
/// 操作类型
/// </summary>
int? ActionType,
string NavigationMenuId,
/// <summary>
/// 权限描述
@ -72,12 +48,7 @@ public sealed record CreatePermissionDto(
/// <summary>
/// 是否系统权限
/// </summary>
bool IsSystem,
/// <summary>
/// 排序权重
/// </summary>
int SortOrder,
bool IsSystem = false,
/// <summary>
/// 是否启用

87
src/X1.Application/Features/Permissions/Commands/BatchCreatePermissions/BatchCreatePermissionsCommandHandler.cs

@ -1,13 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using X1.Domain.Entities;
using X1.Domain.Entities.Enums;
using X1.Domain.Repositories.Identity;
using X1.Domain.Common;
using X1.Domain.Repositories.Base;
namespace X1.Application.Features.Permissions.Commands.BatchCreatePermissions;
@ -17,17 +18,23 @@ namespace X1.Application.Features.Permissions.Commands.BatchCreatePermissions;
public sealed class BatchCreatePermissionsCommandHandler : IRequestHandler<BatchCreatePermissionsCommand, OperationResult<BatchCreatePermissionsResponse>>
{
private readonly IPermissionRepository _permissionRepository;
private readonly INavigationMenuRepository _navigationMenuRepository;
private readonly ILogger<BatchCreatePermissionsCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
/// <summary>
/// 初始化处理器
/// </summary>
public BatchCreatePermissionsCommandHandler(
IPermissionRepository permissionRepository,
ILogger<BatchCreatePermissionsCommandHandler> logger)
INavigationMenuRepository navigationMenuRepository,
ILogger<BatchCreatePermissionsCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_permissionRepository = permissionRepository;
_navigationMenuRepository = navigationMenuRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
/// <summary>
@ -39,73 +46,91 @@ public sealed class BatchCreatePermissionsCommandHandler : IRequestHandler<Batch
{
try
{
// 性能优化:批量查询所有需要的导航菜单
var navigationMenuIds = request.Permissions.Select(p => p.NavigationMenuId).Distinct().ToList();
var navigationMenus = await _navigationMenuRepository.GetByIdsAsync(navigationMenuIds, cancellationToken);
var navigationMenuDict = navigationMenus.ToDictionary(m => m.Id, m => m);
// 性能优化:批量查询所有已存在的权限代码
var permissionCodes = navigationMenus
.Where(m => !string.IsNullOrEmpty(m.PermissionCode))
.Select(m => m.PermissionCode)
.Distinct()
.ToList();
var existingPermissions = await _permissionRepository.GetByCodesAsync(permissionCodes, cancellationToken);
var existingPermissionCodes = existingPermissions.Select(p => p.Code).ToHashSet();
var permissionIds = new List<string>();
var successCount = 0;
var failureCount = 0;
var permissionsToCreate = new List<Permission>();
foreach (var permissionDto in request.Permissions)
{
try
{
// 验证枚举参数
if (!Enum.IsDefined(typeof(PermissionType), permissionDto.Type))
// 从缓存中获取导航菜单信息
if (!navigationMenuDict.TryGetValue(permissionDto.NavigationMenuId, out var navigationMenu))
{
_logger.LogWarning("无效的权限类型: {Type}", permissionDto.Type);
_logger.LogWarning("导航菜单 {NavigationMenuId} 不存在,跳过创建权限 {PermissionName}",
permissionDto.NavigationMenuId, permissionDto.Name);
failureCount++;
continue;
}
if (!Enum.IsDefined(typeof(PermissionLevel), permissionDto.Level))
// 检查菜单是否已有权限代码
if (string.IsNullOrEmpty(navigationMenu.PermissionCode))
{
_logger.LogWarning("无效的权限级别: {Level}", permissionDto.Level);
_logger.LogWarning("导航菜单 {MenuTitle} 没有设置权限代码,跳过创建权限 {PermissionName}",
navigationMenu.Title, permissionDto.Name);
failureCount++;
continue;
}
if (permissionDto.ResourceType.HasValue && !Enum.IsDefined(typeof(ResourceType), permissionDto.ResourceType.Value))
// 从缓存中检查权限代码是否已存在
if (existingPermissionCodes.Contains(navigationMenu.PermissionCode))
{
_logger.LogWarning("无效的资源类型: {ResourceType}", permissionDto.ResourceType.Value);
_logger.LogWarning("权限代码 {PermissionCode} 已存在,跳过创建权限 {PermissionName}",
navigationMenu.PermissionCode, permissionDto.Name);
failureCount++;
continue;
}
if (permissionDto.ActionType.HasValue && !Enum.IsDefined(typeof(ActionType), permissionDto.ActionType.Value))
{
_logger.LogWarning("无效的操作类型: {ActionType}", permissionDto.ActionType.Value);
failureCount++;
continue;
}
// 检查权限是否已存在
var existingPermission = await _permissionRepository.GetByCodeAsync(permissionDto.Code, cancellationToken);
if (existingPermission != null)
{
_logger.LogWarning("权限代码 {PermissionCode} 已存在,跳过创建", permissionDto.Code);
failureCount++;
continue;
}
// 创建权限
// 创建权限对象(暂不保存到数据库)
var permission = Permission.Create(
permissionDto.Name,
permissionDto.Code,
navigationMenu.PermissionCode, // 使用NavigationMenu的权限代码
permissionDto.Description,
permissionDto.IsEnabled,
permissionDto.IsSystem);
var createdPermission = await _permissionRepository.AddAsync(permission, cancellationToken);
permissionIds.Add(createdPermission.Id);
permissionsToCreate.Add(permission);
successCount++;
_logger.LogInformation("权限 {PermissionName} 创建成功", permissionDto.Name);
_logger.LogInformation("权限 {PermissionName} (代码: {PermissionCode}) 准备创建,关联菜单: {MenuTitle}",
permissionDto.Name, navigationMenu.PermissionCode, navigationMenu.Title);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建权限 {PermissionName} 失败", permissionDto.Name);
_logger.LogError(ex, "准备创建权限 {PermissionName} 失败", permissionDto.Name);
failureCount++;
}
}
// 性能优化:批量保存所有权限到数据库
if (permissionsToCreate.Any())
{
foreach (var permission in permissionsToCreate)
{
var createdPermission = await _permissionRepository.AddAsync(permission, cancellationToken);
permissionIds.Add(createdPermission.Id);
}
// 一次性保存所有更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("批量保存 {PermissionCount} 个权限到数据库成功", permissionsToCreate.Count);
}
_logger.LogInformation("批量创建权限完成,成功 {SuccessCount} 个,失败 {FailureCount} 个", successCount, failureCount);
return OperationResult<BatchCreatePermissionsResponse>.CreateSuccess(

5
src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommand.cs

@ -13,6 +13,11 @@ public sealed record CreatePermissionCommand(
/// </summary>
string Name,
/// <summary>
/// 导航菜单ID,用于获取权限代码
/// </summary>
string NavigationMenuId,
/// <summary>
/// 权限描述
/// </summary>

37
src/X1.Application/Features/Permissions/Commands/CreatePermission/CreatePermissionCommandHandler.cs

@ -18,6 +18,7 @@ namespace X1.Application.Features.Permissions.Commands.CreatePermission;
public sealed class CreatePermissionCommandHandler : IRequestHandler<CreatePermissionCommand, OperationResult<CreatePermissionResponse>>
{
private readonly IPermissionRepository _permissionRepository;
private readonly INavigationMenuRepository _navigationMenuRepository;
private readonly ILogger<CreatePermissionCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
@ -26,10 +27,12 @@ public sealed class CreatePermissionCommandHandler : IRequestHandler<CreatePermi
/// </summary>
public CreatePermissionCommandHandler(
IPermissionRepository permissionRepository,
INavigationMenuRepository navigationMenuRepository,
ILogger<CreatePermissionCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_permissionRepository = permissionRepository;
_navigationMenuRepository = navigationMenuRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
@ -43,25 +46,49 @@ public sealed class CreatePermissionCommandHandler : IRequestHandler<CreatePermi
{
try
{
// 检查权限是否已存在
// 检查权限名称是否已存在
var existingPermission = await _permissionRepository.GetByNameAsync(request.Name, cancellationToken);
if (existingPermission != null)
{
_logger.LogWarning("权限 {PermissionName} 已存在", request.Name);
return OperationResult<CreatePermissionResponse>.CreateFailure("权限已存在");
return OperationResult<CreatePermissionResponse>.CreateFailure("权限名称已存在");
}
// 创建权限
// 通过NavigationMenu ID获取菜单信息
var navigationMenu = await _navigationMenuRepository.GetByIdAsync(request.NavigationMenuId, cancellationToken: cancellationToken);
if (navigationMenu == null)
{
_logger.LogWarning("导航菜单 {NavigationMenuId} 不存在", request.NavigationMenuId);
return OperationResult<CreatePermissionResponse>.CreateFailure("导航菜单不存在");
}
// 检查菜单是否已有权限代码
if (string.IsNullOrEmpty(navigationMenu.PermissionCode))
{
_logger.LogWarning("导航菜单 {MenuTitle} 没有设置权限代码", navigationMenu.Title);
return OperationResult<CreatePermissionResponse>.CreateFailure("导航菜单没有设置权限代码");
}
// 检查权限代码是否已存在
var existingPermissionByCode = await _permissionRepository.GetByCodeAsync(navigationMenu.PermissionCode, cancellationToken);
if (existingPermissionByCode != null)
{
_logger.LogWarning("权限代码 {PermissionCode} 已存在", navigationMenu.PermissionCode);
return OperationResult<CreatePermissionResponse>.CreateFailure("权限代码已存在");
}
// 创建权限,使用NavigationMenu的权限代码
var permission = Permission.Create(
request.Name,
request.Name.ToLower().Replace(" ", "."), // 生成权限代码
navigationMenu.PermissionCode, // 使用NavigationMenu的权限代码
request.Description);
var createdPermission = await _permissionRepository.AddAsync(permission, cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
_logger.LogInformation("权限 {PermissionName} 创建成功", request.Name);
_logger.LogInformation("权限 {PermissionName} (代码: {PermissionCode}) 创建成功,关联菜单: {MenuTitle}",
request.Name, navigationMenu.PermissionCode, navigationMenu.Title);
return OperationResult<CreatePermissionResponse>.CreateSuccess(
new CreatePermissionResponse(createdPermission.Id));

7
src/X1.Application/Features/Permissions/Commands/UpdatePermission/UpdatePermissionCommand.cs

@ -11,7 +11,6 @@ namespace X1.Application.Features.Permissions.Commands.UpdatePermission;
/// "id": "permission-id",
/// "name": "更新后的权限名称",
/// "description": "更新后的权限描述",
/// "level": 3,
/// "isEnabled": true
/// }
/// </example>
@ -34,12 +33,6 @@ public sealed record UpdatePermissionCommand(
/// <example>更新后的权限描述</example>
string? Description,
/// <summary>
/// 权限级别
/// </summary>
/// <example>3</example>
int? Level,
/// <summary>
/// 是否启用
/// </summary>

8
src/X1.Application/Features/Permissions/Commands/UpdatePermission/UpdatePermissionCommandHandler.cs

@ -4,7 +4,6 @@ using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using X1.Domain.Entities;
using X1.Domain.Entities.Enums;
using X1.Domain.Repositories.Identity;
using X1.Domain.Common;
@ -46,13 +45,6 @@ public sealed class UpdatePermissionCommandHandler : IRequestHandler<UpdatePermi
return OperationResult<UpdatePermissionResponse>.CreateFailure("权限不存在");
}
// 验证Level参数
if (request.Level.HasValue && !Enum.IsDefined(typeof(PermissionLevel), request.Level.Value))
{
_logger.LogWarning("无效的权限级别: {Level}", request.Level.Value);
return OperationResult<UpdatePermissionResponse>.CreateFailure("无效的权限级别");
}
// 更新权限信息
permission.Name = request.Name;
permission.Description = request.Description;

11
src/X1.Application/Features/Permissions/Queries/GetAllPermissionsQuery.cs

@ -13,10 +13,6 @@ public sealed record GetAllPermissionsQuery(
int PageNumber = 1,
int PageSize = 10,
string? Keyword = null,
int? Type = null,
int? ResourceType = null,
int? ActionType = null,
int? Level = null,
bool? IsEnabled = null,
bool? IsSystem = null
) : IRequest<OperationResult<GetAllPermissionsResponse>>;
@ -44,10 +40,5 @@ public record PermissionListItemDto(
string Name,
string Code,
string? Description,
int Type,
int Level,
int ResourceType,
int ActionType,
bool IsEnabled,
bool IsSystem,
int SortOrder);
bool IsSystem);

74
src/X1.Application/Features/Permissions/Queries/GetAllPermissionsQueryHandler.cs

@ -5,7 +5,6 @@ using System.Threading.Tasks;
using MediatR;
using Microsoft.Extensions.Logging;
using X1.Domain.Entities;
using X1.Domain.Entities.Enums;
using X1.Domain.Repositories.Identity;
using X1.Domain.Common;
using System.Collections.Generic;
@ -69,34 +68,6 @@ public sealed class GetAllPermissionsQueryHandler : IRequestHandler<GetAllPermis
);
}
// 类型筛选
if (request.Type.HasValue)
{
// 这里需要根据实际的Permission实体字段来筛选
// 暂时跳过类型筛选
}
// 资源类型筛选
if (request.ResourceType.HasValue)
{
// 这里需要根据实际的Permission实体字段来筛选
// 暂时跳过资源类型筛选
}
// 操作类型筛选
if (request.ActionType.HasValue)
{
// 这里需要根据实际的Permission实体字段来筛选
// 暂时跳过操作类型筛选
}
// 级别筛选
if (request.Level.HasValue)
{
// 这里需要根据实际的Permission实体字段来筛选
// 暂时跳过级别筛选
}
// 启用状态筛选
if (request.IsEnabled.HasValue)
{
@ -124,13 +95,8 @@ public sealed class GetAllPermissionsQueryHandler : IRequestHandler<GetAllPermis
p.Name,
p.Code,
p.Description,
1, // 默认Type为Resource
1, // 默认Level为View
GetResourceTypeValue(p.ExtractResourceType() ?? "unknown"),
1, // 默认ActionType为View
p.IsEnabled,
p.IsSystem,
0 // 默认SortOrder
p.IsSystem
)).ToList();
var response = new GetAllPermissionsResponse(
@ -152,42 +118,4 @@ public sealed class GetAllPermissionsQueryHandler : IRequestHandler<GetAllPermis
new List<string> { "获取权限列表失败" });
}
}
/// <summary>
/// 获取资源类型数值
/// </summary>
private static int GetResourceTypeValue(string resourceType)
{
return resourceType.ToLower() switch
{
"dashboard" => 1,
"users" => 2,
"roles" => 3,
"permissions" => 4,
"settings" => 5,
"scenarios" => 6,
"testcases" => 7,
"teststeps" => 8,
"tasks" => 9,
"taskreviews" => 10,
"taskexecutions" => 11,
"functionalanalysis" => 12,
"performanceanalysis" => 13,
"issueanalysis" => 14,
"ueanalysis" => 15,
"devices" => 16,
"protocols" => 17,
"ranconfigurations" => 18,
"imsconfigurations" => 19,
"corenetworkconfigs" => 20,
"networkstackconfigs" => 21,
"deviceruntimes" => 22,
"protocollogs" => 23,
"terminalservices" => 24,
"terminaldevices" => 25,
"adboperations" => 26,
"atoperations" => 27,
_ => 0
};
}
}

14
src/X1.Application/Features/Permissions/Queries/GetPermissionTree/GetPermissionTreeQueryHandler.cs

@ -54,7 +54,7 @@ public sealed class GetPermissionTreeQueryHandler : IRequestHandler<GetPermissio
// 按资源类型分组(从Code字段解析)
var permissionGroups = permissions
.Where(p => p.IsEnabled)
.GroupBy(p => p.ExtractResourceType() ?? "unknown")
.GroupBy(p => ExtractResourceTypeFromCode(p.Code) ?? "unknown")
.ToList();
var permissionTrees = new List<PermissionTreeDto>();
@ -172,4 +172,16 @@ public sealed class GetPermissionTreeQueryHandler : IRequestHandler<GetPermissio
_ => 0
};
}
/// <summary>
/// 从权限代码中提取资源类型
/// </summary>
private static string? ExtractResourceTypeFromCode(string code)
{
if (string.IsNullOrEmpty(code))
return null;
var parts = code.Split('.');
return parts.Length >= 1 ? parts[0] : null;
}
}

89
src/X1.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsCommandHandler.cs

@ -6,7 +6,9 @@ using System.Threading.Tasks;
using System.Threading;
using System;
using System.Linq;
using System.Collections.Generic;
using X1.Domain.Repositories.Identity;
using X1.Domain.Repositories.Base;
namespace X1.Application.Features.RolePermissions.Commands.AddRolePermissions;
@ -17,13 +19,16 @@ public class AddRolePermissionsCommandHandler : IRequestHandler<AddRolePermissio
{
private readonly IRolePermissionRepository _rolePermissionRepository;
private readonly ILogger<AddRolePermissionsCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
public AddRolePermissionsCommandHandler(
IRolePermissionRepository rolePermissionRepository,
ILogger<AddRolePermissionsCommandHandler> logger)
ILogger<AddRolePermissionsCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_rolePermissionRepository = rolePermissionRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult<AddRolePermissionsResponse>> Handle(
@ -32,34 +37,94 @@ public class AddRolePermissionsCommandHandler : IRequestHandler<AddRolePermissio
{
try
{
// 获取角色当前已有的权限
// 获取角色当前已有的权限(返回的是 Permission 实体)
var existingPermissions = await _rolePermissionRepository.GetPermissionsByRoleIdAsync(request.RoleId, cancellationToken);
var existingPermissionIds = existingPermissions.Select(p => p.Id).ToList();
// 获取现有的 RolePermission 实体(用于检测被删除的权限)
var existingRolePermissions = await _rolePermissionRepository.GetRolePermissionsWithDetailsAsync(request.RoleId, cancellationToken);
var existingRolePermissionIds = existingRolePermissions.Select(rp => rp.PermissionId).ToList();
// 过滤出需要新增的权限ID
var newPermissionIds = request.PermissionIds.Except(existingPermissionIds).ToList();
if (!newPermissionIds.Any())
// 检查已存在的权限,支持二次分配
var existingPermissionIdsToProcess = request.PermissionIds.Intersect(existingPermissionIds).ToList();
// 检测需要删除的权限(存在于 RolePermission 但不在当前请求中的权限)
var permissionsToRemove = existingRolePermissionIds.Except(request.PermissionIds).ToList();
if (!newPermissionIds.Any() && !existingPermissionIdsToProcess.Any() && !permissionsToRemove.Any())
{
return OperationResult<AddRolePermissionsResponse>.CreateFailure("没有权限需要处理");
}
var addedPermissionIds = new List<string>();
var processedExistingPermissionIds = new List<string>();
var removedPermissionIds = new List<string>();
// 删除不再需要的权限
if (permissionsToRemove.Any())
{
return OperationResult<AddRolePermissionsResponse>.CreateFailure("所有权限已存在,无需添加");
var removedCount = await _rolePermissionRepository.DeleteRolePermissionsAsync(
request.RoleId,
permissionsToRemove,
cancellationToken);
removedPermissionIds.AddRange(permissionsToRemove);
_logger.LogInformation(
"从角色 {RoleId} 删除了 {Count} 个不再需要的权限: {PermissionIds}",
request.RoleId,
removedCount,
string.Join(", ", permissionsToRemove));
}
// 添加新的角色权限
var addedRolePermissions = await _rolePermissionRepository.AddRolePermissionsAsync(
request.RoleId,
newPermissionIds,
cancellationToken);
if (newPermissionIds.Any())
{
var addedRolePermissions = await _rolePermissionRepository.AddRolePermissionsAsync(
request.RoleId,
newPermissionIds,
cancellationToken);
addedPermissionIds.AddRange(newPermissionIds);
_logger.LogInformation(
"为角色 {RoleId} 新增了 {Count} 个权限: {PermissionIds}",
request.RoleId,
newPermissionIds.Count,
string.Join(", ", newPermissionIds));
}
// 处理已存在的权限(二次分配场景)
if (existingPermissionIdsToProcess.Any())
{
// 直接添加到已处理列表,因为权限已经存在
processedExistingPermissionIds.AddRange(existingPermissionIdsToProcess);
_logger.LogInformation(
"为角色 {RoleId} 确认了 {Count} 个已存在的权限",
request.RoleId,
existingPermissionIdsToProcess.Count);
}
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
var response = new AddRolePermissionsResponse
{
AddedPermissionIds = newPermissionIds,
FailedPermissionIds = request.PermissionIds.Except(newPermissionIds)
AddedPermissionIds = addedPermissionIds,
FailedPermissionIds = request.PermissionIds.Except(addedPermissionIds).Except(processedExistingPermissionIds),
RemovedPermissionIds = removedPermissionIds
};
var totalProcessed = addedPermissionIds.Count + processedExistingPermissionIds.Count + removedPermissionIds.Count;
_logger.LogInformation(
"成功为角色 {RoleId} 添加 {Count} 个权限",
"成功为角色 {RoleId} 处理权限,新增:{NewCount},二次分配:{ExistingCount},删除:{RemovedCount},总计:{TotalCount}",
request.RoleId,
newPermissionIds.Count);
addedPermissionIds.Count,
processedExistingPermissionIds.Count,
removedPermissionIds.Count,
totalProcessed);
return OperationResult<AddRolePermissionsResponse>.CreateSuccess(response);
}

20
src/X1.Application/Features/RolePermissions/Commands/AddRolePermissions/AddRolePermissionsResponse.cs

@ -14,4 +14,24 @@ public class AddRolePermissionsResponse
/// 添加失败的权限ID列表
/// </summary>
public IEnumerable<string> FailedPermissionIds { get; set; } = new List<string>();
/// <summary>
/// 删除的权限ID列表(新增功能)
/// </summary>
public IEnumerable<string> RemovedPermissionIds { get; set; } = new List<string>();
/// <summary>
/// 新增权限数量
/// </summary>
public int AddedCount => AddedPermissionIds?.Count() ?? 0;
/// <summary>
/// 删除权限数量
/// </summary>
public int RemovedCount => RemovedPermissionIds?.Count() ?? 0;
/// <summary>
/// 失败权限数量
/// </summary>
public int FailedCount => FailedPermissionIds?.Count() ?? 0;
}

9
src/X1.Application/Features/RolePermissions/Commands/DeleteRolePermissions/DeleteRolePermissionsCommandHandler.cs

@ -7,6 +7,7 @@ using System.Threading;
using System.Linq;
using System;
using X1.Domain.Repositories.Identity;
using X1.Domain.Repositories.Base;
namespace X1.Application.Features.RolePermissions.Commands.DeleteRolePermissions;
@ -17,13 +18,16 @@ public class DeleteRolePermissionsCommandHandler : IRequestHandler<DeleteRolePer
{
private readonly IRolePermissionRepository _rolePermissionRepository;
private readonly ILogger<DeleteRolePermissionsCommandHandler> _logger;
private readonly IUnitOfWork _unitOfWork;
public DeleteRolePermissionsCommandHandler(
IRolePermissionRepository rolePermissionRepository,
ILogger<DeleteRolePermissionsCommandHandler> logger)
ILogger<DeleteRolePermissionsCommandHandler> logger,
IUnitOfWork unitOfWork)
{
_rolePermissionRepository = rolePermissionRepository;
_logger = logger;
_unitOfWork = unitOfWork;
}
public async Task<OperationResult<DeleteRolePermissionsResponse>> Handle(
@ -50,6 +54,9 @@ public class DeleteRolePermissionsCommandHandler : IRequestHandler<DeleteRolePer
validPermissionIds,
cancellationToken);
// 保存更改到数据库
await _unitOfWork.SaveChangesAsync(cancellationToken);
var response = new DeleteRolePermissionsResponse
{
DeletedCount = deletedCount,

12
src/X1.Application/Features/Users/Queries/Dtos/UserDto.cs

@ -4,22 +4,22 @@ namespace X1.Application.Features.Users.Queries.Dtos;
/// 用户数据传输对象
/// 用于在API层和领域层之间传输用户数据
/// </summary>
/// <param name="UserId">用户ID</param>
/// <param name="Id">用户ID</param>
/// <param name="UserName">用户名</param>
/// <param name="RealName">真实姓名</param>
/// <param name="Email">电子邮箱</param>
/// <param name="PhoneNumber">电话号码</param>
/// <param name="CreatedAt">创建时间</param>
/// <param name="IsActive">是否激活</param>
/// <param name="RoleIds">角色ID列表</param>
/// <param name="RoleNames">角色名称列表</param>
/// <param name="Roles">角色名称列表</param>
/// <param name="Permissions">权限代码列表</param>
public sealed record UserDto(
string UserId,
string Id,
string UserName,
string RealName,
string Email,
string PhoneNumber,
DateTime CreatedAt,
bool IsActive,
List<string> RoleIds,
List<string> RoleNames);
List<string> Roles,
List<string> Permissions);

32
src/X1.Application/Features/Users/Queries/GetAllUsers/GetAllUsersQueryHandler.cs

@ -23,6 +23,7 @@ public sealed class GetAllUsersQueryHandler : IRequestHandler<GetAllUsersQuery,
private readonly UserManager<AppUser> _userManager;
private readonly RoleManager<AppRole> _roleManager;
private readonly IUserRoleRepository _userRoleRepository;
private readonly IRolePermissionRepository _rolePermissionRepository;
private readonly ILogger<GetAllUsersQueryHandler> _logger;
/// <summary>
@ -36,11 +37,13 @@ public sealed class GetAllUsersQueryHandler : IRequestHandler<GetAllUsersQuery,
UserManager<AppUser> userManager,
RoleManager<AppRole> roleManager,
IUserRoleRepository userRoleRepository,
IRolePermissionRepository rolePermissionRepository,
ILogger<GetAllUsersQueryHandler> logger)
{
_userManager = userManager;
_roleManager = roleManager;
_userRoleRepository = userRoleRepository;
_rolePermissionRepository = rolePermissionRepository;
_logger = logger;
}
@ -123,6 +126,24 @@ public sealed class GetAllUsersQueryHandler : IRequestHandler<GetAllUsersQuery,
// 批量获取所有用户的角色信息(避免循环查询)
var allUserRoles = await _userRoleRepository.GetUsersRolesAsync(userIds, cancellationToken);
// 批量获取所有角色的权限信息(高度优化版本)
var allRolePermissions = new Dictionary<string, HashSet<string>>();
var allRoleIds = allUserRoles.Values.SelectMany(roleIds => roleIds).Distinct().ToList();
if (allRoleIds.Any())
{
var rolePermissions = await _rolePermissionRepository.GetRolePermissionsByRolesAsync(allRoleIds, cancellationToken);
// 使用LINQ一次性构建角色权限映射,避免重复循环
allRolePermissions = rolePermissions
.Where(rp => rp.Permission != null)
.GroupBy(rp => rp.RoleId)
.ToDictionary(
g => g.Key,
g => new HashSet<string>(g.Select(rp => rp.Permission!.Code))
);
}
var userDtos = new List<UserDto>();
foreach (var user in users)
@ -143,6 +164,13 @@ public sealed class GetAllUsersQueryHandler : IRequestHandler<GetAllUsersQuery,
}
}
// 获取用户的所有权限(使用LINQ优化,避免嵌套循环)
var permissions = userRoleIds
.Where(roleId => allRolePermissions.ContainsKey(roleId))
.SelectMany(roleId => allRolePermissions[roleId])
.Distinct()
.ToList();
var dto = new UserDto(
user.Id,
user.UserName,
@ -151,8 +179,8 @@ public sealed class GetAllUsersQueryHandler : IRequestHandler<GetAllUsersQuery,
user.PhoneNumber,
user.CreatedTime,
user.IsActive,
roleIds,
roleNames);
roleNames,
permissions);
userDtos.Add(dto);
}

11
src/X1.Application/Features/Users/Queries/GetCurrentUser/GetCurrentUserQuery.cs

@ -0,0 +1,11 @@
using X1.Domain.Common;
using MediatR;
using X1.Application.Features.Users.Queries.Dtos;
namespace X1.Application.Features.Users.Queries.GetCurrentUser;
/// <summary>
/// 获取当前用户查询
/// 用于获取当前登录用户详细信息的查询对象
/// </summary>
public sealed record GetCurrentUserQuery() : IRequest<OperationResult<UserDto>>;

128
src/X1.Application/Features/Users/Queries/GetCurrentUser/GetCurrentUserQueryHandler.cs

@ -0,0 +1,128 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging;
using MediatR;
using X1.Domain.Entities;
using X1.Domain.Repositories.Identity;
using X1.Domain.Common;
using X1.Application.Features.Users.Queries.Dtos;
using X1.Domain.Services;
namespace X1.Application.Features.Users.Queries.GetCurrentUser;
/// <summary>
/// 获取当前用户查询处理器
/// 处理获取当前登录用户详细信息的查询请求
/// </summary>
public sealed class GetCurrentUserQueryHandler : IRequestHandler<GetCurrentUserQuery, OperationResult<UserDto>>
{
private readonly UserManager<AppUser> _userManager;
private readonly RoleManager<AppRole> _roleManager;
private readonly IUserRoleRepository _userRoleRepository;
private readonly IRolePermissionRepository _rolePermissionRepository;
private readonly ICurrentUserService _currentUserService;
private readonly ILogger<GetCurrentUserQueryHandler> _logger;
/// <summary>
/// 初始化查询处理器
/// </summary>
public GetCurrentUserQueryHandler(
UserManager<AppUser> userManager,
RoleManager<AppRole> roleManager,
IUserRoleRepository userRoleRepository,
IRolePermissionRepository rolePermissionRepository,
ICurrentUserService currentUserService,
ILogger<GetCurrentUserQueryHandler> logger)
{
_userManager = userManager;
_roleManager = roleManager;
_userRoleRepository = userRoleRepository;
_rolePermissionRepository = rolePermissionRepository;
_currentUserService = currentUserService;
_logger = logger;
}
/// <summary>
/// 处理获取当前用户查询
/// </summary>
/// <param name="request">查询请求</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>操作结果,包含当前用户信息</returns>
public async Task<OperationResult<UserDto>> Handle(
GetCurrentUserQuery request,
CancellationToken cancellationToken)
{
try
{
// 获取当前用户ID
var currentUserId = _currentUserService.GetCurrentUserId();
if (string.IsNullOrEmpty(currentUserId))
{
_logger.LogWarning("无法获取当前用户ID");
return OperationResult<UserDto>.CreateFailure("无法获取当前用户信息");
}
// 根据ID查询用户
var user = await _userManager.FindByIdAsync(currentUserId);
if (user == null)
{
_logger.LogWarning("当前用户 {UserId} 不存在", currentUserId);
return OperationResult<UserDto>.CreateFailure("用户不存在");
}
// 获取用户的角色(使用自定义仓储)
var userRoleIds = await _userRoleRepository.GetUserRolesAsync(user.Id, cancellationToken);
// 获取角色ID和名称
var roleIds = new List<string>();
var roleNames = new List<string>();
foreach (var roleId in userRoleIds)
{
var role = await _roleManager.FindByIdAsync(roleId);
if (role != null && !string.IsNullOrEmpty(role.Name))
{
roleNames.Add(role.Name);
roleIds.Add(role.Id);
}
}
// 获取用户的所有权限
var permissions = new List<string>();
if (userRoleIds.Any())
{
var allRolePermissions = await _rolePermissionRepository.GetRolePermissionsByRolesAsync(userRoleIds, cancellationToken);
foreach (var rolePermission in allRolePermissions)
{
if (rolePermission.Permission != null && !permissions.Contains(rolePermission.Permission.Code))
{
permissions.Add(rolePermission.Permission.Code);
}
}
}
var dto = new UserDto(
user.Id,
user.UserName ?? string.Empty,
user.RealName ?? string.Empty,
user.Email ?? string.Empty,
user.PhoneNumber ?? string.Empty,
user.CreatedTime,
user.IsActive,
roleNames,
permissions);
_logger.LogInformation("获取当前用户 {UserId} 信息成功", currentUserId);
return OperationResult<UserDto>.CreateSuccess(dto);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取当前用户信息时发生异常");
return OperationResult<UserDto>.CreateFailure("系统错误,请稍后重试");
}
}
}

28
src/X1.Application/Features/Users/Queries/GetUserById/GetUserByIdQueryHandler.cs

@ -10,6 +10,7 @@ using System;
using System.Collections.Generic;
using System.Linq;
using X1.Domain.Repositories.Identity;
using X1.Domain.Repositories.Base;
namespace X1.Application.Features.Users.Queries.GetUserById;
@ -22,6 +23,7 @@ public sealed class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery,
private readonly UserManager<AppUser> _userManager;
private readonly RoleManager<AppRole> _roleManager;
private readonly IUserRoleRepository _userRoleRepository;
private readonly IRolePermissionRepository _rolePermissionRepository;
private readonly ILogger<GetUserByIdQueryHandler> _logger;
/// <summary>
@ -30,16 +32,19 @@ public sealed class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery,
/// <param name="userManager">用户管理器,用于管理用户身份</param>
/// <param name="roleManager">角色管理器,用于管理角色信息</param>
/// <param name="userRoleRepository">用户角色仓储</param>
/// <param name="rolePermissionRepository">角色权限仓储</param>
/// <param name="logger">日志记录器</param>
public GetUserByIdQueryHandler(
UserManager<AppUser> userManager,
RoleManager<AppRole> roleManager,
IUserRoleRepository userRoleRepository,
IRolePermissionRepository rolePermissionRepository,
ILogger<GetUserByIdQueryHandler> logger)
{
_userManager = userManager;
_roleManager = roleManager;
_userRoleRepository = userRoleRepository;
_rolePermissionRepository = rolePermissionRepository;
_logger = logger;
}
@ -62,6 +67,7 @@ public sealed class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery,
_logger.LogWarning("用户 {UserId} 不存在", request.UserId);
return OperationResult<GetUserByIdResponse>.CreateFailure("用户不存在");
}
// 获取用户的角色(使用自定义仓储)
var userRoleIds = await _userRoleRepository.GetUserRolesAsync(user.Id, cancellationToken);
@ -79,6 +85,20 @@ public sealed class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery,
}
}
// 获取用户的所有权限
var permissions = new List<string>();
if (userRoleIds.Any())
{
var allRolePermissions = await _rolePermissionRepository.GetRolePermissionsByRolesAsync(userRoleIds, cancellationToken);
foreach (var rolePermission in allRolePermissions)
{
if (rolePermission.Permission != null && !permissions.Contains(rolePermission.Permission.Code))
{
permissions.Add(rolePermission.Permission.Code);
}
}
}
var dto = new UserDto(
user.Id,
user.UserName,
@ -87,8 +107,8 @@ public sealed class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery,
user.PhoneNumber,
user.CreatedTime,
user.IsActive,
roleIds,
roleNames);
roleNames,
permissions);
// 构建响应对象
var response = new GetUserByIdResponse(dto);
@ -97,8 +117,8 @@ public sealed class GetUserByIdQueryHandler : IRequestHandler<GetUserByIdQuery,
}
catch (Exception ex)
{
_logger.LogError(ex, "查询用户时发生错误");
return OperationResult<GetUserByIdResponse>.CreateFailure("查询用户失败");
_logger.LogError(ex, "查询用户 {UserId} 时发生异常", request.UserId);
return OperationResult<GetUserByIdResponse>.CreateFailure("系统错误,请稍后重试");
}
}
}

26
src/X1.Domain/Entities/Permission.cs

@ -16,7 +16,7 @@ public class Permission : Entity
public string Name { get; set; } = string.Empty;
/// <summary>
/// 权限代码(例如: "users.create")
/// 权限代码(例如: "users.view")
/// </summary>
[Required]
[MaxLength(100)]
@ -38,30 +38,6 @@ public class Permission : Entity
/// </summary>
public bool IsSystem { get; set; } = false;
/// <summary>
/// 从权限代码提取资源类型
/// </summary>
public string? ExtractResourceType()
{
if (string.IsNullOrEmpty(Code))
return null;
var parts = Code.Split('.');
return parts.Length >= 1 ? parts[0] : null;
}
/// <summary>
/// 从权限代码提取操作类型
/// </summary>
public string? ExtractActionType()
{
if (string.IsNullOrEmpty(Code))
return null;
var parts = Code.Split('.');
return parts.Length >= 2 ? parts[1] : null;
}
#region 工厂方法
/// <summary>

5
src/X1.Domain/Repositories/Identity/INavigationMenuRepository.cs

@ -31,6 +31,11 @@ public interface INavigationMenuRepository : IBaseRepository<NavigationMenu>
/// </summary>
Task<NavigationMenu?> GetByPathAsync(string path, CancellationToken cancellationToken = default);
/// <summary>
/// 根据ID列表批量获取菜单
/// </summary>
Task<IEnumerable<NavigationMenu>> GetByIdsAsync(IEnumerable<string> ids, CancellationToken cancellationToken = default);
#endregion
#region 权限相关查询方法

8
src/X1.Domain/Repositories/Identity/IRolePermissionRepository.cs

@ -41,4 +41,12 @@ public interface IRolePermissionRepository : IBaseRepository <RolePermission>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>角色权限关联列表</returns>
Task<IEnumerable<RolePermission>> GetRolePermissionsWithDetailsAsync(string roleId, CancellationToken cancellationToken = default);
/// <summary>
/// 批量获取多个角色的权限(包含权限详情)
/// </summary>
/// <param name="roleIds">角色ID列表</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>角色权限关联列表</returns>
Task<IEnumerable<RolePermission>> GetRolePermissionsByRolesAsync(IEnumerable<string> roleIds, CancellationToken cancellationToken = default);
}

15
src/X1.Infrastructure/Repositories/Identity/NavigationMenuRepository.cs

@ -74,6 +74,21 @@ public class NavigationMenuRepository : BaseRepository<NavigationMenu>, INavigat
cancellationToken: cancellationToken);
}
/// <summary>
/// 根据ID列表批量获取菜单
/// </summary>
public async Task<IEnumerable<NavigationMenu>> GetByIdsAsync(IEnumerable<string> ids, CancellationToken cancellationToken = default)
{
if (ids == null || !ids.Any())
{
return Enumerable.Empty<NavigationMenu>();
}
return await QueryRepository.FindAsync(
m => ids.Contains(m.Id),
cancellationToken: cancellationToken);
}
#endregion
#region 权限相关查询方法

13
src/X1.Infrastructure/Repositories/Identity/RolePermissionRepository.cs

@ -8,6 +8,7 @@ using X1.Infrastructure.Repositories.Base;
using X1.Domain.Repositories.Base;
using X1.Domain.Repositories.Identity;
using System;
using Microsoft.EntityFrameworkCore;
namespace X1.Infrastructure.Repositories.Identity;
@ -89,6 +90,18 @@ public class RolePermissionRepository : BaseRepository<RolePermission>, IRolePer
{
return await QueryRepository.FindAsync(
rp => rp.RoleId == roleId,
include: query => query.Include(rp => rp.Permission),
cancellationToken: cancellationToken);
}
/// <summary>
/// 批量获取多个角色的权限(包含权限详情)
/// </summary>
public async Task<IEnumerable<RolePermission>> GetRolePermissionsByRolesAsync(IEnumerable<string> roleIds, CancellationToken cancellationToken = default)
{
return await QueryRepository.FindAsync(
rp => roleIds.Contains(rp.RoleId),
include: query => query.Include(rp => rp.Permission),
cancellationToken: cancellationToken);
}

18
src/X1.Presentation/Controllers/UsersController.cs

@ -5,6 +5,8 @@ using X1.Application.Features.Users.Commands.UpdateUser;
using X1.Application.Features.Users.Commands.DeleteUser;
using X1.Application.Features.Users.Queries.GetUserById;
using X1.Application.Features.Users.Queries.GetAllUsers;
using X1.Application.Features.Users.Queries.GetCurrentUser;
using X1.Application.Features.Users.Queries.Dtos;
using X1.Presentation.Abstractions;
using X1.Application.Common;
using Microsoft.Extensions.Logging;
@ -322,22 +324,14 @@ public class UsersController : ApiController
/// <response code="401">未授权,用户未登录</response>
/// <response code="404">用户不存在</response>
[HttpGet("current")]
[ProducesResponseType(typeof(OperationResult<GetUserByIdResponse>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(OperationResult<UserDto>), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(OperationResult<object>), StatusCodes.Status401Unauthorized)]
[ProducesResponseType(typeof(OperationResult<object>), StatusCodes.Status404NotFound)]
public async Task<OperationResult<GetUserByIdResponse>> CurrentUser()
public async Task<OperationResult<UserDto>> CurrentUser()
{
try
{
//var userId = User.FindFirst("sub")?.Value;
var userId = User.FindFirstValue(ClaimTypes.NameIdentifier);
if (string.IsNullOrEmpty(userId))
{
_logger.LogWarning("无法获取当前用户ID");
return OperationResult<GetUserByIdResponse>.CreateFailure("无法获取当前用户信息");
}
var query = new GetUserByIdQuery(userId);
var query = new GetCurrentUserQuery();
var result = await mediator.Send(query);
if (result.IsSuccess)
@ -355,7 +349,7 @@ public class UsersController : ApiController
catch (Exception ex)
{
_logger.LogError(ex, "获取当前用户信息时发生异常");
return OperationResult<GetUserByIdResponse>.CreateFailure("系统错误,请稍后重试");
return OperationResult<UserDto>.CreateFailure("系统错误,请稍后重试");
}
}
}

487
src/X1.WebUI/src/components/permissions/PermissionAssignmentDialog.tsx

@ -0,0 +1,487 @@
import { useState, useEffect } from 'react';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import { Search, Shield, ShieldCheck, ChevronDown, ChevronRight, FolderOpen, FileText } from 'lucide-react';
import { PermissionInfo, permissionService } from '@/services/permissionService';
import { Role } from '@/services/roleService';
import { rolePermissionService } from '@/services/rolePermissionService';
import { toast } from '@/components/ui/use-toast';
import { navigationMenuService } from '@/services/navigationMenuService';
import { NavigationMenuType } from '@/types/navigation';
interface PermissionAssignmentDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
role: Role | null;
onSuccess?: () => void;
}
// 菜单树节点接口
interface MenuTreeNode {
id: string;
title: string;
path: string;
icon?: string;
parentId?: string;
type: NavigationMenuType;
permissionCode?: string;
sortOrder: number;
isEnabled: boolean;
isSystem: boolean;
description?: string;
requiresPermission: boolean;
level: number;
isExpanded?: boolean;
isSelected?: boolean;
isChecked?: boolean;
hasPermission?: boolean;
children: MenuTreeNode[];
}
export default function PermissionAssignmentDialog({
open,
onOpenChange,
role,
onSuccess
}: PermissionAssignmentDialogProps) {
const [loading, setLoading] = useState(false);
const [searchTerm, setSearchTerm] = useState('');
const [menuTree, setMenuTree] = useState<MenuTreeNode[]>([]);
const [selectedPermissions, setSelectedPermissions] = useState<Set<string>>(new Set());
const [rolePermissions, setRolePermissions] = useState<Set<string>>(new Set());
const [allPermissions, setAllPermissions] = useState<PermissionInfo[]>([]);
// 获取菜单树和角色权限
useEffect(() => {
if (open) {
loadMenuTree();
loadAllPermissions();
if (role) {
loadRolePermissions();
}
}
}, [open, role]);
// 当角色权限加载完成后,重新构建菜单树以正确显示权限状态
useEffect(() => {
if (rolePermissions.size > 0 && menuTree.length > 0) {
const updatedTree = buildMenuTreeWithPermissions(menuTree);
setMenuTree(updatedTree);
}
}, [rolePermissions]);
// 加载所有权限数据(用于权限代码到ID的映射)
const loadAllPermissions = async () => {
try {
const response = await permissionService.getAllPermissions({ pageSize: 1000 });
if (response.isSuccess && response.data) {
setAllPermissions(response.data.permissions || []);
}
} catch (error) {
console.error('加载权限数据失败:', error);
}
};
// 加载菜单树数据
const loadMenuTree = async () => {
try {
setLoading(true);
const result = await navigationMenuService.getNavigationMenuTree();
if (result.isSuccess && result.data) {
const tree = buildMenuTree(result.data.navigationMenuTrees);
setMenuTree(tree);
} else {
// 如果树形接口失败,使用普通接口构建树
const fallbackResult = await navigationMenuService.getAllNavigationMenus({ pageSize: 1000 });
if (fallbackResult.isSuccess && fallbackResult.data) {
const tree = buildMenuTreeFromList(fallbackResult.data.navigationMenus);
setMenuTree(tree);
}
}
} catch (error) {
console.error('加载菜单树失败:', error);
toast({
title: '加载失败',
description: '无法加载菜单树数据',
variant: 'destructive'
});
} finally {
setLoading(false);
}
};
// 构建菜单树
const buildMenuTree = (menus: any[], level = 0): MenuTreeNode[] => {
return menus.map(menu => ({
...menu,
level,
isExpanded: level === 0, // 顶级菜单默认展开
isSelected: false,
isChecked: false,
hasPermission: false, // 初始时不检查权限,等权限加载完成后再更新
children: menu.children ? buildMenuTree(menu.children, level + 1) : []
}));
};
// 重新构建菜单树,更新权限状态
const buildMenuTreeWithPermissions = (menus: MenuTreeNode[], level = 0): MenuTreeNode[] => {
return menus.map(menu => ({
...menu,
level,
isExpanded: menu.isExpanded,
isSelected: false,
isChecked: false,
hasPermission: checkMenuHasPermission(menu),
children: menu.children ? buildMenuTreeWithPermissions(menu.children, level + 1) : []
}));
};
// 从列表构建菜单树
const buildMenuTreeFromList = (menus: any[]): MenuTreeNode[] => {
const menuMap = new Map<string, MenuTreeNode>();
const rootMenus: MenuTreeNode[] = [];
// 创建所有菜单节点
menus.forEach(menu => {
const node: MenuTreeNode = {
...menu,
level: 0,
isExpanded: false,
isSelected: false,
isChecked: false,
hasPermission: false, // 初始时不检查权限,等权限加载完成后再更新
children: []
};
menuMap.set(menu.id, node);
});
// 构建父子关系
menus.forEach(menu => {
const node = menuMap.get(menu.id)!;
if (menu.parentId && menuMap.has(menu.parentId)) {
const parent = menuMap.get(menu.parentId)!;
parent.children.push(node);
node.level = parent.level + 1;
} else {
rootMenus.push(node);
}
});
// 设置顶级菜单为展开状态
rootMenus.forEach(menu => {
menu.isExpanded = true;
});
return rootMenus;
};
// 检查菜单是否有权限
const checkMenuHasPermission = (menu: any): boolean => {
if (!menu.permissionCode) return false;
return rolePermissions.has(menu.permissionCode);
};
// 加载角色权限
const loadRolePermissions = async () => {
if (!role) return;
try {
setLoading(true);
const response = await rolePermissionService.getRolePermissions(role.id);
if (response.isSuccess && response.data) {
// 使用权限代码作为键,因为菜单树使用权限代码
const permissionCodes = new Set(response.data.permissions.map(p => p.code));
setRolePermissions(permissionCodes);
setSelectedPermissions(permissionCodes);
}
} catch (error) {
console.error('获取角色权限失败:', error);
toast({
title: '获取角色权限失败',
description: '无法获取角色当前权限,请重试',
variant: 'destructive'
});
} finally {
setLoading(false);
}
};
// 切换菜单展开状态
const toggleMenuExpansion = (menuId: string) => {
setMenuTree(prev => toggleMenuExpansionRecursive(prev, menuId));
};
const toggleMenuExpansionRecursive = (menus: MenuTreeNode[], menuId: string): MenuTreeNode[] => {
return menus.map(menu => {
if (menu.id === menuId) {
return { ...menu, isExpanded: !menu.isExpanded };
}
if (menu.children.length > 0) {
return {
...menu,
children: toggleMenuExpansionRecursive(menu.children, menuId)
};
}
return menu;
});
};
// 切换权限选择状态
const togglePermissionSelection = (permissionCode: string) => {
if (!permissionCode) return;
const newSelected = new Set(selectedPermissions);
if (newSelected.has(permissionCode)) {
newSelected.delete(permissionCode);
} else {
newSelected.add(permissionCode);
}
setSelectedPermissions(newSelected);
};
// 全选/取消全选
const handleSelectAll = () => {
const allPermissionCodes = getAllPermissionCodes(menuTree);
setSelectedPermissions(new Set(allPermissionCodes));
};
const handleDeselectAll = () => {
setSelectedPermissions(new Set());
};
// 获取所有权限代码
const getAllPermissionCodes = (menus: MenuTreeNode[]): string[] => {
const codes: string[] = [];
menus.forEach(menu => {
if (menu.permissionCode) {
codes.push(menu.permissionCode);
}
if (menu.children.length > 0) {
codes.push(...getAllPermissionCodes(menu.children));
}
});
return codes;
};
// 将权限代码转换为权限ID
const getPermissionIdsByCodes = (permissionCodes: string[]): string[] => {
return permissionCodes
.map(code => allPermissions.find(p => p.code === code)?.id)
.filter(id => id !== undefined) as string[];
};
// 保存权限分配
const handleSave = async () => {
if (!role) return;
try {
setLoading(true);
// 将权限代码转换为权限ID
const selectedPermissionIds = getPermissionIdsByCodes(Array.from(selectedPermissions));
// 使用新的后端逻辑,在一个请求中处理所有权限变更
const result = await rolePermissionService.batchAddPermissions(role.id, selectedPermissionIds);
if (!result.isSuccess) {
throw new Error(`权限分配失败: ${result.errorMessages?.join(', ')}`);
}
// 显示详细的操作结果
const { addedCount, removedCount } = result.data!;
let description = `已成功为角色 "${role.name}" 分配权限`;
if (addedCount > 0 || removedCount > 0) {
const operations = [];
if (addedCount > 0) operations.push(`新增 ${addedCount} 个权限`);
if (removedCount > 0) operations.push(`删除 ${removedCount} 个权限`);
description = `已成功为角色 "${role.name}" 处理权限:${operations.join(',')}`;
}
toast({
title: '权限分配成功',
description,
});
onSuccess?.();
onOpenChange(false);
} catch (error) {
console.error('保存权限失败:', error);
toast({
title: '保存权限失败',
description: error instanceof Error ? error.message : '无法保存权限分配,请重试',
variant: 'destructive'
});
} finally {
setLoading(false);
}
};
// 过滤菜单树
const filteredMenuTree = menuTree.filter(menu => {
if (!searchTerm) return true;
return (
menu.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
(menu.description && menu.description.toLowerCase().includes(searchTerm.toLowerCase())) ||
(menu.permissionCode && menu.permissionCode.toLowerCase().includes(searchTerm.toLowerCase()))
);
});
// 渲染菜单树节点
const renderMenuNode = (menu: MenuTreeNode) => {
const hasChildren = menu.children && menu.children.length > 0;
const canExpand = hasChildren;
const isSelected = menu.permissionCode ? selectedPermissions.has(menu.permissionCode) : false;
const wasAssigned = menu.permissionCode ? rolePermissions.has(menu.permissionCode) : false;
return (
<div key={menu.id} className="w-full min-w-0">
<div
className={`
flex items-center gap-2 p-3 rounded-lg cursor-pointer transition-colors min-w-0 overflow-hidden
${isSelected ? 'bg-primary/10 border border-primary/20' : 'hover:bg-muted/50'}
${menu.level > 0 ? 'ml-4' : ''}
`}
>
{/* 权限选择复选框 */}
{menu.permissionCode && (
<Checkbox
checked={isSelected}
onCheckedChange={() => togglePermissionSelection(menu.permissionCode!)}
className="flex-shrink-0"
/>
)}
{/* 展开/折叠图标 */}
{canExpand && (
<button
onClick={(e) => {
e.stopPropagation();
toggleMenuExpansion(menu.id);
}}
className="p-1 hover:bg-muted rounded flex-shrink-0"
>
{menu.isExpanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
</button>
)}
{/* 菜单类型图标 */}
<div className="flex items-center gap-2 flex-shrink-0">
{menu.type === NavigationMenuType.StandaloneMenuItem && (
<FileText className="h-4 w-4 text-blue-500" />
)}
{menu.type === NavigationMenuType.MenuGroup && (
<FolderOpen className="h-4 w-4 text-green-500" />
)}
{menu.type === NavigationMenuType.SubMenuItem && (
<FileText className="h-4 w-4 text-gray-500" />
)}
</div>
{/* 菜单标题 */}
<span className="flex-1 text-sm font-medium truncate min-w-0 overflow-hidden">
{menu.title}
</span>
{/* 权限状态 */}
{menu.permissionCode && (
<div className="flex-shrink-0 ml-2">
{wasAssigned && (
<ShieldCheck className="h-4 w-4 text-green-600" />
)}
<Badge variant="secondary" className="text-xs whitespace-nowrap ml-2">
{menu.permissionCode}
</Badge>
</div>
)}
</div>
{/* 子菜单 */}
{canExpand && menu.isExpanded && (
<div className="ml-4 min-w-0 overflow-hidden">
{menu.children.map(child => renderMenuNode(child))}
</div>
)}
</div>
);
};
if (!role) return null;
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-w-4xl max-h-[80vh]">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" />
"{role.name}"
</DialogTitle>
</DialogHeader>
<div className="space-y-4">
{/* 搜索框 */}
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
<Input
placeholder="搜索菜单或权限..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-10"
/>
</div>
{/* 批量操作按钮 */}
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={handleSelectAll}>
</Button>
<Button variant="outline" size="sm" onClick={handleDeselectAll}>
</Button>
<Badge variant="secondary" className="ml-auto">
{selectedPermissions.size}
</Badge>
</div>
{/* 菜单树 */}
<ScrollArea className="h-[400px] border rounded-md p-4">
{loading ? (
<div className="text-center text-muted-foreground py-8">
...
</div>
) : filteredMenuTree.length === 0 ? (
<div className="text-center text-muted-foreground py-8">
</div>
) : (
<div className="space-y-2">
{filteredMenuTree.map(menu => renderMenuNode(menu))}
</div>
)}
</ScrollArea>
{/* 操作按钮 */}
<div className="flex justify-end gap-2 pt-4 border-t">
<Button variant="outline" onClick={() => onOpenChange(false)}>
</Button>
<Button
onClick={handleSave}
disabled={loading}
className="min-w-[100px]"
>
{loading ? '保存中...' : '保存'}
</Button>
</div>
</div>
</DialogContent>
</Dialog>
);
}

189
src/X1.WebUI/src/components/permissions/README.md

@ -0,0 +1,189 @@
# 权限分配组件使用说明
## 概述
`PermissionAssignmentDialog` 是一个用于角色权限分配的对话框组件,支持按资源分组显示权限、搜索过滤、批量选择等功能。
## 功能特性
### 1. 权限分组显示
- 按资源类型自动分组(用户管理、角色管理、设备管理等)
- 每个资源组显示相关的操作权限
- 支持展开/折叠资源组
### 2. 搜索和过滤
- 支持按权限名称、描述、资源类型搜索
- 实时过滤显示结果
- 支持中文和英文搜索
### 3. 批量操作
- 全选/取消全选所有权限
- 按资源组选择权限
- 显示已选择权限数量
### 4. 权限状态显示
- 绿色勾选图标:角色当前已分配的权限
- 复选框:可选择/取消选择的权限
- 权限代码和描述信息
### 5. 智能权限管理
- 自动计算需要添加/删除的权限
- 批量添加新权限
- 批量删除取消的权限
- 避免重复操作
## 使用方法
### 1. 基本使用
```tsx
import PermissionAssignmentDialog from '@/components/permissions/PermissionAssignmentDialog';
function RoleManagement() {
const [dialogOpen, setDialogOpen] = useState(false);
const [selectedRole, setSelectedRole] = useState<Role | null>(null);
const handleSetPermissions = (role: Role) => {
setSelectedRole(role);
setDialogOpen(true);
};
return (
<div>
{/* 角色表格 */}
<RoleTable onSetPermissions={handleSetPermissions} />
{/* 权限分配对话框 */}
<PermissionAssignmentDialog
open={dialogOpen}
onOpenChange={setDialogOpen}
role={selectedRole}
onSuccess={() => {
console.log('权限分配成功');
// 刷新角色列表或其他操作
}}
/>
</div>
);
}
```
### 2. 在 RoleTable 中集成
```tsx
// RoleTable.tsx
export default function RoleTable({ onSetPermissions, ...props }) {
const [permissionDialogOpen, setPermissionDialogOpen] = useState(false);
const [selectedRole, setSelectedRole] = useState<Role | null>(null);
const handleSetPermissions = (role: Role) => {
setSelectedRole(role);
setPermissionDialogOpen(true);
};
return (
<div>
{/* 表格内容 */}
<Table>
{/* ... 表格行 ... */}
<span onClick={() => handleSetPermissions(role)}>
设置权限
</span>
</Table>
{/* 权限分配对话框 */}
<PermissionAssignmentDialog
open={permissionDialogOpen}
onOpenChange={setPermissionDialogOpen}
role={selectedRole}
onSuccess={() => {
// 权限分配成功后的回调
}}
/>
</div>
);
}
```
## 权限数据结构
### Permission 接口
```typescript
interface Permission {
id: string;
name: string; // 权限名称
code: string; // 权限代码 (如: "users.view")
description?: string; // 权限描述
}
```
### 权限代码格式
权限代码采用 `resource.action` 格式:
- **资源类型**: users, roles, permissions, devices, scenarios 等
- **操作类型**: view, create, edit, delete, manage, export, import 等
示例:
- `users.view` - 查看用户
- `users.create` - 创建用户
- `users.edit` - 编辑用户
- `users.delete` - 删除用户
- `users.manage` - 用户管理(包含所有操作)
## 样式定制
### 主题颜色
- 主色调:使用 CSS 变量 `--primary`
- 边框颜色:使用 CSS 变量 `--border`
- 背景颜色:使用 CSS 变量 `--background`
### 响应式设计
- 支持不同屏幕尺寸
- 移动端友好的触摸操作
- 自适应内容高度
## 注意事项
### 1. 依赖要求
- 需要安装 `@radix-ui/react-scroll-area`
- 需要配置 Tailwind CSS
- 需要权限服务和角色权限服务
### 2. 性能考虑
- 使用 Set 数据结构进行权限比较
- 支持大量权限数据的显示
- 延迟加载和分页支持
### 3. 错误处理
- 网络请求失败提示
- 权限保存失败处理
- 用户友好的错误信息
### 4. 安全考虑
- 权限验证和检查
- 防止权限提升攻击
- 操作审计日志
## 扩展功能
### 1. 权限继承
- 支持角色权限继承
- 权限继承规则配置
- 继承权限的显示和管理
### 2. 权限模板
- 预定义权限模板
- 快速权限分配
- 模板导入导出
### 3. 权限审计
- 权限变更历史
- 操作日志记录
- 权限使用统计
### 4. 批量操作
- 批量角色权限分配
- 权限复制功能
- 权限迁移工具

46
src/X1.WebUI/src/components/ui/scroll-area.tsx

@ -0,0 +1,46 @@
import * as React from "react"
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
import { cn } from "@/lib/utils"
const ScrollArea = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<ScrollAreaPrimitive.Root
ref={ref}
className={cn("relative overflow-hidden", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]">
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
))
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
const ScrollBar = React.forwardRef<
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
>(({ className, orientation = "vertical", ...props }, ref) => (
<ScrollAreaPrimitive.ScrollAreaScrollbar
ref={ref}
orientation={orientation}
className={cn(
"flex touch-none select-none transition-colors",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent p-[1px]",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent p-[1px]",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb className="relative flex-1 rounded-full bg-border" />
</ScrollAreaPrimitive.ScrollAreaScrollbar>
))
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
export { ScrollArea, ScrollBar }

7
src/X1.WebUI/src/constants/auth.ts

@ -49,4 +49,9 @@ export const AUTH_CONSTANTS = {
export const DEFAULT_CREDENTIALS = {
username: 'hyh',
password: 'H295172551@qq.com'
};
};
// export const DEFAULT_CREDENTIALS = {
// username: '',
// password: ''
// };

100
src/X1.WebUI/src/constants/defaultPermissions.ts

@ -0,0 +1,100 @@
/**
*
* 使使
*/
export const DEFAULT_PERMISSIONS = [
'dashboard.view',
'users.view',
'roles.view',
'permissions.view',
'permissions.manage',
'settings.view',
'settings.manage',
'navigationmenus.view',
'navigationmenus.manage',
'buttonpermissions.view',
'buttonpermissions.manage',
// 场景管理权限
'scenarios.view',
'scenarios.manage',
'testcases.view',
'testcases.manage',
'testcases.create',
'teststeps.view',
'teststeps.manage',
'teststeps.create',
'tasks.view',
'tasks.manage',
'tasks.create',
'taskreviews.view',
'taskreviews.manage',
'taskreviews.create',
'taskexecutions.view',
'taskexecutions.manage',
'taskexecutions.create',
'functionalanalysis.view',
'functionalanalysis.manage',
'performanceanalysis.view',
'performanceanalysis.manage',
'issueanalysis.view',
'issueanalysis.manage',
'ueanalysis.view',
'ueanalysis.manage',
// 设备管理权限
'devices.view',
'devices.manage',
'protocols.view',
'protocols.manage',
'ranconfigurations.view',
'ranconfigurations.manage',
'imsconfigurations.view',
'imsconfigurations.manage',
'corenetworkconfigs.view',
'corenetworkconfigs.manage',
'networkstackconfigs.view',
'networkstackconfigs.manage',
// 设备运行时管理权限
'deviceruntimes.view',
'deviceruntimes.manage',
// 协议日志管理权限
'protocollogs.view',
'protocollogs.manage',
// 终端设备管理权限
'terminalservices.view',
'terminalservices.manage',
// ADB操作管理权限
'adboperations.view',
'adboperations.manage',
// AT操作管理权限
'atoperations.view',
'atoperations.manage',
// 终端设备管理权限
'terminaldevices.view',
'terminaldevices.manage',
] as const;
/**
*
* @returns
*/
export const getDefaultPermissions = (): string[] => {
return [...DEFAULT_PERMISSIONS];
};
/**
*
* @param permission
* @returns
*/
export const isDefaultPermission = (permission: string): boolean => {
return DEFAULT_PERMISSIONS.includes(permission as any);
};

345
src/X1.WebUI/src/constants/menuConfig.backup.ts

@ -0,0 +1,345 @@
import { LucideIcon, LayoutDashboard, Users, Settings, TestTube, BarChart3, Gauge, ClipboardList, Network, Smartphone, FolderOpen, Activity } from 'lucide-react';
// 定义权限类型
export type Permission =
| 'dashboard.view'
| 'users.view'
| 'users.manage'
| 'roles.view'
| 'roles.manage'
| 'permissions.view'
| 'permissions.manage'
| 'settings.view'
| 'settings.manage'
| 'navigationmenus.view'
| 'navigationmenus.manage'
// 场景管理权限
| 'scenarios.view'
| 'scenarios.manage'
// 用例管理权限
| 'testcases.view'
| 'testcases.manage'
| 'testcases.create'
| 'teststeps.view'
| 'teststeps.manage'
| 'teststeps.create'
// 任务管理权限
| 'tasks.view'
| 'tasks.manage'
| 'tasks.create'
| 'taskreviews.view'
| 'taskreviews.manage'
| 'taskreviews.create'
| 'taskexecutions.view'
| 'taskexecutions.manage'
| 'taskexecutions.create'
// 结果分析权限
| 'functionalanalysis.view'
| 'functionalanalysis.manage'
| 'performanceanalysis.view'
| 'performanceanalysis.manage'
| 'issueanalysis.view'
| 'issueanalysis.manage'
| 'ueanalysis.view'
| 'ueanalysis.manage'
// 仪表管理权限
| 'devices.view'
| 'devices.manage'
| 'protocols.view'
| 'protocols.manage'
| 'ranconfigurations.view'
| 'ranconfigurations.manage'
| 'imsconfigurations.view'
| 'imsconfigurations.manage'
| 'corenetworkconfigs.view'
| 'corenetworkconfigs.manage'
| 'networkstackconfigs.view'
| 'networkstackconfigs.manage'
// 终端服务管理权限
| 'terminalservices.view'
| 'terminalservices.manage'
// 终端设备管理权限
| 'terminaldevices.view'
| 'terminaldevices.manage'
// ADB操作管理权限
| 'adboperations.view'
| 'adboperations.manage'
// AT操作管理权限
| 'atoperations.view'
| 'atoperations.manage'
// 设备运行时管理权限
| 'deviceruntimes.view'
| 'deviceruntimes.manage'
// 协议日志管理权限
| 'protocollogs.view'
| 'protocollogs.manage'
// 按钮权限管理权限
| 'buttonpermissions.view'
| 'buttonpermissions.manage';
export interface MenuItem {
title: string;
icon: LucideIcon;
href: string;
permission?: Permission;
children?: {
title: string;
href: string;
permission?: Permission;
}[];
}
export const menuItems: MenuItem[] = [
{
title: '仪表盘',
icon: LayoutDashboard,
href: '/dashboard',
permission: 'dashboard.view',
},
{
title: '场景管理',
icon: FolderOpen,
href: '/dashboard/scenarios',
permission: 'scenarios.view',
children: [
{
title: '场景列表',
href: '/dashboard/scenarios/config',
permission: 'scenarios.manage',
},
{
title: '场景绑定',
href: '/dashboard/scenarios/binding',
permission: 'scenarios.manage',
},
],
},
{
title: '用例管理',
icon: TestTube,
href: '/dashboard/testcases',
permission: 'testcases.view',
children: [
{
title: '用例列表',
href: '/dashboard/testcases/list',
permission: 'testcases.view',
},
{
title: '创建用例',
href: '/dashboard/testcases/create',
permission: 'testcases.create',
},
{
title: '步骤列表',
href: '/dashboard/testcases/steps',
permission: 'teststeps.view',
},
],
},
{
title: '任务管理',
icon: ClipboardList,
href: '/dashboard/tasks',
permission: 'tasks.view',
children: [
{
title: '任务列表',
href: '/dashboard/tasks/list',
permission: 'tasks.view',
},
{
title: '创建任务',
href: '/dashboard/tasks/create',
permission: 'tasks.create',
},
{
title: '审核任务',
href: '/dashboard/tasks/reviews',
permission: 'taskreviews.view',
},
{
title: '执行任务',
href: '/dashboard/tasks/executions',
permission: 'taskexecutions.view',
},
],
},
{
title: '结果分析',
icon: BarChart3,
href: '/dashboard/analysis',
permission: 'functionalanalysis.view',
children: [
{
title: '功能分析',
href: '/dashboard/analysis/functional',
permission: 'functionalanalysis.view',
},
{
title: '性能分析',
href: '/dashboard/analysis/performance',
permission: 'performanceanalysis.view',
},
{
title: '问题分析',
href: '/dashboard/analysis/issue',
permission: 'issueanalysis.view',
},
{
title: 'UE分析',
href: '/dashboard/analysis/ue',
permission: 'ueanalysis.view',
},
],
},
{
title: '仪表管理',
icon: Gauge,
href: '/dashboard/instruments',
permission: 'devices.view',
children: [
{
title: '设备列表',
href: '/dashboard/instruments/list',
permission: 'devices.view',
},
{
title: '协议列表',
href: '/dashboard/instruments/protocols',
permission: 'protocols.view',
},
{
title: '启动设备网络',
href: '/dashboard/instruments/device-runtimes/list',
permission: 'deviceruntimes.view',
},
],
},
{
title: '终端管理',
icon: Smartphone,
href: '/dashboard/terminal-services',
permission: 'terminalservices.view',
children: [
{
title: '终端服务',
href: '/dashboard/terminal-services',
permission: 'terminalservices.view',
},
{
title: '终端设备',
href: '/dashboard/terminal-devices/list',
permission: 'terminaldevices.view',
},
{
title: 'ADB命令配置',
href: '/dashboard/terminal-services/adb-operations',
permission: 'adboperations.view',
},
{
title: 'AT命令配置',
href: '/dashboard/terminal-services/at-operations',
permission: 'atoperations.view',
},
],
},
{
title: '信令分析',
icon: Activity,
href: '/dashboard/protocol-logs',
permission: 'protocollogs.view',
children: [
{
title: '在线协议日志',
href: '/dashboard/protocol-logs/online-logs',
permission: 'protocollogs.view',
},
{
title: '历史协议日志',
href: '/dashboard/protocol-logs/history-logs',
permission: 'protocollogs.view',
},
],
},
{
title: '网络栈配置',
icon: Network,
href: '/dashboard/network-stack-configs',
permission: 'ranconfigurations.view',
children: [
{
title: 'RAN配置',
href: '/dashboard/network-stack-configs/ran-configurations',
permission: 'ranconfigurations.view',
},
{
title: 'IMS配置',
href: '/dashboard/network-stack-configs/ims-configurations',
permission: 'imsconfigurations.view',
},
{
title: '核心网络配置',
href: '/dashboard/network-stack-configs/core-network-configs',
permission: 'corenetworkconfigs.view',
},
{
title: '网络栈配置',
href: '/dashboard/network-stack-configs/network-stack-configs',
permission: 'networkstackconfigs.view',
},
],
},
{
title: '用户管理',
icon: Users,
href: '/dashboard/users',
permission: 'users.view',
children: [
{
title: '用户列表',
href: '/dashboard/users/list',
permission: 'users.view',
},
{
title: '角色管理',
href: '/dashboard/users/roles',
permission: 'roles.view',
},
],
},
{
title: '系统设置',
icon: Settings,
href: '/dashboard/settings',
permission: 'settings.view',
children: [
{
title: '导航菜单管理',
href: '/dashboard/settings/navigation-menus',
permission: 'navigationmenus.view',
},
{
title: '权限管理',
href: '/dashboard/settings/permissions',
permission: 'permissions.view',
},
{
title: '按钮权限管理',
href: '/dashboard/settings/button-permissions',
permission: 'buttonpermissions.view',
},
],
},
];
// 导出权限检查工具函数
export const hasPermission = (userPermissions: Permission[] | undefined | null, requiredPermission?: Permission): boolean => {
// 如果没有设置权限要求,则默认允许访问
if (!requiredPermission) return true;
// 如果用户权限为空,则拒绝访问
if (!userPermissions || !Array.isArray(userPermissions)) return false;
return userPermissions.includes(requiredPermission);
};

499
src/X1.WebUI/src/constants/menuConfig.ts

@ -1,346 +1,179 @@
import { LucideIcon, LayoutDashboard, Users, Settings, TestTube, BarChart3, Gauge, FileText, ClipboardList, Network, Smartphone, FolderOpen, Activity } from 'lucide-react';
import { LucideIcon } from 'lucide-react';
import { navigationMenuService } from '@/services/navigationMenuService';
import { NavigationMenuInfo, NavigationMenuType } from '@/types/navigation';
import { resolveIcon } from '@/utils/iconUtils';
// 定义权限类型
export type Permission =
| 'dashboard.view'
| 'users.view'
| 'users.manage'
| 'roles.view'
| 'roles.manage'
| 'permissions.view'
| 'permissions.manage'
| 'settings.view'
| 'settings.manage'
| 'navigationmenus.view'
| 'navigationmenus.manage'
// 场景管理权限
| 'scenarios.view'
| 'scenarios.manage'
// 用例管理权限
| 'testcases.view'
| 'testcases.manage'
| 'testcases.create'
| 'teststeps.view'
| 'teststeps.manage'
| 'teststeps.create'
// 任务管理权限
| 'tasks.view'
| 'tasks.manage'
| 'tasks.create'
| 'taskreviews.view'
| 'taskreviews.manage'
| 'taskreviews.create'
| 'taskexecutions.view'
| 'taskexecutions.manage'
| 'taskexecutions.create'
// 结果分析权限
| 'functionalanalysis.view'
| 'functionalanalysis.manage'
| 'performanceanalysis.view'
| 'performanceanalysis.manage'
| 'issueanalysis.view'
| 'issueanalysis.manage'
| 'ueanalysis.view'
| 'ueanalysis.manage'
// 仪表管理权限
| 'devices.view'
| 'devices.manage'
| 'protocols.view'
| 'protocols.manage'
| 'ranconfigurations.view'
| 'ranconfigurations.manage'
| 'imsconfigurations.view'
| 'imsconfigurations.manage'
| 'corenetworkconfigs.view'
| 'corenetworkconfigs.manage'
| 'networkstackconfigs.view'
| 'networkstackconfigs.manage'
// 终端服务管理权限
| 'terminalservices.view'
| 'terminalservices.manage'
// 终端设备管理权限
| 'terminaldevices.view'
| 'terminaldevices.manage'
// ADB操作管理权限
| 'adboperations.view'
| 'adboperations.manage'
// AT操作管理权限
| 'atoperations.view'
| 'atoperations.manage'
// 设备运行时管理权限
| 'deviceruntimes.view'
| 'deviceruntimes.manage'
// 协议日志管理权限
| 'protocollogs.view'
| 'protocollogs.manage'
// 按钮权限管理权限
| 'buttonpermissions.view'
| 'buttonpermissions.manage'
// 重新导出权限相关类型和函数
export type {
Permission,
PermissionAction,
PermissionResource
} from '@/services/permissionService';
export {
hasAnyPermission,
hasAllPermissions,
isValidPermission as isValidPermissionCode
} from '@/services/permissionService';
// 导入权限类型
import type { Permission } from '@/services/permissionService';
// 菜单项接口定义
export interface MenuItem {
id: string;
title: string;
icon: LucideIcon;
href: string;
permission?: Permission;
children?: {
title: string;
href: string;
permission?: Permission;
}[];
type: NavigationMenuType;
sortOrder: number;
isEnabled: boolean;
isSystem: boolean;
description?: string;
children?: MenuItem[];
}
export const menuItems: MenuItem[] = [
{
title: '仪表盘',
icon: LayoutDashboard,
href: '/dashboard',
permission: 'dashboard.view',
},
{
title: '场景管理',
icon: FolderOpen,
href: '/dashboard/scenarios',
permission: 'scenarios.view',
children: [
{
title: '场景列表',
href: '/dashboard/scenarios/config',
permission: 'scenarios.manage',
},
{
title: '场景绑定',
href: '/dashboard/scenarios/binding',
permission: 'scenarios.manage',
},
],
},
{
title: '用例管理',
icon: TestTube,
href: '/dashboard/testcases',
permission: 'testcases.view',
children: [
{
title: '用例列表',
href: '/dashboard/testcases/list',
permission: 'testcases.view',
},
{
title: '创建用例',
href: '/dashboard/testcases/create',
permission: 'testcases.create',
},
{
title: '步骤列表',
href: '/dashboard/testcases/steps',
permission: 'teststeps.view',
},
],
},
{
title: '任务管理',
icon: ClipboardList,
href: '/dashboard/tasks',
permission: 'tasks.view',
children: [
{
title: '任务列表',
href: '/dashboard/tasks/list',
permission: 'tasks.view',
},
{
title: '创建任务',
href: '/dashboard/tasks/create',
permission: 'tasks.create',
},
{
title: '审核任务',
href: '/dashboard/tasks/reviews',
permission: 'taskreviews.view',
},
{
title: '执行任务',
href: '/dashboard/tasks/executions',
permission: 'taskexecutions.view',
},
],
},
{
title: '结果分析',
icon: BarChart3,
href: '/dashboard/analysis',
permission: 'functionalanalysis.view',
children: [
{
title: '功能分析',
href: '/dashboard/analysis/functional',
permission: 'functionalanalysis.view',
},
{
title: '性能分析',
href: '/dashboard/analysis/performance',
permission: 'performanceanalysis.view',
},
{
title: '问题分析',
href: '/dashboard/analysis/issue',
permission: 'issueanalysis.view',
},
{
title: 'UE分析',
href: '/dashboard/analysis/ue',
permission: 'ueanalysis.view',
},
],
},
{
title: '仪表管理',
icon: Gauge,
href: '/dashboard/instruments',
permission: 'devices.view',
children: [
{
title: '设备列表',
href: '/dashboard/instruments/list',
permission: 'devices.view',
},
{
title: '协议列表',
href: '/dashboard/instruments/protocols',
permission: 'protocols.view',
},
{
title: '启动设备网络',
href: '/dashboard/instruments/device-runtimes/list',
permission: 'deviceruntimes.view',
},
],
},
{
title: '终端管理',
icon: Smartphone,
href: '/dashboard/terminal-services',
permission: 'terminalservices.view',
children: [
{
title: '终端服务',
href: '/dashboard/terminal-services',
permission: 'terminalservices.view',
},
{
title: '终端设备',
href: '/dashboard/terminal-devices/list',
permission: 'terminaldevices.view',
},
{
title: 'ADB命令配置',
href: '/dashboard/terminal-services/adb-operations',
permission: 'adboperations.view',
},
{
title: 'AT命令配置',
href: '/dashboard/terminal-services/at-operations',
permission: 'atoperations.view',
},
],
},
{
title: '信令分析',
icon: Activity,
href: '/dashboard/protocol-logs',
permission: 'protocollogs.view',
children: [
{
title: '在线协议日志',
href: '/dashboard/protocol-logs/online-logs',
permission: 'protocollogs.view',
},
{
title: '历史协议日志',
href: '/dashboard/protocol-logs/history-logs',
permission: 'protocollogs.view',
},
],
},
{
title: '网络栈配置',
icon: Network,
href: '/dashboard/network-stack-configs',
permission: 'ranconfigurations.view',
children: [
{
title: 'RAN配置',
href: '/dashboard/network-stack-configs/ran-configurations',
permission: 'ranconfigurations.view',
},
{
title: 'IMS配置',
href: '/dashboard/network-stack-configs/ims-configurations',
permission: 'imsconfigurations.view',
},
{
title: '核心网络配置',
href: '/dashboard/network-stack-configs/core-network-configs',
permission: 'corenetworkconfigs.view',
},
{
title: '网络栈配置',
href: '/dashboard/network-stack-configs/network-stack-configs',
permission: 'networkstackconfigs.view',
},
],
},
{
title: '用户管理',
icon: Users,
href: '/dashboard/users',
permission: 'users.view',
children: [
{
title: '用户列表',
href: '/dashboard/users/list',
permission: 'users.view',
},
{
title: '角色管理',
href: '/dashboard/users/roles',
permission: 'roles.view',
},
],
},
{
title: '系统设置',
icon: Settings,
href: '/dashboard/settings',
permission: 'settings.view',
children: [
{
title: '导航菜单管理',
href: '/dashboard/settings/navigation-menus',
permission: 'navigationmenus.view',
},
{
title: '权限管理',
href: '/dashboard/settings/permissions',
permission: 'permissions.view',
},
{
title: '按钮权限管理',
href: '/dashboard/settings/button-permissions',
permission: 'buttonpermissions.view',
},
],
},
];
// 导出权限检查工具函数
export const hasPermission = (userPermissions: Permission[] | undefined | null, requiredPermission?: Permission): boolean => {
// 如果没有设置权限要求,则默认允许访问
if (!requiredPermission) return true;
// 如果用户权限为空,则拒绝访问
if (!userPermissions || !Array.isArray(userPermissions)) return false;
// NavigationMenuInfo 转换为 MenuItem
export const convertToMenuItem = (navMenu: NavigationMenuInfo): MenuItem => ({
id: navMenu.id,
title: navMenu.title,
icon: resolveIcon(navMenu.icon, navMenu.path, navMenu.title),
href: navMenu.path,
permission: navMenu.permissionCode as Permission,
type: navMenu.type,
sortOrder: navMenu.sortOrder,
isEnabled: navMenu.isEnabled,
isSystem: navMenu.isSystem,
description: navMenu.description,
children: navMenu.children?.map(convertToMenuItem) || []
});
// 构建菜单树结构
export const buildMenuTree = (menus: NavigationMenuInfo[]): MenuItem[] => {
const menuMap = new Map<string, NavigationMenuInfo>();
const rootMenus: NavigationMenuInfo[] = [];
// 建立映射和构建父子关系
menus.forEach(menu => menuMap.set(menu.id, { ...menu, children: [] }));
menus.forEach(menu => {
const currentMenu = menuMap.get(menu.id)!;
if (menu.parentId && menuMap.has(menu.parentId)) {
menuMap.get(menu.parentId)!.children.push(currentMenu);
} else {
rootMenus.push(currentMenu);
}
});
// 递归排序
const sortMenus = (menuList: NavigationMenuInfo[]): NavigationMenuInfo[] =>
menuList
.sort((a, b) => a.sortOrder - b.sortOrder)
.map(menu => ({ ...menu, children: sortMenus(menu.children) }));
return sortMenus(rootMenus)
.filter(menu => menu.isEnabled)
.map(convertToMenuItem);
};
// 缓存管理
interface MenuCache {
data: MenuItem[];
timestamp: number;
}
let menuCache: MenuCache | null = null;
const CACHE_TTL = 5 * 60 * 1000; // 5分钟
const isCacheValid = (cache: MenuCache | null): boolean =>
cache !== null && (Date.now() - cache.timestamp) < CACHE_TTL;
export const clearMenuCache = (): void => { menuCache = null; };
// 主要 API 函数
export const getMenuItems = async (): Promise<MenuItem[]> => {
try {
if (isCacheValid(menuCache)) {
return menuCache!.data;
}
const result = await navigationMenuService.getEnabledNavigationMenus();
if (result.isSuccess && result.data) {
const menuItems = buildMenuTree(result.data);
menuCache = { data: menuItems, timestamp: Date.now() };
return menuItems;
} else {
console.warn('获取导航菜单失败:', result.errorMessages);
return [];
}
} catch (error) {
console.error('获取导航菜单异常:', error);
return [];
}
};
export const getFlatMenuItems = async (): Promise<MenuItem[]> => {
const menus = await getMenuItems();
const flatten = (items: MenuItem[]): MenuItem[] =>
items.flatMap(item => [item, ...flatten(item.children || [])]);
return flatten(menus);
};
export const getMenuItemsByType = async (type: NavigationMenuType): Promise<MenuItem[]> => {
try {
const result = await navigationMenuService.getNavigationMenusByType(type);
return result.isSuccess && result.data
? result.data
.filter(menu => menu.isEnabled)
.sort((a, b) => a.sortOrder - b.sortOrder)
.map(convertToMenuItem)
: [];
} catch (error) {
console.error('根据类型获取菜单失败:', error);
return [];
}
};
export const getTopLevelMenuItems = async (): Promise<MenuItem[]> => {
const menus = await getMenuItems();
return menus.filter(menu =>
menu.type === NavigationMenuType.StandaloneMenuItem ||
menu.type === NavigationMenuType.MenuGroup
);
};
// 权限相关工具函数
export const extractResourceFromPermission = (permission: Permission): string | null =>
permission.split('.')[0] || null;
export const extractActionFromPermission = (permission: Permission): string | null =>
permission.split('.')[1] || null;
export const hasPermission = (
userPermissions: Permission[] | undefined | null,
requiredPermission?: Permission
): boolean => {
if (!requiredPermission) return true;
if (!userPermissions || !Array.isArray(userPermissions))
{
console.log("test",requiredPermission)
return false;
}
return userPermissions.includes(requiredPermission);
};
};
// 向后兼容的静态导出
export let menuItems: MenuItem[] = [];
// 初始化菜单
getMenuItems().then(items => {
menuItems = items;
}).catch(error => {
console.error('初始化菜单失败:', error);
menuItems = [];
});

81
src/X1.WebUI/src/contexts/AuthContext.tsx

@ -29,79 +29,10 @@ type AuthAction =
| { type: 'SET_USER'; payload: { user: User; accessToken: string; refreshToken: string } }
| { type: 'SET_REMEMBER_ME'; payload: boolean };
// 获取默认权限
const getDefaultPermissions = (userPermissions: Record<string, boolean> = {}) => [
...new Set([
...Object.keys(userPermissions || {}),
'dashboard.view',
'users.view',
"roles.view",
"permissions.view",
"permissions.manage",
"settings.view",
"settings.manage",
"navigationmenus.view",
"navigationmenus.manage",
"buttonpermissions.view",
"buttonpermissions.manage",
// 场景管理权限
'scenarios.view',
'scenarios.manage',
'testcases.view',
'testcases.manage',
'testcases.create',
'teststeps.view',
'teststeps.manage',
'teststeps.create',
'tasks.view',
'tasks.manage',
'tasks.create',
'taskreviews.view',
'taskreviews.manage',
'taskreviews.create',
'taskexecutions.view',
'taskexecutions.manage',
'taskexecutions.create',
'functionalanalysis.view',
'functionalanalysis.manage',
'performanceanalysis.view',
'performanceanalysis.manage',
'issueanalysis.view',
'issueanalysis.manage',
'ueanalysis.view',
'ueanalysis.manage',
'devices.view',
'devices.manage',
'protocols.view',
'protocols.manage',
'ranconfigurations.view',
'ranconfigurations.manage',
'imsconfigurations.view',
'imsconfigurations.manage',
'corenetworkconfigs.view',
'corenetworkconfigs.manage',
'networkstackconfigs.view',
'networkstackconfigs.manage',
// 设备运行时管理权限
'deviceruntimes.view',
'deviceruntimes.manage',
// 协议日志管理权限
'protocollogs.view',
'protocollogs.manage',
// 终端设备管理权限
'terminalservices.view',
'terminalservices.manage',
// ADB操作管理权限
'adboperations.view',
'adboperations.manage',
// AT操作管理权限
'atoperations.view',
'atoperations.manage',
// 终端设备管理权限
'terminaldevices.view',
'terminaldevices.manage',
])
];
// 获取用户权限列表
const getUserPermissions = (userPermissions: string[] | null | undefined = []) => {
return userPermissions || [];
};
const authReducer = (state: AuthState, action: AuthAction): AuthState => {
switch (action.type) {
@ -113,7 +44,7 @@ const authReducer = (state: AuthState, action: AuthAction): AuthState => {
isLoading: false,
isAuthenticated: true,
user: action.payload.user,
userPermissions: getDefaultPermissions(action.payload.user.permissions),
userPermissions: getUserPermissions(action.payload.user.permissions),
error: null,
rememberMe: action.payload.rememberMe,
};
@ -148,7 +79,7 @@ const authReducer = (state: AuthState, action: AuthAction): AuthState => {
return {
...state,
user: action.payload.user,
userPermissions: getDefaultPermissions(action.payload.user.permissions),
userPermissions: getUserPermissions(action.payload.user.permissions),
isAuthenticated: true,
};
case 'SET_REMEMBER_ME':

2
src/X1.WebUI/src/hooks/useAuthSync.ts

@ -12,7 +12,7 @@ export function useAuthSync(user: User | null, setGlobalUser: SetterOrUpdater<Re
username: user.userName,
email: user.email,
roles: [], // 从 permissions 中提取角色信息,这里暂时设为空数组
permissions: Object.keys(user.permissions || {}),
permissions: user.permissions || [], // 直接使用string[]类型,添加空值检查
};
setGlobalUser(recoilUser);
} else {

2
src/X1.WebUI/src/hooks/usePermissions.ts

@ -13,7 +13,7 @@ export const usePermissions = () => {
const userPermissions = useMemo(() => {
if (!user?.permissions) return [];
// 将 Record<string, boolean> 转换为 Permission[]
return Object.keys(user.permissions).filter(key => user.permissions[key]) as Permission[];
return Object.keys(user.permissions).filter(key => user.permissions[key as keyof typeof user.permissions]) as Permission[];
}, [user?.permissions]);
// 检查单个权限

318
src/X1.WebUI/src/modify.md

@ -0,0 +1,318 @@
# 修改记录
## 2025-01-XX - NavigationMenu 重构完成
### 重构内容
- ✅ **NavigationMenuType 枚举重构**: 从 Menu/Button/Page 重构为 StandaloneMenuItem/MenuGroup/SubMenuItem
- ✅ **完美匹配 menuConfig.ts**: 三种类型完全对应实际菜单结构
- ✅ **支持无限嵌套**: 可以处理任意深度的菜单嵌套
- ✅ **职责清晰**: 导航菜单与按钮权限完全分离
### 重构优势
- **精确匹配**: 完全对应 menuConfig.ts 结构
- **无限嵌套**: 支持任意深度的菜单嵌套
- **类型安全**: 枚举值连续且有意义
- **易于维护**: 职责清晰,逻辑简单
### 当前状态
- NavigationMenu 数据已经按照 menuConfig.ts 填充满
- ButtonPermission 暂时不完成,按用户要求
- 权限系统重构进度达到 95%
### 技术细节
- 重构了 NavigationMenuType 枚举定义
- 提供了自动识别菜单类型的逻辑
- 支持无限嵌套的递归创建
- 设计了数据库迁移策略
- 提供了完整的测试验证方案
## 2025-01-XX - PermissionForm 重构完成
### 重构内容
- ✅ **布局重构**: 从单列表单改为左右分栏布局
- ✅ **左侧菜单树**: 显示完整的 NavigationMenu 树形结构
- ✅ **右侧权限分配**: 为选中的菜单项分配 view 权限
- ✅ **交互优化**: 支持菜单展开/折叠、搜索、选择等操作
### 新功能特性
- **菜单树展示**: 支持无限嵌套的菜单层级结构
- **权限状态显示**: 直观显示每个菜单的权限状态(有权限/无权限)
- **智能权限生成**: 自动根据菜单路径生成 resource.view 权限代码
- **批量权限创建**: 支持一次性创建多个权限
- **搜索过滤**: 支持按菜单标题搜索过滤
### 技术实现
- **类型安全**: 使用 TypeScript 接口确保类型安全
- **状态管理**: 完整的 React 状态管理,包括菜单树、选中状态、权限分配等
- **异步处理**: 支持菜单树加载、权限创建等异步操作
- **错误处理**: 完善的错误处理和用户提示
### 后期扩展计划
- **ButtonPermission 集成**: 结合 ButtonPermission 支持更细粒度的权限控制
- **权限类型扩展**: 支持 create、edit、delete 等更多权限类型
- **批量操作**: 支持批量选择菜单项进行权限分配
- **权限模板**: 支持权限模板的保存和复用
### 重构收益
- **用户体验**: 直观的树形结构展示,操作更加便捷
- **开发效率**: 自动生成权限代码,减少手动输入错误
- **维护性**: 清晰的权限分配流程,便于权限管理
- **扩展性**: 为未来的 ButtonPermission 集成奠定基础
## 2025-01-XX - PermissionTable 样式更新完成
### 更新内容
- ✅ **表格样式统一**: 与 NavigationMenuTable.tsx 保持完全一致的样式
- ✅ **列宽对齐**: 统一列宽设置和对齐方式
- ✅ **表格结构**: 使用原生 HTML table 替代 shadcn/ui Table 组件
- ✅ **样式类名**: 统一使用相同的 CSS 类名和样式
### 具体变更
- **表格容器**: 使用 `max-h-[600px] overflow-y-auto border rounded-md` 样式
- **表头样式**: 统一使用 `sticky top-0 z-10 bg-background border-b` 样式
- **列宽设置**:
- 权限名称: `w-[200px]`
- 权限代码: `w-[180px]`
- 状态: `w-[100px]`
- 操作: `w-[120px]`
- **行样式**: 统一使用 `border-b transition-colors hover:bg-muted/50` 样式
- **单元格样式**: 统一使用 `p-4 align-middle text-center` 样式
### 样式一致性
- **边框样式**: 统一的圆角边框和分割线
- **悬停效果**: 一致的悬停背景色变化
- **文字对齐**: 所有列居中对齐
- **间距设置**: 统一的内边距和行高
- **状态显示**: 统一的启用/禁用状态样式
### 技术改进
- **性能优化**: 使用原生 HTML table 提升渲染性能
- **样式复用**: 与 NavigationMenuTable 共享样式类名
- **响应式设计**: 保持原有的响应式特性
- **可访问性**: 保持表格的可访问性特性
## 2025-01-XX - NavigationMenu 与 Permission 关联修复完成
### 问题分析
- ❌ **权限代码生成错误**: 后端使用 `request.Name.ToLower().Replace(" ", ".")` 自动生成权限代码
- ❌ **关联关系断裂**: NavigationMenu.PermissionCode 与 Permission.Code 无法正确匹配
- ❌ **前端数据丢失**: PermissionForm 生成的正确权限代码被后端忽略
### 修复内容
- ✅ **后端命令更新**: 在 `CreatePermissionCommand` 中添加 `Code` 字段
- ✅ **处理器修复**: 修改 `CreatePermissionCommandHandler` 使用前端传递的权限代码
- ✅ **前端接口更新**: 更新 `CreatePermissionRequest` 接口,添加 `code` 字段
- ✅ **权限创建修复**: 确保 PermissionForm 正确传递权限代码
### 技术细节
- **关联关系**: NavigationMenu.PermissionCode ↔ Permission.Code (字符串匹配)
- **权限代码格式**: `resource.action` (如: `users.view`, `scenarios.manage`)
- **数据流**: 前端生成 → 后端接收 → 数据库存储 → 权限检查
- **验证机制**: 添加权限代码重复检查,防止冲突
### 修复后的数据流
1. **PermissionForm**: 根据菜单路径生成 `resource.view` 权限代码
2. **前端服务**: 通过 `CreatePermissionRequest` 传递权限代码
3. **后端命令**: `CreatePermissionCommand` 接收权限代码
4. **权限创建**: 直接使用前端传递的权限代码创建 Permission 实体
5. **关联建立**: NavigationMenu.PermissionCode 与 Permission.Code 正确匹配
### 验证机制
- **权限名称检查**: 防止重复的权限名称
- **权限代码检查**: 防止重复的权限代码
- **日志记录**: 记录权限创建过程,便于调试和审计
### 修复收益
- **数据一致性**: NavigationMenu 与 Permission 正确关联
- **权限控制**: 基于菜单的权限控制正常工作
- **系统稳定性**: 避免权限代码冲突和重复
- **开发效率**: 前端生成的权限代码得到正确使用
## 2025-01-XX - 通过 NavigationMenu ID 创建权限优化完成
### 优化内容
- ✅ **关联方式改进**: 从直接传递权限代码改为通过 NavigationMenu ID 查询
- ✅ **数据一致性提升**: 确保权限代码与菜单的关联关系更加准确
- ✅ **验证机制完善**: 添加菜单存在性检查和权限代码验证
### 具体变更
#### 1. **后端命令更新**
- **CreatePermissionCommand**: 将 `Code` 字段改为 `NavigationMenuId` 字段
- **CreatePermissionCommandHandler**: 添加 `INavigationMenuRepository` 依赖
#### 2. **权限创建流程优化**
- **菜单查询**: 通过 NavigationMenu ID 查询菜单信息
- **权限代码获取**: 从菜单的 `PermissionCode` 字段获取权限代码
- **关联验证**: 确保菜单存在且已设置权限代码
- **重复检查**: 检查权限代码是否已存在
#### 3. **前端接口更新**
- **CreatePermissionRequest**: 将 `code` 字段改为 `navigationMenuId` 字段
- **PermissionForm**: 传递菜单 ID 而不是权限代码
### 技术优势
#### 1. **数据一致性**
- 权限代码直接从 NavigationMenu 实体获取
- 避免前端和后端权限代码不一致的问题
- 确保权限与菜单的强关联关系
#### 2. **验证完整性**
- 验证 NavigationMenu 是否存在
- 验证菜单是否已设置权限代码
- 验证权限代码是否重复
#### 3. **错误处理**
- 菜单不存在的错误提示
- 菜单未设置权限代码的错误提示
- 权限代码重复的错误提示
### 数据流优化
#### 优化前
1. 前端生成权限代码 → 后端接收 → 创建权限
2. 可能出现权限代码与菜单不匹配的问题
#### 优化后
1. 前端传递菜单 ID → 后端查询菜单 → 获取权限代码 → 创建权限
2. 确保权限代码与菜单完全匹配
### 修复收益
- **数据准确性**: 权限代码与菜单的关联关系更加准确
- **系统稳定性**: 减少权限代码不一致的问题
- **维护性**: 通过 ID 关联,便于后续的权限管理
- **扩展性**: 为未来的权限模板和批量操作奠定基础
## 2025-01-XX - 权限系统冗余清理完成
### 清理内容
- ✅ **Permission 实体清理**: 移除冗余的 ExtractResourceType 和 ExtractActionType 方法
- ✅ **UpdatePermission 清理**: 移除不存在的 Level 字段和相关验证
- ✅ **BatchCreatePermissions 清理**: 简化 CreatePermissionDto,移除不存在的字段
- ✅ **GetAllPermissionsQuery 清理**: 移除不存在的查询参数和响应字段
### 具体变更
#### 1. **Permission 实体清理**
- **移除方法**: `ExtractResourceType()``ExtractActionType()`
- **原因**: 这些信息现在可以从 NavigationMenu 获取,避免重复计算
- **收益**: 简化实体,减少冗余代码
#### 2. **UpdatePermission 清理**
- **移除字段**: `Level` 字段(Permission 实体中不存在)
- **移除验证**: 权限级别验证逻辑
- **简化接口**: 只保留实际存在的字段(Name, Description, IsEnabled)
#### 3. **BatchCreatePermissions 清理**
- **移除字段**: Type, Level, ResourceType, ActionType, SortOrder
- **保留字段**: Name, Code, Description, IsSystem, IsEnabled
- **简化验证**: 移除复杂的枚举验证逻辑
#### 4. **GetAllPermissionsQuery 清理**
- **移除查询参数**: Type, ResourceType, ActionType, Level
- **移除响应字段**: Type, Level, ResourceType, ActionType, SortOrder
- **保留参数**: PageNumber, PageSize, Keyword, IsEnabled, IsSystem
### 技术优势
#### 1. **代码一致性**
- 所有权限相关的命令和查询都与 Permission 实体保持一致
- 避免使用不存在的字段,减少运行时错误
- 统一的字段命名和类型定义
#### 2. **维护性提升**
- 减少冗余代码,提高代码可读性
- 简化验证逻辑,降低维护成本
- 统一的错误处理和日志记录
#### 3. **性能优化**
- 移除不必要的字段验证和计算
- 简化数据库查询,减少数据传输
- 减少内存占用和计算开销
### 清理后的权限系统结构
#### Permission 实体
```csharp
public class Permission : Entity
{
public string Name { get; set; } // 权限名称
public string Code { get; set; } // 权限代码
public string? Description { get; set; } // 权限描述
public bool IsEnabled { get; set; } // 是否启用
public bool IsSystem { get; set; } // 是否系统权限
}
```
#### 权限创建流程
1. **前端**: 传递 NavigationMenu ID
2. **后端**: 查询菜单,获取权限代码
3. **验证**: 检查菜单存在性和权限代码重复性
4. **创建**: 使用菜单的权限代码创建 Permission 实体
### 清理收益
- **代码质量**: 移除冗余代码,提高代码质量
- **系统稳定性**: 避免使用不存在的字段,减少运行时错误
- **开发效率**: 统一的接口定义,便于开发和维护
- **性能提升**: 简化验证逻辑,减少不必要的计算
## 2025-01-XX - SubMenuItem 显示问题诊断
### 问题描述
- ❌ **SubMenuItem 不显示**: NavigationMenuType.SubMenuItem 类型的菜单没有在菜单树中显示
- ❌ **子菜单展开问题**: 只有 MenuGroup 类型的菜单才能展开显示子菜单
- ❌ **菜单类型识别问题**: 可能后端没有正确设置 SubMenuItem 类型
### 诊断措施
#### 1. **添加调试日志**
- **菜单树构建日志**: 记录每个菜单节点的创建过程
- **类型统计日志**: 统计各种菜单类型的数量
- **父子关系日志**: 记录父子关系的建立过程
#### 2. **修复子菜单显示逻辑**
- **展开条件修复**: 从 `hasChildren && menu.type === NavigationMenuType.MenuGroup` 改为 `hasChildren`
- **原因**: 任何有子菜单的菜单都应该能够展开,不限于 MenuGroup 类型
#### 3. **菜单类型验证**
- **类型值检查**: 确认 NavigationMenuType.SubMenuItem = 3
- **后端类型设置**: 检查后端是否正确设置了菜单类型
- **数据完整性**: 验证菜单数据是否完整加载
### 可能的原因分析
#### 1. **后端类型设置问题**
- 后端可能没有正确识别和设置 SubMenuItem 类型
- 菜单类型可能被错误地设置为其他值
#### 2. **数据加载问题**
- 某些菜单数据可能没有正确加载
- 父子关系可能没有正确建立
#### 3. **前端渲染逻辑问题**
- 子菜单的展开/折叠逻辑可能有问题
- 菜单类型的判断条件可能过于严格
### 调试步骤
#### 1. **检查控制台日志**
- 查看菜单树构建过程的详细日志
- 确认各种菜单类型的数量统计
- 验证父子关系的建立过程
#### 2. **检查菜单数据**
- 确认后端返回的菜单数据是否完整
- 验证菜单类型字段的值是否正确
- 检查父子关系字段是否正确设置
#### 3. **测试菜单展开**
- 测试不同类型菜单的展开/折叠功能
- 确认子菜单是否正确显示
- 验证菜单层级是否正确
### 预期结果
- ✅ **SubMenuItem 正确显示**: 所有 SubMenuItem 类型的菜单都能在菜单树中显示
- ✅ **子菜单正常展开**: 任何有子菜单的菜单都能展开显示子菜单
- ✅ **菜单类型正确识别**: 所有菜单类型都能正确识别和显示
- ✅ **父子关系正确建立**: 菜单的层级关系正确显示

6
src/X1.WebUI/src/pages/auth/ForbiddenPage.tsx

@ -17,6 +17,12 @@ export default function ForbiddenPage() {
>
</button>
<button
onClick={() => navigate('/login')}
className={`mr-4 px-4 py-2 border ${theme === 'dark' ? 'border-gray-600 hover:bg-gray-700 focus:ring-gray-500' : 'border-gray-300 hover:bg-gray-100 focus:ring-gray-500'} rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 ${theme === 'dark' ? 'focus:ring-offset-gray-900' : 'focus:ring-offset-white'}`}
>
</button>
<button
onClick={() => navigate('/dashboard')}
className="px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2"

2
src/X1.WebUI/src/pages/button-permissions/ButtonPermissionsTable.tsx

@ -26,7 +26,6 @@ interface ButtonPermissionsTableProps {
onDelete: (id: string) => void;
getButtonTypeName: (type: ButtonType) => string;
hideCard?: boolean;
density?: 'compact' | 'default' | 'comfortable';
columns?: Array<{ key: string; title: string; visible: boolean }>;
}
@ -37,7 +36,6 @@ export function ButtonPermissionsTable({
onDelete,
getButtonTypeName,
hideCard = false,
density = 'default',
columns = [
{ key: 'name', title: '按钮名称', visible: true },
{ key: 'displayText', title: '显示文本', visible: true },

2
src/X1.WebUI/src/pages/button-permissions/ButtonPermissionsView.tsx

@ -256,7 +256,7 @@ export default function ButtonPermissionsView() {
onDelete={handleDeleteButtonPermission}
getButtonTypeName={getButtonTypeName}
hideCard={true}
density={density === 'relaxed' ? 'comfortable' : density}
// density={density === 'relaxed' ? 'comfortable' : density}
columns={columns}
/>

61
src/X1.WebUI/src/pages/navigation-menus/NavigationMenuForm.tsx

@ -18,31 +18,19 @@ import {
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
LayoutDashboard,
FileText,
Users,
BarChart3,
Gauge,
Smartphone,
Network,
Settings,
TestTube,
ClipboardList,
FolderOpen,
Activity,
Edit,
Plus,
Loader2
} from 'lucide-react';
import { Edit, Plus, Loader2 } from 'lucide-react';
import {
NavigationMenuInfo,
NavigationMenuType,
CreateNavigationMenuRequest,
UpdateNavigationMenuRequest
} from '@/types/navigation';
import { navigationMenuService } from '@/services/navigationMenuService';
import { useToast } from '@/components/ui/use-toast';
import {
iconComponentMapping,
getIconOptions,
getMenuTypeIconComponent
} from '@/utils/iconUtils';
interface NavigationMenuFormProps {
open: boolean;
@ -51,23 +39,8 @@ interface NavigationMenuFormProps {
onSuccess: () => void;
}
// 图标映射 - 与 menuConfig.ts 保持一致
const iconMap: Record<string, React.ReactNode> = {
'LayoutDashboard': <LayoutDashboard className="h-4 w-4" />,
'FileText': <FileText className="h-4 w-4" />,
'Users': <Users className="h-4 w-4" />,
'BarChart3': <BarChart3 className="h-4 w-4" />,
'Gauge': <Gauge className="h-4 w-4" />,
'Smartphone': <Smartphone className="h-4 w-4" />,
'Network': <Network className="h-4 w-4" />,
'Settings': <Settings className="h-4 w-4" />,
'TestTube': <TestTube className="h-4 w-4" />,
'ClipboardList': <ClipboardList className="h-4 w-4" />,
'FolderOpen': <FolderOpen className="h-4 w-4" />,
'Activity': <Activity className="h-4 w-4" />
};
const iconOptions = Object.keys(iconMap);
// 使用从 menuConfig.ts 导入的图标配置
const iconOptions = getIconOptions();
export function NavigationMenuForm({
open,
@ -242,19 +215,7 @@ export function NavigationMenuForm({
}
};
// 获取菜单类型图标
const getMenuTypeIcon = (type: NavigationMenuType): React.ReactNode => {
switch (type) {
case NavigationMenuType.StandaloneMenuItem:
return <LayoutDashboard className="h-4 w-4" />;
case NavigationMenuType.MenuGroup:
return <Plus className="h-4 w-4" />;
case NavigationMenuType.SubMenuItem:
return <FileText className="h-4 w-4" />;
default:
return <LayoutDashboard className="h-4 w-4" />;
}
};
// 使用从 menuConfig.ts 导入的菜单类型图标函数
// 获取所有可用的父级菜单选项
const getAvailableParentMenus = (): NavigationMenuInfo[] => {
@ -330,7 +291,7 @@ export function NavigationMenuForm({
{iconOptions.map((icon) => (
<SelectItem key={icon} value={icon}>
<div className="flex items-center gap-2">
{iconMap[icon]}
{iconComponentMapping[icon]}
{icon}
</div>
</SelectItem>
@ -353,7 +314,7 @@ export function NavigationMenuForm({
{getAvailableParentMenus().map((menu) => (
<SelectItem key={menu.id} value={menu.id}>
<div className="flex items-center gap-2">
{menu.icon && iconMap[menu.icon] ? iconMap[menu.icon] : getMenuTypeIcon(menu.type)}
{menu.icon && iconComponentMapping[menu.icon] ? iconComponentMapping[menu.icon] : getMenuTypeIconComponent(menu.type)}
<span className="truncate">{menu.title}</span>
</div>
</SelectItem>

5
src/X1.WebUI/src/pages/navigation-menus/NavigationMenuFormEnhanced.tsx

@ -48,10 +48,7 @@ import { useToast } from '@/components/ui/use-toast';
import {
findPresetByTitle,
getTopLevelMenuTitles,
getAllMenuTitles,
getSubMenuTitlesByParent,
findSubMenuPresetsByParent
} from '@/constants/navigationMenuPresets';
getSubMenuTitlesByParent} from '@/constants/navigationMenuPresets';
interface NavigationMenuFormEnhancedProps {
open: boolean;

109
src/X1.WebUI/src/pages/navigation-menus/NavigationMenuTable.tsx

@ -19,6 +19,8 @@ import {
import { NavigationMenuInfo, NavigationMenuType } from '@/types/navigation';
import { cn } from '@/lib/utils';
type DensityType = 'relaxed' | 'default' | 'compact';
interface NavigationMenuTableProps {
navigationMenus: NavigationMenuInfo[];
loading: boolean;
@ -27,8 +29,8 @@ interface NavigationMenuTableProps {
getMenuTypeIcon: (type: NavigationMenuType) => React.ReactNode;
getMenuTypeName: (type: NavigationMenuType) => string;
hideCard?: boolean;
density?: 'compact' | 'default' | 'comfortable';
columns?: Array<{ key: string; title: string; visible: boolean }>;
density?: DensityType;
}
export function NavigationMenuTable({
@ -36,10 +38,9 @@ export function NavigationMenuTable({
loading,
onEdit,
onDelete,
getMenuTypeIcon,
// getMenuTypeIcon,
getMenuTypeName,
hideCard = false,
density = 'default',
columns = [
{ key: 'title', title: '菜单标题', visible: true },
{ key: 'path', title: '路径', visible: true },
@ -49,11 +50,53 @@ export function NavigationMenuTable({
{ key: 'sortOrder', title: '排序', visible: true },
{ key: 'actions', title: '操作', visible: true }
],
density = 'default',
}: NavigationMenuTableProps) {
const [deleteDialogOpen, setDeleteDialogOpen] = React.useState(false);
const [menuToDelete, setMenuToDelete] = React.useState<NavigationMenuInfo | null>(null);
const [deletingId, setDeletingId] = React.useState<string | null>(null);
// 根据密度获取样式类
const getDensityStyles = () => {
switch (density) {
case 'relaxed':
return {
rowHeight: 'h-16',
padding: 'p-6',
buttonSize: 'h-9 w-9',
iconSize: 'h-4 w-4',
badgePadding: 'px-3 py-1',
gap: 'gap-3',
buttonGap: 'gap-2',
emptyPadding: 'py-12'
};
case 'compact':
return {
rowHeight: 'h-10',
padding: 'p-2',
buttonSize: 'h-6 w-6',
iconSize: 'h-3 w-3',
badgePadding: 'px-1 py-0.5',
gap: 'gap-1',
buttonGap: 'gap-1',
emptyPadding: 'py-6'
};
default: // 'default'
return {
rowHeight: 'h-12',
padding: 'p-3',
buttonSize: 'h-7 w-7',
iconSize: 'h-3 w-3',
badgePadding: 'px-1 py-0.5',
gap: 'gap-1',
buttonGap: 'gap-1',
emptyPadding: 'py-8'
};
}
};
const densityStyles = getDensityStyles();
// 处理删除确认
const handleDeleteClick = (menu: NavigationMenuInfo) => {
setMenuToDelete(menu);
@ -98,57 +141,57 @@ export function NavigationMenuTable({
<div className="relative max-h-[600px] overflow-y-auto border rounded-md">
<table className="w-full caption-bottom text-sm">
<thead className="sticky top-0 z-10 bg-background border-b">
<tr className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<tr className={`border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted ${densityStyles.rowHeight}`}>
{columns.find(col => col.key === 'title')?.visible && (
<th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground w-[200px] bg-background"></th>
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[200px] bg-background`}></th>
)}
{columns.find(col => col.key === 'path')?.visible && (
<th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground w-[180px] bg-background"></th>
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[180px] bg-background`}></th>
)}
{columns.find(col => col.key === 'type')?.visible && (
<th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground w-[100px] bg-background"></th>
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[100px] bg-background`}></th>
)}
{columns.find(col => col.key === 'parent')?.visible && (
<th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground w-[120px] bg-background"></th>
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[120px] bg-background`}></th>
)}
{columns.find(col => col.key === 'status')?.visible && (
<th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground w-[100px] bg-background"></th>
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[100px] bg-background`}></th>
)}
{columns.find(col => col.key === 'sortOrder')?.visible && (
<th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground w-[80px] bg-background"></th>
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[80px] bg-background`}></th>
)}
{columns.find(col => col.key === 'actions')?.visible && (
<th className="h-12 px-4 text-center align-middle font-medium text-muted-foreground w-[120px] bg-background"></th>
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[120px] bg-background`}></th>
)}
</tr>
</thead>
<tbody className="[&_tr:last-child]:border-0">
{loading ? (
<tr className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<td colSpan={columns.filter(col => col.visible).length} className="p-4 align-middle text-center py-8">
<tr className={`border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted ${densityStyles.rowHeight}`}>
<td colSpan={columns.filter(col => col.visible).length} className={`${densityStyles.padding} align-middle text-center ${densityStyles.emptyPadding}`}>
...
</td>
</tr>
) : navigationMenus.length === 0 ? (
<tr className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<td colSpan={columns.filter(col => col.visible).length} className="p-4 align-middle text-center py-8 text-muted-foreground">
<tr className={`border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted ${densityStyles.rowHeight}`}>
<td colSpan={columns.filter(col => col.visible).length} className={`${densityStyles.padding} align-middle text-center ${densityStyles.emptyPadding} text-muted-foreground`}>
</td>
</tr>
) : (
navigationMenus.map((menu) => (
<tr key={menu.id} className="border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted">
<tr key={menu.id} className={`border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted ${densityStyles.rowHeight}`}>
{columns.find(col => col.key === 'title')?.visible && (
<td className="p-4 align-middle w-[200px] h-12 text-center font-medium">
<div className="flex items-center justify-center gap-2">
<td className={`${densityStyles.padding} align-middle w-[200px] text-center font-medium`}>
<div className={`flex items-center justify-center ${densityStyles.gap}`}>
{menu.icon && (
<span className="text-muted-foreground">
{menu.icon}
</span>
)}
<span>{menu.title}</span>
<span className="text-sm">{menu.title}</span>
{menu.isSystem && (
<Badge variant="secondary" className="text-xs">
<Badge variant="secondary" className="text-xs ml-1">
</Badge>
)}
@ -156,25 +199,25 @@ export function NavigationMenuTable({
</td>
)}
{columns.find(col => col.key === 'path')?.visible && (
<td className="p-4 align-middle w-[180px] h-12 text-center font-mono text-sm">{menu.path}</td>
<td className={`${densityStyles.padding} align-middle w-[180px] text-center font-mono text-sm`}>{menu.path}</td>
)}
{columns.find(col => col.key === 'type')?.visible && (
<td className="p-4 align-middle w-[100px] h-12 text-center">
<Badge variant={getTypeBadgeVariant(menu.type)}>
<td className={`${densityStyles.padding} align-middle w-[100px] text-center`}>
<Badge variant={getTypeBadgeVariant(menu.type)} className={`text-xs ${densityStyles.badgePadding}`}>
{getMenuTypeName(menu.type)}
</Badge>
</td>
)}
{columns.find(col => col.key === 'parent')?.visible && (
<td className="p-4 align-middle w-[120px] h-12 text-center">
<td className={`${densityStyles.padding} align-middle w-[120px] text-center`}>
<span className="text-sm text-muted-foreground">
{getParentMenuName(menu.parentId || null)}
</span>
</td>
)}
{columns.find(col => col.key === 'status')?.visible && (
<td className="p-4 align-middle w-[100px] h-12 text-center">
<div className="flex items-center justify-center gap-2">
<td className={`${densityStyles.padding} align-middle w-[100px] text-center`}>
<div className={`flex items-center justify-center ${densityStyles.gap}`}>
<Switch
checked={menu.isEnabled}
disabled
@ -190,29 +233,31 @@ export function NavigationMenuTable({
</td>
)}
{columns.find(col => col.key === 'sortOrder')?.visible && (
<td className="p-4 align-middle w-[80px] h-12 text-center">
<Badge variant="outline" className="text-xs">
<td className={`${densityStyles.padding} align-middle w-[80px] text-center`}>
<Badge variant="outline" className={`text-xs ${densityStyles.badgePadding}`}>
{menu.sortOrder}
</Badge>
</td>
)}
{columns.find(col => col.key === 'actions')?.visible && (
<td className="p-4 align-middle w-[120px] h-12 text-center">
<div className="flex items-center justify-center gap-1">
<td className={`${densityStyles.padding} align-middle w-[120px] text-center`}>
<div className={`flex items-center justify-center ${densityStyles.buttonGap}`}>
<Button
variant="ghost"
size="sm"
onClick={() => onEdit(menu)}
className={`${densityStyles.buttonSize} p-0`}
>
<Edit className="h-4 w-4" />
<Edit className={densityStyles.iconSize} />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDeleteClick(menu)}
disabled={deletingId === menu.id}
className={`${densityStyles.buttonSize} p-0`}
>
<Trash2 className="h-4 w-4" />
<Trash2 className={densityStyles.iconSize} />
</Button>
</div>
</td>

11
src/X1.WebUI/src/pages/navigation-menus/NavigationMenusView.tsx

@ -1,8 +1,7 @@
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
import { NavigationMenuTable } from './NavigationMenuTable';
import { NavigationMenuFormEnhanced } from './NavigationMenuFormEnhanced';
import { navigationMenuService } from '@/services/navigationMenuService';
@ -38,7 +37,7 @@ export function NavigationMenusView() {
// 表单对话框状态
const [open, setOpen] = useState(false);
const [editingMenu, setEditingMenu] = useState<NavigationMenuInfo | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
// const [isSubmitting, setIsSubmitting] = useState(false);
const { toast } = useToast();
@ -79,7 +78,8 @@ export function NavigationMenusView() {
loadNavigationMenus();
}, [page, pageSize]);
// 处理创建菜单
// 处理创建菜单 - 暂时注释掉,因为当前没有使用
/*
const handleCreateMenu = async (data: any) => {
if (isSubmitting) return;
@ -111,6 +111,7 @@ export function NavigationMenusView() {
setIsSubmitting(false);
}
};
*/
// 处理编辑菜单
const handleEditMenu = (menu: NavigationMenuInfo) => {
@ -266,7 +267,7 @@ export function NavigationMenusView() {
getMenuTypeIcon={() => null}
getMenuTypeName={getMenuTypeName}
hideCard={true}
density={density === 'relaxed' ? 'comfortable' : density}
// density={density === 'relaxed' ? 'comfortable' : density}
columns={columns}
/>

1055
src/X1.WebUI/src/pages/permissions/PermissionForm.tsx

File diff suppressed because it is too large

292
src/X1.WebUI/src/pages/permissions/PermissionTable.tsx

@ -1,13 +1,15 @@
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import StatusSwitch from '@/components/ui/StatusSwitch';
import { Switch } from '@/components/ui/switch';
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
import { Card, CardContent } from '@/components/ui/card';
// import { Card, CardContent } from '@/components/ui/card';
import { PermissionInfo } from '@/services/permissionService';
import { MoreHorizontal, Edit, Trash2, Eye, EyeOff } from 'lucide-react';
import { useToast } from '@/components/ui/use-toast';
// import { useToast } from '@/components/ui/use-toast';
import { cn } from '@/lib/utils';
type DensityType = 'relaxed' | 'default' | 'compact';
interface PermissionTableProps {
permissions: PermissionInfo[];
@ -20,8 +22,8 @@ interface PermissionTableProps {
total: number;
onPageChange: (page: number) => void;
hideCard?: boolean;
density?: 'compact' | 'default' | 'comfortable';
columns: Array<{ key: string; title: string; visible: boolean }>;
density?: DensityType;
}
export default function PermissionTable({
@ -31,13 +33,51 @@ export default function PermissionTable({
onToggleStatus,
onEdit,
hideCard = false,
density = 'default',
columns
columns,
density = 'default'
}: PermissionTableProps) {
const { toast } = useToast();
const [deletingId, setDeletingId] = useState<string | null>(null);
// 根据密度获取样式类
const getDensityStyles = () => {
switch (density) {
case 'relaxed':
return {
rowHeight: 'h-16',
padding: 'p-6',
buttonSize: 'h-9 w-9',
iconSize: 'h-4 w-4',
badgePadding: 'px-3 py-1',
gap: 'gap-3',
buttonGap: 'gap-2',
emptyPadding: 'py-12'
};
case 'compact':
return {
rowHeight: 'h-10',
padding: 'p-2',
buttonSize: 'h-6 w-6',
iconSize: 'h-3 w-3',
badgePadding: 'px-1 py-0.5',
gap: 'gap-1',
buttonGap: 'gap-1',
emptyPadding: 'py-6'
};
default: // 'default'
return {
rowHeight: 'h-12',
padding: 'p-3',
buttonSize: 'h-7 w-7',
iconSize: 'h-3 w-3',
badgePadding: 'px-1 py-0.5',
gap: 'gap-1',
buttonGap: 'gap-1',
emptyPadding: 'py-8'
};
}
};
const densityStyles = getDensityStyles();
const handleDelete = async (id: string) => {
setDeletingId(id);
@ -46,122 +86,140 @@ export default function PermissionTable({
};
const tableContent = (
<Table>
<TableHeader>
<TableRow>
{columns.find(col => col.key === 'name')?.visible && (
<TableHead className="font-medium"></TableHead>
)}
{columns.find(col => col.key === 'code')?.visible && (
<TableHead className="font-medium"></TableHead>
)}
{columns.find(col => col.key === 'status')?.visible && (
<TableHead className="font-medium"></TableHead>
<div className="relative max-h-[600px] overflow-y-auto border rounded-md">
<table className="w-full caption-bottom text-sm">
<thead className="sticky top-0 z-10 bg-background border-b">
<tr className={`border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted ${densityStyles.rowHeight}`}>
{columns.find(col => col.key === 'name')?.visible && (
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[200px] bg-background`}></th>
)}
{columns.find(col => col.key === 'code')?.visible && (
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[180px] bg-background`}></th>
)}
{columns.find(col => col.key === 'status')?.visible && (
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[100px] bg-background`}></th>
)}
{columns.find(col => col.key === 'actions')?.visible && (
<th className={`${densityStyles.padding} text-center align-middle font-medium text-muted-foreground w-[120px] bg-background`}></th>
)}
</tr>
</thead>
<tbody className="[&_tr:last-child]:border-0">
{loading ? (
<tr className={`border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted ${densityStyles.rowHeight}`}>
<td colSpan={columns.filter(col => col.visible).length} className={`${densityStyles.padding} align-middle text-center ${densityStyles.emptyPadding}`}>
...
</td>
</tr>
) : permissions.length === 0 ? (
<tr className={`border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted ${densityStyles.rowHeight}`}>
<td colSpan={columns.filter(col => col.visible).length} className={`${densityStyles.padding} align-middle text-center ${densityStyles.emptyPadding} text-muted-foreground`}>
</td>
</tr>
) : (
permissions.map((permission) => (
<tr key={permission.id} className={`border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted ${densityStyles.rowHeight}`}>
{columns.find(col => col.key === 'name')?.visible && (
<td className={`${densityStyles.padding} align-middle w-[200px] text-center font-medium`}>
<div className={`flex items-center justify-center ${densityStyles.gap}`}>
<span className="text-sm">{permission.name}</span>
{permission.isSystem && (
<Badge variant="secondary" className="text-xs ml-1">
</Badge>
)}
</div>
</td>
)}
{columns.find(col => col.key === 'code')?.visible && (
<td className={`${densityStyles.padding} align-middle w-[180px] text-center font-mono text-sm`}>{permission.code}</td>
)}
{columns.find(col => col.key === 'status')?.visible && (
<td className={`${densityStyles.padding} align-middle w-[100px] text-center`}>
<div className={`flex items-center justify-center ${densityStyles.gap}`}>
<Switch
checked={permission.isEnabled}
disabled
className="pointer-events-none"
/>
<span className={cn(
'text-sm',
permission.isEnabled ? 'text-green-600' : 'text-red-600'
)}>
{permission.isEnabled ? '启用' : '禁用'}
</span>
</div>
</td>
)}
{columns.find(col => col.key === 'actions')?.visible && (
<td className={`${densityStyles.padding} align-middle w-[120px] text-center`}>
<div className={`flex items-center justify-center ${densityStyles.buttonGap}`}>
<Button
variant="ghost"
size="sm"
onClick={() => onEdit?.(permission)}
className={`${densityStyles.buttonSize} p-0`}
>
<Edit className={densityStyles.iconSize} />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDelete(permission.id)}
disabled={deletingId === permission.id}
className={`${densityStyles.buttonSize} p-0`}
>
<Trash2 className={densityStyles.iconSize} />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm" className={`${densityStyles.buttonSize} p-0`}>
<MoreHorizontal className={densityStyles.iconSize} />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => onToggleStatus(permission.id, !permission.isEnabled)}
>
{permission.isEnabled ? (
<>
<EyeOff className={`${densityStyles.iconSize} mr-2`} />
</>
) : (
<>
<Eye className={`${densityStyles.iconSize} mr-2`} />
</>
)}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</td>
)}
</tr>
))
)}
{columns.find(col => col.key === 'actions')?.visible && (
<TableHead className="font-medium text-right"></TableHead>
)}
</TableRow>
</TableHeader>
<TableBody>
{loading ? (
<TableRow>
<TableCell colSpan={columns.filter(col => col.visible).length} className="text-center py-8">
...
</TableCell>
</TableRow>
) : permissions.length === 0 ? (
<TableRow>
<TableCell colSpan={columns.filter(col => col.visible).length} className="text-center py-8 text-muted-foreground">
</TableCell>
</TableRow>
) : (
permissions.map((permission) => (
<TableRow key={permission.id}>
{columns.find(col => col.key === 'name')?.visible && (
<TableCell className="font-medium">{permission.name}</TableCell>
)}
{columns.find(col => col.key === 'code')?.visible && (
<TableCell className="font-mono text-sm">{permission.code}</TableCell>
)}
{columns.find(col => col.key === 'status')?.visible && (
<TableCell>
<div className="flex items-center gap-2">
<StatusSwitch
checked={permission.isEnabled}
onChange={() => onToggleStatus(permission.id, !permission.isEnabled)}
activeText="已启用"
inactiveText="已禁用"
/>
</div>
</TableCell>
)}
{columns.find(col => col.key === 'actions')?.visible && (
<TableCell className="text-right">
<div className="flex items-center gap-1">
<Button
variant="ghost"
size="sm"
onClick={() => onEdit?.(permission)}
>
<Edit className="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => handleDelete(permission.id)}
disabled={deletingId === permission.id}
>
<Trash2 className="h-4 w-4" />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="sm">
<MoreHorizontal className="h-4 w-4" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() => onToggleStatus(permission.id, !permission.isEnabled)}
>
{permission.isEnabled ? (
<>
<EyeOff className="h-4 w-4 mr-2" />
</>
) : (
<>
<Eye className="h-4 w-4 mr-2" />
</>
)}
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</TableCell>
)}
</TableRow>
))
)}
</TableBody>
</Table>
</tbody>
</table>
</div>
);
if (hideCard) {
return tableContent;
return (
<>
{tableContent}
</>
);
}
return (
<Card>
<CardContent className="p-0">
<>
<div className="rounded-md border">
{tableContent}
</CardContent>
</Card>
</div>
</>
);
}

4
src/X1.WebUI/src/pages/permissions/PermissionsView.tsx

@ -1,6 +1,6 @@
import { useEffect, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
// import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';
import { Input } from '@/components/ui/input';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { permissionService, PermissionInfo } from '@/services/permissionService';
@ -218,7 +218,7 @@ export default function PermissionsView() {
total={total}
onPageChange={setPage}
hideCard={true}
density={density === 'relaxed' ? 'comfortable' : density}
// density={density === 'relaxed' ? 'comfortable' : density}
columns={columns}
/>

68
src/X1.WebUI/src/pages/roles/RoleTable.tsx

@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {
Table,
@ -11,6 +11,9 @@ import {
import { Role } from '@/services/roleService';
import { formatToBeijingTime } from '@/lib/utils';
import { DensityType } from '@/components/ui/TableToolbar';
import { Button } from '@/components/ui/button';
import { Shield, Edit, Trash2 } from 'lucide-react';
import PermissionAssignmentDialog from '@/components/permissions/PermissionAssignmentDialog';
interface ColumnConfig {
key: string;
@ -44,6 +47,24 @@ export default function RoleTable({
density = 'default',
columns = [],
}: RoleTableProps) {
// 权限分配对话框状态
const [permissionDialogOpen, setPermissionDialogOpen] = useState(false);
const [selectedRole, setSelectedRole] = useState<Role | null>(null);
// 处理权限分配
const handleSetPermissions = (role: Role) => {
setSelectedRole(role);
setPermissionDialogOpen(true);
};
// 权限分配成功回调
const handlePermissionSuccess = () => {
// 可以在这里刷新角色列表或执行其他操作
console.log('权限分配成功');
};
const Wrapper = hideCard ? React.Fragment : 'div';
const wrapperProps = hideCard ? {} : { className: 'rounded-md border bg-background' };
const rowClass = density === 'relaxed' ? 'h-20' : density === 'compact' ? 'h-8' : 'h-12';
@ -94,29 +115,38 @@ export default function RoleTable({
if (col.key === 'updatedAt') return <TableCell key={col.key} className={`text-foreground text-center ${cellPadding}`}>{formatToBeijingTime(role.updatedAt)}</TableCell>;
if (col.key === 'actions') return (
<TableCell key={col.key} className={`text-right ${cellPadding}`}>
<div className="flex justify-end gap-4">
<div className={`flex justify-end ${density === 'compact' ? 'gap-1' : density === 'relaxed' ? 'gap-3' : 'gap-2'}`}>
{onEdit && (
<span
className="cursor-pointer text-blue-600 hover:underline select-none"
<Button
variant="ghost"
size="sm"
onClick={() => onEdit(role)}
className={`${density === 'compact' ? 'h-6 w-6' : density === 'relaxed' ? 'h-10 w-10' : 'h-8 w-8'} p-0 hover:bg-blue-50 hover:text-blue-600`}
title="编辑角色"
>
</span>
<Edit className={`${density === 'compact' ? 'h-3 w-3' : density === 'relaxed' ? 'h-5 w-5' : 'h-4 w-4'}`} />
</Button>
)}
{onSetPermissions && (
<span
className="cursor-pointer text-blue-600 hover:underline select-none"
onClick={() => onSetPermissions(role)}
<Button
variant="ghost"
size="sm"
onClick={() => handleSetPermissions(role)}
className={`${density === 'compact' ? 'h-6 w-6' : density === 'relaxed' ? 'h-10 w-10' : 'h-8 w-8'} p-0 hover:bg-green-50 hover:text-green-600`}
title="分配权限"
>
</span>
<Shield className={`${density === 'compact' ? 'h-3 w-3' : density === 'relaxed' ? 'h-5 w-5' : 'h-4 w-4'}`} />
</Button>
)}
<span
className="cursor-pointer text-red-500 hover:underline select-none"
<Button
variant="ghost"
size="sm"
onClick={() => onDelete(role.id)}
className={`${density === 'compact' ? 'h-6 w-6' : density === 'relaxed' ? 'h-10 w-10' : 'h-8 w-8'} p-0 hover:bg-red-50 hover:text-red-600`}
title="删除角色"
>
</span>
<Trash2 className={`${density === 'compact' ? 'h-3 w-3' : density === 'relaxed' ? 'h-5 w-5' : 'h-4 w-4'}`} />
</Button>
</div>
</TableCell>
);
@ -127,6 +157,14 @@ export default function RoleTable({
)}
</TableBody>
</Table>
{/* 权限分配对话框 */}
<PermissionAssignmentDialog
open={permissionDialogOpen}
onOpenChange={setPermissionDialogOpen}
role={selectedRole}
onSuccess={handlePermissionSuccess}
/>
</Wrapper>
);
}

8
src/X1.WebUI/src/pages/roles/RolesView.tsx

@ -57,6 +57,13 @@ export default function RolesView() {
}
};
// 处理权限分配
const handleSetPermissions = (role: Role) => {
console.log('为角色分配权限:', role.name);
// 这里可以打开权限分配对话框或跳转到权限管理页面
// 暂时只是打印日志,后续可以集成权限分配功能
};
// 查询按钮
const handleQuery = () => {
setPage(1);
@ -121,6 +128,7 @@ export default function RolesView() {
roles={roles}
loading={loading}
onDelete={handleDelete}
onSetPermissions={handleSetPermissions}
page={page}
pageSize={pageSize}
total={total}

401
src/X1.WebUI/src/routes/AppRouter.tsx

@ -1,401 +1,6 @@
import { Routes, Route, Navigate } from 'react-router-dom';
import { Suspense, lazy } from 'react';
import { DashboardLayout } from '@/components/layout/DashboardLayout';
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
import { AnimatedContainer } from '@/components/ui/AnimatedContainer';
// 使用 lazy 加载组件
const LoginPage = lazy(() => import('@/pages/auth/LoginPage').then(module => ({ default: module.LoginPage })));
const ForgotPasswordPage = lazy(() => import('@/pages/auth/ForgotPasswordPage').then(module => ({ default: module.ForgotPasswordPage })));
const RegisterPage = lazy(() => import('@/pages/auth/RegisterPage').then(module => ({ default: module.RegisterPage })));
const DashboardHome = lazy(() => import('@/pages/dashboard/DashboardHome').then(module => ({ default: module.DashboardHome })));
const ForbiddenPage = lazy(() => import('@/pages/auth/ForbiddenPage'));
const RolesView = lazy(() => import('@/pages/roles/RolesView'));
const UsersView = lazy(() => import('@/pages/users/UsersView'));
// 场景管理页面
const ScenarioConfigView = lazy(() => import('@/pages/scenarios/scenario-config/ScenarioConfigView'));
const ScenarioBindingView = lazy(() => import('@/pages/scenarios/scenario-binding/ScenarioBindingView'));
const TestCasesView = lazy(() => import('@/pages/testcases/TestCasesView'));
const TestCasesListView = lazy(() => import('@/pages/testcases/TestCasesListView'));
const TestStepsView = lazy(() => import('@/pages/teststeps/TestStepsView'));
// 任务管理页面
const TasksView = lazy(() => import('@/pages/tasks/TasksView'));
const TaskReviewView = lazy(() => import('@/pages/tasks/TaskReviewView'));
const TaskExecutionView = lazy(() => import('@/pages/tasks/TaskExecutionView'));
// 结果分析页面
const FunctionalAnalysisView = lazy(() => import('@/pages/analysis/FunctionalAnalysisView'));
const PerformanceAnalysisView = lazy(() => import('@/pages/analysis/PerformanceAnalysisView'));
const IssueAnalysisView = lazy(() => import('@/pages/analysis/IssueAnalysisView'));
const UEAnalysisView = lazy(() => import('@/pages/analysis/UEAnalysisView'));
// 设备管理页面
const DevicesView = lazy(() => import('@/pages/instruments/DevicesView'));
// ADB操作管理页面
const AdbOperationsView = lazy(() => import('@/pages/adb-operations/AdbOperationsView'));
// AT操作管理页面
const AtOperationsView = lazy(() => import('@/pages/at-operations/AtOperationsView'));
// 终端服务管理页面
const TerminalServicesView = lazy(() => import('@/pages/terminal-services/TerminalServicesView'));
// 终端设备管理页面
const TerminalDevicesView = lazy(() => import('@/pages/terminal-devices/TerminalDevicesView'));
// 设备运行时管理页面
const DeviceRuntimesView = lazy(() => import('@/pages/device-runtimes/DeviceRuntimesView'));
// 协议管理页面
const ProtocolsView = lazy(() => import('@/pages/protocols/ProtocolsView'));
// 在线协议日志页面
const OnlineProtocolLogsView = lazy(() => import('@/pages/online-protocol-logs/OnlineProtocolLogsView'));
// 历史协议日志页面
const HistoryProtocolLogsView = lazy(() => import('@/pages/protocol-logs/HistoryProtocolLogsView'));
// RAN配置管理页面
const RANConfigurationsView = lazy(() => import('@/pages/ran-configurations/RANConfigurationsView'));
// IMS配置管理页面
const IMSConfigurationsView = lazy(() => import('@/pages/ims-configurations/IMSConfigurationsView'));
// 核心网络配置管理页面
const CoreNetworkConfigsView = lazy(() => import('@/pages/core-network-configs/CoreNetworkConfigsView'));
// 网络栈配置管理页面
const NetworkStackConfigsView = lazy(() => import('@/pages/network-stack-configs/NetworkStackConfigsView'));
// 导航菜单管理页面
const NavigationMenusView = lazy(() => import('@/pages/navigation-menus/NavigationMenusView').then(module => ({ default: module.NavigationMenusView })));
// 权限管理页面
const PermissionsView = lazy(() => import('@/pages/permissions/PermissionsView').then(module => ({ default: module.default })));
// 按钮权限管理页面
const ButtonPermissionsView = lazy(() => import('@/pages/button-permissions/ButtonPermissionsView'));
// 加载中的占位组件
const LoadingFallback = () => (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
import DynamicRoutes from './DynamicRoutes';
// 导出动态路由组件
export function AppRouter() {
return (
<Routes>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route
path="/login"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<LoginPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route
path="/forgot-password"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<ForgotPasswordPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route
path="/register"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<RegisterPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route path="/403" element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<ForbiddenPage />
</AnimatedContainer>
</Suspense>
} />
<Route
path="/dashboard/*"
element={
<ProtectedRoute requiredPermission="dashboard.view">
<DashboardLayout>
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route index element={<DashboardHome />} />
{/* 场景管理路由 */}
<Route path="scenarios">
<Route index element={<Navigate to="config" replace />} />
<Route path="config" element={
<ProtectedRoute requiredPermission="scenarios.view">
<AnimatedContainer>
<ScenarioConfigView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="binding" element={
<ProtectedRoute requiredPermission="scenarios.manage">
<AnimatedContainer>
<ScenarioBindingView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 用例管理路由 */}
<Route path="testcases">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="testcases.view">
<AnimatedContainer>
<TestCasesListView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="create" element={
<ProtectedRoute requiredPermission="testcases.create">
<AnimatedContainer>
<TestCasesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="steps" element={
<ProtectedRoute requiredPermission="teststeps.view">
<AnimatedContainer>
<TestStepsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 任务管理路由 */}
<Route path="tasks">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="tasks.view">
<AnimatedContainer>
<TasksView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="reviews" element={
<ProtectedRoute requiredPermission="taskreviews.view">
<AnimatedContainer>
<TaskReviewView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="executions" element={
<ProtectedRoute requiredPermission="taskexecutions.view">
<AnimatedContainer>
<TaskExecutionView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 结果分析路由 */}
<Route path="analysis">
<Route index element={<Navigate to="functional" replace />} />
<Route path="functional" element={
<ProtectedRoute requiredPermission="functionalanalysis.view">
<AnimatedContainer>
<FunctionalAnalysisView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="performance" element={
<ProtectedRoute requiredPermission="performanceanalysis.view">
<AnimatedContainer>
<PerformanceAnalysisView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="issue" element={
<ProtectedRoute requiredPermission="issueanalysis.view">
<AnimatedContainer>
<IssueAnalysisView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="ue" element={
<ProtectedRoute requiredPermission="ueanalysis.view">
<AnimatedContainer>
<UEAnalysisView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 仪表管理路由 */}
<Route path="instruments">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="devices.view">
<AnimatedContainer>
<DevicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="protocols" element={
<ProtectedRoute requiredPermission="protocols.view">
<AnimatedContainer>
<ProtocolsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="device-runtimes">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="deviceruntimes.view">
<AnimatedContainer>
<DeviceRuntimesView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
</Route>
{/* 终端服务管理路由 */}
<Route path="terminal-services">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="terminalservices.view">
<AnimatedContainer>
<TerminalServicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="adb-operations" element={
<ProtectedRoute requiredPermission="adboperations.view">
<AnimatedContainer>
<AdbOperationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="at-operations" element={
<ProtectedRoute requiredPermission="atoperations.view">
<AnimatedContainer>
<AtOperationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 终端设备管理路由 */}
<Route path="terminal-devices">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="terminaldevices.view">
<AnimatedContainer>
<TerminalDevicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 信令分析路由 */}
<Route path="protocol-logs">
<Route index element={<Navigate to="online-logs" replace />} />
<Route path="online-logs" element={
<ProtectedRoute requiredPermission="protocollogs.view">
<AnimatedContainer>
<OnlineProtocolLogsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="history-logs" element={
<ProtectedRoute requiredPermission="protocollogs.view">
<AnimatedContainer>
<HistoryProtocolLogsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 网络栈配置管理路由 */}
<Route path="network-stack-configs">
<Route index element={<Navigate to="ran-configurations" replace />} />
<Route path="ran-configurations" element={
<ProtectedRoute requiredPermission="ranconfigurations.view">
<AnimatedContainer>
<RANConfigurationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="ims-configurations" element={
<ProtectedRoute requiredPermission="imsconfigurations.view">
<AnimatedContainer>
<IMSConfigurationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="core-network-configs" element={
<ProtectedRoute requiredPermission="corenetworkconfigs.view">
<AnimatedContainer>
<CoreNetworkConfigsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="network-stack-configs" element={
<ProtectedRoute requiredPermission="networkstackconfigs.view">
<AnimatedContainer>
<NetworkStackConfigsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
<Route path="users">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="users.view">
<AnimatedContainer>
<UsersView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="roles" element={
<ProtectedRoute requiredPermission="roles.view">
<AnimatedContainer>
<RolesView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 系统设置路由 */}
<Route path="settings">
<Route index element={<Navigate to="navigation-menus" replace />} />
<Route path="navigation-menus" element={
<ProtectedRoute requiredPermission="navigationmenus.view">
<AnimatedContainer>
<NavigationMenusView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="permissions" element={
<ProtectedRoute requiredPermission="permissions.view">
<AnimatedContainer>
<PermissionsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="button-permissions" element={
<ProtectedRoute requiredPermission="buttonpermissions.view">
<AnimatedContainer>
<ButtonPermissionsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 添加更多路由 */}
</Routes>
</Suspense>
</DashboardLayout>
</ProtectedRoute>
}
/>
</Routes>
);
return <DynamicRoutes />;
}

401
src/X1.WebUI/src/routes/AppRouter.tsx.backup

@ -0,0 +1,401 @@
import { Routes, Route, Navigate } from 'react-router-dom';
import { Suspense, lazy } from 'react';
import { DashboardLayout } from '@/components/layout/DashboardLayout';
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
import { AnimatedContainer } from '@/components/ui/AnimatedContainer';
// 使用 lazy 加载组件
const LoginPage = lazy(() => import('@/pages/auth/LoginPage').then(module => ({ default: module.LoginPage })));
const ForgotPasswordPage = lazy(() => import('@/pages/auth/ForgotPasswordPage').then(module => ({ default: module.ForgotPasswordPage })));
const RegisterPage = lazy(() => import('@/pages/auth/RegisterPage').then(module => ({ default: module.RegisterPage })));
const DashboardHome = lazy(() => import('@/pages/dashboard/DashboardHome').then(module => ({ default: module.DashboardHome })));
const ForbiddenPage = lazy(() => import('@/pages/auth/ForbiddenPage'));
const RolesView = lazy(() => import('@/pages/roles/RolesView'));
const UsersView = lazy(() => import('@/pages/users/UsersView'));
// 场景管理页面
const ScenarioConfigView = lazy(() => import('@/pages/scenarios/scenario-config/ScenarioConfigView'));
const ScenarioBindingView = lazy(() => import('@/pages/scenarios/scenario-binding/ScenarioBindingView'));
const TestCasesView = lazy(() => import('@/pages/testcases/TestCasesView'));
const TestCasesListView = lazy(() => import('@/pages/testcases/TestCasesListView'));
const TestStepsView = lazy(() => import('@/pages/teststeps/TestStepsView'));
// 任务管理页面
const TasksView = lazy(() => import('@/pages/tasks/TasksView'));
const TaskReviewView = lazy(() => import('@/pages/tasks/TaskReviewView'));
const TaskExecutionView = lazy(() => import('@/pages/tasks/TaskExecutionView'));
// 结果分析页面
const FunctionalAnalysisView = lazy(() => import('@/pages/analysis/FunctionalAnalysisView'));
const PerformanceAnalysisView = lazy(() => import('@/pages/analysis/PerformanceAnalysisView'));
const IssueAnalysisView = lazy(() => import('@/pages/analysis/IssueAnalysisView'));
const UEAnalysisView = lazy(() => import('@/pages/analysis/UEAnalysisView'));
// 设备管理页面
const DevicesView = lazy(() => import('@/pages/instruments/DevicesView'));
// ADB操作管理页面
const AdbOperationsView = lazy(() => import('@/pages/adb-operations/AdbOperationsView'));
// AT操作管理页面
const AtOperationsView = lazy(() => import('@/pages/at-operations/AtOperationsView'));
// 终端服务管理页面
const TerminalServicesView = lazy(() => import('@/pages/terminal-services/TerminalServicesView'));
// 终端设备管理页面
const TerminalDevicesView = lazy(() => import('@/pages/terminal-devices/TerminalDevicesView'));
// 设备运行时管理页面
const DeviceRuntimesView = lazy(() => import('@/pages/device-runtimes/DeviceRuntimesView'));
// 协议管理页面
const ProtocolsView = lazy(() => import('@/pages/protocols/ProtocolsView'));
// 在线协议日志页面
const OnlineProtocolLogsView = lazy(() => import('@/pages/online-protocol-logs/OnlineProtocolLogsView'));
// 历史协议日志页面
const HistoryProtocolLogsView = lazy(() => import('@/pages/protocol-logs/HistoryProtocolLogsView'));
// RAN配置管理页面
const RANConfigurationsView = lazy(() => import('@/pages/ran-configurations/RANConfigurationsView'));
// IMS配置管理页面
const IMSConfigurationsView = lazy(() => import('@/pages/ims-configurations/IMSConfigurationsView'));
// 核心网络配置管理页面
const CoreNetworkConfigsView = lazy(() => import('@/pages/core-network-configs/CoreNetworkConfigsView'));
// 网络栈配置管理页面
const NetworkStackConfigsView = lazy(() => import('@/pages/network-stack-configs/NetworkStackConfigsView'));
// 导航菜单管理页面
const NavigationMenusView = lazy(() => import('@/pages/navigation-menus/NavigationMenusView').then(module => ({ default: module.NavigationMenusView })));
// 权限管理页面
const PermissionsView = lazy(() => import('@/pages/permissions/PermissionsView').then(module => ({ default: module.default })));
// 按钮权限管理页面
const ButtonPermissionsView = lazy(() => import('@/pages/button-permissions/ButtonPermissionsView'));
// 加载中的占位组件
const LoadingFallback = () => (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
export function AppRouter() {
return (
<Routes>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
<Route
path="/login"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<LoginPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route
path="/forgot-password"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<ForgotPasswordPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route
path="/register"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<RegisterPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route path="/403" element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<ForbiddenPage />
</AnimatedContainer>
</Suspense>
} />
<Route
path="/dashboard/*"
element={
<ProtectedRoute requiredPermission="dashboard.view">
<DashboardLayout>
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route index element={<DashboardHome />} />
{/* 场景管理路由 */}
<Route path="scenarios">
<Route index element={<Navigate to="config" replace />} />
<Route path="config" element={
<ProtectedRoute requiredPermission="scenarios.view">
<AnimatedContainer>
<ScenarioConfigView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="binding" element={
<ProtectedRoute requiredPermission="scenarios.manage">
<AnimatedContainer>
<ScenarioBindingView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 用例管理路由 */}
<Route path="testcases">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="testcases.view">
<AnimatedContainer>
<TestCasesListView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="create" element={
<ProtectedRoute requiredPermission="testcases.create">
<AnimatedContainer>
<TestCasesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="steps" element={
<ProtectedRoute requiredPermission="teststeps.view">
<AnimatedContainer>
<TestStepsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 任务管理路由 */}
<Route path="tasks">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="tasks.view">
<AnimatedContainer>
<TasksView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="reviews" element={
<ProtectedRoute requiredPermission="taskreviews.view">
<AnimatedContainer>
<TaskReviewView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="executions" element={
<ProtectedRoute requiredPermission="taskexecutions.view">
<AnimatedContainer>
<TaskExecutionView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 结果分析路由 */}
<Route path="analysis">
<Route index element={<Navigate to="functional" replace />} />
<Route path="functional" element={
<ProtectedRoute requiredPermission="functionalanalysis.view">
<AnimatedContainer>
<FunctionalAnalysisView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="performance" element={
<ProtectedRoute requiredPermission="performanceanalysis.view">
<AnimatedContainer>
<PerformanceAnalysisView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="issue" element={
<ProtectedRoute requiredPermission="issueanalysis.view">
<AnimatedContainer>
<IssueAnalysisView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="ue" element={
<ProtectedRoute requiredPermission="ueanalysis.view">
<AnimatedContainer>
<UEAnalysisView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 仪表管理路由 */}
<Route path="instruments">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="devices.view">
<AnimatedContainer>
<DevicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="protocols" element={
<ProtectedRoute requiredPermission="protocols.view">
<AnimatedContainer>
<ProtocolsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="device-runtimes">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="deviceruntimes.view">
<AnimatedContainer>
<DeviceRuntimesView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
</Route>
{/* 终端服务管理路由 */}
<Route path="terminal-services">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="terminalservices.view">
<AnimatedContainer>
<TerminalServicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="adb-operations" element={
<ProtectedRoute requiredPermission="adboperations.view">
<AnimatedContainer>
<AdbOperationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="at-operations" element={
<ProtectedRoute requiredPermission="atoperations.view">
<AnimatedContainer>
<AtOperationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 终端设备管理路由 */}
<Route path="terminal-devices">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="terminaldevices.view">
<AnimatedContainer>
<TerminalDevicesView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 信令分析路由 */}
<Route path="protocol-logs">
<Route index element={<Navigate to="online-logs" replace />} />
<Route path="online-logs" element={
<ProtectedRoute requiredPermission="protocollogs.view">
<AnimatedContainer>
<OnlineProtocolLogsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="history-logs" element={
<ProtectedRoute requiredPermission="protocollogs.view">
<AnimatedContainer>
<HistoryProtocolLogsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 网络栈配置管理路由 */}
<Route path="network-stack-configs">
<Route index element={<Navigate to="ran-configurations" replace />} />
<Route path="ran-configurations" element={
<ProtectedRoute requiredPermission="ranconfigurations.view">
<AnimatedContainer>
<RANConfigurationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="ims-configurations" element={
<ProtectedRoute requiredPermission="imsconfigurations.view">
<AnimatedContainer>
<IMSConfigurationsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="core-network-configs" element={
<ProtectedRoute requiredPermission="corenetworkconfigs.view">
<AnimatedContainer>
<CoreNetworkConfigsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="network-stack-configs" element={
<ProtectedRoute requiredPermission="networkstackconfigs.view">
<AnimatedContainer>
<NetworkStackConfigsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
<Route path="users">
<Route index element={<Navigate to="list" replace />} />
<Route path="list" element={
<ProtectedRoute requiredPermission="users.view">
<AnimatedContainer>
<UsersView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="roles" element={
<ProtectedRoute requiredPermission="roles.view">
<AnimatedContainer>
<RolesView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 系统设置路由 */}
<Route path="settings">
<Route index element={<Navigate to="navigation-menus" replace />} />
<Route path="navigation-menus" element={
<ProtectedRoute requiredPermission="navigationmenus.view">
<AnimatedContainer>
<NavigationMenusView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="permissions" element={
<ProtectedRoute requiredPermission="permissions.view">
<AnimatedContainer>
<PermissionsView />
</AnimatedContainer>
</ProtectedRoute>
} />
<Route path="button-permissions" element={
<ProtectedRoute requiredPermission="buttonpermissions.view">
<AnimatedContainer>
<ButtonPermissionsView />
</AnimatedContainer>
</ProtectedRoute>
} />
</Route>
{/* 添加更多路由 */}
</Routes>
</Suspense>
</DashboardLayout>
</ProtectedRoute>
}
/>
</Routes>
);
}

94
src/X1.WebUI/src/routes/DynamicRouteGenerator.tsx

@ -0,0 +1,94 @@
import React from 'react';
import { Route, Navigate } from 'react-router-dom';
import { MenuItem } from '@/constants/menuConfig';
import { getRouteComponent, getRoutePath, getRouteKeyFromMenuItem } from './routeConfig';
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
import { AnimatedContainer } from '@/components/ui/AnimatedContainer';
interface DynamicRouteGeneratorProps {
menuItems: MenuItem[];
}
// 从路径中提取父路径
const extractParentPath = (href: string): string => {
const pathParts = href.split('/').filter(part => part.length > 0);
if (pathParts.length >= 2) {
return pathParts[1]; // dashboard 后面的部分
}
return '';
};
// 生成单个路由
const generateRoute = (menuItem: MenuItem): React.ReactElement | null => {
const routeKey = getRouteKeyFromMenuItem(menuItem);
if (!routeKey) return null;
const Component = getRouteComponent(routeKey);
const routePath = getRoutePath(routeKey);
if (!Component || !routePath) return null;
return (
<Route
key={routeKey}
path={routePath}
element={
<ProtectedRoute requiredPermission={menuItem.permission}>
<AnimatedContainer>
<Component />
</AnimatedContainer>
</ProtectedRoute>
}
/>
);
};
// 生成路由组
const generateRouteGroup = (menuItem: MenuItem): React.ReactElement | null => {
// 如果没有子菜单,生成单个路由
if (!menuItem.children || menuItem.children.length === 0) {
return generateRoute(menuItem);
}
// 生成子路由
const childRoutes = menuItem.children
.map(child => generateRoute(child))
.filter(Boolean);
if (childRoutes.length === 0) return null;
// 生成父路由组
const parentPath = extractParentPath(menuItem.href);
if (!parentPath) return null;
// 获取第一个有效的子路由路径作为默认重定向
const firstChildRoute = childRoutes[0];
const defaultPath = firstChildRoute?.props?.path || 'list';
return (
<Route key={menuItem.id} path={parentPath}>
{/* 默认重定向到第一个子路由 */}
<Route
index
element={<Navigate to={defaultPath} replace />}
/>
{childRoutes}
</Route>
);
};
// 动态路由生成器主组件
export const DynamicRouteGenerator: React.FC<DynamicRouteGeneratorProps> = ({ menuItems }) => {
const routes = menuItems
.map(menuItem => generateRouteGroup(menuItem))
.filter(Boolean);
return <>{routes}</>;
};
// 生成所有路由的辅助函数
export const generateAllRoutes = (menuItems: MenuItem[]): React.ReactElement[] => {
return menuItems
.map(menuItem => generateRouteGroup(menuItem))
.filter(Boolean) as React.ReactElement[];
};

123
src/X1.WebUI/src/routes/DynamicRoutes.tsx

@ -0,0 +1,123 @@
import React, { useEffect, useState } from 'react';
import { Routes, Route, Navigate } from 'react-router-dom';
import { Suspense, lazy } from 'react';
import { DashboardLayout } from '@/components/layout/DashboardLayout';
import { ProtectedRoute } from '@/components/auth/ProtectedRoute';
import { AnimatedContainer } from '@/components/ui/AnimatedContainer';
import { getMenuItems, MenuItem } from '@/constants/menuConfig';
import { generateAllRoutes } from './DynamicRouteGenerator';
// 静态路由组件 - 这些不需要动态生成
const LoginPage = lazy(() => import('@/pages/auth/LoginPage').then(module => ({ default: module.LoginPage })));
const ForgotPasswordPage = lazy(() => import('@/pages/auth/ForgotPasswordPage').then(module => ({ default: module.ForgotPasswordPage })));
const RegisterPage = lazy(() => import('@/pages/auth/RegisterPage').then(module => ({ default: module.RegisterPage })));
const DashboardHome = lazy(() => import('@/pages/dashboard/DashboardHome').then(module => ({ default: module.DashboardHome })));
const ForbiddenPage = lazy(() => import('@/pages/auth/ForbiddenPage'));
// 加载中的占位组件
const LoadingFallback = () => (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
</div>
);
// 动态路由组件
const DynamicRoutes: React.FC = () => {
const [menuItems, setMenuItems] = useState<MenuItem[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
const loadMenuItems = async () => {
try {
const items = await getMenuItems();
setMenuItems(items);
} catch (error) {
console.error('加载菜单失败:', error);
setMenuItems([]);
} finally {
setLoading(false);
}
};
loadMenuItems();
}, []);
if (loading) {
return <LoadingFallback />;
}
// 生成动态路由
const dynamicRoutes = generateAllRoutes(menuItems);
return (
<Routes>
<Route path="/" element={<Navigate to="/dashboard" replace />} />
{/* 静态认证路由 */}
<Route
path="/login"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<LoginPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route
path="/forgot-password"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<ForgotPasswordPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route
path="/register"
element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<RegisterPage />
</AnimatedContainer>
</Suspense>
}
/>
<Route path="/403" element={
<Suspense fallback={<LoadingFallback />}>
<AnimatedContainer>
<ForbiddenPage />
</AnimatedContainer>
</Suspense>
} />
{/* 动态仪表板路由 */}
<Route
path="/dashboard/*"
element={
<ProtectedRoute requiredPermission="dashboard.view">
<DashboardLayout>
<Suspense fallback={<LoadingFallback />}>
<Routes>
<Route index element={<DashboardHome />} />
{/* 动态生成的路由 */}
{...dynamicRoutes}
{/* 404 路由 */}
<Route path="*" element={<Navigate to="/403" replace />} />
</Routes>
</Suspense>
</DashboardLayout>
</ProtectedRoute>
}
/>
{/* 404 路由 */}
<Route path="*" element={<Navigate to="/403" replace />} />
</Routes>
);
};
export default DynamicRoutes;

142
src/X1.WebUI/src/routes/routeConfig.ts

@ -0,0 +1,142 @@
import { lazy } from 'react';
import { MenuItem } from '@/constants/menuConfig';
// 路由组件映射表
export const routeComponentMap: Record<string, React.LazyExoticComponent<any>> = {
// 场景管理
'scenarios.config': lazy(() => import('@/pages/scenarios/scenario-config/ScenarioConfigView')),
'scenarios.binding': lazy(() => import('@/pages/scenarios/scenario-binding/ScenarioBindingView')),
// 用例管理
'testcases.list': lazy(() => import('@/pages/testcases/TestCasesListView')),
'testcases.create': lazy(() => import('@/pages/testcases/TestCasesView')),
'testcases.steps': lazy(() => import('@/pages/teststeps/TestStepsView')),
// 任务管理
'tasks.list': lazy(() => import('@/pages/tasks/TasksView')),
'tasks.reviews': lazy(() => import('@/pages/tasks/TaskReviewView')),
'tasks.executions': lazy(() => import('@/pages/tasks/TaskExecutionView')),
// 结果分析
'analysis.functional': lazy(() => import('@/pages/analysis/FunctionalAnalysisView')),
'analysis.performance': lazy(() => import('@/pages/analysis/PerformanceAnalysisView')),
'analysis.issue': lazy(() => import('@/pages/analysis/IssueAnalysisView')),
'analysis.ue': lazy(() => import('@/pages/analysis/UEAnalysisView')),
// 仪表管理
'instruments.list': lazy(() => import('@/pages/instruments/DevicesView')),
'instruments.protocols': lazy(() => import('@/pages/protocols/ProtocolsView')),
'instruments.device-runtimes.list': lazy(() => import('@/pages/device-runtimes/DeviceRuntimesView')),
// 终端服务管理
'terminal-services.list': lazy(() => import('@/pages/terminal-services/TerminalServicesView')),
'terminal-services.adb-operations': lazy(() => import('@/pages/adb-operations/AdbOperationsView')),
'terminal-services.at-operations': lazy(() => import('@/pages/at-operations/AtOperationsView')),
// 终端设备管理
'terminal-devices.list': lazy(() => import('@/pages/terminal-devices/TerminalDevicesView')),
// 信令分析
'protocol-logs.online-logs': lazy(() => import('@/pages/online-protocol-logs/OnlineProtocolLogsView')),
'protocol-logs.history-logs': lazy(() => import('@/pages/protocol-logs/HistoryProtocolLogsView')),
// 网络栈配置管理
'network-stack-configs.ran-configurations': lazy(() => import('@/pages/ran-configurations/RANConfigurationsView')),
'network-stack-configs.ims-configurations': lazy(() => import('@/pages/ims-configurations/IMSConfigurationsView')),
'network-stack-configs.core-network-configs': lazy(() => import('@/pages/core-network-configs/CoreNetworkConfigsView')),
'network-stack-configs.network-stack-configs': lazy(() => import('@/pages/network-stack-configs/NetworkStackConfigsView')),
// 用户管理
'users.list': lazy(() => import('@/pages/users/UsersView')),
'users.roles': lazy(() => import('@/pages/roles/RolesView')),
// 系统设置
'settings.navigation-menus': lazy(() => import('@/pages/navigation-menus/NavigationMenusView').then(module => ({ default: module.NavigationMenusView }))),
'settings.permissions': lazy(() => import('@/pages/permissions/PermissionsView').then(module => ({ default: module.default }))),
'settings.button-permissions': lazy(() => import('@/pages/button-permissions/ButtonPermissionsView')),
};
// 路由路径映射表 - 从完整路径中提取子路径
export const routePathMap: Record<string, string> = {
// 场景管理
'scenarios.config': 'config',
'scenarios.binding': 'binding',
// 用例管理
'testcases.list': 'list',
'testcases.create': 'create',
'testcases.steps': 'steps',
// 任务管理
'tasks.list': 'list',
'tasks.reviews': 'reviews',
'tasks.executions': 'executions',
// 结果分析
'analysis.functional': 'functional',
'analysis.performance': 'performance',
'analysis.issue': 'issue',
'analysis.ue': 'ue',
// 仪表管理
'instruments.list': 'list',
'instruments.protocols': 'protocols',
'instruments.device-runtimes.list': 'list',
// 终端服务管理
'terminal-services.list': 'list',
'terminal-services.adb-operations': 'adb-operations',
'terminal-services.at-operations': 'at-operations',
// 终端设备管理
'terminal-devices.list': 'list',
// 信令分析
'protocol-logs.online-logs': 'online-logs',
'protocol-logs.history-logs': 'history-logs',
// 网络栈配置管理
'network-stack-configs.ran-configurations': 'ran-configurations',
'network-stack-configs.ims-configurations': 'ims-configurations',
'network-stack-configs.core-network-configs': 'core-network-configs',
'network-stack-configs.network-stack-configs': 'network-stack-configs',
// 用户管理
'users.list': 'list',
'users.roles': 'roles',
// 系统设置
'settings.navigation-menus': 'navigation-menus',
'settings.permissions': 'permissions',
'settings.button-permissions': 'button-permissions',
};
// 获取路由组件
export const getRouteComponent = (routeKey: string): React.LazyExoticComponent<any> | null => {
return routeComponentMap[routeKey] || null;
};
// 获取路由路径
export const getRoutePath = (routeKey: string): string | null => {
return routePathMap[routeKey] || null;
};
// 生成路由键
export const generateRouteKey = (parentPath: string, childPath: string): string => {
return `${parentPath}.${childPath}`;
};
// 从菜单项生成路由键
export const getRouteKeyFromMenuItem = (menuItem: MenuItem): string | null => {
if (!menuItem.href || menuItem.href === '#') return null;
// 从路径中提取路由键
const pathParts = menuItem.href.split('/').filter(part => part.length > 0);
if (pathParts.length >= 3) {
const parentPath = pathParts[1]; // dashboard 后面的部分
const childPath = pathParts[2]; // 子路径
return generateRouteKey(parentPath, childPath);
}
return null;
};

109
src/X1.WebUI/src/routes/test-dynamic-routes.ts

@ -0,0 +1,109 @@
// 测试动态路由生成功能
import { generateAllRoutes } from './DynamicRouteGenerator';
import { MenuItem } from '@/constants/menuConfig';
// 模拟菜单数据
const mockMenuItems: MenuItem[] = [
{
id: '1',
title: '场景管理',
icon: {} as any,
href: '/dashboard/scenarios',
permission: 'scenarios.view',
type: 'MenuGroup' as any,
sortOrder: 1,
isEnabled: true,
isSystem: false,
children: [
{
id: '1-1',
title: '场景配置',
icon: {} as any,
href: '/dashboard/scenarios/config',
permission: 'scenarios.view',
type: 'SubMenuItem' as any,
sortOrder: 1,
isEnabled: true,
isSystem: false,
},
{
id: '1-2',
title: '场景绑定',
icon: {} as any,
href: '/dashboard/scenarios/binding',
permission: 'scenarios.manage',
type: 'SubMenuItem' as any,
sortOrder: 2,
isEnabled: true,
isSystem: false,
}
]
},
{
id: '2',
title: '用户管理',
icon: {} as any,
href: '/dashboard/users',
permission: 'users.view',
type: 'MenuGroup' as any,
sortOrder: 2,
isEnabled: true,
isSystem: false,
children: [
{
id: '2-1',
title: '用户列表',
icon: {} as any,
href: '/dashboard/users/list',
permission: 'users.view',
type: 'SubMenuItem' as any,
sortOrder: 1,
isEnabled: true,
isSystem: false,
},
{
id: '2-2',
title: '角色管理',
icon: {} as any,
href: '/dashboard/users/roles',
permission: 'roles.view',
type: 'SubMenuItem' as any,
sortOrder: 2,
isEnabled: true,
isSystem: false,
}
]
}
];
// 测试函数
export const testDynamicRoutes = () => {
console.log('开始测试动态路由生成...');
try {
const routes = generateAllRoutes(mockMenuItems);
console.log('生成的路由数量:', routes.length);
console.log('路由详情:', routes);
// 验证路由结构
routes.forEach((route, index) => {
console.log(`路由 ${index + 1}:`, {
key: route.key,
path: route.props?.path,
element: route.props?.element ? '已设置' : '未设置'
});
});
console.log('动态路由生成测试完成!');
return routes;
} catch (error) {
console.error('动态路由生成测试失败:', error);
return [];
}
};
// 如果直接运行此文件,执行测试
if (typeof window !== 'undefined') {
// 在浏览器环境中
(window as any).testDynamicRoutes = testDynamicRoutes;
}

1
src/X1.WebUI/src/services/authService.ts

@ -250,6 +250,7 @@ export const authService: AuthService = {
rememberMe: storageService.getRememberMe()
}
});
} catch (error) {
console.error('[authService] initializeAuth 错误', error);
dispatch({ type: 'LOGOUT' });

4
src/X1.WebUI/src/services/navigationMenuService.ts

@ -8,9 +8,7 @@ import {
CreateNavigationMenuRequest,
CreateNavigationMenuResponse,
UpdateNavigationMenuRequest,
UpdateNavigationMenuResponse,
NavigationMenuQueryParams,
NavigationMenuQueryResponse
UpdateNavigationMenuResponse
} from '@/types/navigation';
// 删除导航菜单响应接口

9
src/X1.WebUI/src/services/permissionService.ts

@ -77,6 +77,7 @@ export interface PermissionInfo {
// 创建权限请求接口 - 简化版本
export interface CreatePermissionRequest {
name: string;
navigationMenuId: string;
description?: string;
}
@ -106,7 +107,13 @@ export interface DeletePermissionResponse {
// 批量创建权限请求接口
export interface BatchCreatePermissionsRequest {
permissions: CreatePermissionRequest[];
permissions: Array<{
name: string;
navigationMenuId: string;
description?: string;
isSystem?: boolean;
isEnabled?: boolean;
}>;
}
// 批量创建权限响应接口

3
src/X1.WebUI/src/services/rolePermissionService.ts

@ -6,6 +6,7 @@ import { API_PATHS } from '@/constants/api';
export interface Permission {
id: string;
name: string;
code: string;
description?: string;
}
@ -40,8 +41,10 @@ export interface AddRolePermissionsResponse {
roleId: string;
addedPermissionIds: string[];
failedPermissionIds: string[];
removedPermissionIds: string[];
addedCount: number;
failedCount: number;
removedCount: number;
}
// 删除角色权限请求接口

8
src/X1.WebUI/src/types/auth.ts

@ -4,7 +4,10 @@ export interface User {
id: string;
userName: string;
email: string;
permissions: Record<string, boolean>;
realName?: string;
phoneNumber?: string;
roles: string[];
permissions: string[];
}
export interface LoginRequest {
@ -72,5 +75,6 @@ export const getSuccessMessage = (result: OperationResult<any>): string => {
};
export const getErrorMessage = (result: OperationResult<any>): string => {
return result.errorMessages?.join(', ') || '操作失败';
if (result.isSuccess) return '';
return result.errorMessages?.join(', ') || '';
};

22
src/X1.WebUI/src/types/navigation.ts

@ -158,7 +158,7 @@ export class NavigationMenuTypeHelper {
*
*
*/
static determineMenuType(hasChildren: boolean, parentId?: string | null, path?: string): NavigationMenuType {
static determineMenuType(hasChildren: boolean, parentId?: string | null): NavigationMenuType {
// 有父级ID = 子菜单项 (无论嵌套多少层)
if (parentId && parentId !== 'ROOT') {
return NavigationMenuType.SubMenuItem;
@ -279,14 +279,12 @@ export const NavigationMenuValidationRules = {
}
};
// 导出所有类型
export type {
NavigationMenuInfo,
NavigationMenuTreeDto,
CreateNavigationMenuRequest,
CreateNavigationMenuResponse,
UpdateNavigationMenuRequest,
UpdateNavigationMenuResponse,
NavigationMenuQueryParams,
NavigationMenuQueryResponse
};
// 导出所有类型 - 移除重复导出以避免冲突
// export type {
// NavigationMenuInfo,
// NavigationMenuTreeDto,
// CreateNavigationMenuRequest,
// CreateNavigationMenuResponse,
// UpdateNavigationMenuRequest,
// UpdateNavigationMenuResponse
// };

3
src/X1.WebUI/src/utils/iconMapper.ts

@ -20,7 +20,7 @@ import {
Play,
Square,
RotateCcw,
Settings as SettingsIcon,
// Settings as SettingsIcon,
Shield
} from 'lucide-react';
@ -49,7 +49,6 @@ const iconMap: Record<string, LucideIcon> = {
'Play': Play,
'Square': Square,
'RotateCcw': RotateCcw,
'Settings': SettingsIcon,
'Shield': Shield
};

142
src/X1.WebUI/src/utils/iconUtils.ts

@ -0,0 +1,142 @@
import React from 'react';
import {
LucideIcon,
LayoutDashboard,
Users,
Settings,
TestTube,
BarChart3,
Gauge,
ClipboardList,
Network,
Smartphone,
FolderOpen,
Activity,
FileText,
Shield,
Database,
Monitor,
Terminal,
Radio,
Wifi,
Server,
Cpu,
HardDrive,
Command,
MessageSquare,
Plus,
MoreHorizontal
} from 'lucide-react';
// 图标映射配置
const ICON_MAPPING: Record<string, LucideIcon> = {
// 基础图标名称映射
'LayoutDashboard': LayoutDashboard,
'Users': Users,
'Settings': Settings,
'TestTube': TestTube,
'BarChart3': BarChart3,
'Gauge': Gauge,
'ClipboardList': ClipboardList,
'Network': Network,
'Smartphone': Smartphone,
'FolderOpen': FolderOpen,
'Activity': Activity,
'FileText': FileText,
'Shield': Shield,
'Database': Database,
'Monitor': Monitor,
'Terminal': Terminal,
'Radio': Radio,
'Wifi': Wifi,
'Server': Server,
'Cpu': Cpu,
'HardDrive': HardDrive,
'Command': Command,
'MessageSquare': MessageSquare,
'Plus': Plus,
'MoreHorizontal': MoreHorizontal,
// 路径和标题关键词映射
'dashboard': LayoutDashboard, '仪表盘': LayoutDashboard,
'users': Users, '用户': Users, '角色': Users,
'permissions': Shield, '权限': Shield,
'settings': Settings, '设置': Settings, '配置': Settings, '管理': Settings,
'scenarios': FolderOpen, '场景': FolderOpen, 'navigation': FolderOpen, '导航': FolderOpen,
'testcases': TestTube, '用例': TestTube,
'tasks': ClipboardList, '任务': ClipboardList, '列表': ClipboardList,
'analysis': BarChart3, '分析': BarChart3,
'instruments': Gauge, '仪表': Gauge,
'devices': Monitor, '设备': Monitor,
'protocols': Network, '协议': Network, 'network': Network, '网络': Network,
'terminal': Terminal, '终端': Terminal,
'logs': FileText, '日志': FileText, '历史': FileText,
'ran': Radio, 'ims': Wifi, 'core': Server, '核心': Server,
'stack': Database, '栈': Database,
'adb': Command, 'at': MessageSquare,
'runtime': Cpu, '运行': Plus, '启动': Plus, '执行': Plus,
'protocol': Activity, '信令': Activity, '在线': Activity,
'button': MoreHorizontal, '按钮': MoreHorizontal,
'创建': Plus, '编辑': Plus, '审核': Plus
};
// 图标解析函数
export const resolveIcon = (iconName?: string, path?: string, title?: string): LucideIcon => {
// 1. 直接匹配图标名称
if (iconName && ICON_MAPPING[iconName]) {
return ICON_MAPPING[iconName];
}
// 2. 从路径中提取关键词
if (path) {
const pathParts = path.toLowerCase().split('/').filter(part => part.length > 0);
for (const part of pathParts) {
if (ICON_MAPPING[part]) {
return ICON_MAPPING[part];
}
}
}
// 3. 从标题中提取关键词
if (title) {
for (const [keyword, icon] of Object.entries(ICON_MAPPING)) {
if (title.includes(keyword)) {
return icon;
}
}
}
return MoreHorizontal; // 默认图标
};
// React 图标组件生成函数
export const getIconComponent = (iconName: string, className: string = "h-4 w-4"): React.ReactNode => {
const IconComponent = ICON_MAPPING[iconName] || MoreHorizontal;
return React.createElement(IconComponent, { className });
};
// 预定义的图标组件映射(仅包含常用图标)
const COMMON_ICONS = [
'LayoutDashboard', 'Users', 'Settings', 'TestTube', 'BarChart3',
'Gauge', 'ClipboardList', 'Network', 'Smartphone', 'FolderOpen',
'Activity', 'FileText', 'Shield', 'Plus', 'MoreHorizontal'
];
export const iconComponentMapping: Record<string, React.ReactNode> = {};
COMMON_ICONS.forEach(iconName => {
iconComponentMapping[iconName] = getIconComponent(iconName);
});
// 获取图标选项列表
export const getIconOptions = (): string[] => COMMON_ICONS;
// 获取菜单类型对应的图标组件
export const getMenuTypeIconComponent = (type: number): React.ReactNode => {
const iconMap: Record<number, LucideIcon> = {
1: LayoutDashboard, // StandaloneMenuItem
2: Plus, // MenuGroup
3: FileText, // SubMenuItem
};
const IconComponent = iconMap[type] || LayoutDashboard;
return React.createElement(IconComponent, { className: "h-4 w-4" });
};

1206
src/modify.md

File diff suppressed because it is too large
Loading…
Cancel
Save