😎1、增加登录配置租户Id参数模式 2、代码优化

This commit is contained in:
zuohuaijun 2025-01-14 14:47:34 +08:00
parent bf2752df8c
commit 6f5d4646df
23 changed files with 347 additions and 205 deletions

View File

@ -45,11 +45,11 @@
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.1.3" />
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.6.0" />
<PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.9.0" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.173" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.174" />
<PackageReference Include="SSH.NET" Version="2024.2.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.5.1" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1162" />
<PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1163" />
<PackageReference Include="UAParser" Version="3.1.47" />
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
</ItemGroup>

View File

@ -48,38 +48,34 @@ public class DictAttribute : ValidationAttribute, ITransient
protected override ValidationResult IsValid(object? value, ValidationContext validationContext)
{
// 判断是否允许空值
if (AllowNullValue && value == null)
return ValidationResult.Success;
if (AllowNullValue && value == null) return ValidationResult.Success;
var valueAsString = value?.ToString();
// 是否忽略空字符串
if (AllowEmptyStrings && string.IsNullOrEmpty(valueAsString))
return ValidationResult.Success;
if (AllowEmptyStrings && string.IsNullOrEmpty(valueAsString)) return ValidationResult.Success;
// 获取属性的类型
var property = validationContext.ObjectType.GetProperty(validationContext.MemberName);
if (property == null)
return new ValidationResult($"未知属性: {validationContext.MemberName}");
var property = validationContext.ObjectType.GetProperty(validationContext.MemberName!);
if (property == null) return new ValidationResult($"未知属性: {validationContext.MemberName}");
var propertyType = Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType;
// 枚举类型验证
if (propertyType.IsEnum)
{
return !Enum.IsDefined(propertyType, value)
? new ValidationResult($"提示:{ErrorMessage}|枚举值【{value}】不是有效的【{propertyType.Name}】枚举类型值!")
: ValidationResult.Success;
if (!Enum.IsDefined(propertyType, value!)) return new ValidationResult($"提示:{ErrorMessage}|枚举值【{value}】不是有效的【{propertyType.Name}】枚举类型值!");
return ValidationResult.Success;
}
// 先尝试从 ValidationContext 的依赖注入容器中拿服务,拿不到或类型不匹配时,再从全局的 App 容器中获取
// 先尝试从 ValidationContext 的依赖注入容器中拿服务,再从全局的 App 容器中获取
if (validationContext.GetService(typeof(SysDictDataService)) is not SysDictDataService sysDictDataService)
sysDictDataService = App.GetRequiredService<SysDictDataService>();
// 获取字典值列表
var dictDataList = sysDictDataService.GetDataList(DictTypeCode).GetAwaiter().GetResult();
// 使用 HashSet 来提高查找效率
var dictHash = new HashSet<string>(dictDataList.Select(u => u.Code));
if (!dictHash.Contains(valueAsString))
return new ValidationResult($"提示:{ErrorMessage}|字典【{DictTypeCode}】不包含【{valueAsString}】!");
if (!dictHash.Contains(valueAsString)) return new ValidationResult($"提示:{ErrorMessage}|字典【{DictTypeCode}】不包含【{valueAsString}】!");
return ValidationResult.Success;
}

View File

@ -71,10 +71,10 @@ public class ConfigConst
/// </summary>
public const string SysDomainLogin = "sys_domain_login";
/// <summary>
/// 租户域名隔离登录验证
/// </summary>
public const string SysTenantHostLogin = "sys_tenant_host_login";
///// <summary>
///// 租户域名隔离登录验证
///// </summary>
//public const string SysTenantHostLogin = "sys_tenant_host_login";
/// <summary>
/// 行政区划同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级

View File

@ -35,9 +35,4 @@ public class SqlSugarConst
/// 默认租户Id
/// </summary>
public const long DefaultTenantId = 1300000000001;
/// <summary>
/// 默认租户域名
/// </summary>
public const string DefaultTenantHost = "gitee.com";
}

View File

@ -70,12 +70,14 @@ public partial class SysCodeGen : EntityBase
[SugarColumn(ColumnDescription = "数据库表名", Length = 128)]
[MaxLength(128)]
public string? TableName { get; set; }
/// <summary>
/// 自身树控件Name
/// 树控件名称
/// </summary>
[SugarColumn(ColumnDescription = "自身树控件Name", Length = 64)]
[SugarColumn(ColumnDescription = "树控件名称", Length = 64)]
[MaxLength(64)]
public string? TreeName { get; set; }
/// <summary>
/// 命名空间
/// </summary>
@ -134,6 +136,7 @@ public partial class SysCodeGen : EntityBase
[SugarColumn(ColumnDescription = "打印模版名称", Length = 32)]
[MaxLength(32)]
public string? PrintName { get; set; }
/// <summary>
/// 左边树形结构表
/// </summary>
@ -147,16 +150,18 @@ public partial class SysCodeGen : EntityBase
[SugarColumn(ColumnDescription = "左边关联字段", Length = 64)]
[MaxLength(64)]
public string? LeftKey { get; set; }
/// <summary>
/// 左边关联主表字段
/// </summary>
[SugarColumn(ColumnDescription = "左边关联主表字段", Length = 64)]
[MaxLength(64)]
public string? LeftPrimaryKey { get; set; }
/// <summary>
/// 左边树Name
/// 左边树名称
/// </summary>
[SugarColumn(ColumnDescription = "左边树Name", Length = 64)]
[SugarColumn(ColumnDescription = "左边树名称", Length = 64)]
[MaxLength(64)]
public string? LeftName { get; set; }
@ -173,6 +178,7 @@ public partial class SysCodeGen : EntityBase
[SugarColumn(ColumnDescription = "右区域下框表关联字段", Length = 64)]
[MaxLength(64)]
public string? BottomKey { get; set; }
/// <summary>
/// 下表关联主表字段
/// </summary>
@ -186,6 +192,7 @@ public partial class SysCodeGen : EntityBase
[SugarColumn(ColumnDescription = "模板文件夹", Length = 64)]
[MaxLength(64)]
public string? Template { get; set; }
/// <summary>
/// 是否使用 Api Service
/// </summary>

View File

@ -4,8 +4,6 @@
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
namespace Admin.NET.Core.SeedData;
/// <summary>
@ -227,11 +225,7 @@ public class SysCodeGenTemplateSeedData : ISqlSugarEntitySeedData<SysCodeGenTemp
}
]
";
List<SysCodeGenTemplate> records = Newtonsoft.Json.JsonConvert.DeserializeObject<List<SysCodeGenTemplate>>(recordList);
// 后处理数据的特殊字段
//for (int i = 0; i < records.Count; i++) { }
var records = JSON.Deserialize<List<SysCodeGenTemplate>>(recordList);
return records;
}
}

View File

@ -31,7 +31,6 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig>
new SysConfig{ Id=1300000000191, Name="RefreshToken过期时间", Code=ConfigConst.SysRefreshTokenExpire, Value="20160", SysFlag=YesNoEnum.Y, Remark="刷新Token过期时间分钟一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)", OrderNo=100, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000201, Name="发送异常日志邮件", Code=ConfigConst.SysErrorMail, Value="False", SysFlag=YesNoEnum.Y, Remark="是否发送异常日志邮件", OrderNo=110, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000211, Name="域登录验证", Code=ConfigConst.SysDomainLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启域登录验证", OrderNo=120, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000221, Name="租户隔离登录验证", Code=ConfigConst.SysTenantHostLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="租户隔离登录验证", OrderNo=130, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysConfig{ Id=1300000000241, Name="行政区划同步层级", Code=ConfigConst.SysRegionSyncLevel, Value="3", SysFlag=YesNoEnum.Y, Remark="行政区划同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级", OrderNo=150, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
// 新业务系统记得更改密匙,通过接口(http://localhost:5005/api/sysCommon/smKeyPair)获取
new SysConfig{ Id=1300000000251, Name="国密SM2密匙", Code=ConfigConst.SysSM2Key, Value="04851D329AA3E38C2E7670AFE70E6E70E92F8769CA27C8766B12209A0FFBA4493B603EF7A0B9B1E16F0E8930C0406EA0B179B68DF28E25334BDEC4AE76D907E9E9;3A61D1D30C6302DABFF36201D936D0143EEF0C850AF28C5CA6D5C045AF8C5C8A", SysFlag=YesNoEnum.Y, Remark="国密SM2密匙", OrderNo=160, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2024-11-21 00:00:00") },

View File

@ -21,7 +21,7 @@ public class SysTenantSeedData : ISqlSugarEntitySeedData<SysTenant>
return
[
new SysTenant{ Id=SqlSugarConst.DefaultTenantId, AppId=SqlSugarConst.DefaultTenantId, OrgId=SqlSugarConst.DefaultTenantId, UserId=1300000000111, Host=SqlSugarConst.DefaultTenantHost, TenantType=TenantTypeEnum.Id, DbType=defaultDbConfig.DbType, Connection=defaultDbConfig.ConnectionString, ConfigId=SqlSugarConst.MainConfigId, Remark="系统默认", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
new SysTenant{ Id=SqlSugarConst.DefaultTenantId, AppId=SqlSugarConst.DefaultTenantId, OrgId=SqlSugarConst.DefaultTenantId, UserId=1300000000111, Host="gitee.com", TenantType=TenantTypeEnum.Id, DbType=defaultDbConfig.DbType, Connection=defaultDbConfig.ConnectionString, ConfigId=SqlSugarConst.MainConfigId, Remark="系统默认", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
];
}
}

View File

@ -26,10 +26,10 @@ public class LoginInput
public string Password { get; set; }
/// <summary>
/// 租户域名
/// 租户Id
/// </summary>
//[Required(ErrorMessage = "租户域名不能为空")]
public string Host { get; set; }
//[Required(ErrorMessage = "租户Id不能为空")]
public long TenantId { get; set; }
/// <summary>
/// 验证码Id
@ -65,10 +65,10 @@ public class LoginPhoneInput
public string Code { get; set; }
/// <summary>
/// 租户域名
/// 租户Id
/// </summary>
//[Required(ErrorMessage = "租户域名不能为空")]
public string Host { get; set; }
//[Required(ErrorMessage = "租户Id不能为空")]
public long TenantId { get; set; }
/// <summary>
/// 登录模式

View File

@ -46,9 +46,6 @@ public class SysAuthService : IDynamicApiController, ITransient
[AllowAnonymous]
public virtual async Task<LoginOutput> Login([Required] LoginInput input)
{
//// 可以根据域名获取具体租户
//var host = _httpContextAccessor.HttpContext.Request.Host;
// 判断密码错误次数缓存30分钟
var keyPasswordErrorTimes = $"{CacheConst.KeyPasswordErrorTimes}{input.Account}";
var passwordErrorTimes = _sysCacheService.Get<int>(keyPasswordErrorTimes);
@ -61,7 +58,7 @@ public class SysAuthService : IDynamicApiController, ITransient
if (await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysCaptcha) && !_captcha.Validate(input.CodeId.ToString(), input.Code)) throw Oops.Oh(ErrorCodeEnum.D0008);
// 获取登录租户和用户
var (tenant, user) = await GetLoginUserAndTenant(input.Host, account: input.Account);
var (tenant, user) = await GetLoginUserAndTenant(input.TenantId, account: input.Account);
// 账号是否被冻结
if (user.Status == StatusEnum.Disable) throw Oops.Oh(ErrorCodeEnum.D1017);
@ -92,53 +89,34 @@ public class SysAuthService : IDynamicApiController, ITransient
/// <summary>
/// 获取登录租户和用户
/// </summary>
/// <param name="host"></param>
/// <param name="tenantId"></param>
/// <param name="account"></param>
/// <param name="phone"></param>
/// <returns></returns>
[NonAction]
public async Task<(SysTenant tenant, SysUser user)> GetLoginUserAndTenant(string host, string account = null, string phone = null)
public async Task<(SysTenant tenant, SysUser user)> GetLoginUserAndTenant(long tenantId, string account = null, string phone = null)
{
// 是否租户隔离登录验证
var isTenantHostLogin = await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysTenantHostLogin);
SysUser user;
SysTenant tenant;
// 租户隔离登录
if (isTenantHostLogin)
// 若没有传值租户Id则从请求页URL参数中获取租户Id空则默认租户
if (tenantId < 1)
{
// 若租户域名为空或为本地域名,则取默认租户域名
if (string.IsNullOrWhiteSpace(host) || host.StartsWith("localhost")) host = SqlSugarConst.DefaultTenantHost;
// 租户是否存在或已禁用
tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Host.Equals(host, StringComparison.CurrentCultureIgnoreCase));
if (tenant?.Status != StatusEnum.Enable) throw Oops.Oh(ErrorCodeEnum.Z1003);
// 根据入参类型、租户查询登录用户
user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter()
.Where(u => u.TenantId == tenant.Id || u.AccountType == AccountTypeEnum.SuperAdmin)
.WhereIF(!string.IsNullOrWhiteSpace(account), u => u.Account.Equals(account))
.WhereIF(!string.IsNullOrWhiteSpace(phone), u => u.Phone.Equals(phone))
.FirstAsync();
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 若登录的是超级管理员,则引用当前绑定的租户,这样登陆后操作的租户数据会与该租户关联
if (user.AccountType == AccountTypeEnum.SuperAdmin) user.TenantId = tenant.Id;
var tenantidStr = _httpContextAccessor.HttpContext.Request.Query["tenantid"].ToString();
tenantId = string.IsNullOrWhiteSpace(tenantidStr) ? SqlSugarConst.DefaultTenantId : long.Parse(tenantidStr);
}
else
{
// 根据入参类型查询登录用户
user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter()
.WhereIF(!string.IsNullOrWhiteSpace(account), u => u.Account.Equals(account))
.WhereIF(!string.IsNullOrWhiteSpace(phone), u => u.Phone.Equals(phone))
.FirstAsync();
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 租户是否存在或已禁用
tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Id == user.TenantId);
if (tenant?.Status != StatusEnum.Enable) throw Oops.Oh(ErrorCodeEnum.Z1003);
}
// 判断租户是否存在及状态
var tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Id == tenantId);
if (tenant?.Status != StatusEnum.Enable) throw Oops.Oh(ErrorCodeEnum.Z1003);
// 判断账号是否存在
var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter()
.Where(u => u.AccountType == AccountTypeEnum.SuperAdmin || u.TenantId == tenantId)
.WhereIF(!string.IsNullOrWhiteSpace(account), u => u.Account.Equals(account))
.WhereIF(!string.IsNullOrWhiteSpace(phone), u => u.Phone.Equals(phone))
.FirstAsync();
_ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
// 如果是超级管理员,则引用登录选择的租户进入系统
if (user.AccountType == AccountTypeEnum.SuperAdmin) user.TenantId = tenantId;
return (tenant, user);
}
@ -223,7 +201,7 @@ public class SysAuthService : IDynamicApiController, ITransient
App.GetRequiredService<SysSmsService>().VerifyCode(new SmsVerifyCodeInput { Phone = input.Phone, Code = input.Code });
// 获取登录租户和用户
var (tenant, user) = await GetLoginUserAndTenant(input.Host, phone: input.Phone);
var (tenant, user) = await GetLoginUserAndTenant(input.TenantId, phone: input.Phone);
return await CreateToken(user, tenant.AppId, input.LoginMode);
}
@ -405,7 +383,7 @@ public class SysAuthService : IDynamicApiController, ITransient
{
Account = auth.UserName,
Password = CryptogramUtil.SM2Encrypt(auth.Password),
Host = SqlSugarConst.DefaultTenantHost
TenantId = SqlSugarConst.DefaultTenantId
});
_sysCacheService.Remove($"{CacheConst.KeyConfig}{ConfigConst.SysCaptcha}");

View File

@ -44,6 +44,7 @@ public class CustomViewEngine : ViewEngineModel
return ClassName[..1].ToLower() + ClassName[1..]; // 首字母小写
}
}
public string? TreeName { get; set; }
public string? LowerTreeName { get; set; }
public string? LeftTab { get; set; }

View File

@ -55,10 +55,12 @@ public class PageCodeGenInput : BasePageInput
/// 数据库表名
/// </summary>
public virtual string TableName { get; set; }
/// <summary>
/// 自身树控件Name
/// 树控件名称
/// </summary>
public string? TreeName { get; set; }
/// <summary>
/// 命名空间
/// </summary>
@ -190,10 +192,12 @@ public class AddCodeGenInput : PageCodeGenInput
/// 左边关联字段
/// </summary>
public string? LeftKey { get; set; }
/// <summary>
/// 左边关联主表字段
/// </summary>
public string? LeftPrimaryKey { get; set; }
/// <summary>
/// 左边树Name
/// </summary>

View File

@ -15,22 +15,25 @@ namespace Admin.NET.Core.Service;
public class SysCodeGenService : IDynamicApiController, ITransient
{
private readonly ISqlSugarClient _db;
private readonly DbConnectionOptions _dbConnectionOptions;
private readonly CodeGenOptions _codeGenOptions;
private readonly SqlSugarRepository<SysCodeGenTemplate> _codeGetTemplateRep;
private readonly SysCodeGenConfigService _codeGenConfigService;
private readonly IViewEngine _viewEngine;
private readonly CodeGenOptions _codeGenOptions;
public SysCodeGenService(ISqlSugarClient db,
IOptions<DbConnectionOptions> dbConnectionOptions,
SqlSugarRepository<SysCodeGenTemplate> codeGetTemplateRep,
SysCodeGenConfigService codeGenConfigService,
IViewEngine viewEngine,
IOptions<CodeGenOptions> codeGenOptions)
{
_db = db;
_dbConnectionOptions = dbConnectionOptions.Value;
_codeGenOptions = codeGenOptions.Value;
_codeGetTemplateRep = codeGetTemplateRep;
_codeGenConfigService = codeGenConfigService;
_viewEngine = viewEngine;
_codeGenOptions = codeGenOptions.Value;
}
/// <summary>
@ -163,7 +166,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
[DisplayName("获取数据库库集合")]
public async Task<List<DatabaseOutput>> GetDatabaseList()
{
var dbConfigs = App.GetOptions<DbConnectionOptions>().ConnectionConfigs;
var dbConfigs = _dbConnectionOptions.ConnectionConfigs;
return await Task.FromResult(dbConfigs.Adapt<List<DatabaseOutput>>());
}
@ -177,7 +180,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
var provider = _db.AsTenant().GetConnectionScope(configId);
var dbTableInfos = provider.DbMaintenance.GetTableInfoList(false);
var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => configId.Equals(u.ConfigId));
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => configId.Equals(u.ConfigId));
IEnumerable<EntityInfo> entityInfos = await GetEntityInfos(false); // 获取所有实体定义
entityInfos = entityInfos.OrderBy(u => u.EntityName.StartsWith("Sys") ? 1 : 0).ThenBy(u => u.EntityName);
@ -217,7 +220,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
// 切库---多库代码生成用
var provider = _db.AsTenant().GetConnectionScope(configId);
var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId);
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == configId);
// 获取实体类型属性
var entityType = provider.DbMaintenance.GetTableInfoList(false).FirstOrDefault(u => u.Name == tableName);
if (entityType == null) return null;
@ -276,7 +279,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
var entityType = GetEntityInfos().GetAwaiter().GetResult().FirstOrDefault(u => u.EntityName == input.TableName);
if (entityType == null)
return null;
var config = App.GetOptions<DbConnectionOptions>().ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId);
var config = _dbConnectionOptions.ConnectionConfigs.FirstOrDefault(u => u.ConfigId.ToString() == input.ConfigId);
var dbTableName = config.DbSettings.EnableUnderLine ? UtilMethods.ToUnderLine(entityType.DbTableName) : entityType.DbTableName;
int bracketIndex = dbTableName.IndexOf('{');
@ -497,11 +500,11 @@ public class SysCodeGenService : IDynamicApiController, ITransient
string tResult = string.Empty; // 模板生成结果
var filename = templateList[i].Name;
//更改默认首页模板
// 更改默认首页模板
if (filename == "web_views_index.vue.vm")
{
filename = string.IsNullOrEmpty(input.LeftTab) ? filename : "web_views_LeftTree.vue.vm";//左树右列表
filename = string.IsNullOrEmpty(input.BottomTab) ? filename : "web_views_BottomIndx.vue.vm";//左数右上列表下列表属性
filename = string.IsNullOrEmpty(input.LeftTab) ? filename : "web_views_LeftTree.vue.vm"; // 左树右列表
filename = string.IsNullOrEmpty(input.BottomTab) ? filename : "web_views_BottomIndx.vue.vm"; // 左数右上列表下列表属性
}
var templateFilePath = Path.Combine(templatePath, filename);
if (!File.Exists(templateFilePath)) continue;
@ -630,7 +633,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
PrintType = input.PrintType!, // 支持打印类型
PrintName = input.PrintName!, // 打印模板名称
IsApiService = input.IsApiService,
RemoteVerify = tableFieldList.Any(t => t.RemoteVerify == true) // 远程验证
RemoteVerify = tableFieldList.Any(t => t.RemoteVerify == true), // 远程验证
TreeName = input.TreeName,
LowerTreeName = string.IsNullOrEmpty(input.TreeName) ? "" : input.TreeName[..1].ToLower() + input.TreeName[1..], // 首字母小写
LeftTab = input.LeftTab,
@ -659,11 +662,11 @@ public class SysCodeGenService : IDynamicApiController, ITransient
string tResult = string.Empty; // 模板生成结果
var filename = templateList[i].Name;
//更改默认首页模板
// 更改默认首页模板
if (filename == "web_views_index.vue.vm")
{
filename = string.IsNullOrEmpty(input.LeftTab) ? filename : "web_views_LeftTree.vue.vm";//左树右列表
filename = string.IsNullOrEmpty(input.BottomTab) ? filename : "web_views_BottomIndx.vue.vm";//左数右上列表下列表属性
filename = string.IsNullOrEmpty(input.LeftTab) ? filename : "web_views_LeftTree.vue.vm"; // 左树右列表
filename = string.IsNullOrEmpty(input.BottomTab) ? filename : "web_views_BottomIndx.vue.vm"; // 左数右上列表下列表属性
}
var templateFilePath = Path.Combine(templatePath, filename);
if (!File.Exists(templateFilePath)) continue;

View File

@ -103,9 +103,7 @@ public class SysUserService : IDynamicApiController, ITransient
[DisplayName("增加用户")]
public virtual async Task<long> AddUser(AddUserInput input)
{
// 是否租户隔离登录验证
var isTenantHostLogin = await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysTenantHostLogin);
var query = _sysUserRep.AsQueryable().ClearFilter().WhereIF(isTenantHostLogin, u => u.TenantId == _userManager.TenantId || u.AccountType == AccountTypeEnum.SuperAdmin);
var query = _sysUserRep.AsQueryable().ClearFilter();
if (await query.AnyAsync(u => u.Account == input.Account)) throw Oops.Oh(ErrorCodeEnum.D1003);
if (!string.IsNullOrWhiteSpace(input.Phone) && await query.AnyAsync(u => u.Phone == input.Phone)) throw Oops.Oh(ErrorCodeEnum.D1032);
@ -138,10 +136,7 @@ public class SysUserService : IDynamicApiController, ITransient
[DisplayName("更新用户")]
public virtual async Task UpdateUser(UpdateUserInput input)
{
// 是否租户隔离登录验证
var isTenantHostLogin = await _sysConfigService.GetConfigValueByCode<bool>(ConfigConst.SysTenantHostLogin);
var query = _sysUserRep.AsQueryable().ClearFilter().Where(u => u.Id != input.Id)
.WhereIF(isTenantHostLogin, u => u.TenantId == _userManager.TenantId || u.AccountType == AccountTypeEnum.SuperAdmin);
var query = _sysUserRep.AsQueryable().ClearFilter().Where(u => u.Id != input.Id);
if (await query.AnyAsync(u => u.Account == input.Account)) throw Oops.Oh(ErrorCodeEnum.D1003);
if (!string.IsNullOrWhiteSpace(input.Phone) && await query.AnyAsync(u => u.Phone == input.Phone)) throw Oops.Oh(ErrorCodeEnum.D1032);

View File

@ -2,7 +2,7 @@
"name": "admin.net.pro",
"type": "module",
"version": "2.4.33",
"lastBuildTime": "2025.01.13",
"lastBuildTime": "2025.01.14",
"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
"author": "zuohuaijun",
"license": "MIT",
@ -88,8 +88,8 @@
"@types/node": "^20.17.12",
"@types/nprogress": "^0.2.3",
"@types/sortablejs": "^1.15.8",
"@typescript-eslint/eslint-plugin": "^8.19.1",
"@typescript-eslint/parser": "^8.19.1",
"@typescript-eslint/eslint-plugin": "^8.20.0",
"@typescript-eslint/parser": "^8.20.0",
"@vitejs/plugin-vue": "^5.2.1",
"@vitejs/plugin-vue-jsx": "^4.1.1",
"@vue/compiler-sfc": "^3.5.13",
@ -100,7 +100,7 @@
"less": "^4.2.1",
"prettier": "^3.4.2",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.83.1",
"sass": "^1.83.3",
"terser": "^5.37.0",
"typescript": "^5.7.3",
"vite": "^6.0.7",

View File

@ -130,6 +130,14 @@ export interface AddCodeGenInput {
*/
connectionString?: string | null;
/**
*
*
* @type {string}
* @memberof AddCodeGenInput
*/
treeName?: string | null;
/**
*
*
@ -249,4 +257,68 @@ export interface AddCodeGenInput {
* @memberof AddCodeGenInput
*/
codeGenTemplateIds?: Array<number> | null;
/**
*
*
* @type {string}
* @memberof AddCodeGenInput
*/
leftTab?: string | null;
/**
*
*
* @type {string}
* @memberof AddCodeGenInput
*/
leftKey?: string | null;
/**
*
*
* @type {string}
* @memberof AddCodeGenInput
*/
leftPrimaryKey?: string | null;
/**
* Name
*
* @type {string}
* @memberof AddCodeGenInput
*/
leftName?: string | null;
/**
*
*
* @type {string}
* @memberof AddCodeGenInput
*/
bottomTab?: string | null;
/**
*
*
* @type {string}
* @memberof AddCodeGenInput
*/
bottomKey?: string | null;
/**
*
*
* @type {string}
* @memberof AddCodeGenInput
*/
bottomPrimaryKey?: string | null;
/**
*
*
* @type {string}
* @memberof AddCodeGenInput
*/
template?: string | null;
}

View File

@ -40,12 +40,12 @@ export interface LoginInput {
password: string;
/**
*
* Id
*
* @type {string}
* @type {number}
* @memberof LoginInput
*/
host?: string | null;
tenantId?: number;
/**
* Id

View File

@ -40,12 +40,12 @@ export interface LoginPhoneInput {
code: string;
/**
*
* Id
*
* @type {string}
* @type {number}
* @memberof LoginPhoneInput
*/
host?: string | null;
tenantId?: number;
/**
* @type {LoginModeEnum}

View File

@ -154,6 +154,14 @@ export interface PageCodeGenInput {
*/
tableName?: string | null;
/**
*
*
* @type {string}
* @memberof PageCodeGenInput
*/
treeName?: string | null;
/**
*
*

View File

@ -150,6 +150,14 @@ export interface SysCodeGen {
*/
tableName?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
treeName?: string | null;
/**
*
*
@ -222,6 +230,70 @@ export interface SysCodeGen {
*/
printName?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
leftTab?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
leftKey?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
leftPrimaryKey?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
leftName?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
bottomTab?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
bottomKey?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
bottomPrimaryKey?: string | null;
/**
*
*
* @type {string}
* @memberof SysCodeGen
*/
template?: string | null;
/**
* 使 Api Service
*

View File

@ -130,6 +130,14 @@ export interface UpdateCodeGenInput {
*/
connectionString?: string | null;
/**
*
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
treeName?: string | null;
/**
*
*
@ -250,6 +258,70 @@ export interface UpdateCodeGenInput {
*/
codeGenTemplateIds?: Array<number> | null;
/**
*
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
leftTab?: string | null;
/**
*
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
leftKey?: string | null;
/**
*
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
leftPrimaryKey?: string | null;
/**
* Name
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
leftName?: string | null;
/**
*
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
bottomTab?: string | null;
/**
*
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
bottomKey?: string | null;
/**
*
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
bottomPrimaryKey?: string | null;
/**
*
*
* @type {string}
* @memberof UpdateCodeGenInput
*/
template?: string | null;
/**
* Id
*

View File

@ -215,8 +215,8 @@ const onSignIn = async () => {
const publicKey = window.__env__.VITE_SM_PUBLIC_KEY;
const password = sm2.doEncrypt(state.ruleForm.password, publicKey, 1);
const host = route.query.host ?? location.host;
const [err, res] = await feature(getAPI(SysAuthApi).apiSysAuthLoginPost({ ...state.ruleForm, password: password, host: host.toString() }));
const tenantid = route.query.tenantid ?? 0;
const [err, res] = await feature(getAPI(SysAuthApi).apiSysAuthLoginPost({ ...state.ruleForm, password: password, tenantId: Number(tenantid) }));
if (err) {
getCaptcha(); //
return;

View File

@ -74,13 +74,9 @@
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="Name字段" prop="treeName"
:rules="[{ required: true, message: '请选择树控件Name字段', trigger: 'blur' }]">
<el-select v-model="state.ruleForm.treeName" @change="treeNameChanged"
value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.columnData" :key="item.columnName"
:label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'"
:value="item" />
<el-form-item label="Name字段" prop="treeName" :rules="[{ required: true, message: '请选择树控件Name字段', trigger: 'blur' }]">
<el-select v-model="state.ruleForm.treeName" @change="treeNameChanged" value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.columnData" :key="item.columnName" :label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'" :value="item" />
</el-select>
</el-form-item>
</el-col>
@ -222,10 +218,8 @@
<el-row :gutter="10">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="树库定位器" prop="configId2">
<el-select v-model="state.ruleForm.configId2" placeholder="库名"
filterable @change="dbChanged2()" class="w100">
<el-option v-for="item in state.dbData" :key="item.configId"
:label="item.configId" :value="item.configId" />
<el-select v-model="state.ruleForm.configId2" placeholder="库名" filterable @change="dbChanged2()" class="w100">
<el-option v-for="item in state.dbData" :key="item.configId" :label="item.configId" :value="item.configId" />
</el-select>
</el-form-item>
</el-col>
@ -234,77 +228,51 @@
<template v-slot:label>
<div>
左边树表
<el-tooltip raw-content
content="若找不到在前端生成的实体/表同上如表有下划线_则因实体去掉划线取不到字段。"
placement="top">
<el-icon size="16"
style="margin-right: 3px; display: inline; vertical-align: middle"><ele-QuestionFilled /></el-icon>
<el-tooltip raw-content content="若找不到在前端生成的实体/表同上如表有下划线_则因实体去掉划线取不到字段。" placement="top">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"><ele-QuestionFilled /></el-icon>
</el-tooltip>
</div>
</template>
<el-select v-model="state.ruleForm.leftTab"
@change="leftTableChanged" value-key="value" filterable
clearable class="w100">
<el-option v-for="item in state.tableData2"
:key="item.entityName"
:label="item.entityName + ' ( ' + item.tableName + ' ) [' + item.tableComment + ']'"
:value="item" />
<el-select v-model="state.ruleForm.leftTab" @change="leftTableChanged" value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.tableData2" :key="item.entityName" :label="item.entityName + ' ( ' + item.tableName + ' ) [' + item.tableComment + ']'" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="左边树关联字段" prop="leftKey">
<el-select v-model="state.ruleForm.leftKey" @change="leftKeyChanged"
value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.lcolumnData"
:key="item.columnName"
:label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'"
:value="item" />
<el-select v-model="state.ruleForm.leftKey" @change="leftKeyChanged" value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.lcolumnData" :key="item.columnName" :label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="关联主表字段" prop="bottomKey">
<template v-slot:label>
<div>
关联主表字段
<el-tooltip raw-content content="先选择主表才可以选择字段。"
placement="top">
<el-icon size="16"
style="margin-right: 3px; display: inline; vertical-align: middle"><ele-QuestionFilled /></el-icon>
<el-tooltip raw-content content="先选择主表才可以选择字段。" placement="top">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"><ele-QuestionFilled /></el-icon>
</el-tooltip>
</div>
</template>
<el-select v-model="state.ruleForm.leftPrimaryKey"
@change="leftPrimaryKeyChanged" value-key="value" filterable
clearable class="w100">
<el-option v-for="item in state.columnData"
:key="item.columnName"
:label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'"
:value="item" />
<el-select v-model="state.ruleForm.leftPrimaryKey" @change="leftPrimaryKeyChanged" value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.columnData" :key="item.columnName" :label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="左边树Name字段" prop="leftName">
<el-select v-model="state.ruleForm.leftName"
@change="leftNameChanged" value-key="value" filterable clearable
class="w100">
<el-option v-for="item in state.lcolumnData"
:key="item.columnName"
:label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'"
:value="item" />
<el-select v-model="state.ruleForm.leftName" @change="leftNameChanged" value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.lcolumnData" :key="item.columnName" :label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="模板" prop="template">
<el-input v-model="state.ruleForm.template" clearable
placeholder="请输入" />
<el-input v-model="state.ruleForm.template" clearable placeholder="请输入" />
</el-form-item>
</el-col>
</el-row>
@ -313,10 +281,8 @@
<el-row :gutter="10">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="右区域下框库定位器" prop="configId3">
<el-select v-model="state.ruleForm.configId3" placeholder="库名"
filterable @change="dbChanged3()" class="w100">
<el-option v-for="item in state.dbData" :key="item.configId"
:label="item.configId" :value="item.configId" />
<el-select v-model="state.ruleForm.configId3" placeholder="库名" filterable @change="dbChanged3()" class="w100">
<el-option v-for="item in state.dbData" :key="item.configId" :label="item.configId" :value="item.configId" />
</el-select>
</el-form-item>
</el-col>
@ -325,33 +291,20 @@
<template v-slot:label>
<div>
下框关联表名称
<el-tooltip raw-content
content="若找不到在前端生成的实体/表同上如表有下划线_则因实体去掉划线取不到字段。"
placement="top">
<el-icon size="16"
style="margin-right: 3px; display: inline; vertical-align: middle"><ele-QuestionFilled /></el-icon>
<el-tooltip raw-content content="若找不到在前端生成的实体/表同上如表有下划线_则因实体去掉划线取不到字段。" placement="top">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"><ele-QuestionFilled /></el-icon>
</el-tooltip>
</div>
</template>
<el-select v-model="state.ruleForm.bottomTab"
@change="bottomTableChanged" value-key="value" filterable
clearable class="w100">
<el-option v-for="item in state.tableData3"
:key="item.entityName"
:label="item.entityName + ' ( ' + item.tableName + ' ) [' + item.tableComment + ']'"
:value="item" />
<el-select v-model="state.ruleForm.bottomTab" @change="bottomTableChanged" value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.tableData3" :key="item.entityName" :label="item.entityName + ' ( ' + item.tableName + ' ) [' + item.tableComment + ']'" :value="item" />
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
<el-form-item label="下框表关联字段" prop="bottomKey">
<el-select v-model="state.ruleForm.bottomKey"
@change="bottomKeyChanged" value-key="value" filterable
clearable class="w100">
<el-option v-for="item in state.bcolumnData"
:key="item.columnName"
:label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'"
:value="item" />
<el-select v-model="state.ruleForm.bottomKey" @change="bottomKeyChanged" value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.bcolumnData" :key="item.columnName" :label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'" :value="item" />
</el-select>
</el-form-item>
</el-col>
@ -360,20 +313,13 @@
<template v-slot:label>
<div>
关联主表字段
<el-tooltip raw-content content="先选择主表才可以选择字段。"
placement="top">
<el-icon size="16"
style="margin-right: 3px; display: inline; vertical-align: middle"><ele-QuestionFilled /></el-icon>
<el-tooltip raw-content content="先选择主表才可以选择字段。" placement="top">
<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"><ele-QuestionFilled /></el-icon>
</el-tooltip>
</div>
</template>
<el-select v-model="state.ruleForm.bottomPrimaryKey"
@change="bottomPrimaryKeyChanged" value-key="value" filterable
clearable class="w100">
<el-option v-for="item in state.columnData"
:key="item.columnName"
:label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'"
:value="item" />
<el-select v-model="state.ruleForm.bottomPrimaryKey" @change="bottomPrimaryKeyChanged" value-key="value" filterable clearable class="w100">
<el-option v-for="item in state.columnData" :key="item.columnName" :label="item.columnName + ' ( ' + item.columnName + ' ) [' + item.columnComment + ']'" :value="item" />
</el-select>
</el-form-item>
</el-col>