feat(admin): 新增管理员角色、账单、订阅、套餐管理功能
- 新增 AdminRolesController 实现角色 CRUD 和权限管理 - 新增 BillingsController 实现账单查询功能 - 新增 SubscriptionsController 实现订阅管理功能 - 新增 TenantPackagesController 实现套餐管理功能 - 新增租户详情、配额使用、账单列表等查询功能 - 新增 TenantPackage、TenantSubscription 等领域实体 - 新增相关枚举:SubscriptionStatus、TenantPackageType 等 - 更新 appsettings 配置文件 - 更新权限授权策略提供者 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Billings.Contracts;
|
||||
using TakeoutSaaS.Domain.Billings.Enums;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Billings.Commands;
|
||||
|
||||
/// <summary>
|
||||
/// 确认收款命令(记录支付 + 立即审核通过 + 同步更新账单状态)。
|
||||
/// </summary>
|
||||
public sealed record ConfirmPaymentCommand : IRequest<PaymentRecordDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 账单 ID。
|
||||
/// </summary>
|
||||
public long BillingId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付金额。
|
||||
/// </summary>
|
||||
public decimal Amount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式。
|
||||
/// </summary>
|
||||
public TenantPaymentMethod Method { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 交易号。
|
||||
/// </summary>
|
||||
public string? TransactionNo { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付凭证 URL。
|
||||
/// </summary>
|
||||
public string? ProofUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注。
|
||||
/// </summary>
|
||||
public string? Notes { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using TakeoutSaaS.Domain.Billings.Enums;
|
||||
using TakeoutSaaS.Shared.Abstractions.Serialization;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Billings.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// 账单详情 DTO。
|
||||
/// </summary>
|
||||
public sealed record BillingDetailDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 账单 ID(雪花,序列化为字符串)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID(雪花,序列化为字符串)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户名称。
|
||||
/// </summary>
|
||||
public string? TenantName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单编号。
|
||||
/// </summary>
|
||||
public string StatementNo { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 账单类型。
|
||||
/// </summary>
|
||||
public TenantBillingType BillingType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单周期开始时间。
|
||||
/// </summary>
|
||||
public DateTime PeriodStart { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单周期结束时间。
|
||||
/// </summary>
|
||||
public DateTime PeriodEnd { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 应付金额。
|
||||
/// </summary>
|
||||
public decimal AmountDue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 已付金额。
|
||||
/// </summary>
|
||||
public decimal AmountPaid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 折扣金额。
|
||||
/// </summary>
|
||||
public decimal DiscountAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 税额。
|
||||
/// </summary>
|
||||
public decimal TaxAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 货币代码。
|
||||
/// </summary>
|
||||
public string Currency { get; init; } = "CNY";
|
||||
|
||||
/// <summary>
|
||||
/// 账单状态。
|
||||
/// </summary>
|
||||
public TenantBillingStatus Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 到期日期。
|
||||
/// </summary>
|
||||
public DateTime DueDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单明细 JSON。
|
||||
/// </summary>
|
||||
public string? LineItemsJson { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注。
|
||||
/// </summary>
|
||||
public string? Notes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 逾期通知时间。
|
||||
/// </summary>
|
||||
public DateTime? OverdueNotifiedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 提醒发送时间。
|
||||
/// </summary>
|
||||
public DateTime? ReminderSentAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间。
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间。
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付记录列表。
|
||||
/// </summary>
|
||||
public IReadOnlyList<PaymentRecordDto> Payments { get; init; } = [];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 支付记录 DTO。
|
||||
/// </summary>
|
||||
public sealed record PaymentRecordDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 支付记录 ID(雪花,序列化为字符串)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单 ID(雪花,序列化为字符串)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long BillingStatementId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付金额。
|
||||
/// </summary>
|
||||
public decimal Amount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付方式。
|
||||
/// </summary>
|
||||
public int Method { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付状态。
|
||||
/// </summary>
|
||||
public int Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 交易号。
|
||||
/// </summary>
|
||||
public string? TransactionNo { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付凭证 URL。
|
||||
/// </summary>
|
||||
public string? ProofUrl { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 支付时间。
|
||||
/// </summary>
|
||||
public DateTime? PaidAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 备注。
|
||||
/// </summary>
|
||||
public string? Notes { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 审核人 ID。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(NullableSnowflakeIdJsonConverter))]
|
||||
public long? VerifiedBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 审核时间。
|
||||
/// </summary>
|
||||
public DateTime? VerifiedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款原因。
|
||||
/// </summary>
|
||||
public string? RefundReason { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 退款时间。
|
||||
/// </summary>
|
||||
public DateTime? RefundedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间。
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using TakeoutSaaS.Domain.Billings.Enums;
|
||||
using TakeoutSaaS.Shared.Abstractions.Serialization;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Billings.Contracts;
|
||||
|
||||
/// <summary>
|
||||
/// 账单列表项 DTO。
|
||||
/// </summary>
|
||||
public sealed record BillingListDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 账单 ID(雪花,序列化为字符串)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long Id { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户 ID(雪花,序列化为字符串)。
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(SnowflakeIdJsonConverter))]
|
||||
public long TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 租户名称。
|
||||
/// </summary>
|
||||
public string? TenantName { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单编号。
|
||||
/// </summary>
|
||||
public string StatementNo { get; init; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 账单类型。
|
||||
/// </summary>
|
||||
public TenantBillingType BillingType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单周期开始时间。
|
||||
/// </summary>
|
||||
public DateTime PeriodStart { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单周期结束时间。
|
||||
/// </summary>
|
||||
public DateTime PeriodEnd { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 应付金额。
|
||||
/// </summary>
|
||||
public decimal AmountDue { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 已付金额。
|
||||
/// </summary>
|
||||
public decimal AmountPaid { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 折扣金额。
|
||||
/// </summary>
|
||||
public decimal DiscountAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 税额。
|
||||
/// </summary>
|
||||
public decimal TaxAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 货币代码。
|
||||
/// </summary>
|
||||
public string Currency { get; init; } = "CNY";
|
||||
|
||||
/// <summary>
|
||||
/// 账单状态。
|
||||
/// </summary>
|
||||
public TenantBillingStatus Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 到期日期。
|
||||
/// </summary>
|
||||
public DateTime DueDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 逾期通知时间。
|
||||
/// </summary>
|
||||
public DateTime? OverdueNotifiedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 创建时间。
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 更新时间。
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Billings.Commands;
|
||||
using TakeoutSaaS.Application.App.Billings.Contracts;
|
||||
using TakeoutSaaS.Domain.Billings.Entities;
|
||||
using TakeoutSaaS.Domain.Billings.Enums;
|
||||
using TakeoutSaaS.Domain.Billings.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Security;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Billings.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 确认收款命令处理器。
|
||||
/// </summary>
|
||||
public sealed class ConfirmPaymentCommandHandler(
|
||||
IBillingRepository billingRepository,
|
||||
ICurrentUserAccessor currentUserAccessor)
|
||||
: IRequestHandler<ConfirmPaymentCommand, PaymentRecordDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<PaymentRecordDto?> Handle(
|
||||
ConfirmPaymentCommand request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 获取账单(带跟踪)
|
||||
var billing = await billingRepository.GetByIdForUpdateAsync(request.BillingId, cancellationToken);
|
||||
|
||||
// 2. 如果不存在,返回 null
|
||||
if (billing is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 获取当前用户 ID
|
||||
var currentUserId = currentUserAccessor.UserId;
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// 4. 创建支付记录(已审核通过状态)
|
||||
var payment = new TenantPayment
|
||||
{
|
||||
TenantId = billing.TenantId,
|
||||
BillingStatementId = billing.Id,
|
||||
Amount = request.Amount,
|
||||
Method = request.Method,
|
||||
Status = TenantPaymentStatus.Success,
|
||||
TransactionNo = request.TransactionNo,
|
||||
ProofUrl = request.ProofUrl,
|
||||
Notes = request.Notes,
|
||||
PaidAt = now,
|
||||
VerifiedBy = currentUserId,
|
||||
VerifiedAt = now,
|
||||
CreatedAt = now,
|
||||
CreatedBy = currentUserId
|
||||
};
|
||||
|
||||
// 5. 添加支付记录
|
||||
await billingRepository.AddPaymentAsync(payment, cancellationToken);
|
||||
|
||||
// 6. 更新账单已付金额
|
||||
billing.AmountPaid += request.Amount;
|
||||
|
||||
// 7. 如果已付金额 >= 应付金额,更新账单状态为已支付
|
||||
if (billing.AmountPaid >= billing.AmountDue)
|
||||
{
|
||||
billing.Status = TenantBillingStatus.Paid;
|
||||
}
|
||||
|
||||
// 8. 更新账单时间戳
|
||||
billing.UpdatedAt = now;
|
||||
|
||||
// 9. 保存变更
|
||||
await billingRepository.SaveChangesAsync(cancellationToken);
|
||||
|
||||
// 10. 返回支付记录 DTO
|
||||
return new PaymentRecordDto
|
||||
{
|
||||
Id = payment.Id,
|
||||
BillingStatementId = payment.BillingStatementId,
|
||||
Amount = payment.Amount,
|
||||
Method = (int)payment.Method,
|
||||
Status = (int)payment.Status,
|
||||
TransactionNo = payment.TransactionNo,
|
||||
ProofUrl = payment.ProofUrl,
|
||||
PaidAt = payment.PaidAt,
|
||||
Notes = payment.Notes,
|
||||
VerifiedBy = payment.VerifiedBy,
|
||||
VerifiedAt = payment.VerifiedAt,
|
||||
RefundReason = payment.RefundReason,
|
||||
RefundedAt = payment.RefundedAt,
|
||||
CreatedAt = payment.CreatedAt
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Billings.Contracts;
|
||||
using TakeoutSaaS.Application.App.Billings.Queries;
|
||||
using TakeoutSaaS.Domain.Billings.Repositories;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Billings.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 获取账单详情查询处理器。
|
||||
/// </summary>
|
||||
public sealed class GetBillingDetailQueryHandler(IBillingRepository billingRepository)
|
||||
: IRequestHandler<GetBillingDetailQuery, BillingDetailDto?>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<BillingDetailDto?> Handle(
|
||||
GetBillingDetailQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询账单详情
|
||||
var detail = await billingRepository.GetDetailAsync(request.BillingId, cancellationToken);
|
||||
|
||||
// 2. 如果不存在,返回 null
|
||||
if (detail is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 映射为 DTO 并返回(支付记录暂时返回空列表)
|
||||
return new BillingDetailDto
|
||||
{
|
||||
Id = detail.Id,
|
||||
TenantId = detail.TenantId,
|
||||
TenantName = detail.TenantName,
|
||||
StatementNo = detail.StatementNo,
|
||||
BillingType = detail.BillingType,
|
||||
PeriodStart = detail.PeriodStart,
|
||||
PeriodEnd = detail.PeriodEnd,
|
||||
AmountDue = detail.AmountDue,
|
||||
AmountPaid = detail.AmountPaid,
|
||||
DiscountAmount = detail.DiscountAmount,
|
||||
TaxAmount = detail.TaxAmount,
|
||||
Currency = detail.Currency,
|
||||
Status = detail.Status,
|
||||
DueDate = detail.DueDate,
|
||||
LineItemsJson = detail.LineItemsJson,
|
||||
Notes = detail.Notes,
|
||||
OverdueNotifiedAt = detail.OverdueNotifiedAt,
|
||||
ReminderSentAt = detail.ReminderSentAt,
|
||||
CreatedAt = detail.CreatedAt,
|
||||
UpdatedAt = detail.UpdatedAt,
|
||||
Payments = []
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Billings.Contracts;
|
||||
using TakeoutSaaS.Application.App.Billings.Queries;
|
||||
using TakeoutSaaS.Domain.Billings.Repositories;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Billings.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// 获取账单列表查询处理器。
|
||||
/// </summary>
|
||||
public sealed class ListBillingsQueryHandler(IBillingRepository billingRepository)
|
||||
: IRequestHandler<ListBillingsQuery, PagedResult<BillingListDto>>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task<PagedResult<BillingListDto>> Handle(
|
||||
ListBillingsQuery request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
// 1. 查询账单列表
|
||||
var (items, totalCount) = await billingRepository.GetListAsync(
|
||||
request.TenantId,
|
||||
request.Status,
|
||||
request.BillingType,
|
||||
request.StartDate,
|
||||
request.EndDate,
|
||||
request.MinAmount,
|
||||
request.MaxAmount,
|
||||
request.Keyword,
|
||||
request.SortBy,
|
||||
request.SortDesc,
|
||||
request.PageNumber,
|
||||
request.PageSize,
|
||||
cancellationToken);
|
||||
|
||||
// 2. 映射为 DTO
|
||||
var dtos = items.Select(b => new BillingListDto
|
||||
{
|
||||
Id = b.Id,
|
||||
TenantId = b.TenantId,
|
||||
TenantName = b.TenantName,
|
||||
StatementNo = b.StatementNo,
|
||||
BillingType = b.BillingType,
|
||||
PeriodStart = b.PeriodStart,
|
||||
PeriodEnd = b.PeriodEnd,
|
||||
AmountDue = b.AmountDue,
|
||||
AmountPaid = b.AmountPaid,
|
||||
DiscountAmount = b.DiscountAmount,
|
||||
TaxAmount = b.TaxAmount,
|
||||
Currency = b.Currency,
|
||||
Status = b.Status,
|
||||
DueDate = b.DueDate,
|
||||
OverdueNotifiedAt = b.OverdueNotifiedAt,
|
||||
CreatedAt = b.CreatedAt,
|
||||
UpdatedAt = b.UpdatedAt
|
||||
}).ToList();
|
||||
|
||||
// 3. 返回分页结果
|
||||
return new PagedResult<BillingListDto>(dtos, totalCount, request.PageNumber, request.PageSize);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Billings.Contracts;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Billings.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 获取账单详情查询。
|
||||
/// </summary>
|
||||
public sealed record GetBillingDetailQuery : IRequest<BillingDetailDto?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 账单 ID。
|
||||
/// </summary>
|
||||
public long BillingId { get; init; }
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
using MediatR;
|
||||
using TakeoutSaaS.Application.App.Billings.Contracts;
|
||||
using TakeoutSaaS.Domain.Billings.Enums;
|
||||
using TakeoutSaaS.Shared.Abstractions.Results;
|
||||
|
||||
namespace TakeoutSaaS.Application.App.Billings.Queries;
|
||||
|
||||
/// <summary>
|
||||
/// 获取账单列表查询。
|
||||
/// </summary>
|
||||
public sealed record ListBillingsQuery : IRequest<PagedResult<BillingListDto>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 租户 ID。
|
||||
/// </summary>
|
||||
public long? TenantId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单状态。
|
||||
/// </summary>
|
||||
public TenantBillingStatus? Status { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 账单类型。
|
||||
/// </summary>
|
||||
public TenantBillingType? BillingType { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 开始日期。
|
||||
/// </summary>
|
||||
public DateTime? StartDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 结束日期。
|
||||
/// </summary>
|
||||
public DateTime? EndDate { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最小金额。
|
||||
/// </summary>
|
||||
public decimal? MinAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 最大金额。
|
||||
/// </summary>
|
||||
public decimal? MaxAmount { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 关键词(账单号、租户名)。
|
||||
/// </summary>
|
||||
public string? Keyword { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 排序字段。
|
||||
/// </summary>
|
||||
public string? SortBy { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否降序。
|
||||
/// </summary>
|
||||
public bool? SortDesc { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 页码(从 1 开始)。
|
||||
/// </summary>
|
||||
public int PageNumber { get; init; } = 1;
|
||||
|
||||
/// <summary>
|
||||
/// 每页条数。
|
||||
/// </summary>
|
||||
public int PageSize { get; init; } = 10;
|
||||
}
|
||||
Reference in New Issue
Block a user