mirror of
https://github.com/nsnail/NetAdmin.git
synced 2025-08-01 17:55:59 +08:00
feat: ✨ 框架代码同步
This commit is contained in:
@ -68,6 +68,7 @@ XML注释文件不存在
|
||||
消息内容不能为空
|
||||
父节点不存在
|
||||
用户不存在
|
||||
用户名不符合要求
|
||||
用户名不能为空
|
||||
用户名不能是手机号码
|
||||
用户名或密码错误
|
||||
|
@ -23,7 +23,7 @@
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.13.0.120203">
|
||||
<PackageReference Include="SonarAnalyzer.CSharp" Version="10.14.0.120626">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
|
@ -15,7 +15,8 @@ public static class ISelectExtensions
|
||||
where TQuery : DataAbstraction, new()
|
||||
{
|
||||
if (req.IgnoreOwner) {
|
||||
me = me.DisableGlobalFilter(Chars.FLG_FREE_SQL_GLOBAL_FILTER_DATA);
|
||||
me = me.DisableGlobalFilter(Chars.FLG_FREE_SQL_GLOBAL_FILTER_SELF, Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT
|
||||
, Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT_WITH_CHILD);
|
||||
}
|
||||
|
||||
return me;
|
||||
|
@ -62,6 +62,23 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit
|
||||
return await GetExportFileStreamAsync<TExport>(fileName, list).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 唯一索引冲突处理
|
||||
/// </summary>
|
||||
protected static async Task OnUniqueIndexConflictAsync(Func<Task> actionTry, Func<Task> actionCatch = null)
|
||||
{
|
||||
try {
|
||||
await actionTry().ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex) when (ex.Message.Contains(Chars.FLG_DB_EXCEPTION_PRIMARY_KEY_CONFLICT) ||
|
||||
ex.Message.Contains(Chars.FLG_DB_EXCEPTION_UNIQUE_CONSTRAINT_CONFLICT) ||
|
||||
ex.Message.Contains(Chars.FLG_DB_EXCEPTION_IDX)) {
|
||||
if (actionCatch != null) {
|
||||
await actionCatch().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实体
|
||||
/// </summary>
|
||||
@ -85,7 +102,8 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit
|
||||
whereExp ??= a => a.Id.Equals(newValue.Id);
|
||||
var update = BuildUpdate(newValue, includeFields, excludeFields, ignoreVersion).Where(whereExp).Where(whereSql);
|
||||
if (disableGlobalDataFilter) {
|
||||
update = update.DisableGlobalFilter(nameof(Chars.FLG_FREE_SQL_GLOBAL_FILTER_DATA));
|
||||
update = update.DisableGlobalFilter(Chars.FLG_FREE_SQL_GLOBAL_FILTER_SELF, Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT
|
||||
, Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT_WITH_CHILD);
|
||||
}
|
||||
|
||||
return update.ExecuteEffectsAsync();
|
||||
@ -106,8 +124,8 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit
|
||||
TEntity newValue //
|
||||
, List<string> includeFields = null //
|
||||
, List<string> excludeFields = null //
|
||||
, Expression<Func<TEntity, bool>> whereExp = null //
|
||||
, string whereSql = null //
|
||||
, Expression<Func<TEntity, bool>> whereExp = null //
|
||||
, string whereSql = null //
|
||||
, bool ignoreVersion = false)
|
||||
{
|
||||
// 默认匹配主键
|
||||
|
@ -19,7 +19,7 @@ public sealed class UserNameAttribute : RegexAttribute
|
||||
public override bool IsValid(object value)
|
||||
{
|
||||
if (!base.IsValid(value)) {
|
||||
ErrorMessageResourceName = nameof(Ln.用户名长度4位以上);
|
||||
ErrorMessageResourceName = nameof(Ln.用户名不符合要求);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3,14 +3,23 @@ namespace NetAdmin.Domain;
|
||||
/// <summary>
|
||||
/// 数据基类
|
||||
/// </summary>
|
||||
public abstract record DataAbstraction
|
||||
public abstract record DataAbstraction : IValidatableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否已验证
|
||||
/// </summary>
|
||||
protected bool HasValidated { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 如果数据校验失败,抛出异常
|
||||
/// </summary>
|
||||
/// <exception cref="NetAdminValidateException">NetAdminValidateException</exception>
|
||||
public void ThrowIfInvalid()
|
||||
{
|
||||
if (HasValidated) {
|
||||
return;
|
||||
}
|
||||
|
||||
var validationResult = this.TryValidate();
|
||||
if (!validationResult.IsValid) {
|
||||
throw new NetAdminValidateException(validationResult.ValidationResults.ToDictionary( //
|
||||
@ -45,4 +54,19 @@ public abstract record DataAbstraction
|
||||
property.SetValue(this, s);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
{
|
||||
HasValidated = true;
|
||||
return ValidateInternal(validationContext);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 内部验证
|
||||
/// </summary>
|
||||
protected virtual IEnumerable<ValidationResult> ValidateInternal(ValidationContext validationContext)
|
||||
{
|
||||
yield return ValidationResult.Success;
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
namespace NetAdmin.Domain.Dto.Dependency;
|
||||
|
||||
/// <summary>
|
||||
/// 工作批请求
|
||||
/// </summary>
|
||||
public record JobReq : DataAbstraction
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理数量
|
||||
/// </summary>
|
||||
public int? Count { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// n秒以前
|
||||
/// </summary>
|
||||
public int? SecondsAgo { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 直到n秒前
|
||||
/// </summary>
|
||||
public int? UntilSecondsAgo { get; init; }
|
||||
}
|
@ -3,7 +3,7 @@ namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
|
||||
/// <summary>
|
||||
/// 请求:创建充值订单
|
||||
/// </summary>
|
||||
public record CreateDepositOrderReq : Sys_DepositOrder, IValidatableObject
|
||||
public record CreateDepositOrderReq : Sys_DepositOrder
|
||||
{
|
||||
/// <inheritdoc cref="Sys_DepositOrder.ActualPayAmount" />
|
||||
public override long ActualPayAmount { get; init; }
|
||||
@ -25,7 +25,7 @@ public record CreateDepositOrderReq : Sys_DepositOrder, IValidatableObject
|
||||
public override int ToPointRate { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
protected override IEnumerable<ValidationResult> ValidateInternal(ValidationContext validationContext)
|
||||
{
|
||||
if (PaymentMode != PaymentModes.USDT) {
|
||||
yield return new ValidationResult(Ln.支付方式不正确, [nameof(PaymentMode)]);
|
||||
|
@ -1,12 +0,0 @@
|
||||
namespace NetAdmin.Domain.Dto.Sys.DepositOrder;
|
||||
|
||||
/// <summary>
|
||||
/// 请求:到账确认
|
||||
/// </summary>
|
||||
public record ReceivedConfirmationReq : DataAbstraction
|
||||
{
|
||||
/// <summary>
|
||||
/// 读取前n条记录
|
||||
/// </summary>
|
||||
public int ReadRecordCount { get; init; }
|
||||
}
|
@ -5,9 +5,8 @@ namespace NetAdmin.Domain.Dto.Sys.Dept;
|
||||
/// </summary>
|
||||
public record CreateDeptReq : Sys_Dept
|
||||
{
|
||||
/// <inheritdoc cref="IFieldEnabled.Enabled" />
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
public override bool Enabled { get; init; }
|
||||
/// <inheritdoc />
|
||||
public override bool Enabled { get; init; } = true;
|
||||
|
||||
/// <inheritdoc cref="Sys_Dept.Name" />
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
|
@ -5,7 +5,7 @@ namespace NetAdmin.Domain.Dto.Sys.Role;
|
||||
/// <summary>
|
||||
/// 请求:创建角色
|
||||
/// </summary>
|
||||
public record CreateRoleReq : Sys_Role, IValidatableObject
|
||||
public record CreateRoleReq : Sys_Role
|
||||
{
|
||||
/// <summary>
|
||||
/// 角色-接口映射
|
||||
@ -58,7 +58,7 @@ public record CreateRoleReq : Sys_Role, IValidatableObject
|
||||
public override string Summary { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
protected override IEnumerable<ValidationResult> ValidateInternal(ValidationContext validationContext)
|
||||
{
|
||||
if (validationContext.MemberName != null) {
|
||||
DashboardLayout = JsonSerializer.Serialize(JsonDocument.Parse(DashboardLayout));
|
||||
|
@ -27,6 +27,11 @@ public record QueryUserRsp : Sys_User
|
||||
/// <inheritdoc cref="Sys_User.Dept" />
|
||||
public new virtual QueryDeptRsp Dept { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// 本部门以及子部门编号
|
||||
/// </summary>
|
||||
public List<long?> DeptIds { get; init; }
|
||||
|
||||
/// <inheritdoc cref="Sys_User.Email" />
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
public override string Email { get; init; }
|
||||
|
@ -6,7 +6,7 @@ namespace NetAdmin.Domain.Dto.Sys.VerifyCode;
|
||||
/// <summary>
|
||||
/// 请求:发送验证码
|
||||
/// </summary>
|
||||
public sealed record SendVerifyCodeReq : Sys_VerifyCode, IValidatableObject
|
||||
public sealed record SendVerifyCodeReq : Sys_VerifyCode
|
||||
{
|
||||
/// <inheritdoc cref="Sys_VerifyCode.DestDevice" />
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
|
||||
@ -35,7 +35,7 @@ public sealed record SendVerifyCodeReq : Sys_VerifyCode, IValidatableObject
|
||||
public VerifyCaptchaReq VerifyCaptchaReq { get; init; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
protected override IEnumerable<ValidationResult> ValidateInternal(ValidationContext validationContext)
|
||||
{
|
||||
ValidationResult validationResult;
|
||||
switch (DeviceType) {
|
||||
|
@ -3,7 +3,7 @@ namespace NetAdmin.Domain.Dto.Sys.WalletTrade;
|
||||
/// <summary>
|
||||
/// 请求:创建钱包交易
|
||||
/// </summary>
|
||||
public record CreateWalletTradeReq : Sys_WalletTrade, IValidatableObject
|
||||
public record CreateWalletTradeReq : Sys_WalletTrade
|
||||
{
|
||||
/// <inheritdoc cref="Sys_WalletTrade.Amount" />
|
||||
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
|
||||
@ -39,7 +39,7 @@ public record CreateWalletTradeReq : Sys_WalletTrade, IValidatableObject
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
|
||||
protected override IEnumerable<ValidationResult> ValidateInternal(ValidationContext validationContext)
|
||||
{
|
||||
var tradeDirection = TradeType.Attr<TradeAttribute>().Direction;
|
||||
if (Amount == 0 || (tradeDirection == TradeDirections.Income && Amount < 0) || (tradeDirection == TradeDirections.Expense && Amount > 0)) {
|
||||
|
@ -46,10 +46,11 @@ public static class Chars
|
||||
public const string FLG_DB_FIELD_TYPE_VARCHAR_7 = "varchar(7)";
|
||||
public const string FLG_DB_INDEX_PREFIX = "idx_{tablename}_";
|
||||
public const string FLG_DB_TABLE_NAME_PREFIX = "";
|
||||
public const string FLG_FREE_SQL_GLOBAL_FILTER_DATA = nameof(FLG_FREE_SQL_GLOBAL_FILTER_DATA);
|
||||
public const string FLG_FREE_SQL_GLOBAL_FILTER_SELF = nameof(FLG_FREE_SQL_GLOBAL_FILTER_SELF);
|
||||
public const string FLG_FREE_SQL_GLOBAL_FILTER_DEPT_WITH_CHILD = nameof(FLG_FREE_SQL_GLOBAL_FILTER_DEPT_WITH_CHILD);
|
||||
public const string FLG_FREE_SQL_GLOBAL_FILTER_DEPT = nameof(FLG_FREE_SQL_GLOBAL_FILTER_DEPT);
|
||||
public const string FLG_FREE_SQL_GLOBAL_FILTER_DELETE = nameof(FLG_FREE_SQL_GLOBAL_FILTER_DELETE);
|
||||
public const string FLG_FREE_SQL_GLOBAL_FILTER_MEMBER = nameof(FLG_FREE_SQL_GLOBAL_FILTER_MEMBER);
|
||||
public const string FLG_FREE_SQL_GLOBAL_FILTER_SELF = nameof(FLG_FREE_SQL_GLOBAL_FILTER_SELF);
|
||||
public const string FLG_FREE_SQL_GLOBAL_FILTER_TENANT = nameof(FLG_FREE_SQL_GLOBAL_FILTER_TENANT);
|
||||
public const string FLG_FRONT_APP_SET_HOME_GRID = "APP_SET_HOME_GRID";
|
||||
public const string FLG_HTTP_HEADER_KEY_ACCESS_TOKEN = "ACCESS-TOKEN";
|
||||
@ -108,7 +109,7 @@ public static class Chars
|
||||
public const string RGX_TELEPHONE = """^((\d{3,4}\-)|)\d{7,8}(|([-\u8f6c]{1}\d{1,5}))$""";
|
||||
public const string RGX_UP_AND_LOWER_NUMBER = """^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).+$""";
|
||||
public const string RGX_URL = """^(https?|ftp):\/\/[^\s/$.?#].[^\s]*\.[^\s]{2,}$""";
|
||||
public const string RGX_USERNAME = "^[a-zA-Z0-9_]{4,16}$";
|
||||
public const string RGX_USERNAME = """^[\u4e00-\u9fa5a-zA-Z0-9_-]{2,16}$""";
|
||||
public const string RGX_VERIFY_CODE = """^\d{4}$""";
|
||||
|
||||
public const string RGXL_CHINESE_NAME
|
||||
|
@ -5,7 +5,7 @@ namespace NetAdmin.Infrastructure.Utils;
|
||||
/// </summary>
|
||||
public static class PhoneNumberHelper
|
||||
{
|
||||
private static readonly IEnumerable<(string CallingCode, CountryCodes CountryCode)> _countryList;
|
||||
private static readonly ImmutableList<(string CallingCode, CountryCodes CountryCode)> _countryList;
|
||||
|
||||
#pragma warning disable S3963
|
||||
static PhoneNumberHelper()
|
||||
@ -21,7 +21,8 @@ public static class PhoneNumberHelper
|
||||
.OrderBy(x => x.Item1)
|
||||
.ThenByDescending(x => x.x.Attr<CountryInfoAttribute>().IsPreferred)
|
||||
.DistinctBy(x => x.Item1)
|
||||
.OrderByDescending(x => x.Item1.Length);
|
||||
.OrderByDescending(x => x.Item1.Length)
|
||||
.ToImmutableList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -29,7 +30,11 @@ public static class PhoneNumberHelper
|
||||
/// </summary>
|
||||
public static CountryCodes? PhoneNumberToCountryCode(string phoneNumber)
|
||||
{
|
||||
return _countryList.FirstOrDefault(x => phoneNumber.Replace("+", string.Empty).Trim().StartsWith(x.CallingCode, StringComparison.Ordinal))
|
||||
.CountryCode;
|
||||
phoneNumber = phoneNumber.Trim();
|
||||
if (phoneNumber.StartsWith('+')) {
|
||||
phoneNumber = phoneNumber[1..];
|
||||
}
|
||||
|
||||
return _countryList.FirstOrDefault(x => phoneNumber.StartsWith(x.CallingCode, StringComparison.Ordinal)).CountryCode;
|
||||
}
|
||||
}
|
@ -24,5 +24,5 @@ public interface IDepositOrderModule : ICrudModule<CreateDepositOrderReq, QueryD
|
||||
/// <summary>
|
||||
/// 到账确认
|
||||
/// </summary>
|
||||
Task<int> ReceivedConfirmationAsync(ReceivedConfirmationReq req);
|
||||
Task<int> ReceivedConfirmationAsync(JobReq req);
|
||||
}
|
@ -3,4 +3,10 @@ namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
|
||||
/// <summary>
|
||||
/// 部门服务
|
||||
/// </summary>
|
||||
public interface IDeptService : IService, IDeptModule;
|
||||
public interface IDeptService : IService, IDeptModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取所有子部门编号
|
||||
/// </summary>
|
||||
Task<IEnumerable<long>> GetChildDeptIdsAsync(long deptId);
|
||||
}
|
@ -3,4 +3,10 @@ namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
|
||||
/// <summary>
|
||||
/// 用户角-色映射服务
|
||||
/// </summary>
|
||||
public interface IUserRoleService : IService, IUserRoleModule;
|
||||
public interface IUserRoleService : IService, IUserRoleModule
|
||||
{
|
||||
/// <summary>
|
||||
/// 通过用户id删除
|
||||
/// </summary>
|
||||
Task<int> BulkDeleteByUserIdAsync(long userId);
|
||||
}
|
@ -146,7 +146,7 @@ public sealed class DepositOrderService(BasicRepository<Sys_DepositOrder, long>
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<int> ReceivedConfirmationAsync(ReceivedConfirmationReq req)
|
||||
public async Task<int> ReceivedConfirmationAsync(JobReq req)
|
||||
{
|
||||
req.ThrowIfInvalid();
|
||||
var ret = 0;
|
||||
@ -165,7 +165,7 @@ public sealed class DepositOrderService(BasicRepository<Sys_DepositOrder, long>
|
||||
})
|
||||
.ConfigureAwait(false)).ToList();
|
||||
var apiResult = await S<ITronScanClient>()
|
||||
.TransfersAsync(S<IOptions<TronScanOptions>>().Value.Token, req.ReadRecordCount, config.Trc20ReceiptAddress)
|
||||
.TransfersAsync(S<IOptions<TronScanOptions>>().Value.Token, req.Count!.Value, config.Trc20ReceiptAddress)
|
||||
.ConfigureAwait(false);
|
||||
foreach (var apiItem in apiResult.TokenTransfers.Where(x => x.TokenInfo.TokenAbbr == "USDT" && x.Confirmed && x.ContractRet == "SUCCESS" &&
|
||||
x.FinalResult == "SUCCESS")) {
|
||||
|
@ -104,6 +104,12 @@ public sealed class DeptService(BasicRepository<Sys_Dept, long> rpo) //
|
||||
return ret.Adapt<QueryDeptRsp>();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<IEnumerable<long>> GetChildDeptIdsAsync(long deptId)
|
||||
{
|
||||
return await Rpo.Where(a => a.Id == deptId).AsTreeCte().ToListAsync(a => a.Id).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<PagedQueryRsp<QueryDeptRsp>> PagedQueryAsync(PagedQueryReq<QueryDeptReq> req)
|
||||
{
|
||||
|
@ -122,7 +122,9 @@ public sealed class DicCatalogService(BasicRepository<Sys_DicCatalog, long> rpo)
|
||||
|
||||
private ISelect<Sys_DicCatalog> QueryInternal(QueryReq<QueryDicCatalogReq> req)
|
||||
{
|
||||
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
|
||||
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter)
|
||||
.WhereIf(req.Filter?.Id > 0, a => a.Id == req.Filter.Id)
|
||||
.WhereIf(req.Filter?.Code?.Length > 0, a => a.Code == req.Filter.Code);
|
||||
|
||||
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
|
||||
switch (req.Order) {
|
||||
|
@ -83,9 +83,15 @@ public sealed class UserInviteService(BasicRepository<Sys_UserInvite, long> rpo)
|
||||
public Task<List<long>> GetAssociatedUserIdAsync(long userId)
|
||||
{
|
||||
return Rpo.Orm.Select<Sys_UserInvite>()
|
||||
.DisableGlobalFilter(Chars.FLG_FREE_SQL_GLOBAL_FILTER_DATA)
|
||||
.DisableGlobalFilter(Chars.FLG_FREE_SQL_GLOBAL_FILTER_SELF, Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT
|
||||
, Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT_WITH_CHILD)
|
||||
.Where(a => a.Id == userId)
|
||||
.AsTreeCte(up: true, disableGlobalFilters: [Chars.FLG_FREE_SQL_GLOBAL_FILTER_DATA])
|
||||
.AsTreeCte( //
|
||||
up: true
|
||||
, disableGlobalFilters: [
|
||||
Chars.FLG_FREE_SQL_GLOBAL_FILTER_SELF, Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT
|
||||
, Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT_WITH_CHILD
|
||||
])
|
||||
.ToListAsync(a => a.Id);
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,12 @@ public sealed class UserRoleService(BasicRepository<Sys_UserRole, long> rpo) //
|
||||
return ret;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> BulkDeleteByUserIdAsync(long userId)
|
||||
{
|
||||
return Rpo.DeleteAsync(a => a.UserId == userId);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<long> CountAsync(QueryReq<QueryUserRoleReq> req)
|
||||
{
|
||||
|
@ -2,6 +2,7 @@ using NetAdmin.Application.Extensions;
|
||||
using NetAdmin.Domain.Attributes.DataValidation;
|
||||
using NetAdmin.Domain.Contexts;
|
||||
using NetAdmin.Domain.DbMaps.Sys;
|
||||
using NetAdmin.Domain.Dto.Sys.Dept;
|
||||
using NetAdmin.Domain.Dto.Sys.User;
|
||||
using NetAdmin.Domain.Dto.Sys.UserInvite;
|
||||
using NetAdmin.Domain.Dto.Sys.UserProfile;
|
||||
@ -9,6 +10,7 @@ using NetAdmin.Domain.Dto.Sys.UserWallet;
|
||||
using NetAdmin.Domain.Dto.Sys.VerifyCode;
|
||||
using NetAdmin.Domain.Events.Sys;
|
||||
using NetAdmin.Domain.Extensions;
|
||||
using Yitter.IdGenerator;
|
||||
|
||||
namespace NetAdmin.SysComponent.Application.Services.Sys;
|
||||
|
||||
@ -108,8 +110,10 @@ public sealed class UserService(
|
||||
req.ThrowIfInvalid();
|
||||
var roles = await CreateEditCheckAsync(req).ConfigureAwait(false);
|
||||
|
||||
var newDeptId = YitIdHelper.NextId();
|
||||
|
||||
// 主表
|
||||
var entity = req.Adapt<Sys_User>();
|
||||
var entity = req.Adapt<Sys_User>() with { DeptId = newDeptId };
|
||||
var dbUser = await Rpo.InsertAsync(entity).ConfigureAwait(false);
|
||||
|
||||
// 分表
|
||||
@ -134,6 +138,9 @@ public sealed class UserService(
|
||||
// 邀请表
|
||||
_ = await userInviteService.CreateAsync((req.Invite ?? new CreateUserInviteReq()) with { Id = dbUser.Id }).ConfigureAwait(false);
|
||||
|
||||
// 创建一个用户自己的部门
|
||||
_ = S<IDeptService>().CreateAsync(new CreateDeptReq { Id = newDeptId, Name = $"{req.UserName}的部门", ParentId = req.Invite?.OwnerDeptId ?? 0 });
|
||||
|
||||
// 发布用户创建事件
|
||||
var ret = userList.First();
|
||||
await eventPublisher.PublishAsync(new UserCreatedEvent(ret.Adapt<UserInfoRsp>())).ConfigureAwait(false);
|
||||
@ -477,7 +484,9 @@ public sealed class UserService(
|
||||
.IncludeMany(a => a.Roles, OtherIncludes)
|
||||
.ToOneAsync()
|
||||
.ConfigureAwait(false);
|
||||
return dbUser.Adapt<UserInfoRsp>();
|
||||
|
||||
var deptIds = await S<IDeptService>().GetChildDeptIdsAsync(dbUser.DeptId).ConfigureAwait(false);
|
||||
return dbUser.Adapt<UserInfoRsp>() with { DeptIds = deptIds.ToList().ConvertAll(x => (long?)x) };
|
||||
|
||||
static void OtherIncludes(ISelect<Sys_Role> select)
|
||||
{
|
||||
|
@ -79,7 +79,7 @@ public sealed class DepositOrderCache(IDistributedCache cache, IDepositOrderServ
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<int> ReceivedConfirmationAsync(ReceivedConfirmationReq req)
|
||||
public Task<int> ReceivedConfirmationAsync(JobReq req)
|
||||
{
|
||||
return Service.ReceivedConfirmationAsync(req);
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ public sealed class DepositOrderController(IDepositOrderCache cache)
|
||||
/// <summary>
|
||||
/// 到账确认
|
||||
/// </summary>
|
||||
public Task<int> ReceivedConfirmationAsync(ReceivedConfirmationReq req)
|
||||
public Task<int> ReceivedConfirmationAsync(JobReq req)
|
||||
{
|
||||
return Cache.ReceivedConfirmationAsync(req);
|
||||
}
|
||||
|
@ -5,7 +5,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="9.0.7"/>
|
||||
<PackageReference Include="xunit" Version="2.9.3"/>
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.2">
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.3">
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
</PackageReference>
|
||||
|
@ -21,9 +21,21 @@ public static class ServiceCollectionExtensions
|
||||
(Startup.Args.SyncStructure ? FreeSqlInitMethods.SyncStructure : FreeSqlInitMethods.None) |
|
||||
(Startup.Args.InsertSeedData ? FreeSqlInitMethods.InsertSeedData : FreeSqlInitMethods.None), freeSql => {
|
||||
// 数据权限过滤器
|
||||
// 本人
|
||||
_ = freeSql.GlobalFilter.ApplyOnlyIf<IFieldOwner>( //
|
||||
Chars.FLG_FREE_SQL_GLOBAL_FILTER_DATA, () => ContextUserInfo.Create()?.Roles.All(x => x.DataScope == DataScopes.Self) ?? false
|
||||
Chars.FLG_FREE_SQL_GLOBAL_FILTER_SELF, () => ContextUserInfo.Create()?.Roles.All(x => x.DataScope == DataScopes.Self) ?? false
|
||||
, a => a.OwnerId == ContextUserInfo.Create().Id);
|
||||
|
||||
// 本部门
|
||||
_ = freeSql.GlobalFilter.ApplyOnlyIf<IFieldOwner>( //
|
||||
Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT, () => ContextUserInfo.Create()?.Roles.All(x => x.DataScope == DataScopes.Dept) ?? false
|
||||
, a => ContextUserInfo.Create().DeptId == a.OwnerDeptId);
|
||||
|
||||
// 本部门和子部门
|
||||
_ = freeSql.GlobalFilter.ApplyOnlyIf<IFieldOwner>( //
|
||||
Chars.FLG_FREE_SQL_GLOBAL_FILTER_DEPT_WITH_CHILD
|
||||
, () => ContextUserInfo.Create()?.Roles.All(x => x.DataScope == DataScopes.DeptWithChild) ?? false
|
||||
, a => ContextUserInfo.Create().DeptIds.Contains(a.OwnerDeptId));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,16 +1,22 @@
|
||||
<template>
|
||||
<el-table-column v-bind="$attrs">
|
||||
<template #default="{ row }">
|
||||
<div :style="{ display: $TOOL.getNestedProperty(row, $attrs.prop) ? 'flex' : 'none' }" class="el-table-column-avatar">
|
||||
<div
|
||||
:style="{
|
||||
display: $TOOL.getNestedProperty(row, $attrs.prop) ? 'flex' : 'none',
|
||||
' justify-content': 'center',
|
||||
'align-items': 'center',
|
||||
gap: '0.5rem',
|
||||
}">
|
||||
<el-avatar
|
||||
v-if="$TOOL.getNestedProperty(row, $attrs.nestProp)"
|
||||
:src="getAvatar(row, $attrs.nestProp)"
|
||||
@click="click($TOOL.getNestedProperty(row, $attrs.prop))"
|
||||
size="small"
|
||||
style="cursor: pointer" />
|
||||
<div>
|
||||
<div style="line-height: 1.2rem">
|
||||
<p>{{ $TOOL.getNestedProperty(row, $attrs.nestProp) }}</p>
|
||||
<p v-if="$attrs.nestProp2">{{ $TOOL.getNestedProperty(row, $attrs.nestProp2) }}</p>
|
||||
<p v-if="$attrs.nestProp2" style="color: var(--el-color-info-light-3)">{{ $TOOL.getNestedProperty(row, $attrs.nestProp2) }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<save-dialog v-if="dialog.save" @closed="dialog.save = null" @mounted="$refs.saveDialog.open(dialog.save)" ref="saveDialog" />
|
||||
|
@ -1,5 +1,11 @@
|
||||
<template>
|
||||
<sc-dialog v-model="visible" :full-screen="dialogFullScreen" :title="titleMap[mode]" @closed="$emit(`closed`)" destroy-on-close>
|
||||
<sc-dialog
|
||||
v-model="visible"
|
||||
:full-screen="dialogFullScreen.includes(mode)"
|
||||
:title="titleMap[mode]"
|
||||
@closed="$emit(`closed`)"
|
||||
destroy-on-close
|
||||
ref="dialog">
|
||||
<div v-loading="loading">
|
||||
<el-tabs v-model="tabId" :class="{ 'hide-tabs': !tabs || !tabs[mode] || tabs[mode].length === 0 }">
|
||||
<el-tab-pane :label="$t(`基本信息`)" name="basic">
|
||||
@ -35,12 +41,14 @@
|
||||
v-else-if="typeof form[i] === `boolean` || item.isBoolean"
|
||||
v-model="form[i]"
|
||||
:disabled="item.disabled?.includes(mode)" />
|
||||
<component
|
||||
v-bind="item.detail?.props"
|
||||
v-else-if="item.detail?.vModelValue"
|
||||
v-model:value="form[i]"
|
||||
:disabled="item.disabled?.includes(mode)"
|
||||
:is="item.detail?.is ?? `el-input`" />
|
||||
<template v-else-if="item.detail?.vModelValue">
|
||||
<component
|
||||
v-bind="item.detail?.props"
|
||||
v-if="this.opened"
|
||||
v-model:value="form[i]"
|
||||
:disabled="item.disabled?.includes(mode)"
|
||||
:is="item.detail.is" />
|
||||
</template>
|
||||
<component
|
||||
v-bind="item.detail?.props"
|
||||
v-else
|
||||
@ -52,7 +60,7 @@
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane v-bind="item" v-for="(item, i) in tabs[mode]" v-if="tabs" :key="i" :name="item.name">
|
||||
<component v-if="item.name === tabId" :is="item.component" :ref="item.ref" @closed="paneClosed" />
|
||||
<component v-bind="item.props" v-if="item.name === tabId" :is="item.component" :row="form" @closed="paneClosed" />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
@ -66,12 +74,20 @@
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
watch: {
|
||||
mode(n) {
|
||||
if (this.dialogFullScreen.includes(n) && !this.$refs.dialog.isFullscreen) {
|
||||
this.$refs.dialog.setFullscreen()
|
||||
}
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mode: '',
|
||||
opened: false,
|
||||
tabId: `basic`,
|
||||
rules: {},
|
||||
visible: false,
|
||||
mode: `add`,
|
||||
loading: false,
|
||||
form: {},
|
||||
titleMap: {
|
||||
@ -115,11 +131,14 @@ export default {
|
||||
|
||||
if (data.row?.id) {
|
||||
const res = await this.$API[this.entityName].get.post({ id: data.row.id })
|
||||
Object.assign(this.form, res.data)
|
||||
Object.assign(this.form, this.$TOOL.nestedToDotNotation(res.data))
|
||||
this.titleMap.edit = this.$t(`编辑{summary}: {id}`, { summary: this.summary, id: this.form.id })
|
||||
this.titleMap.view = this.$t(`查看{summary}: {id}`, { summary: this.summary, id: this.form.id })
|
||||
} else {
|
||||
Object.assign(this.form, this.$TOOL.nestedToDotNotation(data.row))
|
||||
}
|
||||
this.loading = false
|
||||
this.opened = true
|
||||
return this
|
||||
},
|
||||
|
||||
@ -134,7 +153,7 @@ export default {
|
||||
}
|
||||
const method = this.mode === `add` ? this.$API[this.entityName].create : this.$API[this.entityName].edit
|
||||
try {
|
||||
const res = await method.post(this.form)
|
||||
const res = await method.post(this.$TOOL.dotNotationToNested(this.form))
|
||||
this.$emit(`success`, res.data, this.mode)
|
||||
this.visible = false
|
||||
this.$message.success(this.$t(`操作成功`))
|
||||
@ -155,7 +174,7 @@ export default {
|
||||
entityName: { type: String },
|
||||
summary: { type: String },
|
||||
columns: { type: Array },
|
||||
dialogFullScreen: { type: Boolean },
|
||||
dialogFullScreen: { type: Array },
|
||||
tabs: { type: Array },
|
||||
},
|
||||
}
|
||||
|
@ -45,21 +45,29 @@
|
||||
<!-- 表格主体-->
|
||||
<el-main class="nopadding">
|
||||
<sc-table
|
||||
v-bind="
|
||||
Object.assign(
|
||||
{
|
||||
queryApi: $API[entityName].pagedQuery,
|
||||
exportApi: $API[entityName].export,
|
||||
remoteFilter: true,
|
||||
remoteSort: true,
|
||||
rowKey: `id`,
|
||||
stripe: true,
|
||||
pageSize: 20,
|
||||
defaultSort: { prop: `id`, order: `descending` },
|
||||
},
|
||||
tableProps,
|
||||
)
|
||||
"
|
||||
:context-extra="this.table.menu.extra"
|
||||
:context-menus="Object.keys(this.columns)"
|
||||
:context-opers="this.operations"
|
||||
:default-sort="{ prop: `id`, order: `descending` }"
|
||||
:export-api="$API[entityName].export"
|
||||
:params="query"
|
||||
:query-api="$API[entityName].pagedQuery"
|
||||
:vue="this"
|
||||
@data-change="onDataChange"
|
||||
@selection-change="onSelectionChange"
|
||||
ref="table"
|
||||
remote-filter
|
||||
remote-sort
|
||||
row-key="id"
|
||||
stripe>
|
||||
ref="table">
|
||||
<el-table-column type="selection" width="50" />
|
||||
<template v-for="(item, i) in columns" :key="i">
|
||||
<component
|
||||
@ -160,6 +168,10 @@ export default {
|
||||
},
|
||||
},
|
||||
created() {
|
||||
for (const f of this.dyFilters) {
|
||||
this.query.dynamicFilter.filters.push(f)
|
||||
}
|
||||
|
||||
const searchFields = []
|
||||
this.searchControls = []
|
||||
for (const item in this.columns) {
|
||||
@ -274,7 +286,7 @@ export default {
|
||||
|
||||
// ---------------------------- ↓ 搜索栏事件 ----------------------------
|
||||
async onReset() {
|
||||
Object.entries(this.$refs.selectFilter.selected).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = [``]))
|
||||
Object.entries(this.$refs.selectFilter?.selected ?? []).forEach(([key, _]) => (this.$refs.selectFilter.selected[key] = [``]))
|
||||
},
|
||||
async onSearch(form) {
|
||||
if (Array.isArray(form.dy.createdTime)) {
|
||||
@ -299,6 +311,13 @@ export default {
|
||||
})
|
||||
}
|
||||
|
||||
for (const item of this.dyFilters) {
|
||||
const exists = this.query.dynamicFilter.filters.find((x) => x.field === item.field)
|
||||
if (!exists) {
|
||||
this.query.dynamicFilter.filters.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
await this.$refs.table.upData()
|
||||
},
|
||||
// ---------------------------- 搜索栏事件 ↑ ----------------------------
|
||||
@ -352,7 +371,13 @@ export default {
|
||||
loading?.close()
|
||||
},
|
||||
async onAddClick() {
|
||||
this.dialog.detail = { mode: `add` }
|
||||
const row = {}
|
||||
for (const i in this.columns) {
|
||||
if (this.columns[i].default) {
|
||||
Object.assign(row, Object.fromEntries([[i, this.columns[i].default]]))
|
||||
}
|
||||
}
|
||||
this.dialog.detail = { mode: `add`, row }
|
||||
},
|
||||
async onSwitchChange(row, method) {
|
||||
try {
|
||||
@ -420,8 +445,19 @@ export default {
|
||||
type: `root`,
|
||||
})
|
||||
}
|
||||
|
||||
for (const f of this.dyFilters) {
|
||||
this.$refs.search.selectInputKey = f.field
|
||||
this.$refs.search.form.dy[f.field] = f.value
|
||||
this.$refs.search.keeps.push({
|
||||
field: f.field,
|
||||
value: f.value,
|
||||
type: 'dy',
|
||||
})
|
||||
}
|
||||
},
|
||||
props: {
|
||||
dyFilters: { type: Array, default: [] },
|
||||
keywords: { type: String },
|
||||
entityName: { type: String },
|
||||
summary: { type: String },
|
||||
@ -431,8 +467,9 @@ export default {
|
||||
operations: { type: Array, default: [`view`, `add`, `edit`, `del`] },
|
||||
rowButtons: { type: Array, default: [] },
|
||||
rightButtons: { type: Array, default: [] },
|
||||
dialogFullScreen: { type: Boolean },
|
||||
dialogFullScreen: { type: Array, default: [] },
|
||||
tabs: { type: Array },
|
||||
tableProps: { type: Object, default: {} },
|
||||
},
|
||||
watch: {},
|
||||
}
|
||||
|
@ -635,3 +635,7 @@ textarea {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ace_placeholder {
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
@ -55,7 +55,7 @@ export default {
|
||||
global.user?.roles.findIndex((x) => x.ignorePermissionControl) >= 0
|
||||
? ['*/*/*']
|
||||
: preloads[1]?.data?.roles
|
||||
?.map((x) => x.apiIds.join(','))
|
||||
?.map((x) => (x.apiIds ?? []).join(','))
|
||||
?.join(',')
|
||||
.split(',')
|
||||
},
|
||||
|
@ -251,6 +251,59 @@ tool.objCopy = function (obj) {
|
||||
return JSON.parse(JSON.stringify(obj))
|
||||
}
|
||||
|
||||
/* 带点属性转嵌套对象 */
|
||||
tool.dotNotationToNested = function (obj) {
|
||||
const result = {}
|
||||
|
||||
for (const key in obj) {
|
||||
if (key.includes('.')) {
|
||||
// 处理带点的键
|
||||
const keys = key.split('.')
|
||||
let current = result
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const part = keys[i]
|
||||
|
||||
// 如果是最后一个部分,设置值
|
||||
if (i === keys.length - 1) {
|
||||
current[part] = obj[key]
|
||||
} else {
|
||||
// 如果不是最后一个部分,确保对象存在
|
||||
current[part] = current[part] || {}
|
||||
current = current[part]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 直接复制不带点的键
|
||||
result[key] = obj[key]
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
/* 将嵌套对象转带点属性 */
|
||||
tool.nestedToDotNotation = function (obj, prefix = '', result = {}) {
|
||||
// 处理 null 或 undefined 的情况
|
||||
if (obj === null || obj === undefined) {
|
||||
return result
|
||||
}
|
||||
|
||||
for (const key in obj) {
|
||||
// 更安全的属性检查方式
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const newKey = prefix ? `${prefix}.${key}` : key
|
||||
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
|
||||
// 递归处理嵌套对象
|
||||
this.nestedToDotNotation(obj[key], newKey, result)
|
||||
} else {
|
||||
// 基本类型或数组,直接赋值
|
||||
result[newKey] = obj[key]
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
/* 获取嵌套属性 */
|
||||
tool.getNestedProperty = function (obj, path) {
|
||||
if (!path) return null
|
||||
|
@ -2,7 +2,7 @@
|
||||
<common-page :title="$t('注册新账号')">
|
||||
<el-steps :active="stepActive" finish-status="success" simple>
|
||||
<el-step :title="$t('填写账号')" />
|
||||
<el-step v-if="config.registerInviteRequired" :title="$t('验证手机')" />
|
||||
<el-step v-if="config.registerMobileRequired" :title="$t('验证手机')" />
|
||||
<el-step :title="$t('注册成功')" />
|
||||
</el-steps>
|
||||
<el-form v-if="stepActive === 0" :model="form" :rules="rules" @keyup.enter="next" label-width="15rem" ref="stepForm_0" size="large">
|
||||
@ -49,11 +49,11 @@
|
||||
</div>
|
||||
<el-form size="large" style="text-align: center">
|
||||
<el-button v-if="stepActive > 0 && stepActive < 2" @click="pre" size="large">{{ $t('上一步') }}</el-button>
|
||||
<el-button v-if="stepActive < (this.config.registerInviteRequired ? 1 : 0)" @click="next" size="large" type="primary">{{
|
||||
<el-button v-if="stepActive < (this.config.registerMobileRequired ? 1 : 0)" @click="next" size="large" type="primary">{{
|
||||
$t('下一步')
|
||||
}}</el-button>
|
||||
<el-button
|
||||
v-if="stepActive === (this.config.registerInviteRequired ? 1 : 0)"
|
||||
v-if="stepActive === (this.config.registerMobileRequired ? 1 : 0)"
|
||||
:loading="loading"
|
||||
@click="save"
|
||||
size="large"
|
||||
|
@ -1,202 +1,79 @@
|
||||
<template>
|
||||
<el-container>
|
||||
<el-header v-loading="statistics.total === '...'" class="el-header-statistics">
|
||||
<el-row :gutter="15">
|
||||
<el-col :lg="24">
|
||||
<el-card shadow="never">
|
||||
<sc-statistic :title="$t('总数')" :value="statistics.total" group-separator />
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-header>
|
||||
<el-header>
|
||||
<div class="left-panel">
|
||||
<na-search
|
||||
:controls="[
|
||||
{
|
||||
type: 'select-input',
|
||||
field: [
|
||||
'dy',
|
||||
[
|
||||
{ label: $t('用户编号'), key: 'id' },
|
||||
{ label: $t('用户名'), key: 'user.userName' },
|
||||
],
|
||||
],
|
||||
placeholder: $t('匹配内容'),
|
||||
style: 'width:25rem',
|
||||
selectStyle: 'width:8rem',
|
||||
},
|
||||
]"
|
||||
:vue="this"
|
||||
@reset="onReset"
|
||||
@search="onSearch"
|
||||
dateFormat="YYYY-MM-DD HH:mm:ss"
|
||||
dateType="datetimerange"
|
||||
dateValueFormat="YYYY-MM-DD HH:mm:ss"
|
||||
ref="search" />
|
||||
</div>
|
||||
<div class="right-panel">
|
||||
<el-dropdown v-show="this.selection.length > 0">
|
||||
<el-button type="primary">
|
||||
{{ $t('批量操作') }}
|
||||
<el-icon>
|
||||
<el-icon-arrow-down />
|
||||
</el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="setCommissionRatio">设置返佣比率</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-header>
|
||||
<el-main class="nopadding">
|
||||
<sc-table
|
||||
:context-menus="['id', 'user.userName', 'createdTime', 'commissionRatio']"
|
||||
:context-opers="[]"
|
||||
:default-sort="{ prop: 'sort', order: 'descending' }"
|
||||
:params="query"
|
||||
:query-api="$API.sys_userinvite.query"
|
||||
:vue="this"
|
||||
@data-change="getStatistics"
|
||||
@selection-change="
|
||||
(items) => {
|
||||
selection = items
|
||||
}
|
||||
"
|
||||
default-expand-all
|
||||
hidePagination
|
||||
ref="table"
|
||||
remote-filter
|
||||
remote-sort
|
||||
row-key="id"
|
||||
stripe>
|
||||
<el-table-column type="selection" width="50" />
|
||||
<el-table-column :label="$t('用户编号')" prop="id" sortable="custom" />
|
||||
<na-col-avatar :label="$t('用户名')" prop="user.userName" />
|
||||
<el-table-column
|
||||
:formatter="(row) => `${(row.commissionRatio / 100).toFixed(2)}%`"
|
||||
:label="$t('返佣比率')"
|
||||
align="right"
|
||||
prop="commissionRatio"
|
||||
sortable="custom" />
|
||||
<el-table-column :label="$t('注册时间')" align="right" prop="createdTime" sortable="custom" />
|
||||
</sc-table>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</template>
|
||||
<na-table-page
|
||||
:columns="{
|
||||
'user.userName': {
|
||||
label: $t(`邀请关系`),
|
||||
show: [`list`],
|
||||
width: 300,
|
||||
},
|
||||
id: {
|
||||
headerAlign: `center`,
|
||||
is: `na-col-user`,
|
||||
clickOpenDialog: this.$GLOBAL.hasApiPermission(`api/sys/user/get`),
|
||||
nestProp: `user.userName`,
|
||||
nestProp2: `id`,
|
||||
width: 170,
|
||||
label: $t(`新用户`),
|
||||
show: [`list`],
|
||||
},
|
||||
createdTime: {
|
||||
label: $t(`注册时间`),
|
||||
align: `right`,
|
||||
show: [`list`],
|
||||
width: 170,
|
||||
},
|
||||
ownerId: {
|
||||
headerAlign: `center`,
|
||||
is: `na-col-user`,
|
||||
clickOpenDialog: this.$GLOBAL.hasApiPermission(`api/sys/user/get`),
|
||||
nestProp: `owner.userName`,
|
||||
nestProp2: `ownerId`,
|
||||
width: 170,
|
||||
label: $t(`邀请人`),
|
||||
show: [`list`],
|
||||
},
|
||||
}"
|
||||
:dy-filters="dyFilters"
|
||||
:operations="operations"
|
||||
:row-buttons="[{ title: $t(`更改邀请人`), click: modifyInviter }]"
|
||||
:summary="$t(`号码明细`)"
|
||||
:table-props="{
|
||||
queryApi: $API.sys_userinvite.query,
|
||||
defaultExpandAll: true,
|
||||
hidePagination: true,
|
||||
}"
|
||||
entity-name="sys_userinvite" />
|
||||
|
||||
<set-inviter-dialog
|
||||
v-if="dialog.setInviter"
|
||||
@closed="dialog.setInviter = null"
|
||||
@mounted="$refs.setInviterDialog.open(dialog.setInviter)"
|
||||
@success="(data, mode) => table.handleUpdate($refs.table, data, mode)"
|
||||
ref="setInviterDialog"></set-inviter-dialog>
|
||||
</template>
|
||||
<script>
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
import table from '@/config/table'
|
||||
import naColOperation from '@/config/na-col-operation'
|
||||
|
||||
const naColAvatar = defineAsyncComponent(() => import('@/components/na-col-avatar'))
|
||||
const setInviterDialog = defineAsyncComponent(() => import('./set-inviter'))
|
||||
export default {
|
||||
components: {
|
||||
naColAvatar,
|
||||
},
|
||||
computed: {
|
||||
naColOperation() {
|
||||
return naColOperation
|
||||
},
|
||||
table() {
|
||||
return table
|
||||
},
|
||||
setInviterDialog,
|
||||
},
|
||||
created() {},
|
||||
data() {
|
||||
return {
|
||||
statistics: {
|
||||
total: '...',
|
||||
},
|
||||
dialog: {},
|
||||
loading: false,
|
||||
query: {
|
||||
dynamicFilter: {
|
||||
filters: [],
|
||||
},
|
||||
filter: {},
|
||||
keywords: this.keywords,
|
||||
},
|
||||
selection: [],
|
||||
dyFilters: [],
|
||||
operations: [],
|
||||
}
|
||||
},
|
||||
inject: ['reload'],
|
||||
methods: {
|
||||
async setCommissionRatio() {
|
||||
let loading
|
||||
try {
|
||||
const prompt = await this.$prompt(this.$t('1 代表 0.01%'), this.$t('设置返佣比率'), {
|
||||
inputPattern: /^[0-9]\d*$/,
|
||||
inputErrorMessage: this.$t('返佣比率不正确'),
|
||||
})
|
||||
loading = this.$loading()
|
||||
const res = await Promise.all(
|
||||
this.selection.map((x) => this.$API.sys_userinvite.setCommissionRatio.post(Object.assign(x, { commissionRatio: prompt.value }))),
|
||||
)
|
||||
this.$message.success(
|
||||
this.$t(`操作成功 {count}/{total} 项`, {
|
||||
count: this.selection.length,
|
||||
total: res.map((x) => x.data ?? 0).reduce((a, b) => a + b, 0),
|
||||
}),
|
||||
)
|
||||
this.$refs.table.refresh()
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
loading?.close()
|
||||
},
|
||||
async getStatistics() {
|
||||
this.statistics.total = this.$refs.table?.tableData?.length
|
||||
},
|
||||
//重置
|
||||
onReset() {},
|
||||
//搜索
|
||||
async onSearch(form) {
|
||||
if (Array.isArray(form.dy.createdTime)) {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'createdTime',
|
||||
operator: 'dateRange',
|
||||
value: form.dy.createdTime.map((x) => x.replace(/ 00:00:00$/, '')),
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy['id'] === 'string' && form.dy['id'].trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'id',
|
||||
operator: 'eq',
|
||||
value: form.dy['id'],
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof form.dy['user.userName'] === 'string' && form.dy['user.userName'].trim() !== '') {
|
||||
this.query.dynamicFilter.filters.push({
|
||||
field: 'user.userName',
|
||||
operator: 'eq',
|
||||
value: form.dy['user.userName'],
|
||||
})
|
||||
}
|
||||
|
||||
await this.$refs.table.upData()
|
||||
modifyInviter(row) {
|
||||
// this.dialog.setInviter = { data: row }
|
||||
},
|
||||
},
|
||||
async mounted() {
|
||||
if (this.keywords) {
|
||||
this.$refs.search.form.root.keywords = this.keywords
|
||||
this.$refs.search.keeps.push({
|
||||
field: 'keywords',
|
||||
value: this.keywords,
|
||||
type: 'root',
|
||||
})
|
||||
}
|
||||
|
||||
this.onReset()
|
||||
props: {
|
||||
row: { type: Object },
|
||||
},
|
||||
props: ['keywords'],
|
||||
watch: {},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped />
|
59
src/frontend/admin/src/views/sys/invite/set-inviter.vue
Normal file
59
src/frontend/admin/src/views/sys/invite/set-inviter.vue
Normal file
@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<sc-dialog v-model="visible" :title="$t(`修改邀请人`)" @closed="$emit('closed')" destroy-on-close>
|
||||
<el-form :model="form" label-position="right" label-width="12rem" ref="form">
|
||||
<el-form-item :label="$t(`用户`)">
|
||||
<el-input v-model="form.user.userName" disabled placeholder="placeholder"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t(`邀请人`)">
|
||||
<sc-select
|
||||
v-model="form.ownerId"
|
||||
:config="{ props: { label: `userName`, value: `id` } }"
|
||||
:query-api="$API.sys_user.query"
|
||||
clearable
|
||||
filterable />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="visible = false">取消</el-button>
|
||||
<el-button :disabled="loading" :loading="loading" @click="submit" type="primary">保存</el-button>
|
||||
</template>
|
||||
</sc-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
components: {},
|
||||
data() {
|
||||
return {
|
||||
loading: true,
|
||||
visible: false,
|
||||
form: {},
|
||||
}
|
||||
},
|
||||
emits: ['success', 'closed', 'mounted'],
|
||||
methods: {
|
||||
//显示
|
||||
async open(data) {
|
||||
this.visible = true
|
||||
this.loading = true
|
||||
Object.assign(this.form, data.data)
|
||||
this.loading = false
|
||||
return this
|
||||
},
|
||||
|
||||
//表单提交方法
|
||||
async submit() {
|
||||
this.loading = true
|
||||
this.$API.sys_userinvite.edit.post(this.form)
|
||||
this.$emit('success')
|
||||
this.visible = false
|
||||
this.loading = false
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$emit('mounted')
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped />
|
@ -158,7 +158,7 @@
|
||||
align="center"
|
||||
prop="dataScope"
|
||||
sortable="custom"
|
||||
width="120" />
|
||||
width="180" />
|
||||
<el-table-column :label="$t('显示仪表板')" align="center" prop="displayDashboard" sortable="custom" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.displayDashboard" @change="changeDisplayDashboard($event, row)" />
|
||||
|
@ -295,7 +295,7 @@ export default {
|
||||
userName: [
|
||||
{
|
||||
required: true,
|
||||
message: '4位以上字母、数字或下划线',
|
||||
message: '2位以上中文数字或字母',
|
||||
pattern: this.$GLOBAL.chars.RGX_USERNAME,
|
||||
},
|
||||
],
|
||||
|
Reference in New Issue
Block a user