feat: 营销管理

This commit is contained in:
tk
2025-06-30 15:00:17 +08:00
committed by nsnail
parent f6aee1be0f
commit 62ac7790e8
54 changed files with 1125 additions and 38 deletions

View File

@ -57,11 +57,11 @@ USDT
已校验
已读
并且
归属角色
归属部门
微信支付
成功
或者
归属角色
归属部门
手机
手机号
执行耗时
@ -108,6 +108,7 @@ USDT
用户代理
用户名
用户导出
用户邀请导出
用户钱包导出
电子邮箱

View File

@ -107,6 +107,27 @@
"Title": "充值订单",
"Type": 1
},
// ------------------------------ 营销管理 ------------------------------
{
"Icon": "el-icon-share",
"Id": 692575802241032,
"Name": "market",
"Path": "/market",
"Sort": 98,
"Title": "营销管理",
"Type": 1
},
{
"Component": "sys/invite",
"Icon": "el-icon-connection",
"Id": 692575802245126,
"Name": "sys/invite",
"ParentId": 692575802241032,
"Path": "/market/invite",
"Sort": 100,
"Title": "邀请管理",
"Type": 1
},
// ------------------------------ 系统管理 ------------------------------
{
"Icon": "sc-icon-App",

View File

@ -102,5 +102,9 @@
{
"ApiId": "api/sys/deposit.order/pay.confirm",
"RoleId": 371729946431493,
},
{
"ApiId": "api/sys/user.invite/query",
"RoleId": 371729946431493,
}
]

View File

@ -30,5 +30,13 @@
{
"MenuId": 690907673255944,
"RoleId": 371729946431493
},
{
"MenuId": 692575802241032,
"RoleId": 371729946431493
},
{
"MenuId": 692575802245126,
"RoleId": 371729946431493
}
]

View File

@ -3,24 +3,27 @@
"DeptId": 372119301627909,
"Enabled": true,
"Id": 370942943322181,
"InviteCode": "Q09Y8O",
"Password": "A8E87D23-49BC-25A1-1C7C-59186BEF5D15",
"Token": "A9AFD92E-A33D-4152-9A6C-A9C141D24887",
"UserName": "root"
"UserName": "root",
},
{
"DeptId": 372119301627909,
"Enabled": true,
"Id": 560217289236492,
"InviteCode": "7ZH5PB",
"Password": "A8E87D23-49BC-25A1-1C7C-59186BEF5D15",
"Token": "4208EA97-B32F-4E39-A290-4C0D27B61EBF",
"UserName": "user"
"UserName": "user",
},
{
"DeptId": 372119301627909,
"Enabled": true,
"Id": 664362432344581,
"InviteCode": "47Q56H",
"Password": "A8E87D23-49BC-25A1-1C7C-59186BEF5D15",
"Token": "751D599B-2B8C-417C-9565-CCF2363F5F6F",
"UserName": "jobs"
"UserName": "jobs",
}
]

View File

@ -0,0 +1,15 @@
[
{
"Id": 370942943322181,
},
{
"Id": 560217289236492,
"OwnerDeptId": 372119301627909,
"OwnerId": 370942943322181,
},
{
"Id": 664362432344581,
"OwnerDeptId": 372119301627909,
"OwnerId": 370942943322181,
}
]

View File

@ -71,6 +71,7 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit
/// <param name="whereExp">查询表达式</param>
/// <param name="whereSql">查询sql</param>
/// <param name="ignoreVersion">是否忽略版本锁</param>
/// <param name="disableGlobalDataFilter">是否忽略全局数据权限过滤</param>
/// <returns>更新行数</returns>
protected Task<int> UpdateAsync( //
TEntity newValue //
@ -78,11 +79,15 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit
, List<string> excludeFields = null //
, Expression<Func<TEntity, bool>> whereExp = null //
, string whereSql = null //
, bool ignoreVersion = false)
, bool ignoreVersion = false, bool disableGlobalDataFilter = false)
{
// 默认匹配主键
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));
}
return update.ExecuteEffectsAsync();
}
@ -101,8 +106,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)
{
// 默认匹配主键

View File

@ -22,6 +22,22 @@ public record Sys_Config : VersionEntity, IFieldEnabled
[JsonIgnore]
public virtual bool Enabled { get; init; }
/// <summary>
/// 必须邀请注册
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual bool RegisterInviteRequired { get; init; }
/// <summary>
/// 必须手机号注册
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual bool RegisterMobileRequired { get; init; }
/// <summary>
/// Trc20收款地址
/// </summary>

View File

@ -5,9 +5,10 @@ namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 用户基本信息表
/// </summary>
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Email), nameof(Email), true)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Mobile), nameof(Mobile), true)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(UserName), nameof(UserName), true)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Email), nameof(Email), true)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(Mobile), nameof(Mobile), true)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(InviteCode), nameof(InviteCode), true)]
[SqlIndex(Chars.FLG_DB_INDEX_PREFIX + nameof(UserName), nameof(UserName), true)]
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_User))]
public record Sys_User : VersionEntity, IFieldSummary, IFieldEnabled, IRegister
{
@ -51,6 +52,21 @@ public record Sys_User : VersionEntity, IFieldSummary, IFieldEnabled, IRegister
[JsonIgnore]
public virtual bool Enabled { get; init; }
/// <summary>
/// 用户邀请
/// </summary>
[CsvIgnore]
[JsonIgnore]
public Sys_UserInvite Invite { get; init; }
/// <summary>
/// 邀请码
/// </summary>
[Column(DbType = Chars.FLG_DB_FIELD_TYPE_CHAR_6)]
[CsvIgnore]
[JsonIgnore]
public virtual string InviteCode { get; init; }
/// <summary>
/// 最后登录时间
/// </summary>

View File

@ -0,0 +1,48 @@
namespace NetAdmin.Domain.DbMaps.Sys;
/// <summary>
/// 用户邀请表
/// </summary>
[Table(Name = Chars.FLG_DB_TABLE_NAME_PREFIX + nameof(Sys_UserInvite))]
public record Sys_UserInvite : VersionEntity, IFieldOwner
{
/// <summary>
/// 子节点
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(OwnerId))]
public IEnumerable<Sys_UserInvite> Children { get; init; }
/// <summary>
/// 归属
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(OwnerId))]
public Sys_User Owner { get; init; }
/// <summary>
/// 归属部门编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerDeptId { get; init; }
/// <summary>
/// 归属用户编号
/// </summary>
[Column]
[CsvIgnore]
[JsonIgnore]
public virtual long? OwnerId { get; init; }
/// <summary>
/// 用户
/// </summary>
[CsvIgnore]
[JsonIgnore]
[Navigate(nameof(Id))]
public Sys_User User { get; init; }
}

View File

@ -14,6 +14,14 @@ public record CreateConfigReq : Sys_Config
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool Enabled { get; init; }
/// <inheritdoc cref="Sys_Config.RegisterInviteRequired" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool RegisterInviteRequired { get; init; }
/// <inheritdoc cref="Sys_Config.RegisterMobileRequired" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool RegisterMobileRequired { get; init; }
/// <inheritdoc cref="Sys_Config.Trc20ReceiptAddress" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
[Length(34, 34)]

View File

@ -24,6 +24,14 @@ public record QueryConfigRsp : Sys_Config
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="Sys_Config.RegisterInviteRequired" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool RegisterInviteRequired { get; init; }
/// <inheritdoc cref="Sys_Config.RegisterMobileRequired" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override bool RegisterMobileRequired { get; init; }
/// <inheritdoc cref="Sys_Config.Trc20ReceiptAddress" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string Trc20ReceiptAddress { get; init; }

View File

@ -0,0 +1,13 @@
namespace NetAdmin.Domain.Dto.Sys.User;
/// <summary>
/// 请求:检查邀请码是否正确
/// </summary>
public sealed record CheckInviterAvailableReq : DataAbstraction
{
/// <summary>
/// 邀请码
/// </summary>
[Required]
public string Code { get; init; }
}

View File

@ -1,3 +1,4 @@
using NetAdmin.Domain.Dto.Sys.UserInvite;
using NetAdmin.Domain.Dto.Sys.UserProfile;
namespace NetAdmin.Domain.Dto.Sys.User;
@ -7,6 +8,15 @@ namespace NetAdmin.Domain.Dto.Sys.User;
/// </summary>
public sealed record CreateUserReq : CreateEditUserReq
{
/// <inheritdoc />
public override bool Enabled { get; init; } = true;
/// <inheritdoc cref="Sys_User.Invite" />
public new CreateUserInviteReq Invite { get; init; }
/// <inheritdoc />
public override string InviteCode { get; init; } = ((long)new[] { 60466176, int.MaxValue }.Rand()).Sys36().ToUpperInvariant();
/// <inheritdoc cref="CreateEditUserReq.PasswordText" />
[Required(ErrorMessageResourceType = typeof(Ln), ErrorMessageResourceName = nameof(Ln.密码不能为空))]
public override string PasswordText { get; init; }
@ -18,6 +28,7 @@ public sealed record CreateUserReq : CreateEditUserReq
public override void Register(TypeAdapterConfig config)
{
_ = config.ForType<RegisterUserReq, CreateUserReq>() //
.Ignore(a => a.InviteCode)
.Map(d => d.Mobile, s => s.VerifySmsCodeReq.DestDevice)
//

View File

@ -39,6 +39,10 @@ public record QueryUserRsp : Sys_User
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="Sys_User.InviteCode" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string InviteCode { get; init; }
/// <inheritdoc cref="Sys_User.LastLoginTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override DateTime? LastLoginTime { get; init; }

View File

@ -7,6 +7,11 @@ namespace NetAdmin.Domain.Dto.Sys.User;
/// </summary>
public sealed record RegisterUserReq : Sys_User
{
/// <summary>
/// 邀请者的邀请码
/// </summary>
public string Inviter { get; init; }
/// <summary>
/// 密码
/// </summary>

View File

@ -0,0 +1,6 @@
namespace NetAdmin.Domain.Dto.Sys.UserInvite;
/// <summary>
/// 请求:创建用户邀请
/// </summary>
public record CreateUserInviteReq : Sys_UserInvite;

View File

@ -0,0 +1,15 @@
namespace NetAdmin.Domain.Dto.Sys.UserInvite;
/// <summary>
/// 请求:编辑用户邀请
/// </summary>
public record EditUserInviteReq : CreateUserInviteReq
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -0,0 +1,11 @@
namespace NetAdmin.Domain.Dto.Sys.UserInvite;
/// <summary>
/// 请求:查询用户邀请
/// </summary>
public sealed record QueryUserInviteReq : Sys_UserInvite
{
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
}

View File

@ -0,0 +1,60 @@
using NetAdmin.Domain.Dto.Sys.User;
namespace NetAdmin.Domain.Dto.Sys.UserInvite;
/// <summary>
/// 响应:查询用户邀请
/// </summary>
public record QueryUserInviteRsp : Sys_UserInvite
{
/// <inheritdoc cref="Sys_UserInvite.Children" />
public new virtual IEnumerable<QueryUserInviteRsp> Children { get; init; }
/// <inheritdoc cref="IFieldCreatedTime.CreatedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override DateTime CreatedTime { get; init; }
/// <inheritdoc cref="IFieldCreatedUser.CreatedUserId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? CreatedUserId { get; init; }
/// <inheritdoc cref="IFieldCreatedUser.CreatedUserName" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string CreatedUserName { get; init; }
/// <inheritdoc cref="EntityBase{T}.Id" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Id { get; init; }
/// <inheritdoc cref="IFieldModifiedTime.ModifiedTime" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override DateTime? ModifiedTime { get; init; }
/// <inheritdoc cref="IFieldModifiedUser.ModifiedUserId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? ModifiedUserId { get; init; }
/// <inheritdoc cref="IFieldModifiedUser.ModifiedUserName" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override string ModifiedUserName { get; init; }
/// <inheritdoc cref="Sys_UserInvite.Owner" />
[CsvIgnore]
public new virtual QueryUserRsp Owner { get; init; }
/// <inheritdoc cref="IFieldOwner.OwnerDeptId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? OwnerDeptId { get; init; }
/// <inheritdoc cref="IFieldOwner.OwnerId" />
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public override long? OwnerId { get; init; }
/// <inheritdoc cref="Sys_UserInvite.User" />
[CsvIgnore]
public new virtual QueryUserRsp User { get; init; }
/// <inheritdoc cref="IFieldVersion.Version" />
[JsonIgnore(Condition = JsonIgnoreCondition.Never)]
public override long Version { get; init; }
}

View File

@ -1,5 +1,3 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.UserWallet;
/// <summary>

View File

@ -1,5 +1,3 @@
using NetAdmin.Domain.DbMaps.Sys;
namespace NetAdmin.Domain.Dto.Sys.WalletTrade;
/// <summary>

View File

@ -46,7 +46,7 @@ public sealed class CollectionJsonTypeInfoResolver : DefaultJsonTypeInfoResolver
/// </summary>
private static PropertyInfo GetNewProperty(string memberName, object obj)
{
return obj.GetType().GetProperties().Where(x => x.Name == memberName).First(x => x.DeclaringType == x.ReflectedType);
return obj.GetType().GetProperties().Where(x => x.Name == memberName).FirstOrDefault(x => x.DeclaringType == x.ReflectedType);
}
/// <summary>
@ -61,7 +61,7 @@ public sealed class CollectionJsonTypeInfoResolver : DefaultJsonTypeInfoResolver
}
catch (AmbiguousMatchException) {
// 这里处理子类new隐藏父类同名属性 取得多个同名属性的问题
prop = GetNewProperty(memberName, obj).GetValue(obj);
prop = GetNewProperty(memberName, obj)?.GetValue(obj);
}
return prop switch { string => prop, ICollection { Count: > 0 } => prop, _ => null };
@ -79,7 +79,7 @@ public sealed class CollectionJsonTypeInfoResolver : DefaultJsonTypeInfoResolver
obj.GetType().GetProperty(memberName!)?.SetValue(obj, val);
}
catch (AmbiguousMatchException) {
GetNewProperty(memberName, obj).SetValue(obj, val);
GetNewProperty(memberName, obj)?.SetValue(obj, val);
}
};
}

View File

@ -19,6 +19,7 @@ public static class Chars
public const string FLG_DB_EXCEPTION_PRIMARY_KEY_CONFLICT = "PRIMARY KEY";
public const string FLG_DB_EXCEPTION_UNIQUE_CONSTRAINT_CONFLICT = "UNIQUE constraint";
public const string FLG_DB_FIELD_TYPE_CHAR_34 = "char(34)";
public const string FLG_DB_FIELD_TYPE_CHAR_6 = "char(6)";
public const string FLG_DB_FIELD_TYPE_NVARCHAR = "nvarchar";
public const string FLG_DB_FIELD_TYPE_NVARCHAR_1022 = "nvarchar(1022)";
public const string FLG_DB_FIELD_TYPE_NVARCHAR_127 = "nvarchar(127)";

View File

@ -8,7 +8,7 @@
<PackageReference Include="Gurion" Version="1.2.15" Label="refs"/>
<PackageReference Include="Microsoft.Extensions.Caching.StackExchangeRedis" Version="9.0.6"/>
<PackageReference Include="Minio" Version="6.0.5"/>
<PackageReference Include="NSExt" Version="2.3.6"/>
<PackageReference Include="NSExt" Version="2.3.7"/>
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.6"/>
</ItemGroup>
<ItemGroup>

View File

@ -16,6 +16,11 @@ public interface IConfigModule : ICrudModule<CreateConfigReq, QueryConfigRsp //
/// </summary>
Task<QueryConfigRsp> GetLatestConfigAsync();
/// <summary>
/// 获取注册配置
/// </summary>
Task<QueryConfigRsp> GetRegisterConfigAsync();
/// <summary>
/// 设置配置启用状态
/// </summary>

View File

@ -0,0 +1,12 @@
using NetAdmin.Domain.Dto.Sys.UserInvite;
namespace NetAdmin.SysComponent.Application.Modules.Sys;
/// <summary>
/// 用户邀请模块
/// </summary>
public interface IUserInviteModule : ICrudModule<CreateUserInviteReq, QueryUserInviteRsp // 创建类型
, EditUserInviteReq // 编辑类型
, QueryUserInviteReq, QueryUserInviteRsp // 查询类型
, DelReq // 删除类型
>;

View File

@ -12,6 +12,11 @@ public partial interface IUserModule : ICrudModule<CreateUserReq, QueryUserRsp /
, DelReq // 删除类型
>
{
/// <summary>
/// 检查邀请码是否正确
/// </summary>
Task<bool> CheckInviterAvailableAsync(CheckInviterAvailableReq req);
/// <summary>
/// 检查手机号码是否可用
/// </summary>

View File

@ -92,6 +92,16 @@ public sealed class ConfigService(BasicRepository<Sys_Config, long> rpo) //
return ret.FirstOrDefault();
}
/// <inheritdoc />
public async Task<QueryConfigRsp> GetRegisterConfigAsync()
{
var latestConfigAsync = await GetLatestConfigAsync().ConfigureAwait(false);
return new QueryConfigRsp {
RegisterInviteRequired = latestConfigAsync.RegisterInviteRequired
, RegisterMobileRequired = latestConfigAsync.RegisterMobileRequired
};
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryConfigRsp>> PagedQueryAsync(PagedQueryReq<QueryConfigReq> req)
{

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
/// <summary>
/// 用户邀请服务
/// </summary>
public interface IUserInviteService : IService, IUserInviteModule;

View File

@ -0,0 +1,130 @@
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.UserInvite;
using NetAdmin.Domain.Extensions;
namespace NetAdmin.SysComponent.Application.Services.Sys;
/// <inheritdoc cref="IUserInviteService" />
public sealed class UserInviteService(BasicRepository<Sys_UserInvite, long> rpo) //
: RepositoryService<Sys_UserInvite, long, IUserInviteService>(rpo), IUserInviteService
{
/// <inheritdoc />
public async Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
req.ThrowIfInvalid();
var ret = 0;
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var item in req.Items) {
ret += await DeleteAsync(item).ConfigureAwait(false);
}
return ret;
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryUserInviteReq> req)
{
req.ThrowIfInvalid();
return QueryInternal(req).WithNoLockNoWait().CountAsync();
}
/// <inheritdoc />
public async Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryUserInviteReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req with { Order = Orders.None })
.WithNoLockNoWait()
.GroupBy(req.GetToListExp<Sys_UserInvite>())
.ToDictionaryAsync(a => a.Count())
.ConfigureAwait(false);
return ret.Select(x => new KeyValuePair<IImmutableDictionary<string, string>, int>(
req.RequiredFields.ToImmutableDictionary(
y => y, y => typeof(Sys_UserInvite).GetProperty(y)!.GetValue(x.Key)?.ToString()), x.Value))
.Where(x => x.Key.Any(y => !y.Value.NullOrEmpty()))
.OrderByDescending(x => x.Value);
}
/// <inheritdoc />
public async Task<QueryUserInviteRsp> CreateAsync(CreateUserInviteReq req)
{
req.ThrowIfInvalid();
var ret = await Rpo.InsertAsync(req).ConfigureAwait(false);
return ret.Adapt<QueryUserInviteRsp>();
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
req.ThrowIfInvalid();
return Rpo.DeleteAsync(a => a.Id == req.Id);
}
/// <inheritdoc />
public async Task<QueryUserInviteRsp> EditAsync(EditUserInviteReq req)
{
req.ThrowIfInvalid();
#if DBTYPE_SQLSERVER
return (await UpdateReturnListAsync(req).ConfigureAwait(false)).FirstOrDefault()?.Adapt<QueryUserInviteRsp>();
#else
return await UpdateAsync(req).ConfigureAwait(false) > 0 ? await GetAsync(new QueryUserInviteReq { Id = req.Id }).ConfigureAwait(false) : null;
#endif
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryUserInviteReq> req)
{
req.ThrowIfInvalid();
return ExportAsync<QueryUserInviteReq, QueryUserInviteRsp>(QueryInternal, req, Ln.);
}
/// <inheritdoc />
public async Task<QueryUserInviteRsp> GetAsync(QueryUserInviteReq req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(new QueryReq<QueryUserInviteReq> { Filter = req, Order = Orders.None }).ToOneAsync().ConfigureAwait(false);
return ret.Adapt<QueryUserInviteRsp>();
}
/// <inheritdoc />
public async Task<PagedQueryRsp<QueryUserInviteRsp>> PagedQueryAsync(PagedQueryReq<QueryUserInviteReq> req)
{
req.ThrowIfInvalid();
var list = await QueryInternal(req)
.Page(req.Page, req.PageSize)
.WithNoLockNoWait()
.Count(out var total)
.ToListAsync(req)
.ConfigureAwait(false);
return new PagedQueryRsp<QueryUserInviteRsp>(req.Page, req.PageSize, total, list.Adapt<IEnumerable<QueryUserInviteRsp>>());
}
/// <inheritdoc />
public async Task<IEnumerable<QueryUserInviteRsp>> QueryAsync(QueryReq<QueryUserInviteReq> req)
{
req.ThrowIfInvalid();
var ret = await QueryInternal(req).Include(a => a.Owner).Include(a => a.User).WithNoLockNoWait().ToTreeListAsync().ConfigureAwait(false);
return ret.Adapt<IEnumerable<QueryUserInviteRsp>>();
}
private ISelect<Sys_UserInvite> QueryInternal(QueryReq<QueryUserInviteReq> req)
{
var ret = Rpo.Select.WhereDynamicFilter(req.DynamicFilter).WhereDynamic(req.Filter);
// ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault
switch (req.Order) {
case Orders.None:
return ret;
case Orders.Random:
return ret.OrderByRandom();
}
ret = ret.OrderByPropertyNameIf(req.Prop?.Length > 0, req.Prop, req.Order == Orders.Ascending);
if (!req.Prop?.Equals(nameof(req.Filter.Id), StringComparison.OrdinalIgnoreCase) ?? true) {
ret = ret.OrderByDescending(a => a.Id);
}
return ret;
}
}

View File

@ -2,6 +2,7 @@ using NetAdmin.Domain.Attributes.DataValidation;
using NetAdmin.Domain.Contexts;
using NetAdmin.Domain.DbMaps.Sys;
using NetAdmin.Domain.Dto.Sys.User;
using NetAdmin.Domain.Dto.Sys.UserInvite;
using NetAdmin.Domain.Dto.Sys.UserProfile;
using NetAdmin.Domain.Dto.Sys.UserWallet;
using NetAdmin.Domain.Dto.Sys.VerifyCode;
@ -14,6 +15,7 @@ namespace NetAdmin.SysComponent.Application.Services.Sys;
public sealed class UserService(
BasicRepository<Sys_User, long> rpo //
, IUserProfileService userProfileService //
, IUserInviteService userInviteService //
, IUserWalletService userWalletService //
, IVerifyCodeService verifyCodeService //
, IEventPublisher eventPublisher) //
@ -52,6 +54,13 @@ public sealed class UserService(
return ret;
}
/// <inheritdoc />
public Task<bool> CheckInviterAvailableAsync(CheckInviterAvailableReq req)
{
req.ThrowIfInvalid();
return Rpo.Select.Where(a => a.InviteCode == req.Code && a.Enabled).AnyAsync();
}
/// <inheritdoc />
public async Task<bool> CheckMobileAvailableAsync(CheckMobileAvailableReq req)
{
@ -116,14 +125,14 @@ public sealed class UserService(
.ConfigureAwait(false);
// 钱包表
_ = await userWalletService.CreateAsync(new CreateUserWalletReq() with //
{
Id = dbUser.Id, OwnerId = dbUser.Id, OwnerDeptId = dbUser.DeptId
})
_ = await userWalletService.CreateAsync(new CreateUserWalletReq { Id = dbUser.Id, OwnerId = dbUser.Id, OwnerDeptId = dbUser.DeptId })
.ConfigureAwait(false);
var userList = await QueryAsync(new QueryReq<QueryUserReq> { Filter = new QueryUserReq { Id = dbUser.Id } }).ConfigureAwait(false);
// 邀请表
_ = await userInviteService.CreateAsync((req.Invite ?? new CreateUserInviteReq()) with { Id = dbUser.Id }).ConfigureAwait(false);
// 发布用户创建事件
var ret = userList.First();
await eventPublisher.PublishAsync(new UserCreatedEvent(ret.Adapt<UserInfoRsp>())).ConfigureAwait(false);
@ -295,12 +304,34 @@ public sealed class UserService(
public async Task<UserInfoRsp> RegisterAsync(RegisterUserReq req)
{
req.ThrowIfInvalid();
if (!await verifyCodeService.VerifyAsync(req.VerifySmsCodeReq).ConfigureAwait(false)) {
var config = await S<IConfigService>().GetLatestConfigAsync().ConfigureAwait(false);
if (config.RegisterMobileRequired && !await verifyCodeService.VerifyAsync(req.VerifySmsCodeReq).ConfigureAwait(false)) {
throw new NetAdminInvalidOperationException(Ln.);
}
var createReq = req.Adapt<CreateUserReq>() with { Profile = new CreateUserProfileReq() };
return (await CreateAsync(createReq).ConfigureAwait(false)).Adapt<UserInfoRsp>();
long? inviterId = null;
long? inviterDeptId = null;
if (!req.Inviter.NullOrEmpty() || config.RegisterInviteRequired) {
if (req.Inviter.NullOrEmpty()) {
throw new NetAdminInvalidOperationException(Ln.);
}
var inviter = await GetAsync(new QueryUserReq { InviteCode = req.Inviter }).ConfigureAwait(false);
if (inviter?.Enabled != true) {
throw new NetAdminInvalidOperationException(Ln.);
}
inviterId = inviter.Id;
inviterDeptId = inviter.DeptId;
}
var createReq = req.Adapt<CreateUserReq>() with {
Profile = new CreateUserProfileReq()
, Invite = new CreateUserInviteReq { OwnerId = inviterId, OwnerDeptId = inviterDeptId }
};
return (await CreateAsync(createReq with { Mobile = config.RegisterMobileRequired ? createReq.Mobile : null }).ConfigureAwait(false))
.Adapt<UserInfoRsp>();
}
/// <inheritdoc />
@ -505,6 +536,8 @@ public sealed class UserService(
.WhereIf(deptIds != null, a => deptIds.Contains(a.DeptId))
.WhereIf( //
req.Filter?.Id > 0, a => a.Id == req.Filter.Id)
.WhereIf( //
req.Filter?.InviteCode?.Length > 0, a => a.InviteCode == req.Filter.InviteCode)
.WhereIf( //
req.Filter?.RoleId > 0, a => Enumerable.Any(a.Roles, b => b.Id == req.Filter.RoleId))
.WhereIf( //

View File

@ -60,6 +60,12 @@ public sealed class ConfigCache(IDistributedCache cache, IConfigService service)
return Service.GetLatestConfigAsync();
}
/// <inheritdoc />
public Task<QueryConfigRsp> GetRegisterConfigAsync()
{
return Service.GetRegisterConfigAsync();
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryConfigRsp>> PagedQueryAsync(PagedQueryReq<QueryConfigReq> req)
{

View File

@ -0,0 +1,6 @@
namespace NetAdmin.SysComponent.Cache.Sys.Dependency;
/// <summary>
/// 用户邀请缓存
/// </summary>
public interface IUserInviteCache : ICache<IDistributedCache, IUserInviteService>, IUserInviteModule;

View File

@ -13,6 +13,12 @@ public sealed class UserCache(IDistributedCache cache, IUserService service, IVe
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<bool> CheckInviterAvailableAsync(CheckInviterAvailableReq req)
{
return Service.CheckInviterAvailableAsync(req);
}
/// <inheritdoc />
public Task<bool> CheckMobileAvailableAsync(CheckMobileAvailableReq req)
{

View File

@ -0,0 +1,68 @@
using NetAdmin.Domain.Dto.Sys.UserInvite;
namespace NetAdmin.SysComponent.Cache.Sys;
/// <inheritdoc cref="IUserInviteCache" />
public sealed class UserInviteCache(IDistributedCache cache, IUserInviteService service)
: DistributedCache<IUserInviteService>(cache, service), IScoped, IUserInviteCache
{
/// <inheritdoc />
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Service.BulkDeleteAsync(req);
}
/// <inheritdoc />
public Task<long> CountAsync(QueryReq<QueryUserInviteReq> req)
{
return Service.CountAsync(req);
}
/// <inheritdoc />
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryUserInviteReq> req)
{
return Service.CountByAsync(req);
}
/// <inheritdoc />
public Task<QueryUserInviteRsp> CreateAsync(CreateUserInviteReq req)
{
return Service.CreateAsync(req);
}
/// <inheritdoc />
public Task<int> DeleteAsync(DelReq req)
{
return Service.DeleteAsync(req);
}
/// <inheritdoc />
public Task<QueryUserInviteRsp> EditAsync(EditUserInviteReq req)
{
return Service.EditAsync(req);
}
/// <inheritdoc />
public Task<IActionResult> ExportAsync(QueryReq<QueryUserInviteReq> req)
{
return Service.ExportAsync(req);
}
/// <inheritdoc />
public Task<QueryUserInviteRsp> GetAsync(QueryUserInviteReq req)
{
return Service.GetAsync(req);
}
/// <inheritdoc />
public Task<PagedQueryRsp<QueryUserInviteRsp>> PagedQueryAsync(PagedQueryReq<QueryUserInviteReq> req)
{
return Service.PagedQueryAsync(req);
}
/// <inheritdoc />
public Task<IEnumerable<QueryUserInviteRsp>> QueryAsync(QueryReq<QueryUserInviteReq> req)
{
return Service.QueryAsync(req);
}
}

View File

@ -85,6 +85,15 @@ public sealed class ConfigController(IConfigCache cache) : ControllerBase<IConfi
return Cache.GetLatestConfigAsync();
}
/// <summary>
/// 获取注册配置
/// </summary>
[AllowAnonymous]
public Task<QueryConfigRsp> GetRegisterConfigAsync()
{
return Cache.GetRegisterConfigAsync();
}
/// <summary>
/// 分页查询配置
/// </summary>

View File

@ -20,6 +20,15 @@ public sealed class UserController(IUserCache cache, IConfigCache configCache) :
return Cache.BulkDeleteAsync(req);
}
/// <summary>
/// 检查邀请码是否正确
/// </summary>
[AllowAnonymous]
public Task<bool> CheckInviterAvailableAsync(CheckInviterAvailableReq req)
{
return Cache.CheckInviterAvailableAsync(req);
}
/// <summary>
/// 检查手机号码是否可用
/// </summary>

View File

@ -0,0 +1,96 @@
using NetAdmin.Domain.Dto.Sys.UserInvite;
namespace NetAdmin.SysComponent.Host.Controllers.Sys;
/// <summary>
/// 用户邀请服务
/// </summary>
[ApiDescriptionSettings(nameof(Sys), Module = nameof(Sys))]
[Produces(Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_JSON)]
public sealed class UserInviteController(IUserInviteCache cache) : ControllerBase<IUserInviteCache, IUserInviteService>(cache), IUserInviteModule
{
/// <summary>
/// 批量删除用户邀请
/// </summary>
[Transaction]
public Task<int> BulkDeleteAsync(BulkReq<DelReq> req)
{
return Cache.BulkDeleteAsync(req);
}
/// <summary>
/// 用户邀请计数
/// </summary>
public Task<long> CountAsync(QueryReq<QueryUserInviteReq> req)
{
return Cache.CountAsync(req);
}
/// <summary>
/// 用户邀请分组计数
/// </summary>
public Task<IOrderedEnumerable<KeyValuePair<IImmutableDictionary<string, string>, int>>> CountByAsync(QueryReq<QueryUserInviteReq> req)
{
return Cache.CountByAsync(req);
}
/// <summary>
/// 创建用户邀请
/// </summary>
[Transaction]
public Task<QueryUserInviteRsp> CreateAsync(CreateUserInviteReq req)
{
return Cache.CreateAsync(req);
}
/// <summary>
/// 删除用户邀请
/// </summary>
[Transaction]
public Task<int> DeleteAsync(DelReq req)
{
return Cache.DeleteAsync(req);
}
/// <summary>
/// 编辑用户邀请
/// </summary>
[Transaction]
public Task<QueryUserInviteRsp> EditAsync(EditUserInviteReq req)
{
return Cache.EditAsync(req);
}
/// <summary>
/// 导出用户邀请
/// </summary>
[NonAction]
public Task<IActionResult> ExportAsync(QueryReq<QueryUserInviteReq> req)
{
return Cache.ExportAsync(req);
}
/// <summary>
/// 获取单个用户邀请
/// </summary>
public Task<QueryUserInviteRsp> GetAsync(QueryUserInviteReq req)
{
return Cache.GetAsync(req);
}
/// <summary>
/// 分页查询用户邀请
/// </summary>
public Task<PagedQueryRsp<QueryUserInviteRsp>> PagedQueryAsync(PagedQueryReq<QueryUserInviteReq> req)
{
return Cache.PagedQueryAsync(req);
}
/// <summary>
/// 查询用户邀请
/// </summary>
public Task<IEnumerable<QueryUserInviteRsp>> QueryAsync(QueryReq<QueryUserInviteReq> req)
{
return Cache.QueryAsync(req);
}
}

View File

@ -99,6 +99,14 @@ public class ConfigTests(WebTestApplicationFactory<Startup> factory, ITestOutput
return null;
}
/// <inheritdoc />
public async Task<QueryConfigRsp> GetRegisterConfigAsync()
{
var rsp = await PostJsonAsync(typeof(ConfigController));
Assert.True(rsp.IsSuccessStatusCode);
return null;
}
/// <inheritdoc />
[InlineData(null)]
[Theory]

View File

@ -24,6 +24,17 @@ public class UserTests(WebTestApplicationFactory<Startup> factory, ITestOutputHe
return 0;
}
/// <inheritdoc />
[InlineData(null)]
[Theory]
[TestPriority(100200)]
public async Task<bool> CheckInviterAvailableAsync(CheckInviterAvailableReq req)
{
var rsp = await PostJsonAsync(typeof(UserController), new CheckInviterAvailableReq { Code = "111111" });
Assert.True(rsp.IsSuccessStatusCode);
return false;
}
/// <inheritdoc />
[InlineData(null)]
[Theory]
@ -195,6 +206,17 @@ public class UserTests(WebTestApplicationFactory<Startup> factory, ITestOutputHe
return null;
}
/// <inheritdoc />
[InlineData(null)]
[Theory]
[TestPriority(101600)]
public async Task<IEnumerable<QueryUserRsp>> QueryRelationAsync(QueryReq<QueryUserReq> req)
{
var rsp = await PostJsonAsync(typeof(UserController), req);
Assert.True(rsp.IsSuccessStatusCode);
return null;
}
/// <inheritdoc />
[InlineData(null)]
[Theory]

View File

@ -104,6 +104,17 @@ export default {
},
},
/**
* 获取注册配置
*/
getRegisterConfig: {
url: `${config.API_URL}/api/sys/config/get.register.config`,
name: `获取注册配置`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询配置
*/

View File

@ -114,4 +114,15 @@ export default {
return await http.post(this.url, data, config)
},
},
/**
* 到账确认
*/
receivedConfirmation: {
url: `${config.API_URL}/api/sys/deposit.order/received.confirmation`,
name: `到账确认`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -5,6 +5,17 @@
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 检查邀请码是否正确
*/
checkInviterAvailable: {
url: `${config.API_URL}/api/sys/user/check.inviter.available`,
name: `检查邀请码是否正确`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 检查手机号码是否可用
*/

View File

@ -0,0 +1,106 @@
/**
* 用户邀请服务
* @module @/api/sys/user.invite
*/
import config from '@/config'
import http from '@/utils/request'
export default {
/**
* 批量删除用户邀请
*/
bulkDelete: {
url: `${config.API_URL}/api/sys/user.invite/bulk.delete`,
name: `批量删除用户邀请`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 用户邀请计数
*/
count: {
url: `${config.API_URL}/api/sys/user.invite/count`,
name: `用户邀请计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 用户邀请分组计数
*/
countBy: {
url: `${config.API_URL}/api/sys/user.invite/count.by`,
name: `用户邀请分组计数`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 创建用户邀请
*/
create: {
url: `${config.API_URL}/api/sys/user.invite/create`,
name: `创建用户邀请`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 删除用户邀请
*/
delete: {
url: `${config.API_URL}/api/sys/user.invite/delete`,
name: `删除用户邀请`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 编辑用户邀请
*/
edit: {
url: `${config.API_URL}/api/sys/user.invite/edit`,
name: `编辑用户邀请`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 获取单个用户邀请
*/
get: {
url: `${config.API_URL}/api/sys/user.invite/get`,
name: `获取单个用户邀请`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 分页查询用户邀请
*/
pagedQuery: {
url: `${config.API_URL}/api/sys/user.invite/paged.query`,
name: `分页查询用户邀请`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
/**
* 查询用户邀请
*/
query: {
url: `${config.API_URL}/api/sys/user.invite/query`,
name: `查询用户邀请`,
post: async function (data = {}, config = {}) {
return await http.post(this.url, data, config)
},
},
}

View File

@ -193,7 +193,7 @@ export default {
用于登录系统: 'Used to log in to the system',
用户代理: 'User agent',
用户名: 'Username',
用户名: 'User name',
用户名: 'User name',
用户注册: 'User registration',
用户注册设置: 'User registration settings',
用户编号: 'User ID',

View File

@ -193,7 +193,7 @@ export default {
用于登录系统: '用于登录系统',
用户代理: '用户代理',
用户名: '用户名',
用户名: '用户名',
用户名: '用户名',
用户注册: '用户注册',
用户注册设置: '用户注册设置',
用户编号: '用户编号',

View File

@ -2,7 +2,7 @@
<common-page :title="$t('注册新账号')">
<el-steps :active="stepActive" finish-status="success" simple>
<el-step :title="$t('填写账号')" />
<el-step :title="$t('验证手机')" />
<el-step v-if="config.registerInviteRequired" :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">
@ -29,6 +29,9 @@
show-password
type="password"></el-input>
</el-form-item>
<el-form-item :label="$t('邀请码')" prop="inviter">
<el-input v-model="form.inviter" :placeholder="$t('请输入邀请码')" clearable maxlength="6"></el-input>
</el-form-item>
<el-form-item label="" prop="agree">
<el-checkbox v-model="form.agree" label="">{{ $t('我已阅读并同意') }}</el-checkbox>
<span @click="showAgree = true" class="link">{{ $t('平台服务协议') }}</span>
@ -52,8 +55,17 @@
</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 < 1" @click="next" size="large" type="primary">{{ $t('下一步') }}</el-button>
<el-button v-if="stepActive === 1" :loading="loading" @click="save" size="large" type="primary">{{ $t('提交') }}</el-button>
<el-button v-if="stepActive < (this.config.registerInviteRequired ? 1 : 0)" @click="next" size="large" type="primary">{{
$t('下一步')
}}</el-button>
<el-button
v-if="stepActive === (this.config.registerInviteRequired ? 1 : 0)"
:loading="loading"
@click="save"
size="large"
type="primary"
>{{ $t('提交') }}</el-button
>
</el-form>
<el-dialog v-model="showAgree" :title="$t('平台服务协议')" destroy-on-close>
<template #footer>
@ -87,6 +99,7 @@ export default {
},
data() {
return {
config: {},
loading: false,
stepActive: 0,
showAgree: false,
@ -128,6 +141,22 @@ export default {
trigger: 'blur',
},
],
inviter: [
{
validator: async (rule, valueEquals, callback) => {
if (!valueEquals) return callback()
try {
const res = await this.$API.sys_user.checkInviterAvailable.post({
code: valueEquals,
})
return res.data ? callback() : callback(new Error(this.$t('邀请码不正确')))
} catch (ex) {
return callback(new Error(ex.data.msg.code[0].children))
}
},
trigger: 'blur',
},
],
verifySmsCodeReq: {
destDevice: [phoneConfig.mobile(this), phoneConfig.mobileNoUsed(this, () => this.form.id)],
code: phoneConfig.code(this),
@ -135,6 +164,17 @@ export default {
},
}
},
async created() {
const res = await this.$API.sys_config.getRegisterConfig.post({})
Object.assign(this.config, res.data)
if (this.config.registerInviteRequired) {
this.rules.inviter.push({
required: true,
message: this.$t('请输入邀请码'),
trigger: 'blur',
})
}
},
async mounted() {},
methods: {
pre() {
@ -153,7 +193,13 @@ export default {
this.loading = true
try {
await this.$API.sys_user.register.post(this.form)
const postData = Object.assign({}, this.form)
if (!postData.verifySmsCodeReq.destDevice) {
postData.verifySmsCodeReq.destDevice = '13838381438'
postData.verifySmsCodeReq.deviceType = 1
postData.verifySmsCodeReq.code = '1234'
}
await this.$API.sys_user.register.post(postData)
this.stepActive += 2
} catch {
//

View File

@ -5,9 +5,18 @@
<scUpload v-model="form.avatar" :onSuccess="updateUser" :title="$t('上传头像')"></scUpload>
</el-form-item>
<el-form-item :label="$t('用户编号')">
<el-input v-model="form.id" readonly></el-input>
<div class="flex w100p gap05">
<el-input v-model="form.id" readonly></el-input>
<el-button v-copy="form.id">{{ $t('复制') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="$t('用户名称')">
<el-form-item :label="$t('邀请码')">
<div class="flex w100p gap05">
<el-input v-model="form.inviteCode" readonly></el-input>
<el-button v-copy="form.inviteCode">{{ $t('复制') }}</el-button>
</div>
</el-form-item>
<el-form-item :label="$t('用户名')">
<el-input v-model="form.userName" readonly></el-input>
</el-form-item>
<el-form-item :label="$t('密码')">

View File

@ -1,9 +1,6 @@
<template>
<el-card :header="$t('授权信息')" shadow="never">
<el-form :model="form" :rules="rules" label-width="10rem" ref="form">
<el-form-item :label="$t('用户标识')">
<el-input v-model="form.id" class="font-monospace" readonly></el-input>
</el-form-item>
<el-form-item :label="$t('授权令牌')">
<el-input v-model="form.token" class="font-monospace" readonly rows="10" type="textarea"></el-input>
</el-form-item>

View File

@ -79,11 +79,21 @@
<el-table-column :label="$t('用户注册')" align="center">
<el-table-column :label="$t('默认部门')" align="center" prop="userRegisterDept.name" width="150" />
<el-table-column :label="$t('默认角色')" align="center" prop="userRegisterRole.name" width="150" />
<el-table-column :label="$t('人工审核')" align="center" prop="userRegisterConfirm" width="120">
<el-table-column :label="$t('人工审核')" align="center" prop="userRegisterConfirm" width="100">
<template #default="{ row }">
<el-switch v-model="row.userRegisterConfirm" @change="changeSwitch($event, row)"></el-switch>
</template>
</el-table-column>
<el-table-column :label="$t('邀请注册')" align="center" prop="registerInviteRequired" width="100">
<template #default="{ row }">
<el-switch v-model="row.registerInviteRequired" @change="changeSwitch($event, row)"></el-switch>
</template>
</el-table-column>
<el-table-column :label="$t('手机注册')" align="center" prop="registerMobileRequired" width="100">
<template #default="{ row }">
<el-switch v-model="row.registerMobileRequired" @change="changeSwitch($event, row)"></el-switch>
</template>
</el-table-column>
</el-table-column>
<el-table-column :label="$t('财务配置')" align="center">
<el-table-column :label="$t('人民币兑点数比率')" align="center" prop="cnyToPointRate" width="150" />

View File

@ -23,6 +23,12 @@
<el-form-item :label="$t('开启人工审核')" prop="userRegisterConfirm">
<el-switch v-model="form.userRegisterConfirm"></el-switch>
</el-form-item>
<el-form-item :label="$t('邀请注册')" prop="registerInviteRequired">
<el-switch v-model="form.registerInviteRequired"></el-switch>
</el-form-item>
<el-form-item :label="$t('手机注册')" prop="registerMobileRequired">
<el-switch v-model="form.registerMobileRequired"></el-switch>
</el-form-item>
</div>
</el-collapse-item>
<el-collapse-item :title="$t('财务配置')" name="2">

View File

@ -0,0 +1,171 @@
<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">
<scStatistic :title="$t('总数')" :value="statistics.total" group-separator></scStatistic>
</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-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="nopadding">
<scTable
:context-menus="['id', 'user.userName', 'createdTime']"
: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" />
<naColAvatar :label="$t('用户名')" prop="user.userName" />
<el-table-column :label="$t('注册时间')" prop="createdTime" sortable="custom" />
</scTable>
</el-main>
</el-container>
</template>
<script>
import { defineAsyncComponent } from 'vue'
import table from '@/config/table'
import naColOperation from '@/config/naColOperation'
const naColAvatar = defineAsyncComponent(() => import('@/components/naColAvatar'))
export default {
components: {
naColAvatar,
},
computed: {
naColOperation() {
return naColOperation
},
table() {
return table
},
},
created() {},
data() {
return {
statistics: {
total: '...',
},
dialog: {},
loading: false,
query: {
dynamicFilter: {
filters: [],
},
filter: {},
keywords: this.keywords,
},
selection: [],
}
},
inject: ['reload'],
methods: {
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()
},
},
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: ['keywords'],
watch: {},
}
</script>
<style scoped></style>

View File

@ -25,6 +25,9 @@
<el-form-item :label="$t('登录账号')" prop="userName">
<el-input v-model="form.userName" :placeholder="$t('用于登录系统')" clearable></el-input>
</el-form-item>
<el-form-item v-if="mode === 'view'" :label="$t('邀请码')" prop="inviteCode">
<el-input v-model="form.inviteCode" clearable></el-input>
</el-form-item>
<el-row :gutter="10">
<el-col :lg="12">
<el-form-item :label="$t('手机号')" prop="mobile">