refactor: 移除租户角色管理CQRS
This commit is contained in:
@@ -1,24 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 绑定角色权限(覆盖式)。
|
||||
/// </summary>
|
||||
public sealed record BindRolePermissionsCommand : IRequest<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色 ID。
|
||||
/// </summary>
|
||||
public long RoleId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID(可选,空则取当前上下文)。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限 ID 集合。
|
||||
/// </summary>
|
||||
public long[] PermissionIds { get; init; } = Array.Empty<long>();
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 创建角色。
|
||||
/// </summary>
|
||||
public sealed record CreateRoleCommand : IRequest<RoleDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户 ID(空则取当前上下文)。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码。
|
||||
/// </summary>
|
||||
public string Code { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 描述。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色。
|
||||
/// </summary>
|
||||
public sealed record DeleteRoleCommand : IRequest<bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色 ID。
|
||||
/// </summary>
|
||||
public long RoleId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID(空则取当前上下文)。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 更新角色。
|
||||
/// </summary>
|
||||
public sealed record UpdateRoleCommand : IRequest<RoleDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色 ID。
|
||||
/// </summary>
|
||||
public long RoleId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID(空则取当前上下文)。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色描述。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
using TakeoutSaaS.Domain.Identity.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// 角色详情 DTO。
|
||||
/// </summary>
|
||||
public sealed record RoleDetailDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色所属 Portal。
|
||||
/// </summary>
|
||||
public PortalType Portal { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色 ID。
|
||||
/// </summary>
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID(Portal=Tenant 必填;Portal=Admin 为空)。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 角色名称。
|
||||
/// </summary>
|
||||
public string Name { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色编码。
|
||||
/// </summary>
|
||||
public string Code { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 描述。
|
||||
/// </summary>
|
||||
public string? Description { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 权限列表。
|
||||
/// </summary>
|
||||
public IReadOnlyList<PermissionDto> Permissions { get; init; } = [];
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 绑定角色权限处理器。
|
||||
/// </summary>
|
||||
public sealed class BindRolePermissionsCommandHandler(
|
||||
IRolePermissionRepository rolePermissionRepository)
|
||||
: IRequestHandler<BindRolePermissionsCommand, bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理角色权限绑定请求。
|
||||
/// </summary>
|
||||
/// <param name="request">绑定命令。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>执行结果。</returns>
|
||||
public async Task<bool> Handle(BindRolePermissionsCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 固定绑定租户侧角色权限
|
||||
var portal = PortalType.Tenant;
|
||||
|
||||
// 2. (空行后) 校验租户参数
|
||||
if (!request.TenantId.HasValue || request.TenantId.Value <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.ValidationFailed, "TenantId 不能为空");
|
||||
}
|
||||
|
||||
// 3. (空行后) 获取租户标识
|
||||
var tenantId = request.TenantId.Value;
|
||||
|
||||
// 4. (空行后) 覆盖式绑定权限
|
||||
var distinctPermissionIds = request.PermissionIds
|
||||
.Where(id => id > 0)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
|
||||
await rolePermissionRepository.ReplaceRolePermissionsAsync(portal, tenantId, request.RoleId, distinctPermissionIds, cancellationToken);
|
||||
await rolePermissionRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 5. 返回执行结果
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Domain.Identity.Entities;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 创建角色处理器。
|
||||
/// </summary>
|
||||
public sealed class CreateRoleCommandHandler(
|
||||
IRoleRepository roleRepository)
|
||||
: IRequestHandler<CreateRoleCommand, RoleDto>
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理创建角色请求。
|
||||
/// </summary>
|
||||
/// <param name="request">创建命令。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>创建后的角色 DTO。</returns>
|
||||
public async Task<RoleDto> Handle(CreateRoleCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 固定创建租户侧角色
|
||||
var portal = PortalType.Tenant;
|
||||
|
||||
// 2. (空行后) 校验租户参数
|
||||
if (!request.TenantId.HasValue || request.TenantId.Value <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.ValidationFailed, "TenantId 不能为空");
|
||||
}
|
||||
|
||||
// 3. (空行后) 获取租户标识
|
||||
var tenantId = request.TenantId.Value;
|
||||
|
||||
// 4. (空行后) 归一化输入并校验唯一
|
||||
var name = request.Name?.Trim() ?? string.Empty;
|
||||
var code = request.Code?.Trim() ?? string.Empty;
|
||||
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(code))
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.BadRequest, "角色名称与编码不能为空");
|
||||
}
|
||||
|
||||
var exists = await roleRepository.FindByCodeAsync(portal, tenantId, code, cancellationToken);
|
||||
if (exists is not null)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.Conflict, "角色编码已存在");
|
||||
}
|
||||
|
||||
// 5. 构建角色实体
|
||||
var role = new Role
|
||||
{
|
||||
Portal = portal,
|
||||
TenantId = tenantId,
|
||||
Name = name,
|
||||
Code = code,
|
||||
Description = request.Description
|
||||
};
|
||||
|
||||
// 6. 持久化
|
||||
await roleRepository.AddAsync(role, cancellationToken);
|
||||
await roleRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 7. 返回 DTO
|
||||
return new RoleDto
|
||||
{
|
||||
Id = role.Id,
|
||||
Portal = role.Portal,
|
||||
TenantId = role.TenantId,
|
||||
Name = role.Name,
|
||||
Code = role.Code,
|
||||
Description = role.Description
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 删除角色处理器。
|
||||
/// </summary>
|
||||
public sealed class DeleteRoleCommandHandler(
|
||||
IRoleRepository roleRepository)
|
||||
: IRequestHandler<DeleteRoleCommand, bool>
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理删除角色请求。
|
||||
/// </summary>
|
||||
/// <param name="request">删除命令。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>执行结果。</returns>
|
||||
public async Task<bool> Handle(DeleteRoleCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 固定删除租户侧角色
|
||||
var portal = PortalType.Tenant;
|
||||
|
||||
// 2. (空行后) 校验租户参数
|
||||
if (!request.TenantId.HasValue || request.TenantId.Value <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.ValidationFailed, "TenantId 不能为空");
|
||||
}
|
||||
|
||||
// 3. (空行后) 获取租户标识并删除角色
|
||||
var tenantId = request.TenantId.Value;
|
||||
|
||||
await roleRepository.DeleteAsync(portal, tenantId, request.RoleId, cancellationToken);
|
||||
await roleRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 4. (空行后) 返回执行结果
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
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.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 角色详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class RoleDetailQueryHandler(
|
||||
IRoleRepository roleRepository,
|
||||
IRolePermissionRepository rolePermissionRepository,
|
||||
IPermissionRepository permissionRepository)
|
||||
: IRequestHandler<RoleDetailQuery, RoleDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<RoleDetailDto?> Handle(RoleDetailQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 固定查询租户侧角色详情
|
||||
var portal = PortalType.Tenant;
|
||||
|
||||
// 2. (空行后) 校验租户参数
|
||||
if (!request.TenantId.HasValue || request.TenantId.Value <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.ValidationFailed, "TenantId 不能为空");
|
||||
}
|
||||
|
||||
// 3. (空行后) 获取租户标识并查询角色
|
||||
var tenantId = request.TenantId.Value;
|
||||
var role = await roleRepository.FindByIdAsync(portal, tenantId, request.RoleId, cancellationToken);
|
||||
if (role is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. 查询角色权限关系
|
||||
var relations = await rolePermissionRepository.GetByRoleIdsAsync(portal, tenantId, new[] { role.Id }, cancellationToken);
|
||||
var permissionIds = relations.Select(x => x.PermissionId).ToArray();
|
||||
|
||||
// 5. 拉取权限实体
|
||||
var permissions = permissionIds.Length == 0
|
||||
? Array.Empty<Domain.Identity.Entities.Permission>()
|
||||
: await permissionRepository.GetByIdsAsync(permissionIds, cancellationToken);
|
||||
|
||||
// 6. 映射 DTO
|
||||
var permissionDtos = permissions
|
||||
.Select(x => new PermissionDto
|
||||
{
|
||||
Portal = x.Portal,
|
||||
Id = x.Id,
|
||||
ParentId = x.ParentId,
|
||||
SortOrder = x.SortOrder,
|
||||
Type = x.Type,
|
||||
Code = x.Code,
|
||||
Name = x.Name,
|
||||
Description = x.Description
|
||||
})
|
||||
.ToList();
|
||||
|
||||
// 7. (空行后) 返回角色详情
|
||||
return new RoleDetailDto
|
||||
{
|
||||
Id = role.Id,
|
||||
Portal = role.Portal,
|
||||
TenantId = role.TenantId,
|
||||
Name = role.Name,
|
||||
Code = role.Code,
|
||||
Description = role.Description,
|
||||
Permissions = permissionDtos
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,71 +0,0 @@
|
||||
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.Constants;
|
||||
using TakeoutSaaS.Shared.Abstractions.Exceptions;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 角色分页查询处理器。
|
||||
/// </summary>
|
||||
public sealed class SearchRolesQueryHandler(
|
||||
IRoleRepository roleRepository)
|
||||
: IRequestHandler<SearchRolesQuery, PagedResult<RoleDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行角色分页查询。
|
||||
/// </summary>
|
||||
/// <param name="request">查询参数。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>分页结果。</returns>
|
||||
public async Task<PagedResult<RoleDto>> Handle(SearchRolesQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 固定查询租户侧角色
|
||||
var portal = PortalType.Tenant;
|
||||
|
||||
// 2. (空行后) 校验租户参数
|
||||
if (!request.TenantId.HasValue || request.TenantId.Value <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.ValidationFailed, "TenantId 不能为空");
|
||||
}
|
||||
|
||||
// 3. (空行后) 获取租户标识并查询角色
|
||||
var tenantId = request.TenantId.Value;
|
||||
var roles = await roleRepository.SearchAsync(portal, tenantId, request.Keyword, cancellationToken);
|
||||
|
||||
// 4. 排序
|
||||
var sorted = request.SortBy?.ToLowerInvariant() switch
|
||||
{
|
||||
"name" => request.SortDescending
|
||||
? roles.OrderByDescending(x => x.Name)
|
||||
: roles.OrderBy(x => x.Name),
|
||||
_ => request.SortDescending
|
||||
? roles.OrderByDescending(x => x.CreatedAt)
|
||||
: roles.OrderBy(x => x.CreatedAt)
|
||||
};
|
||||
|
||||
// 5. 分页
|
||||
var paged = sorted
|
||||
.Skip((request.Page - 1) * request.PageSize)
|
||||
.Take(request.PageSize)
|
||||
.ToList();
|
||||
|
||||
// 6. 映射 DTO
|
||||
var items = paged.Select(role => new RoleDto
|
||||
{
|
||||
Id = role.Id,
|
||||
Portal = role.Portal,
|
||||
TenantId = role.TenantId,
|
||||
Name = role.Name,
|
||||
Code = role.Code,
|
||||
Description = role.Description
|
||||
}).ToList();
|
||||
|
||||
// 7. 返回分页结果
|
||||
return new PagedResult<RoleDto>(items, request.Page, request.PageSize, roles.Count);
|
||||
}
|
||||
}
|
||||
@@ -1,62 +0,0 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Commands;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 更新角色处理器。
|
||||
/// </summary>
|
||||
public sealed class UpdateRoleCommandHandler(
|
||||
IRoleRepository roleRepository)
|
||||
: IRequestHandler<UpdateRoleCommand, RoleDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行角色更新。
|
||||
/// </summary>
|
||||
/// <param name="request">更新命令。</param>
|
||||
/// <param name="cancellationToken">取消标记。</param>
|
||||
/// <returns>更新后的角色 DTO 或 null。</returns>
|
||||
public async Task<RoleDto?> Handle(UpdateRoleCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 固定更新租户侧角色
|
||||
var portal = PortalType.Tenant;
|
||||
|
||||
// 2. (空行后) 校验租户参数
|
||||
if (!request.TenantId.HasValue || request.TenantId.Value <= 0)
|
||||
{
|
||||
throw new BusinessException(ErrorCodes.ValidationFailed, "TenantId 不能为空");
|
||||
}
|
||||
|
||||
// 3. (空行后) 获取租户标识并查询角色
|
||||
var tenantId = request.TenantId.Value;
|
||||
var role = await roleRepository.FindByIdAsync(portal, tenantId, request.RoleId, cancellationToken);
|
||||
if (role == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 4. 更新字段
|
||||
role.Name = request.Name;
|
||||
role.Description = request.Description;
|
||||
|
||||
// 5. 持久化
|
||||
await roleRepository.UpdateAsync(role, cancellationToken);
|
||||
await roleRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 6. 返回 DTO
|
||||
return new RoleDto
|
||||
{
|
||||
Id = role.Id,
|
||||
Portal = role.Portal,
|
||||
TenantId = role.TenantId,
|
||||
Name = role.Name,
|
||||
Code = role.Code,
|
||||
Description = role.Description
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 查询角色详情(含权限)。
|
||||
/// </summary>
|
||||
public sealed class RoleDetailQuery : IRequest<RoleDetailDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色 ID。
|
||||
/// </summary>
|
||||
public long RoleId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID(空则取当前上下文)。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.Identity.Contracts;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.Identity.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 分页查询角色。
|
||||
/// </summary>
|
||||
public sealed class SearchRolesQuery : IRequest<PagedResult<RoleDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 指定查询的租户 ID(空则取当前上下文)。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 搜索关键字。
|
||||
/// </summary>
|
||||
public string? Keyword { get; init; }
|
||||
/// <summary>
|
||||
/// 页码(从 1 开始)。
|
||||
/// </summary>
|
||||
public int Page { get; init; } = 1;
|
||||
/// <summary>
|
||||
/// 每页条数。
|
||||
/// </summary>
|
||||
public int PageSize { get; init; } = 20;
|
||||
/// <summary>
|
||||
/// 排序字段。
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
/// <summary>
|
||||
/// 是否降序。
|
||||
/// </summary>
|
||||
public bool SortDescending { get; init; } = true;
|
||||
}
|
||||
Reference in New Issue
Block a user