diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantRolesController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantRolesController.cs
index 4cc8315..963929b 100644
--- a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantRolesController.cs
+++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantRolesController.cs
@@ -19,6 +19,41 @@ namespace TakeoutSaaS.AdminApi.Controllers;
[Route("api/admin/v{version:apiVersion}/tenants/{tenantId:long}/roles")]
public sealed class TenantRolesController(IMediator mediator) : BaseApiController
{
+ ///
+ /// 获取租户角色列表。
+ ///
+ /// 租户 ID。
+ /// 关键字(角色名称/编码)。
+ /// 页码(从 1 开始)。
+ /// 每页条数。
+ /// 取消标记。
+ /// 角色分页列表。
+ [HttpGet]
+ [PermissionAuthorize("identity:role:read")]
+ [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)]
+ public async Task>> List(
+ long tenantId,
+ [FromQuery] string? keyword,
+ [FromQuery] int page = 1,
+ [FromQuery] int pageSize = 20,
+ CancellationToken cancellationToken = default)
+ {
+ // 1. 构造查询
+ var query = new ListTenantRolesQuery
+ {
+ TenantId = tenantId,
+ Keyword = keyword,
+ Page = page,
+ PageSize = pageSize
+ };
+
+ // 2. 执行查询
+ var result = await mediator.Send(query, cancellationToken);
+
+ // 3. 返回分页结果
+ return ApiResponse>.Ok(result);
+ }
+
///
/// 创建租户角色。
///
@@ -125,4 +160,29 @@ public sealed class TenantRolesController(IMediator mediator) : BaseApiControlle
// 3. 返回更新结果
return ApiResponse.Ok(result);
}
+
+ ///
+ /// 删除租户角色。
+ ///
+ /// 租户 ID。
+ /// 角色 ID。
+ /// 取消标记。
+ /// 删除结果。
+ [HttpDelete("{roleId:long}")]
+ [PermissionAuthorize("identity:role:delete")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> Delete(
+ long tenantId,
+ long roleId,
+ CancellationToken cancellationToken)
+ {
+ // 1. 构造命令
+ var command = new DeleteTenantRoleCommand { TenantId = tenantId, RoleId = roleId };
+
+ // 2. 执行删除
+ var result = await mediator.Send(command, cancellationToken);
+
+ // 3. 返回删除结果
+ return ApiResponse.Ok(result);
+ }
}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Commands/DeleteTenantRoleCommand.cs b/src/Application/TakeoutSaaS.Application/Identity/Commands/DeleteTenantRoleCommand.cs
new file mode 100644
index 0000000..9c18368
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/Identity/Commands/DeleteTenantRoleCommand.cs
@@ -0,0 +1,19 @@
+using MediatR;
+
+namespace TakeoutSaaS.Application.Identity.Commands;
+
+///
+/// 删除租户角色命令。
+///
+public sealed record DeleteTenantRoleCommand : IRequest
+{
+ ///
+ /// 租户 ID(由路由绑定)。
+ ///
+ public long TenantId { get; init; }
+
+ ///
+ /// 角色 ID(由路由绑定)。
+ ///
+ public long RoleId { get; init; }
+}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteTenantRoleCommandHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteTenantRoleCommandHandler.cs
new file mode 100644
index 0000000..b55db20
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/DeleteTenantRoleCommandHandler.cs
@@ -0,0 +1,33 @@
+using MediatR;
+using TakeoutSaaS.Application.Identity.Commands;
+using TakeoutSaaS.Domain.Identity.Enums;
+using TakeoutSaaS.Domain.Identity.Repositories;
+using TakeoutSaaS.Shared.Abstractions.Constants;
+using TakeoutSaaS.Shared.Abstractions.Exceptions;
+
+namespace TakeoutSaaS.Application.Identity.Handlers;
+
+///
+/// 删除租户角色命令处理器。
+///
+public sealed class DeleteTenantRoleCommandHandler(IRoleRepository roleRepository)
+ : IRequestHandler
+{
+ ///
+ public async Task Handle(DeleteTenantRoleCommand request, CancellationToken cancellationToken)
+ {
+ // 1. 校验角色存在
+ var role = await roleRepository.FindByIdAsync(PortalType.Tenant, request.TenantId, request.RoleId, cancellationToken);
+ if (role is null)
+ {
+ throw new BusinessException(ErrorCodes.NotFound, "角色不存在");
+ }
+
+ // 2. 执行软删除
+ await roleRepository.DeleteAsync(PortalType.Tenant, request.TenantId, request.RoleId, cancellationToken);
+ await roleRepository.SaveChangesAsync(cancellationToken);
+
+ // 3. 返回成功
+ return true;
+ }
+}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Handlers/ListTenantRolesQueryHandler.cs b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ListTenantRolesQueryHandler.cs
new file mode 100644
index 0000000..51c10fe
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/Identity/Handlers/ListTenantRolesQueryHandler.cs
@@ -0,0 +1,48 @@
+using MediatR;
+using TakeoutSaaS.Application.Identity.Contracts;
+using TakeoutSaaS.Application.Identity.Queries;
+using TakeoutSaaS.Domain.Identity.Enums;
+using TakeoutSaaS.Domain.Identity.Repositories;
+using TakeoutSaaS.Shared.Abstractions.Results;
+
+namespace TakeoutSaaS.Application.Identity.Handlers;
+
+///
+/// 获取租户角色列表查询处理器。
+///
+public sealed class ListTenantRolesQueryHandler(IRoleRepository roleRepository)
+ : IRequestHandler>
+{
+ ///
+ public async Task> Handle(ListTenantRolesQuery request, CancellationToken cancellationToken)
+ {
+ // 1. 查询租户下所有角色
+ var roles = await roleRepository.SearchAsync(PortalType.Tenant, request.TenantId, request.Keyword, cancellationToken);
+
+ // 2. 计算分页参数
+ var totalCount = roles.Count;
+ var page = Math.Max(1, request.Page);
+ var pageSize = Math.Clamp(request.PageSize, 1, 100);
+
+ // 3. 应用分页
+ var pagedRoles = roles
+ .OrderBy(r => r.Code)
+ .Skip((page - 1) * pageSize)
+ .Take(pageSize)
+ .ToArray();
+
+ // 4. 映射 DTO
+ var dtos = pagedRoles.Select(role => new RoleDto
+ {
+ Portal = role.Portal,
+ Id = role.Id,
+ TenantId = role.TenantId,
+ Code = role.Code,
+ Name = role.Name,
+ Description = role.Description
+ }).ToArray();
+
+ // 5. 返回分页结果
+ return new PagedResult(dtos, page, pageSize, totalCount);
+ }
+}
diff --git a/src/Application/TakeoutSaaS.Application/Identity/Queries/ListTenantRolesQuery.cs b/src/Application/TakeoutSaaS.Application/Identity/Queries/ListTenantRolesQuery.cs
new file mode 100644
index 0000000..256f635
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/Identity/Queries/ListTenantRolesQuery.cs
@@ -0,0 +1,31 @@
+using MediatR;
+using TakeoutSaaS.Application.Identity.Contracts;
+using TakeoutSaaS.Shared.Abstractions.Results;
+
+namespace TakeoutSaaS.Application.Identity.Queries;
+
+///
+/// 获取租户角色列表查询。
+///
+public sealed record ListTenantRolesQuery : IRequest>
+{
+ ///
+ /// 租户 ID。
+ ///
+ public long TenantId { get; init; }
+
+ ///
+ /// 关键字(角色名称/编码)。
+ ///
+ public string? Keyword { get; init; }
+
+ ///
+ /// 页码(从 1 开始)。
+ ///
+ public int Page { get; init; } = 1;
+
+ ///
+ /// 每页条数。
+ ///
+ public int PageSize { get; init; } = 20;
+}