mirror of
https://github.com/nsnail/NetAdmin.git
synced 2025-07-04 09:38:15 +08:00
feat: ✨ 营销管理
This commit is contained in:
@ -57,11 +57,11 @@ USDT
|
||||
已校验
|
||||
已读
|
||||
并且
|
||||
归属角色
|
||||
归属部门
|
||||
微信支付
|
||||
成功
|
||||
或者
|
||||
归属角色
|
||||
归属部门
|
||||
手机
|
||||
手机号
|
||||
执行耗时
|
||||
@ -108,6 +108,7 @@ USDT
|
||||
用户代理
|
||||
用户名
|
||||
用户导出
|
||||
用户邀请导出
|
||||
用户钱包导出
|
||||
电子邮箱
|
||||
男
|
||||
|
@ -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",
|
||||
|
@ -102,5 +102,9 @@
|
||||
{
|
||||
"ApiId": "api/sys/deposit.order/pay.confirm",
|
||||
"RoleId": 371729946431493,
|
||||
},
|
||||
{
|
||||
"ApiId": "api/sys/user.invite/query",
|
||||
"RoleId": 371729946431493,
|
||||
}
|
||||
]
|
@ -30,5 +30,13 @@
|
||||
{
|
||||
"MenuId": 690907673255944,
|
||||
"RoleId": 371729946431493
|
||||
},
|
||||
{
|
||||
"MenuId": 692575802241032,
|
||||
"RoleId": 371729946431493
|
||||
},
|
||||
{
|
||||
"MenuId": 692575802245126,
|
||||
"RoleId": 371729946431493
|
||||
}
|
||||
]
|
@ -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",
|
||||
}
|
||||
]
|
15
assets/seed-data/Sys_UserInvite.json
Normal file
15
assets/seed-data/Sys_UserInvite.json
Normal file
@ -0,0 +1,15 @@
|
||||
[
|
||||
{
|
||||
"Id": 370942943322181,
|
||||
},
|
||||
{
|
||||
"Id": 560217289236492,
|
||||
"OwnerDeptId": 372119301627909,
|
||||
"OwnerId": 370942943322181,
|
||||
},
|
||||
{
|
||||
"Id": 664362432344581,
|
||||
"OwnerDeptId": 372119301627909,
|
||||
"OwnerId": 370942943322181,
|
||||
}
|
||||
]
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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>
|
||||
|
@ -7,6 +7,7 @@ namespace NetAdmin.Domain.DbMaps.Sys;
|
||||
/// </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(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>
|
||||
|
@ -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; }
|
||||
}
|
@ -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)]
|
||||
|
@ -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; }
|
||||
|
@ -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; }
|
||||
}
|
@ -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)
|
||||
|
||||
//
|
||||
|
@ -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; }
|
||||
|
@ -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>
|
||||
|
@ -0,0 +1,6 @@
|
||||
namespace NetAdmin.Domain.Dto.Sys.UserInvite;
|
||||
|
||||
/// <summary>
|
||||
/// 请求:创建用户邀请
|
||||
/// </summary>
|
||||
public record CreateUserInviteReq : Sys_UserInvite;
|
@ -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; }
|
||||
}
|
@ -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; }
|
||||
}
|
@ -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; }
|
||||
}
|
@ -1,5 +1,3 @@
|
||||
using NetAdmin.Domain.DbMaps.Sys;
|
||||
|
||||
namespace NetAdmin.Domain.Dto.Sys.UserWallet;
|
||||
|
||||
/// <summary>
|
||||
|
@ -1,5 +1,3 @@
|
||||
using NetAdmin.Domain.DbMaps.Sys;
|
||||
|
||||
namespace NetAdmin.Domain.Dto.Sys.WalletTrade;
|
||||
|
||||
/// <summary>
|
||||
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -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)";
|
||||
|
@ -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>
|
||||
|
@ -16,6 +16,11 @@ public interface IConfigModule : ICrudModule<CreateConfigReq, QueryConfigRsp //
|
||||
/// </summary>
|
||||
Task<QueryConfigRsp> GetLatestConfigAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取注册配置
|
||||
/// </summary>
|
||||
Task<QueryConfigRsp> GetRegisterConfigAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 设置配置启用状态
|
||||
/// </summary>
|
||||
|
@ -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 // 删除类型
|
||||
>;
|
@ -12,6 +12,11 @@ public partial interface IUserModule : ICrudModule<CreateUserReq, QueryUserRsp /
|
||||
, DelReq // 删除类型
|
||||
>
|
||||
{
|
||||
/// <summary>
|
||||
/// 检查邀请码是否正确
|
||||
/// </summary>
|
||||
Task<bool> CheckInviterAvailableAsync(CheckInviterAvailableReq req);
|
||||
|
||||
/// <summary>
|
||||
/// 检查手机号码是否可用
|
||||
/// </summary>
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -0,0 +1,6 @@
|
||||
namespace NetAdmin.SysComponent.Application.Services.Sys.Dependency;
|
||||
|
||||
/// <summary>
|
||||
/// 用户邀请服务
|
||||
/// </summary>
|
||||
public interface IUserInviteService : IService, IUserInviteModule;
|
@ -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;
|
||||
}
|
||||
}
|
@ -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( //
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -0,0 +1,6 @@
|
||||
namespace NetAdmin.SysComponent.Cache.Sys.Dependency;
|
||||
|
||||
/// <summary>
|
||||
/// 用户邀请缓存
|
||||
/// </summary>
|
||||
public interface IUserInviteCache : ICache<IDistributedCache, IUserInviteService>, IUserInviteModule;
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
@ -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]
|
||||
|
@ -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)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 分页查询配置
|
||||
*/
|
||||
|
@ -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)
|
||||
},
|
||||
},
|
||||
}
|
@ -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)
|
||||
},
|
||||
},
|
||||
|
||||
/**
|
||||
* 检查手机号码是否可用
|
||||
*/
|
||||
|
106
src/frontend/admin/src/api/sys/userinvite.js
Normal file
106
src/frontend/admin/src/api/sys/userinvite.js
Normal 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)
|
||||
},
|
||||
},
|
||||
}
|
@ -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',
|
||||
|
@ -193,7 +193,7 @@ export default {
|
||||
用于登录系统: '用于登录系统',
|
||||
用户代理: '用户代理',
|
||||
用户名: '用户名',
|
||||
用户名称: '用户名称',
|
||||
用户名: '用户名',
|
||||
用户注册: '用户注册',
|
||||
用户注册设置: '用户注册设置',
|
||||
用户编号: '用户编号',
|
||||
|
@ -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 {
|
||||
//
|
||||
|
@ -5,9 +5,18 @@
|
||||
<scUpload v-model="form.avatar" :onSuccess="updateUser" :title="$t('上传头像')"></scUpload>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('用户编号')">
|
||||
<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('密码')">
|
||||
|
@ -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>
|
||||
|
@ -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" />
|
||||
|
@ -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">
|
||||
|
171
src/frontend/admin/src/views/sys/invite/index.vue
Normal file
171
src/frontend/admin/src/views/sys/invite/index.vue
Normal 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>
|
@ -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">
|
||||
|
Reference in New Issue
Block a user