diff --git a/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs
new file mode 100644
index 0000000..ef06c54
--- /dev/null
+++ b/src/Api/TakeoutSaaS.AdminApi/Controllers/TenantsController.cs
@@ -0,0 +1,42 @@
+using MediatR;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using TakeoutSaaS.Application.App.Tenants.Contracts;
+using TakeoutSaaS.Application.App.Tenants.Queries;
+using TakeoutSaaS.Module.Authorization.Attributes;
+using TakeoutSaaS.Shared.Abstractions.Results;
+using TakeoutSaaS.Shared.Web.Api;
+
+namespace TakeoutSaaS.AdminApi.Controllers;
+
+///
+/// 租户管理。
+///
+[ApiVersion("1.0")]
+[Authorize]
+[Route("api/admin/v{version:apiVersion}/tenants")]
+public sealed class TenantsController(IMediator mediator) : BaseApiController
+{
+ ///
+ /// 获取租户列表(用于下拉选择器)。
+ ///
+ /// 关键字(租户名称/编码)。
+ /// 取消标记。
+ /// 租户列表。
+ [HttpGet]
+ [PermissionAuthorize("tenant:read")]
+ [ProducesResponseType(typeof(ApiResponse>), StatusCodes.Status200OK)]
+ public async Task>> List(
+ [FromQuery] string? keyword,
+ CancellationToken cancellationToken)
+ {
+ // 1. 构造查询
+ var query = new ListTenantsQuery { Keyword = keyword };
+
+ // 2. 执行查询
+ var result = await mediator.Send(query, cancellationToken);
+
+ // 3. 返回租户列表
+ return ApiResponse>.Ok(result);
+ }
+}
diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Contracts/TenantListItemDto.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Contracts/TenantListItemDto.cs
new file mode 100644
index 0000000..7500979
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Contracts/TenantListItemDto.cs
@@ -0,0 +1,37 @@
+using System.Text.Json.Serialization;
+using TakeoutSaaS.Domain.Tenants.Enums;
+using TakeoutSaaS.Shared.Abstractions.Serialization;
+
+namespace TakeoutSaaS.Application.App.Tenants.Contracts;
+
+///
+/// 租户列表项 DTO(用于下拉选择器)。
+///
+public sealed class TenantListItemDto
+{
+ ///
+ /// 租户 ID(雪花,序列化为字符串)。
+ ///
+ [JsonConverter(typeof(SnowflakeIdJsonConverter))]
+ public long Id { get; init; }
+
+ ///
+ /// 租户编码。
+ ///
+ public string Code { get; init; } = string.Empty;
+
+ ///
+ /// 租户名称。
+ ///
+ public string Name { get; init; } = string.Empty;
+
+ ///
+ /// 租户简称。
+ ///
+ public string? ShortName { get; init; }
+
+ ///
+ /// 租户状态。
+ ///
+ public TenantStatus Status { get; init; }
+}
diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ListTenantsQueryHandler.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ListTenantsQueryHandler.cs
new file mode 100644
index 0000000..c511ae2
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Handlers/ListTenantsQueryHandler.cs
@@ -0,0 +1,30 @@
+using MediatR;
+using TakeoutSaaS.Application.App.Tenants.Contracts;
+using TakeoutSaaS.Application.App.Tenants.Queries;
+using TakeoutSaaS.Domain.Tenants.Repositories;
+
+namespace TakeoutSaaS.Application.App.Tenants.Handlers;
+
+///
+/// 获取租户列表查询处理器。
+///
+public sealed class ListTenantsQueryHandler(ITenantRepository tenantRepository)
+ : IRequestHandler>
+{
+ ///
+ public async Task> Handle(ListTenantsQuery request, CancellationToken cancellationToken)
+ {
+ // 1. 查询租户列表
+ var tenants = await tenantRepository.GetAllAsync(request.Keyword, cancellationToken);
+
+ // 2. 映射 DTO
+ return tenants.Select(t => new TenantListItemDto
+ {
+ Id = t.Id,
+ Code = t.Code,
+ Name = t.Name,
+ ShortName = t.ShortName,
+ Status = t.Status
+ }).ToArray();
+ }
+}
diff --git a/src/Application/TakeoutSaaS.Application/App/Tenants/Queries/ListTenantsQuery.cs b/src/Application/TakeoutSaaS.Application/App/Tenants/Queries/ListTenantsQuery.cs
new file mode 100644
index 0000000..7ed6481
--- /dev/null
+++ b/src/Application/TakeoutSaaS.Application/App/Tenants/Queries/ListTenantsQuery.cs
@@ -0,0 +1,15 @@
+using MediatR;
+using TakeoutSaaS.Application.App.Tenants.Contracts;
+
+namespace TakeoutSaaS.Application.App.Tenants.Queries;
+
+///
+/// 获取租户列表查询(用于下拉选择器)。
+///
+public sealed record ListTenantsQuery : IRequest>
+{
+ ///
+ /// 关键字(租户名称/编码)。
+ ///
+ public string? Keyword { get; init; }
+}
diff --git a/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantRepository.cs b/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantRepository.cs
index 504da3a..e766c55 100644
--- a/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantRepository.cs
+++ b/src/Domain/TakeoutSaaS.Domain/Tenants/Repositories/ITenantRepository.cs
@@ -22,4 +22,12 @@ public interface ITenantRepository
/// 取消标记。
/// 租户列表(仅返回找到的租户)。
Task> FindByIdsAsync(IReadOnlyCollection tenantIds, CancellationToken cancellationToken = default);
+
+ ///
+ /// 获取所有租户列表(用于下拉选择器)。
+ ///
+ /// 关键字(租户名称/编码)。
+ /// 取消标记。
+ /// 租户列表。
+ Task> GetAllAsync(string? keyword, CancellationToken cancellationToken = default);
}
diff --git a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs
index 2c4f5a3..ae4a260 100644
--- a/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs
+++ b/src/Infrastructure/TakeoutSaaS.Infrastructure/App/Repositories/EfTenantRepository.cs
@@ -34,4 +34,23 @@ public sealed class EfTenantRepository(TakeoutAdminDbContext context) : ITenantR
.Where(x => tenantIds.Contains(x.Id))
.ToListAsync(cancellationToken);
}
+
+ ///
+ public async Task> GetAllAsync(string? keyword, CancellationToken cancellationToken = default)
+ {
+ // 1. 构建基础查询
+ var query = context.Tenants
+ .AsNoTracking()
+ .Where(x => x.DeletedAt == null);
+
+ // 2. 应用关键字过滤
+ if (!string.IsNullOrWhiteSpace(keyword))
+ {
+ var normalized = keyword.Trim();
+ query = query.Where(x => x.Name.Contains(normalized) || x.Code.Contains(normalized));
+ }
+
+ // 3. 返回列表
+ return await query.OrderBy(x => x.Code).ToListAsync(cancellationToken);
+ }
}