Преглед на файлове

feat: 移除应用管理模块,改用租户模块管理站点信息

喵你个旺呀 преди 1 година
родител
ревизия
44e447d202
променени са 79 файла, в които са добавени 1146 реда и са изтрити 3007 реда
  1. 0 25
      Admin.NET/Admin.NET.Application/SeedData/SysAppSeedData.cs
  2. 0 5
      Admin.NET/Admin.NET.Core/Const/ClaimConst.cs
  3. 43 43
      Admin.NET/Admin.NET.Core/Const/ConfigConst.cs
  4. 0 10
      Admin.NET/Admin.NET.Core/Const/SqlSugarConst.cs
  5. 0 82
      Admin.NET/Admin.NET.Core/Entity/SysApp.cs
  6. 0 41
      Admin.NET/Admin.NET.Core/Entity/SysAppMenu.cs
  7. 66 23
      Admin.NET/Admin.NET.Core/Entity/SysTenant.cs
  8. 27 0
      Admin.NET/Admin.NET.Core/Entity/SysTenantMenu.cs
  9. 12 0
      Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs
  10. 3 3
      Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs
  11. 0 25
      Admin.NET/Admin.NET.Core/SeedData/SysAppSeedData.cs
  12. 2 2
      Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs
  13. 1 9
      Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs
  14. 8 5
      Admin.NET/Admin.NET.Core/SeedData/SysTenantMenuSeedData.cs
  15. 1 1
      Admin.NET/Admin.NET.Core/SeedData/SysTenantSeedData.cs
  16. 0 173
      Admin.NET/Admin.NET.Core/Service/Application/Dto/SysAppInput.cs
  17. 0 103
      Admin.NET/Admin.NET.Core/Service/Application/Dto/SysAppOutput.cs
  18. 0 192
      Admin.NET/Admin.NET.Core/Service/Application/SysAppService.cs
  19. 6 6
      Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginInput.cs
  20. 22 50
      Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs
  21. 0 8
      Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs
  22. 2 2
      Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs
  23. 29 67
      Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs
  24. 8 8
      Admin.NET/Admin.NET.Core/Service/Menu/Dto/MenuInput.cs
  25. 59 83
      Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs
  26. 5 4
      Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs
  27. 1 1
      Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthService.cs
  28. 1 1
      Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleMenuInput.cs
  29. 1 0
      Admin.NET/Admin.NET.Core/Service/Role/SysRoleService.cs
  30. 69 7
      Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantInput.cs
  31. 2 7
      Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantOutput.cs
  32. 141 48
      Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs
  33. 2 4
      Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs
  34. 1 6
      Admin.NET/Admin.NET.Core/Service/User/UserManager.cs
  35. 0 1
      Web/src/api-services/api.ts
  36. 0 720
      Web/src/api-services/apis/sys-app-api.ts
  37. 13 21
      Web/src/api-services/apis/sys-menu-api.ts
  38. 113 33
      Web/src/api-services/apis/sys-tenant-api.ts
  39. 6 0
      Web/src/api-services/models/add-menu-input.ts
  40. 1 1
      Web/src/api-services/models/add-pos-input.ts
  41. 0 80
      Web/src/api-services/models/add-sys-app-input.ts
  42. 62 8
      Web/src/api-services/models/add-tenant-input.ts
  43. 0 57
      Web/src/api-services/models/admin-result-sql-sugar-paged-list-sys-app-output.ts
  44. 0 26
      Web/src/api-services/models/base-id-input.ts
  45. 0 70
      Web/src/api-services/models/base-page-input.ts
  46. 0 32
      Web/src/api-services/models/change-app-input.ts
  47. 1 9
      Web/src/api-services/models/index.ts
  48. 2 2
      Web/src/api-services/models/info-save-input.ts
  49. 3 3
      Web/src/api-services/models/login-input.ts
  50. 3 3
      Web/src/api-services/models/login-phone-input.ts
  51. 0 6
      Web/src/api-services/models/page-dict-data-input.ts
  52. 1 1
      Web/src/api-services/models/role-menu-input.ts
  53. 0 63
      Web/src/api-services/models/sql-sugar-paged-list-sys-app-output.ts
  54. 0 128
      Web/src/api-services/models/sys-app-output.ts
  55. 1 1
      Web/src/api-services/models/sys-pos.ts
  56. 5 5
      Web/src/api-services/models/tenant-menu-input.ts
  57. 50 8
      Web/src/api-services/models/tenant-output.ts
  58. 6 0
      Web/src/api-services/models/update-menu-input.ts
  59. 1 1
      Web/src/api-services/models/update-pos-input.ts
  60. 0 86
      Web/src/api-services/models/update-sys-app-input.ts
  61. 62 8
      Web/src/api-services/models/update-tenant-input.ts
  62. 0 1
      Web/src/i18n/lang/zh-cn.ts
  63. 0 1
      Web/src/i18n/lang/zh-tw.ts
  64. 6 3
      Web/src/i18n/pages/login/en.ts
  65. 6 3
      Web/src/i18n/pages/login/zh-cn.ts
  66. 6 3
      Web/src/i18n/pages/login/zh-tw.ts
  67. 0 91
      Web/src/layout/navBars/topBar/changeApp.vue
  68. 2 9
      Web/src/layout/navBars/topBar/user.vue
  69. 18 8
      Web/src/views/login/component/account.vue
  70. 13 0
      Web/src/views/login/component/mobile.vue
  71. 15 1
      Web/src/views/login/component/scan.vue
  72. 23 5
      Web/src/views/login/index.vue
  73. 0 198
      Web/src/views/system/app/component/editApp.vue
  74. 0 147
      Web/src/views/system/app/index.vue
  75. 4 4
      Web/src/views/system/infoSetting/index.vue
  76. 19 3
      Web/src/views/system/menu/index.vue
  77. 167 105
      Web/src/views/system/tenant/component/editTenant.vue
  78. 11 5
      Web/src/views/system/tenant/component/grantMenu.vue
  79. 14 3
      Web/src/views/system/tenant/index.vue

+ 0 - 25
Admin.NET/Admin.NET.Application/SeedData/SysAppSeedData.cs

@@ -1,25 +0,0 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Application;
-
-/// <summary>
-/// 系统应用表种子数据
-/// </summary>
-public class SysAppSeedData : ISqlSugarEntitySeedData<SysApp>
-{
-    /// <summary>
-    /// 种子数据
-    /// </summary>
-    /// <returns></returns>
-    public IEnumerable<SysApp> HasData()
-    {
-        return new[]
-        {
-            new SysApp{ Id=1310000000001, Name=ApplicationConst.GroupName, Logo="/upload/logo.png", Title="Admin.App", ViceTitle="Admin.App", ViceDesc="站在巨人肩膀上的 .NET 通用权限开发框架", Watermark="Admin.App", Copyright="Copyright \u00a9 2021-present Admin.NET All rights reserved.", Icp="省ICP备12345678号", Remark=ApplicationConst.GroupName, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-        };
-    }
-}

+ 0 - 5
Admin.NET/Admin.NET.Core/Const/ClaimConst.cs

@@ -12,11 +12,6 @@ namespace Admin.NET.Core;
 public class ClaimConst
 {
     /// <summary>
-    /// 应用Id
-    /// </summary>
-    public const string AppId = "AppId";
-
-    /// <summary>
     /// 用户Id
     /// </summary>
     public const string UserId = "UserId";

+ 43 - 43
Admin.NET/Admin.NET.Core/Const/ConfigConst.cs

@@ -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>
     /// 数据校验日志
@@ -96,43 +96,43 @@ public class ConfigConst
     /// </summary>
     public const string SysWebConfigGroup = "WebConfig";
 
-    /// <summary>
-    /// 系统图标
-    /// </summary>
-    public const string SysWebLogo = "sys_web_logo";
-
-    /// <summary>
-    /// 系统主标题
-    /// </summary>
-    public const string SysWebTitle = "sys_web_title";
-
-    /// <summary>
-    /// 系统副标题
-    /// </summary>
-    public const string SysWebViceTitle = "sys_web_viceTitle";
-
-    /// <summary>
-    /// 系统描述
-    /// </summary>
-    public const string SysWebViceDesc = "sys_web_viceDesc";
-
-    /// <summary>
-    /// 水印内容
-    /// </summary>
-    public const string SysWebWatermark = "sys_web_watermark";
-
-    /// <summary>
-    /// 版权说明
-    /// </summary>
-    public const string SysWebCopyright = "sys_web_copyright";
-
-    /// <summary>
-    /// ICP备案号
-    /// </summary>
-    public const string SysWebIcp = "sys_web_icp";
-
-    /// <summary>
-    /// ICP地址
-    /// </summary>
-    public const string SysWebIcpUrl = "sys_web_icpUrl";
+    // /// <summary>
+    // /// 系统图标
+    // /// </summary>
+    // public const string SysWebLogo = "sys_web_logo";
+    //
+    // /// <summary>
+    // /// 系统主标题
+    // /// </summary>
+    // public const string SysWebTitle = "sys_web_title";
+    //
+    // /// <summary>
+    // /// 系统副标题
+    // /// </summary>
+    // public const string SysWebViceTitle = "sys_web_viceTitle";
+    //
+    // /// <summary>
+    // /// 系统描述
+    // /// </summary>
+    // public const string SysWebViceDesc = "sys_web_viceDesc";
+    //
+    // /// <summary>
+    // /// 水印内容
+    // /// </summary>
+    // public const string SysWebWatermark = "sys_web_watermark";
+    //
+    // /// <summary>
+    // /// 版权说明
+    // /// </summary>
+    // public const string SysWebCopyright = "sys_web_copyright";
+    //
+    // /// <summary>
+    // /// ICP备案号
+    // /// </summary>
+    // public const string SysWebIcp = "sys_web_icp";
+    //
+    // /// <summary>
+    // /// ICP地址
+    // /// </summary>
+    // public const string SysWebIcpUrl = "sys_web_icpUrl";
 }

+ 0 - 10
Admin.NET/Admin.NET.Core/Const/SqlSugarConst.cs

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

+ 0 - 82
Admin.NET/Admin.NET.Core/Entity/SysApp.cs

@@ -1,82 +0,0 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core;
-
-/// <summary>
-/// 系统应用表
-/// </summary>
-[SysTable]
-[SugarTable(null, "系统应用表")]
-public partial class SysApp : EntityBase
-{
-    /// <summary>
-    /// 名称
-    /// </summary>
-    [SugarColumn(ColumnDescription = "名称", Length = 32), Required, MaxLength(32)]
-    public virtual string Name { get; set; }
-
-    /// <summary>
-    /// 图标
-    /// </summary>
-    [SugarColumn(ColumnDescription = "图标", Length = 256), Required, MaxLength(256)]
-    public virtual string? Logo { get; set; }
-
-    /// <summary>
-    /// 标题
-    /// </summary>
-    [SugarColumn(ColumnDescription = "标题", Length = 32), MaxLength(32)]
-    public string Title { get; set; }
-
-    /// <summary>
-    /// 副标题
-    /// </summary>
-    [SugarColumn(ColumnDescription = "副标题", Length = 32), MaxLength(32)]
-    public string ViceTitle { get; set; }
-
-    /// <summary>
-    /// 副描述
-    /// </summary>
-    [SugarColumn(ColumnDescription = "副描述", Length = 64), MaxLength(64)]
-    public string? ViceDesc { get; set; }
-
-    /// <summary>
-    /// 水印
-    /// </summary>
-    [SugarColumn(ColumnDescription = "水印", Length = 32), MaxLength(32)]
-    public string? Watermark { get; set; }
-
-    /// <summary>
-    /// 版权信息
-    /// </summary>
-    [SugarColumn(ColumnDescription = "版权信息", Length = 64), MaxLength(64)]
-    public string? Copyright { get; set; }
-
-    /// <summary>
-    /// ICP备案号
-    /// </summary>
-    [SugarColumn(ColumnDescription = "ICP备案号", Length = 32), MaxLength(32)]
-    public string? Icp { get; set; }
-
-    /// <summary>
-    /// 排序
-    /// </summary>
-    [IgnoreUpdateSeedColumn]
-    [SugarColumn(ColumnDescription = "排序")]
-    public int OrderNo { get; set; } = 100;
-
-    /// <summary>
-    /// 备注
-    /// </summary>
-    [SugarColumn(ColumnDescription = "备注", Length = 256), MaxLength(256)]
-    public string? Remark { get; set; }
-
-    /// <summary>
-    /// 应用租户
-    /// </summary>
-    [Navigate(NavigateType.OneToMany, nameof(SysTenant.AppId))]
-    public List<SysTenant> TenantList { get; set; }
-}

+ 0 - 41
Admin.NET/Admin.NET.Core/Entity/SysAppMenu.cs

@@ -1,41 +0,0 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core;
-
-/// <summary>
-/// 系统应用菜单表
-/// </summary>
-[SugarTable(null, "系统应用菜单表")]
-[SysTable]
-public partial class SysAppMenu : EntityBaseId
-{
-    /// <summary>
-    /// 应用Id
-    /// </summary>
-    [SugarColumn(ColumnDescription = "应用Id")]
-    public long AppId { get; set; }
-
-    /// <summary>
-    /// 应用
-    /// </summary>
-    [Newtonsoft.Json.JsonIgnore]
-    [System.Text.Json.Serialization.JsonIgnore]
-    [Navigate(NavigateType.OneToOne, nameof(AppId))]
-    public SysApp SysApp { get; set; }
-
-    /// <summary>
-    /// 菜单Id
-    /// </summary>
-    [SugarColumn(ColumnDescription = "菜单Id")]
-    public long MenuId { get; set; }
-
-    /// <summary>
-    /// 菜单
-    /// </summary>
-    [Navigate(NavigateType.OneToOne, nameof(MenuId))]
-    public SysMenu SysMenu { get; set; }
-}

+ 66 - 23
Admin.NET/Admin.NET.Core/Entity/SysTenant.cs

@@ -4,6 +4,8 @@
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
+using Tea.Utils;
+
 namespace Admin.NET.Core;
 
 /// <summary>
@@ -16,84 +18,125 @@ public partial class SysTenant : EntityBase
     /// <summary>
     /// 应用Id
     /// </summary>
+    [Obsolete("废弃字段, 请勿使用")]
     [SugarColumn(ColumnDescription = "应用Id")]
-    public long AppId { get; set; }
-
+    public virtual long? AppId { get; set; }
+    
     /// <summary>
-    /// 用户Id
+    /// 租管用户Id
     /// </summary>
-    [SugarColumn(ColumnDescription = "用户Id")]
-    public long UserId { get; set; }
+    [SugarColumn(ColumnDescription = "租管用户Id")]
+    public virtual long UserId { get; set; }
 
     /// <summary>
     /// 机构Id
     /// </summary>
     [SugarColumn(ColumnDescription = "机构Id")]
-    public long OrgId { get; set; }
+    public virtual long OrgId { get; set; }
 
     /// <summary>
     /// 域名
     /// </summary>
     [SugarColumn(ColumnDescription = "域名", Length = 128)]
     [MaxLength(128)]
-    public string? Host { get; set; }
+    public virtual string? Host { get; set; }
 
     /// <summary>
     /// 租户类型
     /// </summary>
     [SugarColumn(ColumnDescription = "租户类型")]
-    public TenantTypeEnum TenantType { get; set; }
+    public virtual TenantTypeEnum TenantType { get; set; }
 
     /// <summary>
     /// 数据库类型
     /// </summary>
     [SugarColumn(ColumnDescription = "数据库类型")]
-    public SqlSugar.DbType DbType { get; set; }
+    public virtual SqlSugar.DbType DbType { get; set; }
 
     /// <summary>
     /// 数据库连接
     /// </summary>
     [SugarColumn(ColumnDescription = "数据库连接", Length = 256)]
     [MaxLength(256)]
-    public string? Connection { get; set; }
+    public virtual string? Connection { get; set; }
 
     /// <summary>
     /// 数据库标识
     /// </summary>
     [SugarColumn(ColumnDescription = "数据库标识", Length = 64)]
     [MaxLength(64)]
-    public string? ConfigId { get; set; }
+    public virtual string? ConfigId { get; set; }
 
     /// <summary>
     /// 从库连接/读写分离
     /// </summary>
     [SugarColumn(ColumnDescription = "从库连接/读写分离", ColumnDataType = StaticConfig.CodeFirst_BigString)]
-    public string? SlaveConnections { get; set; }
+    public virtual string? SlaveConnections { get; set; }
+
+    /// <summary>
+    /// 图标
+    /// </summary>
+    [SugarColumn(ColumnDescription = "图标", Length = 256), Required, MaxLength(256)]
+    public virtual string? Logo { get; set; }
+
+    /// <summary>
+    /// 标题
+    /// </summary>
+    [SugarColumn(ColumnDescription = "标题", Length = 32), MaxLength(32)]
+    public virtual string Title { get; set; }
+
+    /// <summary>
+    /// 副标题
+    /// </summary>
+    [SugarColumn(ColumnDescription = "副标题", Length = 32), MaxLength(32)]
+    public virtual string ViceTitle { get; set; }
+
+    /// <summary>
+    /// 副描述
+    /// </summary>
+    [SugarColumn(ColumnDescription = "副描述", Length = 64), MaxLength(64)]
+    public virtual string? ViceDesc { get; set; }
+
+    /// <summary>
+    /// 水印
+    /// </summary>
+    [SugarColumn(ColumnDescription = "水印", Length = 32), MaxLength(32)]
+    public virtual string? Watermark { get; set; }
+
+    /// <summary>
+    /// 版权信息
+    /// </summary>
+    [SugarColumn(ColumnDescription = "版权信息", Length = 64), MaxLength(64)]
+    public virtual string? Copyright { get; set; }
+
+    /// <summary>
+    /// ICP备案号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "ICP备案号", Length = 32), MaxLength(32)]
+    public virtual string? Icp { get; set; }
+    
+    /// <summary>
+    /// ICP地址
+    /// </summary>
+    [SugarColumn(ColumnDescription = "ICP地址", Length = 32), MaxLength(32)]
+    public virtual string? IcpUrl { get; set; }
 
     /// <summary>
     /// 排序
     /// </summary>
     [SugarColumn(ColumnDescription = "排序")]
-    public int OrderNo { get; set; } = 100;
+    public virtual int OrderNo { get; set; } = 100;
 
     /// <summary>
     /// 备注
     /// </summary>
     [SugarColumn(ColumnDescription = "备注", Length = 128)]
     [MaxLength(128)]
-    public string? Remark { get; set; }
+    public virtual string? Remark { get; set; }
 
     /// <summary>
     /// 状态
     /// </summary>
     [SugarColumn(ColumnDescription = "状态")]
-    public StatusEnum Status { get; set; } = StatusEnum.Enable;
-
-    /// <summary>
-    /// 应用
-    /// </summary>
-    [Newtonsoft.Json.JsonIgnore]
-    [System.Text.Json.Serialization.JsonIgnore]
-    [Navigate(NavigateType.OneToOne, nameof(AppId))]
-    public SysApp SysApp { get; set; }
+    public virtual StatusEnum Status { get; set; } = StatusEnum.Enable;
 }

+ 27 - 0
Admin.NET/Admin.NET.Core/Entity/SysTenantMenu.cs

@@ -0,0 +1,27 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 系统租户菜单表
+/// </summary>
+[SugarTable(null, "系统租户菜单表")]
+[SysTable]
+public class SysTenantMenu : EntityBaseId
+{
+    /// <summary>
+    /// 租户Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "租户Id")]
+    public long TenantId { get; set; }
+
+    /// <summary>
+    /// 菜单Id
+    /// </summary>
+    [SugarColumn(ColumnDescription = "菜单Id")]
+    public long MenuId { get; set; }
+}

+ 12 - 0
Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs

@@ -402,6 +402,12 @@ public enum ErrorCodeEnum
     /// </summary>
     [ErrorCodeItemMetadata("父节点不能为按钮类型")]
     D4010,
+    
+    /// <summary>
+    /// 租户不能为空
+    /// </summary>
+    [ErrorCodeItemMetadata("租户不能为空")]
+    D4011,
 
     /// <summary>
     /// 已存在同名或同编码应用
@@ -558,6 +564,12 @@ public enum ErrorCodeEnum
     /// </summary>
     [ErrorCodeItemMetadata("已存在同名的租户域名")]
     D1303,
+    
+    /// <summary>
+    /// 授权菜单存在重复项
+    /// </summary>
+    [ErrorCodeItemMetadata("授权菜单存在重复项")]
+    D1304,
 
     /// <summary>
     /// 该表代码模板已经生成过

+ 3 - 3
Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs

@@ -41,9 +41,9 @@ public class AppEventSubscriber : IEventSubscriber, ISingleton, IDisposable
         //var mailTempPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Temp\\ErrorMail.tp");
         //var mailTemp = File.ReadAllText(mailTempPath);
         //var mail = await _serviceScope.ServiceProvider.GetRequiredService<IViewEngine>().RunCompileFromCachedAsync(mailTemp, );
-
-        var webTitle = await _serviceScope.ServiceProvider.GetRequiredService<SysConfigService>().GetConfigValue<string>(ConfigConst.SysWebTitle);
-        var title = $"{webTitle} 系统异常";
+        var tenantId = App.GetService<UserManager>()?.TenantId ?? SqlSugarConst.DefaultTenantId;
+        var tenant = await App.GetService<ISqlSugarClient>().Queryable<SysTenant>().FirstAsync(t => t.Id == tenantId);
+        var title = $"{tenant?.Title} 系统异常";
         await _serviceScope.ServiceProvider.GetRequiredService<SysEmailService>().SendEmail(JSON.Serialize(context.Source.Payload), title);
     }
 

+ 0 - 25
Admin.NET/Admin.NET.Core/SeedData/SysAppSeedData.cs

@@ -1,25 +0,0 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core;
-
-/// <summary>
-/// 系统应用表种子数据
-/// </summary>
-public class SysAppSeedData : ISqlSugarEntitySeedData<SysApp>
-{
-    /// <summary>
-    /// 种子数据
-    /// </summary>
-    /// <returns></returns>
-    public IEnumerable<SysApp> HasData()
-    {
-        return new[]
-        {
-            new SysApp{ Id=SqlSugarConst.DefaultAppId, Name="默认应用", Logo="/upload/logo.png", Title="Admin.NET", ViceTitle="Admin.NET", ViceDesc="站在巨人肩膀上的 .NET 通用权限开发框架", Watermark="Admin.NET", Copyright="Copyright \u00a9 2021-present Admin.NET All rights reserved.", Icp="省ICP备12345678号", Remark="系统默认应用", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-        };
-    }
-}

+ 2 - 2
Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs

@@ -31,7 +31,7 @@ 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=1300000000212, Name="租户隔离登录验证", Code=ConfigConst.SysTenantHostLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="租户隔离登录验证", OrderNo=370, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            //new SysConfig{ Id=1300000000212, Name="租户隔离登录验证", Code=ConfigConst.SysTenantHostLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="租户隔离登录验证", OrderNo=370, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             new SysConfig{ Id=1300000000221, Name="数据校验日志", Code=ConfigConst.SysValidationLog, Value="True", SysFlag=YesNoEnum.Y, Remark="是否数据校验日志", OrderNo=130, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             new SysConfig{ Id=1300000000231, Name="行政区域同步层级", Code=ConfigConst.SysRegionSyncLevel, Value="3", SysFlag=YesNoEnum.Y, Remark="行政区域同步层级 1-省级,2-市级,3-区县级,4-街道级,5-村级", OrderNo=140, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             // new SysConfig{ Id=1300000000301, Name="系统主标题", Code=ConfigConst.SysWebTitle, Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="系统主标题", OrderNo=300, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
@@ -41,7 +41,7 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig>
             // new SysConfig{ Id=1300000000341, Name="版权说明", Code=ConfigConst.SysWebCopyright, Value="Copyright © 2021-present Admin.NET All rights reserved.", SysFlag=YesNoEnum.Y, Remark="版权说明", OrderNo=340, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             // new SysConfig{ Id=1300000000351, Name="系统图标", Code=ConfigConst.SysWebLogo, Value="/upload/logo.png", SysFlag=YesNoEnum.Y, Remark="系统图标", OrderNo=350, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             // new SysConfig{ Id=1300000000361, Name="ICP备案号", Code=ConfigConst.SysWebIcp, Value="省ICP备12345678号", SysFlag=YesNoEnum.Y, Remark="ICP备案号", OrderNo=360, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000371, Name="ICP地址", Code=ConfigConst.SysWebIcpUrl, Value="https://beian.miit.gov.cn", SysFlag=YesNoEnum.Y, Remark="ICP地址", OrderNo=370, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            // new SysConfig{ Id=1300000000371, Name="ICP地址", Code=ConfigConst.SysWebIcpUrl, Value="https://beian.miit.gov.cn", SysFlag=YesNoEnum.Y, Remark="ICP地址", OrderNo=370, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
         };
     }
 }

+ 1 - 9
Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs

@@ -90,15 +90,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1310000000198, Pid=1310000000191, Title="同步域组织", Permission="sysLdap:syncOrg", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=160 },
 
             new SysMenu{ Id=1310000000301, Pid=0, Title="平台管理", Path="/platform", Name="platform", Component="Layout", Icon="ele-Menu", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=11000 },
-
-            new SysMenu{ Id=1310000001311, Pid=1310000000301, Title="应用管理", Path="/platform/app", Name="sysApp", Component="/system/app/index", Icon="ele-Orange", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=90 },
-            new SysMenu{ Id=1310000001312, Pid=1310000001311, Title="查询", Permission="sysApp:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
-            new SysMenu{ Id=1310000001313, Pid=1310000001311, Title="编辑", Permission="sysApp:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
-            new SysMenu{ Id=1310000001314, Pid=1310000001311, Title="增加", Permission="sysApp:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
-            new SysMenu{ Id=1310000001315, Pid=1310000001311, Title="删除", Permission="sysApp:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
-            new SysMenu{ Id=1310000001316, Pid=1310000001311, Title="授权菜单", Permission="sysApp:grantMenu", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
-            new SysMenu{ Id=1310000001317, Pid=1310000001311, Title="切换应用", Permission="sysApp:changeApp", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
-
+            
             new SysMenu{ Id=1310000000311, Pid=1310000000301, Title="租户管理", Path="/platform/tenant", Name="sysTenant", Component="/system/tenant/index", Icon="ele-School", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1310000000312, Pid=1310000000311, Title="查询", Permission="sysTenant:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1310000000313, Pid=1310000000311, Title="编辑", Permission="sysTenant:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },

+ 8 - 5
Admin.NET/Admin.NET.Core/SeedData/SysAppMenuSeedData.cs → Admin.NET/Admin.NET.Core/SeedData/SysTenantMenuSeedData.cs

@@ -7,17 +7,20 @@
 namespace Admin.NET.Core;
 
 /// <summary>
-/// 系统应用菜单表种子数据
+/// 系统租户菜单表种子数据
 /// </summary>
-public class SysAppMenuSeedData : ISqlSugarEntitySeedData<SysAppMenu>
+[IgnoreUpdateSeed]
+public class SysTenantMenuSeedData : ISqlSugarEntitySeedData<SysTenantMenu>
 {
     /// <summary>
     /// 种子数据
     /// </summary>
     /// <returns></returns>
-    public IEnumerable<SysAppMenu> HasData()
+    public IEnumerable<SysTenantMenu> HasData()
     {
-        long id = 1300000000001;
-        return new SysMenuSeedData().HasData().Select(m => new SysAppMenu { Id = id++, AppId = SqlSugarConst.DefaultAppId, MenuId = m.Id });
+        var id = 1300000000101;
+        var tenant = new SysTenantSeedData().HasData().First();
+        var menuList = new SysMenuSeedData().HasData().ToList();
+        return menuList.Select(u => new SysTenantMenu { Id = id++, TenantId = tenant.Id, MenuId = u.Id });
     }
 }

+ 1 - 1
Admin.NET/Admin.NET.Core/SeedData/SysTenantSeedData.cs

@@ -21,7 +21,7 @@ public class SysTenantSeedData : ISqlSugarEntitySeedData<SysTenant>
 
         return new[]
         {
-            new SysTenant{ Id=SqlSugarConst.DefaultTenantId, AppId=SqlSugarConst.DefaultAppId, 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, OrgId=SqlSugarConst.DefaultTenantId, UserId=1300000000111, Host="gitee.com", TenantType=TenantTypeEnum.Id, DbType=defaultDbConfig.DbType, Connection=defaultDbConfig.ConnectionString, ConfigId=SqlSugarConst.MainConfigId, Logo="/upload/logo.png", Title="Admin.NET", ViceTitle="Admin.NET", ViceDesc="站在巨人肩膀上的 .NET 通用权限开发框架", Watermark="Admin.NET", Copyright="Copyright \u00a9 2021-present Admin.NET All rights reserved.", Icp="省ICP备12345678号", IcpUrl="https://beian.miit.gov.cn", Remark="系统默认", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
         };
     }
 }

+ 0 - 173
Admin.NET/Admin.NET.Core/Service/Application/Dto/SysAppInput.cs

@@ -1,173 +0,0 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core.Service;
-
-/// <summary>
-/// 应用基础输入参数
-/// </summary>
-public class SysAppBaseInput
-{
-    /// <summary>
-    /// 主键Id
-    /// </summary>
-    public virtual long? Id { get; set; }
-
-    /// <summary>
-    /// 名称
-    /// </summary>
-    [Required(ErrorMessage = "名称不能为空")]
-    public virtual string Name { get; set; }
-
-    /// <summary>
-    /// 标题
-    /// </summary>
-    [Required(ErrorMessage = "标题不能为空")]
-    public virtual string Title { get; set; }
-
-    /// <summary>
-    /// 副标题
-    /// </summary>
-    [Required(ErrorMessage = "副标题不能为空")]
-    public virtual string ViceTitle { get; set; }
-
-    /// <summary>
-    /// 副描述
-    /// </summary>
-    public virtual string? ViceDesc { get; set; }
-
-    /// <summary>
-    /// 水印
-    /// </summary>
-    public virtual string? Watermark { get; set; }
-
-    /// <summary>
-    /// 版权信息
-    /// </summary>
-    public virtual string? Copyright { get; set; }
-
-    /// <summary>
-    /// ICP备案号
-    /// </summary>
-    public virtual string? Icp { get; set; }
-
-    /// <summary>
-    /// 排序
-    /// </summary>
-    [Required(ErrorMessage = "排序不能为空")]
-    public virtual int? OrderNo { get; set; }
-
-    /// <summary>
-    /// 备注
-    /// </summary>
-    public virtual string? Remark { get; set; }
-
-    /// <summary>
-    /// 图标
-    /// </summary>
-    public virtual string? Logo { get; set; }
-}
-
-/// <summary>
-/// 应用增加输入参数
-/// </summary>
-public class AddSysAppInput
-{
-    /// <summary>
-    /// 图标
-    /// </summary>
-    [Required(ErrorMessage = "Logo不能为空")]
-    public string Logo { get; set; }
-
-    /// <summary>
-    /// 名称
-    /// </summary>
-    [Required(ErrorMessage = "名称不能为空")]
-    public string Name { get; set; }
-
-    /// <summary>
-    /// 标题
-    /// </summary>
-    [Required(ErrorMessage = "标题不能为空")]
-    public string Title { get; set; }
-
-    /// <summary>
-    /// 副标题
-    /// </summary>
-    [Required(ErrorMessage = "副标题不能为空")]
-    public string ViceTitle { get; set; }
-
-    /// <summary>
-    /// 副描述
-    /// </summary>
-    [Required(ErrorMessage = "副描述不能为空")]
-    public string ViceDesc { get; set; }
-
-    /// <summary>
-    /// 水印
-    /// </summary>
-    [Required(ErrorMessage = "水印不能为空")]
-    public string Watermark { get; set; }
-
-    /// <summary>
-    /// 版权信息
-    /// </summary>
-    [Required(ErrorMessage = "版权信息不能为空")]
-    public string Copyright { get; set; }
-
-    /// <summary>
-    /// ICP备案号
-    /// </summary>
-    [Required(ErrorMessage = "备案号不能为空")]
-    public string Icp { get; set; }
-
-    /// <summary>
-    /// 排序
-    /// </summary>
-    [Required(ErrorMessage = "排序不能为空")]
-    public int OrderNo { get; set; } = 100;
-
-    /// <summary>
-    /// 备注
-    /// </summary>
-    [MaxLength(256, ErrorMessage = "备注字符长度不能超过256")]
-    public string Remark { get; set; }
-}
-
-/// <summary>
-/// 应用更新输入参数
-/// </summary>
-public class UpdateSysAppInput : AddSysAppInput
-{
-    /// <summary>
-    /// 主键Id
-    /// </summary>
-    [Required(ErrorMessage = "主键Id不能为空")]
-    public long Id { get; set; }
-}
-
-/// <summary>
-/// 授权应用菜单
-/// </summary>
-public class UpdateAppMenuInput : BaseIdInput
-{
-    /// <summary>
-    /// 菜单Id集合
-    /// </summary>
-    public List<long> MenuIdList { get; set; }
-}
-
-/// <summary>
-/// 租户iId
-/// </summary>
-public class ChangeAppInput : BaseIdInput
-{
-    /// <summary>
-    /// 租户Id
-    /// </summary>
-    [Required(ErrorMessage = "租户不能为空")]
-    public long TenantId { get; set; }
-}

+ 0 - 103
Admin.NET/Admin.NET.Core/Service/Application/Dto/SysAppOutput.cs

@@ -1,103 +0,0 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core.Service;
-
-/// <summary>
-/// 应用输出参数
-/// </summary>
-public class SysAppOutput
-{
-    /// <summary>
-    /// 主键Id
-    /// </summary>
-    public long Id { get; set; }
-
-    /// <summary>
-    /// 名称
-    /// </summary>
-    public string Name { get; set; }
-
-    /// <summary>
-    /// 图标
-    /// </summary>
-    public string? Logo { get; set; }
-
-    /// <summary>
-    /// 标题
-    /// </summary>
-    public string Title { get; set; }
-
-    /// <summary>
-    /// 副标题
-    /// </summary>
-    public string ViceTitle { get; set; }
-
-    /// <summary>
-    /// 副描述
-    /// </summary>
-    public string? ViceDesc { get; set; }
-
-    /// <summary>
-    /// 水印
-    /// </summary>
-    public string? Watermark { get; set; }
-
-    /// <summary>
-    /// 版权信息
-    /// </summary>
-    public string? Copyright { get; set; }
-
-    /// <summary>
-    /// ICP备案号
-    /// </summary>
-    public string? Icp { get; set; }
-
-    /// <summary>
-    /// 排序
-    /// </summary>
-    public int OrderNo { get; set; }
-
-    /// <summary>
-    /// 备注
-    /// </summary>
-    public string? Remark { get; set; }
-
-    /// <summary>
-    /// 创建时间
-    /// </summary>
-    public DateTime? CreateTime { get; set; }
-
-    /// <summary>
-    /// 更新时间
-    /// </summary>
-    public DateTime? UpdateTime { get; set; }
-
-    /// <summary>
-    /// 创建者Id
-    /// </summary>
-    public long? CreateUserId { get; set; }
-
-    /// <summary>
-    /// 创建者姓名
-    /// </summary>
-    public string? CreateUserName { get; set; }
-
-    /// <summary>
-    /// 修改者Id
-    /// </summary>
-    public long? UpdateUserId { get; set; }
-
-    /// <summary>
-    /// 修改者姓名
-    /// </summary>
-    public string? UpdateUserName { get; set; }
-
-    /// <summary>
-    /// 软删除
-    /// </summary>
-    public bool IsDelete { get; set; }
-}

+ 0 - 192
Admin.NET/Admin.NET.Core/Service/Application/SysAppService.cs

@@ -1,192 +0,0 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
-//
-// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
-//
-// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
-
-namespace Admin.NET.Core.Service;
-
-/// <summary>
-/// 系统应用服务 🧩
-/// </summary>
-[ApiDescriptionSettings(Name = "SysApp", Order = 495)]
-public class SysAppService : IDynamicApiController, ITransient
-{
-    private readonly SqlSugarRepository<SysAppMenu> _sysAppMenuRep;
-    private readonly SqlSugarRepository<SysApp> _sysAppRep;
-    private readonly SysAuthService _sysAuthService;
-    private readonly UserManager _userManager;
-
-    public SysAppService(SqlSugarRepository<SysApp> sysAppRep, SqlSugarRepository<SysAppMenu> sysAppMenuRep, SysAuthService sysAuthService, UserManager userManager)
-    {
-        _sysAppRep = sysAppRep;
-        _userManager = userManager;
-        _sysAppMenuRep = sysAppMenuRep;
-        _sysAuthService = sysAuthService;
-    }
-
-    /// <summary>
-    /// 分页查询应用 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [DisplayName("分页查询应用")]
-    [ApiDescriptionSettings(Name = "Page"), HttpPost]
-    public async Task<SqlSugarPagedList<SysAppOutput>> Page(BasePageInput input)
-    {
-        input.Keyword = input.Keyword?.Trim();
-        var query = _sysAppRep.AsQueryable()
-            .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Name.Contains(input.Keyword) ||
-               u.Title.Contains(input.Keyword) || u.ViceTitle.Contains(input.Keyword) ||
-               u.ViceDesc.Contains(input.Keyword) || u.Remark.Contains(input.Keyword))
-            .OrderBy(u => new { u.OrderNo, u.Id })
-            .Select<SysAppOutput>();
-        return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
-    }
-
-    /// <summary>
-    /// 增加应用 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [DisplayName("增加应用")]
-    [ApiDescriptionSettings(Name = "Add"), HttpPost]
-    public async Task<long> Add(AddSysAppInput input)
-    {
-        var entity = input.Adapt<SysApp>();
-        return await _sysAppRep.InsertAsync(entity) ? entity.Id : 0;
-    }
-
-    /// <summary>
-    /// 更新应用 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [DisplayName("更新应用")]
-    [ApiDescriptionSettings(Name = "Update"), HttpPost]
-    public async Task Update(UpdateSysAppInput input)
-    {
-        _ = await _sysAppRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
-
-        var entity = input.Adapt<SysApp>();
-        await _sysAppRep.AsUpdateable(entity).ExecuteCommandAsync();
-    }
-
-    /// <summary>
-    /// 删除应用 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [DisplayName("删除应用")]
-    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
-    public async Task Delete(BaseIdInput input)
-    {
-        var entity = await _sysAppRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
-
-        // 禁止删除存在关联租户的应用
-        if (await _sysAppRep.Context.Queryable<SysTenant>().AnyAsync(u => u.AppId == input.Id)) throw Oops.Oh(ErrorCodeEnum.A1001);
-
-        // 禁止删除存在关联菜单的应用
-        if (await _sysAppMenuRep.AsQueryable().AnyAsync(u => u.AppId == input.Id)) throw Oops.Oh(ErrorCodeEnum.A1002);
-
-        await _sysAppRep.DeleteAsync(entity);
-    }
-
-    /// <summary>
-    /// 获取授权菜单 🔖
-    /// </summary>
-    /// <param name="id"></param>
-    /// <returns></returns>
-    [UnitOfWork]
-    [DisplayName("获取授权菜单")]
-    [ApiDescriptionSettings(Name = "GrantMenu"), HttpGet]
-    public async Task<List<long>> GrantMenu([FromQuery] long id)
-    {
-        return await _sysAppMenuRep.AsQueryable().Where(u => u.AppId == id).Select(u => u.MenuId).ToListAsync();
-    }
-
-    /// <summary>
-    /// 授权菜单 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [UnitOfWork]
-    [DisplayName("授权菜单")]
-    [ApiDescriptionSettings(Name = "GrantMenu"), HttpPost]
-    public async Task GrantMenu(UpdateAppMenuInput input)
-    {
-        input.MenuIdList ??= new();
-
-        await _sysAppMenuRep.DeleteAsync(u => u.AppId == input.Id);
-
-        var list = input.MenuIdList.Select(id => new SysAppMenu { AppId = input.Id, MenuId = id }).ToList();
-
-        await _sysAppMenuRep.InsertRangeAsync(list);
-
-        // 清除应用下其他模块越权的授权数据,包括角色菜单,用户收藏菜单
-        var tenantIds = await _sysAppRep.Context.Queryable<SysTenant>().Where(u => u.AppId == input.Id).Select(u => u.Id).ToListAsync();
-        var roleIds = await _sysAppRep.Context.Queryable<SysRole>().Where(u => tenantIds.Contains((long)u.TenantId)).Select(u => u.Id).ToListAsync();
-        var userIds = await _sysAppRep.Context.Queryable<SysUser>().Where(u => tenantIds.Contains((long)u.TenantId)).Select(u => u.Id).ToListAsync();
-        await _sysAppRep.Context.Deleteable<SysRoleMenu>().Where(u => roleIds.Contains(u.RoleId) && !input.MenuIdList.Contains(u.MenuId)).ExecuteCommandAsync();
-        await _sysAppRep.Context.Deleteable<SysUserMenu>().Where(u => userIds.Contains(u.UserId) && !input.MenuIdList.Contains(u.MenuId)).ExecuteCommandAsync();
-    }
-
-    /// <summary>
-    /// 获取切换应用数据 🔖
-    /// </summary>
-    /// <returns></returns>
-    [UnitOfWork]
-    [DisplayName("切换应用")]
-    [ApiDescriptionSettings(Name = "ChangeApp"), HttpGet]
-    public async Task<dynamic> GetChangeAppData()
-    {
-        var list = await _sysAppRep.AsQueryable().Includes(u => u.TenantList).ToListAsync();
-        return new
-        {
-            _userManager.AppId,
-            _userManager.TenantId,
-            SelectList = list.Where(u => u.TenantList.Count > 0).Select(u => new
-            {
-                u.Id,
-                Value = u.Id,
-                Label = u.Name,
-                Children = u.TenantList.Select(t => new
-                {
-                    t.Id,
-                    Value = t.Id,
-                    Label = t.Host ?? (t.Id + ""),
-                })
-            })
-        };
-    }
-
-    /// <summary>
-    /// 切换应用 🔖
-    /// </summary>
-    /// <param name="input"></param>
-    /// <returns></returns>
-    [UnitOfWork]
-    [DisplayName("切换应用")]
-    [ApiDescriptionSettings(Name = "ChangeApp"), HttpPost]
-    public async Task<LoginOutput> ChangeApp(ChangeAppInput input)
-    {
-        _ = await _sysAppRep.Context.Queryable<SysTenant>().FirstAsync(u => u.Id == input.TenantId) ?? throw Oops.Oh(ErrorCodeEnum.Z1003);
-        _ = await _sysAppRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
-
-        var user = await _sysAppRep.Context.Queryable<SysUser>().FirstAsync(u => u.Id == _userManager.UserId);
-        user.TenantId = input.TenantId;
-
-        return await _sysAuthService.CreateToken(user, input.Id);
-    }
-
-    /// <summary>
-    /// 获取当前应用信息
-    /// </summary>
-    /// <returns></returns>
-    [NonAction]
-    public async Task<SysApp> GetCurrentAppInfo()
-    {
-        var appId = _userManager.AppId > 0 ? _userManager.AppId : SqlSugarConst.DefaultAppId;
-        return await _sysAppRep.GetFirstAsync(u => u.Id == appId) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
-    }
-}

+ 6 - 6
Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginInput.cs

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

+ 22 - 50
Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs

@@ -68,7 +68,7 @@ public class SysAuthService : IDynamicApiController, ITransient
         if (await _sysConfigService.GetConfigValue<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);
@@ -93,59 +93,30 @@ public class SysAuthService : IDynamicApiController, ITransient
         // 登录成功则清空密码错误次数
         _sysCacheService.Remove(keyPasswordErrorTimes);
 
-        return await CreateToken(user, tenant.AppId);
+        return await CreateToken(user);
     }
 
     /// <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.GetConfigValue<bool>(ConfigConst.SysTenantHostLogin);
-
-        SysUser user;
-        SysTenant tenant;
-
-        // 租户隔离登录
-        if (isTenantHostLogin)
-        {
-            // 若租户域名为空或为本地域名,则取默认租户域名
-            if (string.IsNullOrWhiteSpace(host) || host.StartsWith("localhost")) host = SqlSugarConst.DefaultTenantHost;
-
-            // 租户是否存在或已禁用
-            tenant = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysTenant>>().GetFirstAsync(u => u.Host == host.ToLower());
-            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;
-        }
-        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);
 
         return (tenant, user);
     }
@@ -229,9 +200,9 @@ 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 (_, user) = await GetLoginUserAndTenant(input.TenantId, phone: input.Phone);
 
-        return await CreateToken(user, tenant.AppId);
+        return await CreateToken(user);
     }
 
     /// <summary>
@@ -241,7 +212,7 @@ public class SysAuthService : IDynamicApiController, ITransient
     /// <param name="appId"></param>
     /// <returns></returns>
     [NonAction]
-    internal virtual async Task<LoginOutput> CreateToken(SysUser user, long appId)
+    internal virtual async Task<LoginOutput> CreateToken(SysUser user)
     {
         // 单用户登录
         await _sysOnlineUserService.SingleLogin(user.Id);
@@ -250,7 +221,6 @@ public class SysAuthService : IDynamicApiController, ITransient
         var tokenExpire = await _sysConfigService.GetTokenExpire();
         var accessToken = JWTEncryption.Encrypt(new Dictionary<string, object>
         {
-            { ClaimConst.AppId, appId },
             { ClaimConst.UserId, user.Id },
             { ClaimConst.TenantId, user.TenantId },
             { ClaimConst.Account, user.Account },
@@ -309,7 +279,7 @@ public class SysAuthService : IDynamicApiController, ITransient
         var roleIds = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysUserRole>>().AsQueryable()
             .Where(u => u.UserId == user.Id).Select(u => u.RoleId).ToListAsync();
         // 获取水印文字(若系统水印为空则全局为空)
-        var watermarkText = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysWebWatermark);
+        var watermarkText = (await _sysUserRep.Context.Queryable<SysTenant>().FirstAsync(u => u.Id == user.TenantId))?.Watermark;
         if (!string.IsNullOrWhiteSpace(watermarkText))
             watermarkText += $"-{user.RealName}"; // $"-{user.RealName}-{_httpContextAccessor.HttpContext.GetRemoteIpAddressToIPv4(true)}-{DateTime.Now}";
         return new LoginUserOutput
@@ -399,11 +369,13 @@ public class SysAuthService : IDynamicApiController, ITransient
         {
             _sysCacheService.Set($"{CacheConst.KeyConfig}{ConfigConst.SysCaptcha}", false);
 
+            // 尝试从发起请求页的地址栏中获取租户id
+            var tenantId = Regex.Match(App.HttpContext.Request.Headers.Referer.ToString() ?? "", @"(?<=t=)(\d+)").Value;
             await Login(new LoginInput
             {
                 Account = auth.UserName,
                 Password = CryptogramUtil.SM2Encrypt(auth.Password),
-                Host = SqlSugarConst.DefaultTenantHost
+                TenantId = string.IsNullOrWhiteSpace(tenantId) ? SqlSugarConst.DefaultTenantId : long.Parse(tenantId)
             });
 
             _sysCacheService.Remove($"{CacheConst.KeyConfig}{ConfigConst.SysCaptcha}");

+ 0 - 8
Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs

@@ -475,15 +475,12 @@ public class SysCodeGenService : IDynamicApiController, ITransient
         await DeleteMenuTree(title, pid == 0 ? MenuTypeEnum.Dir : MenuTypeEnum.Menu);
 
         var parentMenuPath = "";
-        var appId = _userManager.AppId;
-        var appMenuList = new List<SysAppMenu>();
         var lowerClassName = className[..1].ToLower() + className[1..];
         if (pid == 0)
         {
             // 新增目录,并记录Id
             var dirMenu = new SysMenu { Pid = 0, Title = title, Type = MenuTypeEnum.Dir, Icon = "robot", Path = "/" + className.ToLower(), Component = "Layout" };
             pid = (await _db.Insertable(dirMenu).ExecuteReturnEntityAsync()).Id;
-            appMenuList.Add(new SysAppMenu { AppId = appId, MenuId = pid });
         }
         else
         {
@@ -494,7 +491,6 @@ public class SysCodeGenService : IDynamicApiController, ITransient
         // 新增菜单,并记录Id
         var rootMenu = new SysMenu { Pid = pid, Title = title, Type = MenuTypeEnum.Menu, Icon = menuIcon, Path = $"{parentMenuPath}/{className.ToLower()}", Component = $"/{pagePath}/{lowerClassName}/index" };
         pid = (await _db.Insertable(rootMenu).ExecuteReturnEntityAsync()).Id;
-        appMenuList.Add(new SysAppMenu { AppId = appId, MenuId = pid });
 
         var orderNo = 100;
         var menuList = new List<SysMenu>
@@ -518,10 +514,6 @@ public class SysCodeGenService : IDynamicApiController, ITransient
             menuList.Add(new SysMenu { Title = $"上传{column.ColumnComment}", Permission = $"{lowerClassName}:upload{column.PropertyName}", Pid = pid, Type = MenuTypeEnum.Btn, OrderNo = orderNo += 10 });
 
         await _db.Insertable(menuList).ExecuteCommandAsync();
-
-        // 新增应用菜单关联
-        appMenuList.AddRange(menuList.Select(u => new SysAppMenu { AppId = appId, MenuId = u.Id }));
-        await _db.Insertable(appMenuList).ExecuteCommandAsync();
     }
 
     /// <summary>

+ 2 - 2
Admin.NET/Admin.NET.Core/Service/Config/Dto/InfoInput.cs

@@ -66,10 +66,10 @@ public class InfoSaveInput
     /// <summary>
     /// 登录二次验证
     /// </summary>
-    public bool? SysSecondVer { get; set; }
+    public bool SysSecondVer { get; set; }
 
     /// <summary>
     /// 图形验证码
     /// </summary>
-    public bool? SysCaptcha { get; set; }
+    public bool SysCaptcha { get; set; }
 }

+ 29 - 67
Admin.NET/Admin.NET.Core/Service/Config/SysConfigService.cs

@@ -221,12 +221,12 @@ public class SysConfigService : IDynamicApiController, ITransient
     [DisplayName("批量更新参数配置值")]
     public async Task BatchUpdateConfig(List<BatchConfigInput> input)
     {
-        foreach (var Config in input)
+        foreach (var config in input)
         {
-            var info = await _sysConfigRep.GetFirstAsync(c => c.Code == Config.Code);
+            var info = await _sysConfigRep.GetFirstAsync(c => c.Code == config.Code);
             if (info == null) continue;
 
-            await _sysConfigRep.AsUpdateable().SetColumns(u => u.Value == Config.Value).Where(u => u.Code == Config.Code).ExecuteCommandAsync();
+            await _sysConfigRep.AsUpdateable().SetColumns(u => u.Value == config.Value).Where(u => u.Code == config.Code).ExecuteCommandAsync();
             Remove(info);
         }
     }
@@ -240,29 +240,22 @@ public class SysConfigService : IDynamicApiController, ITransient
     [DisplayName("获取系统信息")]
     public async Task<dynamic> GetSysInfo()
     {
-        // var sysLogo = await GetConfigValue<string>(ConfigConst.SysWebLogo);
-        // var sysTitle = await GetConfigValue<string>(ConfigConst.SysWebTitle);
-        // var sysViceTitle = await GetConfigValue<string>(ConfigConst.SysWebViceTitle);
-        // var sysViceDesc = await GetConfigValue<string>(ConfigConst.SysWebViceDesc);
-        // var sysWatermark = await GetConfigValue<string>(ConfigConst.SysWebWatermark);
-        // var sysCopyright = await GetConfigValue<string>(ConfigConst.SysWebCopyright);
-        // var sysIcp = await GetConfigValue<string>(ConfigConst.SysWebIcp);
-        var app = await App.GetRequiredService<SysAppService>().GetCurrentAppInfo();
-
-        var sysIcpUrl = await GetConfigValue<string>(ConfigConst.SysWebIcpUrl);
+        var tenant = await App.GetService<SysTenantService>().GetCurrentTenant();
+        tenant ??= await _sysConfigRep.Context.Queryable<SysTenant>().FirstAsync(u => u.Id == SqlSugarConst.DefaultTenantId);
+        _ = tenant ?? throw Oops.Oh(ErrorCodeEnum.D1002);
+        
         var sysSecondVer = await GetConfigValue<bool>(ConfigConst.SysSecondVer);
         var sysCaptcha = await GetConfigValue<bool>(ConfigConst.SysCaptcha);
-
         return new
         {
-            SysLogo = app.Logo,
-            SysTitle = app.Title,
-            SysViceTitle = app.ViceTitle,
-            SysViceDesc = app.ViceDesc,
-            SysWatermark = app.Watermark,
-            SysCopyright = app.Copyright,
-            SysIcp = app.Icp,
-            SysIcpUrl = sysIcpUrl,
+            SysLogo = tenant.Logo,
+            SysTitle = tenant.Title,
+            SysViceTitle = tenant.ViceTitle,
+            SysViceDesc = tenant.ViceDesc,
+            SysWatermark = tenant.Watermark,
+            SysCopyright = tenant.Copyright,
+            SysIcp = tenant.Icp,
+            SysIcpUrl = tenant.IcpUrl,
             SysSecondVer = sysSecondVer,
             SysCaptcha = sysCaptcha
         };
@@ -276,51 +269,20 @@ public class SysConfigService : IDynamicApiController, ITransient
     [DisplayName("保存系统信息")]
     public async Task SaveSysInfo(InfoSaveInput input)
     {
-        // logo 不为空才保存
-        var app = await App.GetRequiredService<SysAppService>().GetCurrentAppInfo();
-        if (!string.IsNullOrEmpty(input.SysLogoBase64))
-        {
-            // 旧图标文件相对路径
-            var oldSysLogoRelativeFilePath = app.Logo ?? "";
-            var oldSysLogoAbsoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, oldSysLogoRelativeFilePath.TrimStart('/'));
-
-            var groups = Regex.Match(input.SysLogoBase64, @"data:image/(?<type>.+?);base64,(?<data>.+)").Groups;
-            //var type = groups["type"].Value;
-            var base64Data = groups["data"].Value;
-            var binData = Convert.FromBase64String(base64Data);
-            // 根据文件名取扩展名
-            var ext = string.IsNullOrWhiteSpace(input.SysLogoFileName) ? ".png" : Path.GetExtension(input.SysLogoFileName);
-            // 本地图标保存路径
-            var path = "upload";
-            var fileName = $"{app.ViceTitle}-logo{ext}".ToLower();
-            var absoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, path, fileName);
-
-            // 删除已存在文件
-            if (File.Exists(oldSysLogoAbsoluteFilePath)) File.Delete(oldSysLogoAbsoluteFilePath);
-
-            // 创建文件夹
-            var absoluteFileDir = Path.GetDirectoryName(absoluteFilePath);
-            if (!Directory.Exists(absoluteFileDir)) Directory.CreateDirectory(absoluteFileDir);
-
-            // 保存图标文件
-            await File.WriteAllBytesAsync(absoluteFilePath, binData);
-
-            // 保存图标配置
-            app.Logo = $"/{path}/{fileName}";
-        }
-
-        app.Title = input.SysTitle;
-        app.ViceTitle = input.SysViceTitle;
-        app.ViceDesc = input.SysViceDesc;
-        app.Watermark = input.SysWatermark;
-        app.Copyright = input.SysCopyright;
-        app.Icp = input.SysIcp;
-
-        await _sysConfigRep.Context.Updateable(app).ExecuteCommandAsync();
-
-        await UpdateConfigValue(ConfigConst.SysWebIcpUrl, input.SysIcpUrl);
-        await UpdateConfigValue(ConfigConst.SysSecondVer, (input.SysSecondVer ?? false).ToString());
-        await UpdateConfigValue(ConfigConst.SysCaptcha, (input.SysCaptcha ?? true).ToString());
+        var tenant = await App.GetService<SysTenantService>().GetCurrentTenant() ?? throw Oops.Oh(ErrorCodeEnum.D1002);
+        if (!string.IsNullOrEmpty(input.SysLogoBase64)) App.GetService<SysTenantService>().SetLogoUrl(tenant, input.SysLogoBase64, input.SysLogoFileName);
+
+        tenant.Title = input.SysTitle;
+        tenant.ViceTitle = input.SysViceTitle;
+        tenant.ViceDesc = input.SysViceDesc;
+        tenant.Watermark = input.SysWatermark;
+        tenant.Copyright = input.SysCopyright;
+        tenant.IcpUrl = input.SysIcpUrl;
+        tenant.Icp = input.SysIcp;
+
+        await _sysConfigRep.Context.Updateable(tenant).ExecuteCommandAsync();
+        await UpdateConfigValue(ConfigConst.SysSecondVer, input.SysSecondVer.ToString());
+        await UpdateConfigValue(ConfigConst.SysCaptcha, input.SysSecondVer.ToString());
     }
 
     private void Remove(SysConfig config)

+ 8 - 8
Admin.NET/Admin.NET.Core/Service/Menu/Dto/MenuInput.cs

@@ -17,16 +17,11 @@ public class MenuInput
     /// 菜单类型(1目录 2菜单 3按钮)
     /// </summary>
     public MenuTypeEnum? Type { get; set; }
-
-    /// <summary>
-    /// 获取所有菜单
-    /// </summary>
-    public bool All { get; set; }
-
+    
     /// <summary>
-    /// 应用Id
+    /// 租户Id
     /// </summary>
-    public long AppId { get; set; }
+    public virtual long TenantId { get; set; }
 }
 
 public class AddMenuInput : SysMenu
@@ -36,6 +31,11 @@ public class AddMenuInput : SysMenu
     /// </summary>
     [Required(ErrorMessage = "菜单名称不能为空")]
     public override string Title { get; set; }
+    
+    /// <summary>
+    /// 租户Id
+    /// </summary>
+    public long TenantId { get; set; }
 }
 
 public class UpdateMenuInput : AddMenuInput

+ 59 - 83
Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs

@@ -4,6 +4,8 @@
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
+using OfficeOpenXml.FormulaParsing.Excel.Functions.Text;
+
 namespace Admin.NET.Core.Service;
 
 /// <summary>
@@ -12,28 +14,29 @@ namespace Admin.NET.Core.Service;
 [ApiDescriptionSettings(Order = 450)]
 public class SysMenuService : IDynamicApiController, ITransient
 {
-    private readonly UserManager _userManager;
+    private readonly SqlSugarRepository<SysTenantMenu> _sysTenantMenuRep;
     private readonly SqlSugarRepository<SysMenu> _sysMenuRep;
-    private readonly SqlSugarRepository<SysAppMenu> _sysAppMenuRep;
     private readonly SysRoleMenuService _sysRoleMenuService;
     private readonly SysUserRoleService _sysUserRoleService;
     private readonly SysUserMenuService _sysUserMenuService;
     private readonly SysCacheService _sysCacheService;
+    private readonly UserManager _userManager;
 
-    public SysMenuService(UserManager userManager,
-        SqlSugarRepository<SysAppMenu> sysAppMenuRep,
+    public SysMenuService(
+        SqlSugarRepository<SysTenantMenu> sysTenantMenuRep,
         SqlSugarRepository<SysMenu> sysMenuRep,
         SysRoleMenuService sysRoleMenuService,
         SysUserRoleService sysUserRoleService,
         SysUserMenuService sysUserMenuService,
-        SysCacheService sysCacheService)
+        SysCacheService sysCacheService,
+        UserManager userManager)
     {
         _userManager = userManager;
         _sysMenuRep = sysMenuRep;
-        _sysAppMenuRep = sysAppMenuRep;
         _sysRoleMenuService = sysRoleMenuService;
         _sysUserRoleService = sysUserRoleService;
         _sysUserMenuService = sysUserMenuService;
+        _sysTenantMenuRep = sysTenantMenuRep;
         _sysCacheService = sysCacheService;
     }
 
@@ -44,40 +47,22 @@ public class SysMenuService : IDynamicApiController, ITransient
     [DisplayName("获取登录菜单树")]
     public async Task<List<MenuOutput>> GetLoginMenuTree()
     {
-        if (_userManager.SuperAdmin)
+        var (query, _) = GetSugarQueryableAndTenantId(_userManager.TenantId);
+        if (_userManager.SuperAdmin || _userManager.SysAdmin)
         {
-            var menuList = await _sysMenuRep.AsQueryable()
-                .InnerJoin<SysAppMenu>((u, am) => am.AppId == _userManager.AppId && u.Id == am.MenuId)
-                .Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable)
-                .OrderBy(u => new { u.OrderNo, u.Id }).Distinct().ToTreeAsync(u => u.Children, u => u.Pid, 0);
+            var menuList = await query.Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable)
+                .OrderBy(u => new { u.OrderNo, u.Id })
+                .ToTreeAsync(u => u.Children, u => u.Pid, 0);
             return menuList.Adapt<List<MenuOutput>>();
         }
+
         var menuIdList = await GetMenuIdList();
-        var menuTree = await _sysMenuRep.AsQueryable()
-            .InnerJoin<SysAppMenu>((u, am) => am.AppId == _userManager.AppId && u.Id == am.MenuId)
-            .Where(u => u.Status == StatusEnum.Enable)
-            .OrderBy(u => new { u.OrderNo, u.Id }).Distinct().ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray());
-        DeleteBtnFromMenuTree(menuTree);
+        var menuTree = await query.Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable)
+            .OrderBy(u => new { u.OrderNo, u.Id }).ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray());
         return menuTree.Adapt<List<MenuOutput>>();
     }
 
     /// <summary>
-    /// 删除登录菜单树里面的按钮
-    /// </summary>
-    private static void DeleteBtnFromMenuTree(List<SysMenu> menuList)
-    {
-        if (menuList == null) return;
-        for (var i = menuList.Count - 1; i >= 0; i--)
-        {
-            var menu = menuList[i];
-            if (menu.Type == MenuTypeEnum.Btn)
-                menuList.Remove(menu);
-            else if (menu.Children.Count > 0)
-                DeleteBtnFromMenuTree(menu.Children);
-        }
-    }
-
-    /// <summary>
     /// 获取菜单列表 🔖
     /// </summary>
     /// <returns></returns>
@@ -85,14 +70,8 @@ public class SysMenuService : IDynamicApiController, ITransient
     public async Task<List<SysMenu>> GetList([FromQuery] MenuInput input)
     {
         var menuIdList = _userManager.SuperAdmin ? new List<long>() : await GetMenuIdList();
-
-        // 仅超级管理员可以获取全部菜单
-        var joinApp = _userManager.SuperAdmin == false || (_userManager.SuperAdmin && !input.All);
-        // 超级管理员可根据传参获取指定应用的菜单
-        var appId = _userManager.SuperAdmin && input.AppId > 0 ? input.AppId : _userManager.AppId;
-        var query = _sysMenuRep.AsQueryable()
-            .InnerJoinIF<SysAppMenu>(joinApp, (u, am) => am.AppId == appId && u.Id == am.MenuId);
-
+        var (query, _) = GetSugarQueryableAndTenantId(input.TenantId);
+        
         // 有筛选条件时返回list列表(防止构造不出树)
         if (!string.IsNullOrWhiteSpace(input.Title) || input.Type is > 0)
         {
@@ -116,31 +95,24 @@ public class SysMenuService : IDynamicApiController, ITransient
     [DisplayName("增加菜单")]
     public async Task AddMenu(AddMenuInput input)
     {
+        var (query, tenantId) = GetSugarQueryableAndTenantId(input.TenantId);
+        
         var isExist = input.Type != MenuTypeEnum.Btn
-            ? await _sysMenuRep.IsAnyAsync(u => u.Title == input.Title && u.Pid == input.Pid)
-            : await _sysMenuRep.IsAnyAsync(u => u.Permission == input.Permission);
+            ? await query.AnyAsync(u => u.Title == input.Title && u.Pid == input.Pid)
+            : await query.AnyAsync(u => u.Permission == input.Permission);
         if (isExist) throw Oops.Oh(ErrorCodeEnum.D4000);
 
-        if (!string.IsNullOrWhiteSpace(input.Name))
-        {
-            if (await _sysMenuRep.IsAnyAsync(u => u.Name == input.Name))
-                throw Oops.Oh(ErrorCodeEnum.D4009);
-        }
+        if (!string.IsNullOrWhiteSpace(input.Name) && await query.AnyAsync(u => u.Name == input.Name)) throw Oops.Oh(ErrorCodeEnum.D4009);
 
-        if (input.Pid != 0)
-        {
-            if (await _sysMenuRep.IsAnyAsync(u => u.Id == input.Pid && u.Type == MenuTypeEnum.Btn))
-                throw Oops.Oh(ErrorCodeEnum.D4010);
-        }
+        if (input.Pid != 0 && await query.AnyAsync(u => u.Id == input.Pid && u.Type == MenuTypeEnum.Btn)) throw Oops.Oh(ErrorCodeEnum.D4010);
 
         // 校验菜单参数
         var sysMenu = input.Adapt<SysMenu>();
         CheckMenuParam(sysMenu);
 
+        // 保存菜单和租户菜单关联数据
         await _sysMenuRep.InsertAsync(sysMenu);
-
-        // 保存应用菜单关联
-        await _sysAppMenuRep.InsertAsync(new SysAppMenu { AppId = _userManager.AppId, MenuId = sysMenu.Id });
+        await _sysTenantMenuRep.InsertAsync(new SysTenantMenu { TenantId = tenantId, MenuId = sysMenu.Id });
 
         // 清除缓存
         DeleteMenuCache();
@@ -155,26 +127,17 @@ public class SysMenuService : IDynamicApiController, ITransient
     [DisplayName("更新菜单")]
     public async Task UpdateMenu(UpdateMenuInput input)
     {
-        if (input.Id == input.Pid)
-            throw Oops.Oh(ErrorCodeEnum.D4008);
+        if (input.Id == input.Pid) throw Oops.Oh(ErrorCodeEnum.D4008);
+        var (query, _) = GetSugarQueryableAndTenantId(input.TenantId);
 
         var isExist = input.Type != MenuTypeEnum.Btn
-            ? await _sysMenuRep.IsAnyAsync(u => u.Title == input.Title && u.Type == input.Type && u.Pid == input.Pid && u.Id != input.Id)
-            : await _sysMenuRep.IsAnyAsync(u => u.Permission == input.Permission && u.Id != input.Id);
-        if (isExist)
-            throw Oops.Oh(ErrorCodeEnum.D4000);
+            ? await query.AnyAsync(u => u.Title == input.Title && u.Type == input.Type && u.Pid == input.Pid && u.Id != input.Id)
+            : await query.AnyAsync(u => u.Permission == input.Permission && u.Id != input.Id);
+        if (isExist) throw Oops.Oh(ErrorCodeEnum.D4000);
 
-        if (!string.IsNullOrWhiteSpace(input.Name))
-        {
-            if (await _sysMenuRep.IsAnyAsync(u => u.Id != input.Id && u.Name == input.Name))
-                throw Oops.Oh(ErrorCodeEnum.D4009);
-        }
+        if (!string.IsNullOrWhiteSpace(input.Name) && await query.AnyAsync(u => u.Id != input.Id && u.Name == input.Name)) throw Oops.Oh(ErrorCodeEnum.D4009);
 
-        if (input.Pid != 0)
-        {
-            if (await _sysMenuRep.IsAnyAsync(u => u.Id == input.Pid && u.Type == MenuTypeEnum.Btn))
-                throw Oops.Oh(ErrorCodeEnum.D4010);
-        }
+        if (input.Pid != 0 && await query.AnyAsync(u => u.Id == input.Pid && u.Type == MenuTypeEnum.Btn)) throw Oops.Oh(ErrorCodeEnum.D4010);
 
         // 校验菜单参数
         var sysMenu = input.Adapt<SysMenu>();
@@ -196,10 +159,13 @@ public class SysMenuService : IDynamicApiController, ITransient
     [DisplayName("删除菜单")]
     public async Task DeleteMenu(DeleteMenuInput input)
     {
-        var menuTreeList = await _sysMenuRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id, true);
+        var menuTreeList = await _sysMenuRep.AsQueryable().ToChildListAsync(u => u.Pid, input.Id);
         var menuIdList = menuTreeList.Select(u => u.Id).ToList();
 
         await _sysMenuRep.DeleteAsync(u => menuIdList.Contains(u.Id));
+        
+        // 级联删除租户菜单数据
+        await _sysTenantMenuRep.AsDeleteable().Where(u => menuIdList.Contains(u.MenuId)).ExecuteCommandAsync();
 
         // 级联删除角色菜单数据
         await _sysRoleMenuService.DeleteRoleMenuByMenuIdList(menuIdList);
@@ -207,9 +173,6 @@ public class SysMenuService : IDynamicApiController, ITransient
         // 级联删除用户收藏菜单
         await _sysUserMenuService.DeleteMenuList(menuIdList);
 
-        // 删除应用菜单关联
-        await _sysAppMenuRep.AsDeleteable().Where(u => menuIdList.Contains(u.MenuId)).ExecuteCommandAsync();
-
         // 清除缓存
         DeleteMenuCache();
     }
@@ -253,14 +216,14 @@ public class SysMenuService : IDynamicApiController, ITransient
         var userId = _userManager.UserId;
         var permissions = _sysCacheService.Get<List<string>>(CacheConst.KeyUserButton + userId);
         if (permissions != null) return permissions;
-
-        var menuIdList = _userManager.SuperAdmin ? new() : await GetMenuIdList();
-        permissions = menuIdList.Count > 0 || _userManager.SuperAdmin
-            ? await _sysMenuRep.AsQueryable()
-                .Where(u => u.Type == MenuTypeEnum.Btn)
-                .WhereIF(menuIdList.Count > 0, u => menuIdList.Contains(u.Id))
-                .Select(u => u.Permission).ToListAsync()
-            : new();
+        
+        var menuIdList = _userManager.SuperAdmin || _userManager.SysAdmin ? new() : await GetMenuIdList();
+        
+        permissions = await _sysMenuRep.AsQueryable().Where(u => u.Type == MenuTypeEnum.Btn)
+            .WhereIF(menuIdList.Count > 0, u => menuIdList.Contains(u.Id))
+            .InnerJoinIF<SysTenantMenu>(!_userManager.SuperAdmin, (u, t) => t.TenantId == _userManager.TenantId && u.Id == t.MenuId)
+            .Select(u => u.Permission).ToListAsync();
+        
         _sysCacheService.Set(CacheConst.KeyUserButton + userId, permissions, TimeSpan.FromDays(7));
 
         return permissions;
@@ -283,6 +246,19 @@ public class SysMenuService : IDynamicApiController, ITransient
 
         return permissions;
     }
+    
+    /// <summary>
+    /// 根据租户id获取构建菜单联表查询实例
+    /// </summary>
+    /// <returns></returns>
+    [NonAction]
+    public (ISugarQueryable<SysMenu, SysTenantMenu> query, long tenantId) GetSugarQueryableAndTenantId(long tenantId)
+    {
+        if (!_userManager.SuperAdmin) tenantId = _userManager.TenantId;
+        var query = _sysMenuRep.AsQueryable().ClearFilter()
+            .InnerJoinIF<SysTenantMenu>(tenantId > 0, (u, t) => t.TenantId == tenantId && u.Id == t.MenuId);
+        return (query, tenantId);
+    }
 
     /// <summary>
     /// 清除菜单和按钮缓存

+ 5 - 4
Admin.NET/Admin.NET.Core/Service/Message/SysEmailService.cs

@@ -15,13 +15,13 @@ namespace Admin.NET.Core.Service;
 [ApiDescriptionSettings(Order = 370)]
 public class SysEmailService : IDynamicApiController, ITransient
 {
+    private readonly SqlSugarRepository<SysTenant> _sysTenantRep;
     private readonly EmailOptions _emailOptions;
-    private readonly SysConfigService _sysConfigService;
 
-    public SysEmailService(IOptions<EmailOptions> emailOptions, SysConfigService sysConfigService)
+    public SysEmailService(IOptions<EmailOptions> emailOptions, SqlSugarRepository<SysTenant> sysTenantRep)
     {
         _emailOptions = emailOptions.Value;
-        _sysConfigService = sysConfigService;
+        _sysTenantRep = sysTenantRep;
     }
 
     /// <summary>
@@ -34,7 +34,8 @@ public class SysEmailService : IDynamicApiController, ITransient
     [DisplayName("发送邮件")]
     public async Task SendEmail([Required] string content, string title = "", string toEmail = "")
     {
-        var webTitle = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysWebTitle);
+        long.TryParse(App.User?.FindFirst(ClaimConst.TenantId)?.Value ?? SqlSugarConst.DefaultTenantId.ToString(), out var tenantId);
+        var webTitle = (await _sysTenantRep.GetFirstAsync(u => u.Id == tenantId))?.Title;
         title = string.IsNullOrWhiteSpace(title) ? $"{webTitle} 系统邮件" : title;
         var message = new MimeMessage();
 

+ 1 - 1
Admin.NET/Admin.NET.Core/Service/OAuth/SysOAuthService.cs

@@ -113,7 +113,7 @@ public class SysOAuthService : IDynamicApiController, ITransient
         }
 
         // 构建Token令牌
-        var token = await App.GetRequiredService<SysAuthService>().CreateToken(wechatUser.SysUser, SqlSugarConst.DefaultAppId);
+        var token = await App.GetRequiredService<SysAuthService>().CreateToken(wechatUser.SysUser);
 
         return new RedirectResult($"{redirectUrl}/#/login?token={token.AccessToken}");
     }

+ 1 - 1
Admin.NET/Admin.NET.Core/Service/Role/Dto/RoleMenuInput.cs

@@ -7,7 +7,7 @@
 namespace Admin.NET.Core.Service;
 
 /// <summary>
-/// 授权角色菜单
+/// 系统角色菜单
 /// </summary>
 public class RoleMenuInput : BaseIdInput
 {

+ 1 - 0
Admin.NET/Admin.NET.Core/Service/Role/SysRoleService.cs

@@ -142,6 +142,7 @@ public class SysRoleService : IDynamicApiController, ITransient
         var userIds = await _sysUserRoleService.GetUserIdList(input.Id);
         if (userIds != null && userIds.Count > 0) throw Oops.Oh(ErrorCodeEnum.D1025);
 
+        var sysRole = await _sysRoleRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
         await _sysRoleRep.DeleteAsync(sysRole);
 
         // 级联删除角色机构数据

+ 69 - 7
Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantInput.cs

@@ -30,12 +30,6 @@ public class PageTenantInput : BasePageInput
 public class AddTenantInput : TenantOutput
 {
     /// <summary>
-    /// 应用Id
-    /// </summary>
-    [Required(ErrorMessage = "应用不能为空")]
-    public new long? AppId { get; set; }
-
-    /// <summary>
     /// 租户名称
     /// </summary>
     [Required(ErrorMessage = "租户名称不能为空"), MinLength(2, ErrorMessage = "租户名称不能少于2个字符")]
@@ -51,17 +45,85 @@ public class AddTenantInput : TenantOutput
     /// 租户域名
     /// </summary>
     [Required(ErrorMessage = "域名不能为空"), MinLength(5, ErrorMessage = "域名不能少于5个字符")]
-    public new string Host { get; set; }
+    public override string Host { get; set; }
+    
+    /// <summary>
+    /// 系统主标题
+    /// </summary>
+    [Required(ErrorMessage = "系统主标题不能为空")]
+    public override string Title { get; set; }
+
+    /// <summary>
+    /// 系统副标题
+    /// </summary>
+    [Required(ErrorMessage = "系统副标题不能为空")]
+    public override string ViceTitle { get; set; }
+
+    /// <summary>
+    /// 系统描述
+    /// </summary>
+    [Required(ErrorMessage = "系统描述不能为空")]
+    public override string ViceDesc { get; set; }
+
+    /// <summary>
+    /// 水印内容
+    /// </summary>
+    [Required(ErrorMessage = "水印内容不能为空")]
+    public override string Watermark { get; set; }
+
+    /// <summary>
+    /// 版权说明
+    /// </summary>
+    [Required(ErrorMessage = "版权说明不能为空")]
+    public override string Copyright { get; set; }
+
+    /// <summary>
+    /// ICP备案号
+    /// </summary>
+    [Required(ErrorMessage = "ICP备案号不能为空")]
+    public override string Icp { get; set; }
+
+    /// <summary>
+    /// ICP地址
+    /// </summary>
+    [Required(ErrorMessage = "ICP地址不能为空")]
+    public override string IcpUrl { get; set; }
+    
+    /// <summary>
+    /// Logo图片Base64码
+    /// </summary>
+    [Required(ErrorMessage = "Logo图片不能为空"), MinLength(5, ErrorMessage = "Logo图片不能少于5个字符")]
+    public virtual string LogoBase64 { get; set; }
+    
+    /// <summary>
+    /// Logo文件名
+    /// </summary>
+    public virtual string LogoFileName { get; set; }
 }
 
 public class UpdateTenantInput : AddTenantInput
 {
+    /// <summary>
+    /// Logo图片Base64码
+    /// </summary>
+    public override string LogoBase64 { get; set; }
 }
 
 public class DeleteTenantInput : BaseIdInput
 {
 }
 
+/// <summary>
+/// 租户菜单
+/// </summary>
+public class TenantMenuInput : BaseIdInput
+{
+    /// <summary>
+    /// 菜单Id集合
+    /// </summary>
+    public List<long> MenuIdList { get; set; }
+}
+
 public class TenantUserInput
 {
     /// <summary>

+ 2 - 7
Admin.NET/Admin.NET.Core/Service/Tenant/Dto/TenantOutput.cs

@@ -14,11 +14,6 @@ public class TenantOutput : SysTenant
     public virtual string Name { get; set; }
 
     /// <summary>
-    /// 关联应用名称
-    /// </summary>
-    public virtual string AppName { get; set; }
-
-    /// <summary>
     /// 管理员账号
     /// </summary>
     public virtual string AdminAccount { get; set; }
@@ -26,10 +21,10 @@ public class TenantOutput : SysTenant
     /// <summary>
     /// 电子邮箱
     /// </summary>
-    public string Email { get; set; }
+    public virtual string Email { get; set; }
 
     /// <summary>
     /// 电话
     /// </summary>
-    public string Phone { get; set; }
+    public virtual string Phone { get; set; }
 }

+ 141 - 48
Admin.NET/Admin.NET.Core/Service/Tenant/SysTenantService.cs

@@ -18,12 +18,12 @@ public class SysTenantService : IDynamicApiController, ITransient
     private readonly SqlSugarRepository<SysPos> _sysPosRep;
     private readonly SqlSugarRepository<SysUser> _sysUserRep;
     private readonly SqlSugarRepository<SysUserExtOrg> _sysUserExtOrgRep;
+    private readonly SqlSugarRepository<SysTenantMenu> _sysTenantMenuRep;
     private readonly SqlSugarRepository<SysRoleMenu> _sysRoleMenuRep;
     private readonly SqlSugarRepository<SysUserRole> _userRoleRep;
-    private readonly SysUserRoleService _sysUserRoleService;
-    private readonly SysRoleMenuService _sysRoleMenuService;
     private readonly SysConfigService _sysConfigService;
     private readonly SysCacheService _sysCacheService;
+    private readonly UploadOptions _uploadOptions;
 
     public SysTenantService(SqlSugarRepository<SysTenant> sysTenantRep,
         SqlSugarRepository<SysOrg> sysOrgRep,
@@ -31,10 +31,10 @@ public class SysTenantService : IDynamicApiController, ITransient
         SqlSugarRepository<SysPos> sysPosRep,
         SqlSugarRepository<SysUser> sysUserRep,
         SqlSugarRepository<SysUserExtOrg> sysUserExtOrgRep,
+        SqlSugarRepository<SysTenantMenu> sysTenantMenuRep,
         SqlSugarRepository<SysRoleMenu> sysRoleMenuRep,
         SqlSugarRepository<SysUserRole> userRoleRep,
-        SysUserRoleService sysUserRoleService,
-        SysRoleMenuService sysRoleMenuService,
+        IOptions<UploadOptions> uploadOptions,
         SysConfigService sysConfigService,
         SysCacheService sysCacheService)
     {
@@ -43,11 +43,11 @@ public class SysTenantService : IDynamicApiController, ITransient
         _sysRoleRep = sysRoleRep;
         _sysPosRep = sysPosRep;
         _sysUserRep = sysUserRep;
+        _sysTenantMenuRep = sysTenantMenuRep;
         _sysUserExtOrgRep = sysUserExtOrgRep;
         _sysRoleMenuRep = sysRoleMenuRep;
         _userRoleRep = userRoleRep;
-        _sysUserRoleService = sysUserRoleService;
-        _sysRoleMenuService = sysRoleMenuService;
+        _uploadOptions = uploadOptions.Value;
         _sysConfigService = sysConfigService;
         _sysCacheService = sysCacheService;
     }
@@ -63,19 +63,16 @@ public class SysTenantService : IDynamicApiController, ITransient
         return await _sysTenantRep.AsQueryable()
             .LeftJoin<SysUser>((u, a) => u.UserId == a.Id)
             .LeftJoin<SysOrg>((u, a, b) => u.OrgId == b.Id)
-            .LeftJoin<SysApp>((u, a, b, c) => u.AppId == c.Id)
             .WhereIF(!string.IsNullOrWhiteSpace(input.Phone), (u, a) => a.Phone.Contains(input.Phone.Trim()))
             .WhereIF(!string.IsNullOrWhiteSpace(input.Name), (u, a, b) => b.Name.Contains(input.Name.Trim()))
             .Where(u => u.Id.ToString() != SqlSugarConst.MainConfigId) // 排除默认主库/主租户
             .OrderBy(u => new { u.OrderNo, u.Id })
-            .Select((u, a, b, c) => new TenantOutput
+            .Select((u, a, b) => new TenantOutput
             {
                 Id = u.Id,
                 OrgId = b.Id,
                 Name = b.Name,
                 UserId = a.Id,
-                AppId = u.AppId,
-                AppName = c.Name,
                 AdminAccount = a.Account,
                 Phone = a.Phone,
                 Host = u.Host,
@@ -91,9 +88,43 @@ public class SysTenantService : IDynamicApiController, ITransient
                 CreateUserName = u.CreateUserName,
                 UpdateTime = u.UpdateTime,
                 UpdateUserName = u.UpdateUserName,
-            })
+            }, true)
             .ToPagedListAsync(input.Page, input.PageSize);
     }
+    
+    /// <summary>
+    /// 获取租户列表
+    /// </summary>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [DisplayName("获取租户列表"), HttpGet]
+    public async Task<dynamic> GetList()
+    {
+        return await _sysTenantRep.AsQueryable()
+            .LeftJoin<SysOrg>((u, a) => u.OrgId == a.Id)
+            .Where(u => u.Status == StatusEnum.Enable)
+            .Select((u, a) => new
+            {
+                Label = a.Name,
+                Value = u.Id,
+                Host = u.Host.ToLower()
+            }).ToListAsync();
+    }
+    
+    /// <summary>
+    /// 获取当前租户
+    /// </summary>
+    /// <returns></returns>
+    [NonAction]
+    public async Task<SysTenant> GetCurrentTenant()
+    {
+        var tenantId = long.Parse(App.User?.FindFirst(ClaimConst.TenantId)?.Value ?? "0");
+        var host = App.HttpContext.Request.Host.Host.ToLower();
+        return await _sysTenantRep.AsQueryable()
+            .WhereIF(tenantId > 0, u => u.Id == tenantId)
+            .WhereIF(tenantId <= 0, u => SqlFunc.ToLower(u.Host).Equals(host))
+            .FirstAsync();
+    }
 
     /// <summary>
     /// 获取库隔离的租户列表
@@ -147,6 +178,10 @@ public class SysTenantService : IDynamicApiController, ITransient
         }
 
         var tenant = input.Adapt<TenantOutput>();
+        
+        // 设置logo
+        SetLogoUrl(tenant, input.LogoBase64, input.LogoFileName);
+        
         await _sysTenantRep.InsertAsync(tenant);
         await InitNewTenant(tenant);
 
@@ -154,6 +189,48 @@ public class SysTenantService : IDynamicApiController, ITransient
     }
 
     /// <summary>
+    /// 设置logo
+    /// </summary>
+    /// <param name="tenant"></param>
+    /// <param name="logoBase64"></param>
+    /// <param name="logoFileName"></param>
+    [NonAction]
+    public void SetLogoUrl(SysTenant tenant, string logoBase64, string logoFileName)
+    {
+        // 旧图标文件相对路径
+        var oldSysLogoRelativeFilePath = tenant.Logo ?? "";
+        var oldSysLogoAbsoluteFilePath = Path.Combine(App.WebHostEnvironment.WebRootPath, oldSysLogoRelativeFilePath.TrimStart('/'));
+
+        var groups = Regex.Match(logoBase64, @"data:image/(?<type>.+?);base64,(?<data>.+)").Groups;
+        
+        //var type = groups["type"].Value;
+        var base64Data = groups["data"].Value;
+        var binData = Convert.FromBase64String(base64Data);
+        
+        // 根据文件名取扩展名
+        var ext = string.IsNullOrWhiteSpace(logoFileName) ? ".png" : Path.GetExtension(logoFileName);
+        
+        // 本地图标保存路径
+        var fileName = $"{tenant.ViceTitle}-logo{ext}".ToLower();
+        var path = _uploadOptions.Path.Replace("/{yyyy}/{MM}/{dd}", "");
+        path = path.StartsWith("/") || Regex.IsMatch(path, "^[A-Z|a-z]:") ? path : Path.Combine(App.WebHostEnvironment.WebRootPath, path);
+        var absoluteFilePath = Path.Combine(path, fileName);
+
+        // 删除已存在文件
+        if (File.Exists(oldSysLogoAbsoluteFilePath)) File.Delete(oldSysLogoAbsoluteFilePath);
+
+        // 创建文件夹
+        var absoluteFileDir = Path.GetDirectoryName(absoluteFilePath);
+        if (!Directory.Exists(absoluteFileDir)) Directory.CreateDirectory(absoluteFileDir);
+
+        // 保存图标文件
+        File.WriteAllBytesAsync(absoluteFilePath, binData);
+
+        // 保存图标配置
+        tenant.Logo = $"/upload/{fileName}";
+    }
+
+    /// <summary>
     /// 设置租户状态 🔖
     /// </summary>
     /// <param name="input"></param>
@@ -183,30 +260,26 @@ public class SysTenantService : IDynamicApiController, ITransient
         var newOrg = new SysOrg { TenantId = tenantId, Pid = 0, Name = tenantName, Code = tenantName, Remark = tenantName, };
         await _sysOrgRep.InsertAsync(newOrg);
 
-        // 初始化角色
-        var newRole = new SysRole { TenantId = tenantId, Name = "租管-" + tenantName, Code = CommonConst.SysAdminRole, DataScope = DataScopeEnum.All, Remark = tenantName };
-        await _sysRoleRep.InsertAsync(newRole);
-
         // 初始化职位
-        var newPos = new SysPos { TenantId = tenantId, Name = "管-" + tenantName, Code = tenantName, Remark = tenantName };
+        var newPos = new SysPos { TenantId = tenantId, Name = "管理员-" + tenantName, Code = tenantName, Remark = tenantName };
         await _sysPosRep.InsertAsync(newPos);
 
-        // 初始化系统账号
+        // 初始化租户管理员账号
         var password = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysPassword);
         var newUser = new SysUser
         {
             TenantId = tenantId,
             Account = tenant.AdminAccount,
             Password = CryptogramUtil.Encrypt(password),
-            NickName = "租管",
+            NickName = "系统管理员",
             Email = tenant.Email,
             Phone = tenant.Phone,
             AccountType = AccountTypeEnum.SysAdmin,
             OrgId = newOrg.Id,
             PosId = newPos.Id,
             Birthday = DateTime.Parse("2000-01-01"),
-            RealName = "租管",
-            Remark = "租管" + tenantName,
+            RealName = "系统管理员",
+            Remark = "系统管理员" + tenantName,
         };
         await _sysUserRep.InsertAsync(newUser);
 
@@ -218,16 +291,15 @@ public class SysTenantService : IDynamicApiController, ITransient
         await _sysTenantRep.UpdateAsync(u => new SysTenant { UserId = newUser.Id, OrgId = newOrg.Id }, u => u.Id == tenantId);
 
         // 默认租户管理员角色菜单集合
-        var menuIdList = new List<long> { 1300000000111,1300000000121, // 工作台
-            1310000000111,1310000000112,1310000000113,1310000000114,1310000000115,1310000000116,1310000000117,1310000000118,1310000000119,1310000000120,1310000000121, // 账号
-            1310000000131,1310000000132,1310000000133,1310000000134,1310000000135,1310000000136,1310000000137,1310000000138, 1310000000322, // 角色
-            1310000000141,1310000000142,1310000000143,1310000000144,1310000000145, // 机构
-            1310000000151,1310000000152,1310000000153,1310000000154,1310000000155,1310000000156, // 职位
-            1310000000161,1310000000162,1310000000163,1310000000164,1310000000165, // 个人中心
-            1310000000171,1310000000172,1310000000173,1310000000174,1310000000175,1310000000176, // 通知公告
-            1310000000801  // 关于项目
-        };
-        await _sysRoleMenuService.GrantRoleMenu(new RoleMenuInput() { Id = newRole.Id, MenuIdList = menuIdList });
+        var menuList = new List<SysMenu>();
+        var allMenuList = new SysMenuSeedData().HasData().ToList();
+        var titleList = new List<string> { "工作台", "系统管理", "账号管理", "角色管理", "机构管理", "职位管理", "个人中心", "通知公告", "平台管理", "菜单管理","系统配置", "关于项目" };
+        foreach (var menu in allMenuList.Where(u => titleList.Contains(u.Title)))
+        {
+            menuList.Add(menu);
+            if (menu.Type == MenuTypeEnum.Menu) menuList.AddRange(allMenuList.Where(u => u.Pid == menu.Id));
+        }
+        await GrantMenu(new TenantMenuInput { Id = tenantId, MenuIdList = menuList.Select(u => u.Id).ToList() });
     }
 
     /// <summary>
@@ -235,6 +307,7 @@ public class SysTenantService : IDynamicApiController, ITransient
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
+    [UnitOfWork]
     [ApiDescriptionSettings(Name = "Delete"), HttpPost]
     [DisplayName("删除租户")]
     public async Task DeleteTenant(DeleteTenantInput input)
@@ -251,6 +324,12 @@ public class SysTenantService : IDynamicApiController, ITransient
         await CacheTenant(input.Id);
 
         // 删除与租户相关的表数据
+        await _sysTenantMenuRep.AsDeleteable().Where(u => u.TenantId == input.Id).ExecuteCommandAsync();
+        var menuIds = await _sysTenantRep.Context.Queryable<SysMenu>().Select(u => u.Id).ToListAsync();
+        await _sysTenantRep.Context.Deleteable<SysRoleMenu>().Where(u => menuIds.Contains(u.Id)).ExecuteCommandAsync();
+        await _sysTenantRep.Context.Deleteable<SysUserMenu>().Where(u => menuIds.Contains(u.Id)).ExecuteCommandAsync();
+        await _sysTenantRep.Context.Deleteable<SysMenu>().Where(u => menuIds.Contains(u.Id)).ExecuteCommandAsync();
+        
         var users = await _sysUserRep.AsQueryable().ClearFilter().Where(u => u.TenantId == input.Id).ToListAsync();
         var userIds = users.Select(u => u.Id).ToList();
         await _sysUserRep.AsDeleteable().Where(u => userIds.Contains(u.Id)).ExecuteCommandAsync();
@@ -310,8 +389,12 @@ public class SysTenantService : IDynamicApiController, ITransient
         if (!string.IsNullOrWhiteSpace(input.SlaveConnections) && !JSON.IsValid(input.SlaveConnections))
             throw Oops.Oh(ErrorCodeEnum.D1302);
 
-        input.AppId = null;
-        await _sysTenantRep.AsUpdateable(input.Adapt<TenantOutput>()).IgnoreColumns(true).ExecuteCommandAsync();
+        // 设置logo
+        var tenant = input.Adapt<SysTenant>();
+        if (!string.IsNullOrWhiteSpace(input.LogoBase64)) SetLogoUrl(tenant, input.LogoBase64, input.LogoFileName);
+
+        // 更新租户信息
+        await _sysTenantRep.AsUpdateable(tenant).IgnoreColumns(true).ExecuteCommandAsync();
 
         // 更新系统机构
         await _sysOrgRep.UpdateAsync(u => new SysOrg() { Name = input.Name }, u => u.Id == input.OrgId);
@@ -323,33 +406,43 @@ public class SysTenantService : IDynamicApiController, ITransient
     }
 
     /// <summary>
-    /// 授权租户管理员角色菜单 🔖
+    /// 授权租户菜单 🔖
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
     [UnitOfWork]
-    [DisplayName("授权租户管理员角色菜单")]
-    public async Task GrantMenu(RoleMenuInput input)
+    [DisplayName("授权租户菜单")]
+    public async Task GrantMenu(TenantMenuInput input)
     {
-        // 获取租户管理员角色【sys_admin】
-        var adminRole = await _sysRoleRep.AsQueryable().ClearFilter()
-            .FirstAsync(u => u.Code == CommonConst.SysAdminRole && u.TenantId == input.Id && u.IsDelete == false);
-        if (adminRole == null) return;
-
-        input.Id = adminRole.Id; // 重置租户管理员角色Id
-        await _sysRoleMenuService.GrantRoleMenu(input);
+        // 获取需要授权的菜单列表
+        var menuList = await _sysTenantRep.Context.Queryable<SysMenu>().ClearFilter()
+            .Where(u => input.MenuIdList.Contains(u.Id))
+            .InnerJoin<SysTenantMenu>((u, t) => t.TenantId == input.Id && u.Id == t.MenuId)
+            .ToListAsync();
+
+        // 检查是否存在重复菜单
+        if (menuList.Where(u => u.Type != MenuTypeEnum.Btn).GroupBy(u => new { u.Pid, u.Title }).Any(u => u.Count() > 1) ||
+            menuList.Where(u => u.Type == MenuTypeEnum.Btn).GroupBy(u => u.Permission).Any(u => u.Count() > 1))
+            throw Oops.Oh(ErrorCodeEnum.D1304);
+        
+        // 检查路由是否重复
+        if (menuList.Where(u => !string.IsNullOrWhiteSpace(u.Name)).GroupBy(u => u.Name).Any(u => u.Count() > 1)) 
+            throw Oops.Oh(ErrorCodeEnum.D4009);
+        
+        await _sysTenantMenuRep.AsDeleteable().Where(u => u.TenantId == input.Id).ExecuteCommandAsync();
+        var sysTenantMenuList = input.MenuIdList.Select(menuId => new SysTenantMenu { TenantId = input.Id, MenuId = menuId }).ToList();
+        await _sysTenantMenuRep.InsertRangeAsync(sysTenantMenuList);
     }
 
     /// <summary>
-    /// 获取租户管理员角色拥有菜单Id集合 🔖
+    /// 获取租户菜单Id集合 🔖
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
-    [DisplayName("获取租户管理员角色拥有菜单Id集合")]
-    public async Task<List<long>> GetOwnMenuList([FromQuery] TenantUserInput input)
+    [DisplayName("获取租户菜单Id集合")]
+    public async Task<List<long>> GetTenantMenuList([FromQuery] BaseIdInput input)
     {
-        var roleIds = await _sysUserRoleService.GetUserRoleIdList(input.UserId);
-        return await _sysRoleMenuService.GetRoleMenuIdList(new List<long> { roleIds[0] });
+        return await _sysTenantMenuRep.AsQueryable().Where(u => u.TenantId == input.Id).Select(u => u.MenuId).ToListAsync();
     }
 
     /// <summary>
@@ -362,7 +455,7 @@ public class SysTenantService : IDynamicApiController, ITransient
     {
         var password = await _sysConfigService.GetConfigValue<string>(ConfigConst.SysPassword);
         var encryptPassword = CryptogramUtil.Encrypt(password);
-        await _sysUserRep.UpdateAsync(u => new SysUser() { Password = encryptPassword }, u => u.Id == input.UserId);
+        await _sysUserRep.UpdateAsync(u => new SysUser { Password = encryptPassword }, u => u.Id == input.UserId);
         return password;
     }
 

+ 2 - 4
Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs

@@ -101,8 +101,7 @@ public class SysUserService : IDynamicApiController, ITransient
     public virtual async Task<long> AddUser(AddUserInput input)
     {
         // 是否租户隔离登录验证
-        var isTenantHostLogin = await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysTenantHostLogin);
-        var query = _sysUserRep.AsQueryable().ClearFilter().WhereIF(isTenantHostLogin, u => u.TenantId == _userManager.TenantId || u.AccountType == AccountTypeEnum.SuperAdmin);
+        var query = _sysUserRep.AsQueryable().ClearFilter().Where(u => u.TenantId == _userManager.TenantId || u.AccountType == AccountTypeEnum.SuperAdmin);
 
         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);
@@ -137,9 +136,8 @@ public class SysUserService : IDynamicApiController, ITransient
     public virtual async Task UpdateUser(UpdateUserInput input)
     {
         // 是否租户隔离登录验证
-        var isTenantHostLogin = await _sysConfigService.GetConfigValue<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);
+            .Where(u => u.TenantId == _userManager.TenantId || u.AccountType == AccountTypeEnum.SuperAdmin);
 
         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);

+ 1 - 6
Admin.NET/Admin.NET.Core/Service/User/UserManager.cs

@@ -12,12 +12,7 @@ namespace Admin.NET.Core;
 public class UserManager : IScoped
 {
     private readonly IHttpContextAccessor _httpContextAccessor;
-
-    /// <summary>
-    /// 应用ID
-    /// </summary>
-    public long AppId => (_httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.AppId)?.Value).ToLong();
-
+    
     /// <summary>
     /// 用户ID
     /// </summary>

+ 0 - 1
Web/src/api-services/api.ts

@@ -11,7 +11,6 @@
  * https://github.com/swagger-api/swagger-codegen.git
  * Do not edit the class manually.
  */export * from './apis/apijsonapi';
-export * from './apis/sys-app-api';
 export * from './apis/sys-auth-api';
 export * from './apis/sys-cache-api';
 export * from './apis/sys-code-gen-api';

+ 0 - 720
Web/src/api-services/apis/sys-app-api.ts

@@ -1,720 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-import globalAxios, { AxiosResponse, AxiosInstance, AxiosRequestConfig } from 'axios';
-import { Configuration } from '../configuration';
-// Some imports not used depending on template conditions
-// @ts-ignore
-import { BASE_PATH, COLLECTION_FORMATS, RequestArgs, BaseAPI, RequiredError } from '../base';
-import { AddSysAppInput } from '../models';
-import { AdminResultInt64 } from '../models';
-import { AdminResultListInt64 } from '../models';
-import { AdminResultLoginOutput } from '../models';
-import { AdminResultObject } from '../models';
-import { AdminResultSqlSugarPagedListSysAppOutput } from '../models';
-import { BaseIdInput } from '../models';
-import { BasePageInput } from '../models';
-import { ChangeAppInput } from '../models';
-import { UpdateAppMenuInput } from '../models';
-import { UpdateSysAppInput } from '../models';
-/**
- * SysAppApi - axios parameter creator
- * @export
- */
-export const SysAppApiAxiosParamCreator = function (configuration?: Configuration) {
-    return {
-        /**
-         * 
-         * @summary 增加应用 🔖
-         * @param {AddSysAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysAppAddPost: async (body?: AddSysAppInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysApp/add`;
-            // use dummy base URL string because the URL constructor only accepts absolute URLs.
-            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
-            let baseOptions;
-            if (configuration) {
-                baseOptions = configuration.baseOptions;
-            }
-            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
-            const localVarHeaderParameter = {} as any;
-            const localVarQueryParameter = {} as any;
-
-            // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
-
-            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
-
-            const query = new URLSearchParams(localVarUrlObj.search);
-            for (const key in localVarQueryParameter) {
-                query.set(key, localVarQueryParameter[key]);
-            }
-            for (const key in options.params) {
-                query.set(key, options.params[key]);
-            }
-            localVarUrlObj.search = (new URLSearchParams(query)).toString();
-            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
-            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
-            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
-
-            return {
-                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
-                options: localVarRequestOptions,
-            };
-        },
-        /**
-         * 
-         * @summary 获取切换应用数据 🔖
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysAppChangeAppGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysApp/changeApp`;
-            // use dummy base URL string because the URL constructor only accepts absolute URLs.
-            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
-            let baseOptions;
-            if (configuration) {
-                baseOptions = configuration.baseOptions;
-            }
-            const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options};
-            const localVarHeaderParameter = {} as any;
-            const localVarQueryParameter = {} as any;
-
-            // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
-
-            const query = new URLSearchParams(localVarUrlObj.search);
-            for (const key in localVarQueryParameter) {
-                query.set(key, localVarQueryParameter[key]);
-            }
-            for (const key in options.params) {
-                query.set(key, options.params[key]);
-            }
-            localVarUrlObj.search = (new URLSearchParams(query)).toString();
-            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
-            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-
-            return {
-                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
-                options: localVarRequestOptions,
-            };
-        },
-        /**
-         * 
-         * @summary 切换应用 🔖
-         * @param {ChangeAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysAppChangeAppPost: async (body?: ChangeAppInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysApp/changeApp`;
-            // use dummy base URL string because the URL constructor only accepts absolute URLs.
-            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
-            let baseOptions;
-            if (configuration) {
-                baseOptions = configuration.baseOptions;
-            }
-            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
-            const localVarHeaderParameter = {} as any;
-            const localVarQueryParameter = {} as any;
-
-            // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
-
-            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
-
-            const query = new URLSearchParams(localVarUrlObj.search);
-            for (const key in localVarQueryParameter) {
-                query.set(key, localVarQueryParameter[key]);
-            }
-            for (const key in options.params) {
-                query.set(key, options.params[key]);
-            }
-            localVarUrlObj.search = (new URLSearchParams(query)).toString();
-            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
-            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
-            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
-
-            return {
-                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
-                options: localVarRequestOptions,
-            };
-        },
-        /**
-         * 
-         * @summary 删除应用 🔖
-         * @param {BaseIdInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysAppDeletePost: async (body?: BaseIdInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysApp/delete`;
-            // use dummy base URL string because the URL constructor only accepts absolute URLs.
-            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
-            let baseOptions;
-            if (configuration) {
-                baseOptions = configuration.baseOptions;
-            }
-            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
-            const localVarHeaderParameter = {} as any;
-            const localVarQueryParameter = {} as any;
-
-            // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
-
-            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
-
-            const query = new URLSearchParams(localVarUrlObj.search);
-            for (const key in localVarQueryParameter) {
-                query.set(key, localVarQueryParameter[key]);
-            }
-            for (const key in options.params) {
-                query.set(key, options.params[key]);
-            }
-            localVarUrlObj.search = (new URLSearchParams(query)).toString();
-            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
-            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
-            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
-
-            return {
-                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
-                options: localVarRequestOptions,
-            };
-        },
-        /**
-         * 
-         * @summary 获取授权菜单 🔖
-         * @param {number} [id] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysAppGrantMenuGet: async (id?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysApp/grantMenu`;
-            // use dummy base URL string because the URL constructor only accepts absolute URLs.
-            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
-            let baseOptions;
-            if (configuration) {
-                baseOptions = configuration.baseOptions;
-            }
-            const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options};
-            const localVarHeaderParameter = {} as any;
-            const localVarQueryParameter = {} as any;
-
-            // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
-
-            if (id !== undefined) {
-                localVarQueryParameter['id'] = id;
-            }
-
-            const query = new URLSearchParams(localVarUrlObj.search);
-            for (const key in localVarQueryParameter) {
-                query.set(key, localVarQueryParameter[key]);
-            }
-            for (const key in options.params) {
-                query.set(key, options.params[key]);
-            }
-            localVarUrlObj.search = (new URLSearchParams(query)).toString();
-            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
-            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-
-            return {
-                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
-                options: localVarRequestOptions,
-            };
-        },
-        /**
-         * 
-         * @summary 授权菜单 🔖
-         * @param {UpdateAppMenuInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysAppGrantMenuPost: async (body?: UpdateAppMenuInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysApp/grantMenu`;
-            // use dummy base URL string because the URL constructor only accepts absolute URLs.
-            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
-            let baseOptions;
-            if (configuration) {
-                baseOptions = configuration.baseOptions;
-            }
-            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
-            const localVarHeaderParameter = {} as any;
-            const localVarQueryParameter = {} as any;
-
-            // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
-
-            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
-
-            const query = new URLSearchParams(localVarUrlObj.search);
-            for (const key in localVarQueryParameter) {
-                query.set(key, localVarQueryParameter[key]);
-            }
-            for (const key in options.params) {
-                query.set(key, options.params[key]);
-            }
-            localVarUrlObj.search = (new URLSearchParams(query)).toString();
-            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
-            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
-            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
-
-            return {
-                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
-                options: localVarRequestOptions,
-            };
-        },
-        /**
-         * 
-         * @summary 分页查询应用 🔖
-         * @param {BasePageInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysAppPagePost: async (body?: BasePageInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysApp/page`;
-            // use dummy base URL string because the URL constructor only accepts absolute URLs.
-            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
-            let baseOptions;
-            if (configuration) {
-                baseOptions = configuration.baseOptions;
-            }
-            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
-            const localVarHeaderParameter = {} as any;
-            const localVarQueryParameter = {} as any;
-
-            // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
-
-            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
-
-            const query = new URLSearchParams(localVarUrlObj.search);
-            for (const key in localVarQueryParameter) {
-                query.set(key, localVarQueryParameter[key]);
-            }
-            for (const key in options.params) {
-                query.set(key, options.params[key]);
-            }
-            localVarUrlObj.search = (new URLSearchParams(query)).toString();
-            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
-            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
-            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
-
-            return {
-                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
-                options: localVarRequestOptions,
-            };
-        },
-        /**
-         * 
-         * @summary 更新应用 🔖
-         * @param {UpdateSysAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysAppUpdatePost: async (body?: UpdateSysAppInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysApp/update`;
-            // use dummy base URL string because the URL constructor only accepts absolute URLs.
-            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
-            let baseOptions;
-            if (configuration) {
-                baseOptions = configuration.baseOptions;
-            }
-            const localVarRequestOptions :AxiosRequestConfig = { method: 'POST', ...baseOptions, ...options};
-            const localVarHeaderParameter = {} as any;
-            const localVarQueryParameter = {} as any;
-
-            // authentication Bearer required
-            // http bearer authentication required
-            if (configuration && configuration.accessToken) {
-                const accessToken = typeof configuration.accessToken === 'function'
-                    ? await configuration.accessToken()
-                    : await configuration.accessToken;
-                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
-            }
-
-            localVarHeaderParameter['Content-Type'] = 'application/json-patch+json';
-
-            const query = new URLSearchParams(localVarUrlObj.search);
-            for (const key in localVarQueryParameter) {
-                query.set(key, localVarQueryParameter[key]);
-            }
-            for (const key in options.params) {
-                query.set(key, options.params[key]);
-            }
-            localVarUrlObj.search = (new URLSearchParams(query)).toString();
-            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
-            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
-            const needsSerialization = (typeof body !== "string") || localVarRequestOptions.headers['Content-Type'] === 'application/json';
-            localVarRequestOptions.data =  needsSerialization ? JSON.stringify(body !== undefined ? body : {}) : (body || "");
-
-            return {
-                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
-                options: localVarRequestOptions,
-            };
-        },
-    }
-};
-
-/**
- * SysAppApi - functional programming interface
- * @export
- */
-export const SysAppApiFp = function(configuration?: Configuration) {
-    return {
-        /**
-         * 
-         * @summary 增加应用 🔖
-         * @param {AddSysAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppAddPost(body?: AddSysAppInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultInt64>>> {
-            const localVarAxiosArgs = await SysAppApiAxiosParamCreator(configuration).apiSysAppAddPost(body, options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
-        /**
-         * 
-         * @summary 获取切换应用数据 🔖
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppChangeAppGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultObject>>> {
-            const localVarAxiosArgs = await SysAppApiAxiosParamCreator(configuration).apiSysAppChangeAppGet(options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
-        /**
-         * 
-         * @summary 切换应用 🔖
-         * @param {ChangeAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppChangeAppPost(body?: ChangeAppInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultLoginOutput>>> {
-            const localVarAxiosArgs = await SysAppApiAxiosParamCreator(configuration).apiSysAppChangeAppPost(body, options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
-        /**
-         * 
-         * @summary 删除应用 🔖
-         * @param {BaseIdInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppDeletePost(body?: BaseIdInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
-            const localVarAxiosArgs = await SysAppApiAxiosParamCreator(configuration).apiSysAppDeletePost(body, options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
-        /**
-         * 
-         * @summary 获取授权菜单 🔖
-         * @param {number} [id] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppGrantMenuGet(id?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListInt64>>> {
-            const localVarAxiosArgs = await SysAppApiAxiosParamCreator(configuration).apiSysAppGrantMenuGet(id, options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
-        /**
-         * 
-         * @summary 授权菜单 🔖
-         * @param {UpdateAppMenuInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppGrantMenuPost(body?: UpdateAppMenuInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
-            const localVarAxiosArgs = await SysAppApiAxiosParamCreator(configuration).apiSysAppGrantMenuPost(body, options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
-        /**
-         * 
-         * @summary 分页查询应用 🔖
-         * @param {BasePageInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppPagePost(body?: BasePageInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultSqlSugarPagedListSysAppOutput>>> {
-            const localVarAxiosArgs = await SysAppApiAxiosParamCreator(configuration).apiSysAppPagePost(body, options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
-        /**
-         * 
-         * @summary 更新应用 🔖
-         * @param {UpdateSysAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppUpdatePost(body?: UpdateSysAppInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
-            const localVarAxiosArgs = await SysAppApiAxiosParamCreator(configuration).apiSysAppUpdatePost(body, options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
-    }
-};
-
-/**
- * SysAppApi - factory interface
- * @export
- */
-export const SysAppApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
-    return {
-        /**
-         * 
-         * @summary 增加应用 🔖
-         * @param {AddSysAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppAddPost(body?: AddSysAppInput, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultInt64>> {
-            return SysAppApiFp(configuration).apiSysAppAddPost(body, options).then((request) => request(axios, basePath));
-        },
-        /**
-         * 
-         * @summary 获取切换应用数据 🔖
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppChangeAppGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultObject>> {
-            return SysAppApiFp(configuration).apiSysAppChangeAppGet(options).then((request) => request(axios, basePath));
-        },
-        /**
-         * 
-         * @summary 切换应用 🔖
-         * @param {ChangeAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppChangeAppPost(body?: ChangeAppInput, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultLoginOutput>> {
-            return SysAppApiFp(configuration).apiSysAppChangeAppPost(body, options).then((request) => request(axios, basePath));
-        },
-        /**
-         * 
-         * @summary 删除应用 🔖
-         * @param {BaseIdInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppDeletePost(body?: BaseIdInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
-            return SysAppApiFp(configuration).apiSysAppDeletePost(body, options).then((request) => request(axios, basePath));
-        },
-        /**
-         * 
-         * @summary 获取授权菜单 🔖
-         * @param {number} [id] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppGrantMenuGet(id?: number, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListInt64>> {
-            return SysAppApiFp(configuration).apiSysAppGrantMenuGet(id, options).then((request) => request(axios, basePath));
-        },
-        /**
-         * 
-         * @summary 授权菜单 🔖
-         * @param {UpdateAppMenuInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppGrantMenuPost(body?: UpdateAppMenuInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
-            return SysAppApiFp(configuration).apiSysAppGrantMenuPost(body, options).then((request) => request(axios, basePath));
-        },
-        /**
-         * 
-         * @summary 分页查询应用 🔖
-         * @param {BasePageInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppPagePost(body?: BasePageInput, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultSqlSugarPagedListSysAppOutput>> {
-            return SysAppApiFp(configuration).apiSysAppPagePost(body, options).then((request) => request(axios, basePath));
-        },
-        /**
-         * 
-         * @summary 更新应用 🔖
-         * @param {UpdateSysAppInput} [body] 
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysAppUpdatePost(body?: UpdateSysAppInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
-            return SysAppApiFp(configuration).apiSysAppUpdatePost(body, options).then((request) => request(axios, basePath));
-        },
-    };
-};
-
-/**
- * SysAppApi - object-oriented interface
- * @export
- * @class SysAppApi
- * @extends {BaseAPI}
- */
-export class SysAppApi extends BaseAPI {
-    /**
-     * 
-     * @summary 增加应用 🔖
-     * @param {AddSysAppInput} [body] 
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysAppApi
-     */
-    public async apiSysAppAddPost(body?: AddSysAppInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultInt64>> {
-        return SysAppApiFp(this.configuration).apiSysAppAddPost(body, options).then((request) => request(this.axios, this.basePath));
-    }
-    /**
-     * 
-     * @summary 获取切换应用数据 🔖
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysAppApi
-     */
-    public async apiSysAppChangeAppGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultObject>> {
-        return SysAppApiFp(this.configuration).apiSysAppChangeAppGet(options).then((request) => request(this.axios, this.basePath));
-    }
-    /**
-     * 
-     * @summary 切换应用 🔖
-     * @param {ChangeAppInput} [body] 
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysAppApi
-     */
-    public async apiSysAppChangeAppPost(body?: ChangeAppInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultLoginOutput>> {
-        return SysAppApiFp(this.configuration).apiSysAppChangeAppPost(body, options).then((request) => request(this.axios, this.basePath));
-    }
-    /**
-     * 
-     * @summary 删除应用 🔖
-     * @param {BaseIdInput} [body] 
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysAppApi
-     */
-    public async apiSysAppDeletePost(body?: BaseIdInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
-        return SysAppApiFp(this.configuration).apiSysAppDeletePost(body, options).then((request) => request(this.axios, this.basePath));
-    }
-    /**
-     * 
-     * @summary 获取授权菜单 🔖
-     * @param {number} [id] 
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysAppApi
-     */
-    public async apiSysAppGrantMenuGet(id?: number, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListInt64>> {
-        return SysAppApiFp(this.configuration).apiSysAppGrantMenuGet(id, options).then((request) => request(this.axios, this.basePath));
-    }
-    /**
-     * 
-     * @summary 授权菜单 🔖
-     * @param {UpdateAppMenuInput} [body] 
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysAppApi
-     */
-    public async apiSysAppGrantMenuPost(body?: UpdateAppMenuInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
-        return SysAppApiFp(this.configuration).apiSysAppGrantMenuPost(body, options).then((request) => request(this.axios, this.basePath));
-    }
-    /**
-     * 
-     * @summary 分页查询应用 🔖
-     * @param {BasePageInput} [body] 
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysAppApi
-     */
-    public async apiSysAppPagePost(body?: BasePageInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultSqlSugarPagedListSysAppOutput>> {
-        return SysAppApiFp(this.configuration).apiSysAppPagePost(body, options).then((request) => request(this.axios, this.basePath));
-    }
-    /**
-     * 
-     * @summary 更新应用 🔖
-     * @param {UpdateSysAppInput} [body] 
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysAppApi
-     */
-    public async apiSysAppUpdatePost(body?: UpdateSysAppInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
-        return SysAppApiFp(this.configuration).apiSysAppUpdatePost(body, options).then((request) => request(this.axios, this.basePath));
-    }
-}

+ 13 - 21
Web/src/api-services/apis/sys-menu-api.ts

@@ -130,12 +130,11 @@ export const SysMenuApiAxiosParamCreator = function (configuration?: Configurati
          * @summary 获取菜单列表 🔖
          * @param {string} [title] 标题
          * @param {MenuTypeEnum} [type] 菜单类型(1目录 2菜单 3按钮)
-         * @param {boolean} [all] 获取所有菜单
-         * @param {number} [appId] 应用Id
+         * @param {number} [tenantId] 租户Id
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        apiSysMenuListGet: async (title?: string, type?: MenuTypeEnum, all?: boolean, appId?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+        apiSysMenuListGet: async (title?: string, type?: MenuTypeEnum, tenantId?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
             const localVarPath = `/api/sysMenu/list`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, 'https://example.com');
@@ -164,12 +163,8 @@ export const SysMenuApiAxiosParamCreator = function (configuration?: Configurati
                 localVarQueryParameter['Type'] = type;
             }
 
-            if (all !== undefined) {
-                localVarQueryParameter['All'] = all;
-            }
-
-            if (appId !== undefined) {
-                localVarQueryParameter['AppId'] = appId;
+            if (tenantId !== undefined) {
+                localVarQueryParameter['TenantId'] = tenantId;
             }
 
             const query = new URLSearchParams(localVarUrlObj.search);
@@ -364,13 +359,12 @@ export const SysMenuApiFp = function(configuration?: Configuration) {
          * @summary 获取菜单列表 🔖
          * @param {string} [title] 标题
          * @param {MenuTypeEnum} [type] 菜单类型(1目录 2菜单 3按钮)
-         * @param {boolean} [all] 获取所有菜单
-         * @param {number} [appId] 应用Id
+         * @param {number} [tenantId] 租户Id
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysMenuListGet(title?: string, type?: MenuTypeEnum, all?: boolean, appId?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListSysMenu>>> {
-            const localVarAxiosArgs = await SysMenuApiAxiosParamCreator(configuration).apiSysMenuListGet(title, type, all, appId, options);
+        async apiSysMenuListGet(title?: string, type?: MenuTypeEnum, tenantId?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListSysMenu>>> {
+            const localVarAxiosArgs = await SysMenuApiAxiosParamCreator(configuration).apiSysMenuListGet(title, type, tenantId, options);
             return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
                 const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
                 return axios.request(axiosRequestArgs);
@@ -450,13 +444,12 @@ export const SysMenuApiFactory = function (configuration?: Configuration, basePa
          * @summary 获取菜单列表 🔖
          * @param {string} [title] 标题
          * @param {MenuTypeEnum} [type] 菜单类型(1目录 2菜单 3按钮)
-         * @param {boolean} [all] 获取所有菜单
-         * @param {number} [appId] 应用Id
+         * @param {number} [tenantId] 租户Id
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysMenuListGet(title?: string, type?: MenuTypeEnum, all?: boolean, appId?: number, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListSysMenu>> {
-            return SysMenuApiFp(configuration).apiSysMenuListGet(title, type, all, appId, options).then((request) => request(axios, basePath));
+        async apiSysMenuListGet(title?: string, type?: MenuTypeEnum, tenantId?: number, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListSysMenu>> {
+            return SysMenuApiFp(configuration).apiSysMenuListGet(title, type, tenantId, options).then((request) => request(axios, basePath));
         },
         /**
          * 
@@ -523,14 +516,13 @@ export class SysMenuApi extends BaseAPI {
      * @summary 获取菜单列表 🔖
      * @param {string} [title] 标题
      * @param {MenuTypeEnum} [type] 菜单类型(1目录 2菜单 3按钮)
-     * @param {boolean} [all] 获取所有菜单
-     * @param {number} [appId] 应用Id
+     * @param {number} [tenantId] 租户Id
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof SysMenuApi
      */
-    public async apiSysMenuListGet(title?: string, type?: MenuTypeEnum, all?: boolean, appId?: number, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListSysMenu>> {
-        return SysMenuApiFp(this.configuration).apiSysMenuListGet(title, type, all, appId, options).then((request) => request(this.axios, this.basePath));
+    public async apiSysMenuListGet(title?: string, type?: MenuTypeEnum, tenantId?: number, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListSysMenu>> {
+        return SysMenuApiFp(this.configuration).apiSysMenuListGet(title, type, tenantId, options).then((request) => request(this.axios, this.basePath));
     }
     /**
      * 

+ 113 - 33
Web/src/api-services/apis/sys-tenant-api.ts

@@ -20,13 +20,14 @@ import { AddTenantInput } from '../models';
 import { AdminResultInt32 } from '../models';
 import { AdminResultListInt64 } from '../models';
 import { AdminResultListSysUser } from '../models';
+import { AdminResultObject } from '../models';
 import { AdminResultSqlSugarPagedListTenantOutput } from '../models';
 import { AdminResultString } from '../models';
 import { DeleteTenantInput } from '../models';
 import { PageTenantInput } from '../models';
-import { RoleMenuInput } from '../models';
 import { TenantIdInput } from '../models';
 import { TenantInput } from '../models';
+import { TenantMenuInput } from '../models';
 import { TenantUserInput } from '../models';
 import { UpdateTenantInput } from '../models';
 /**
@@ -181,12 +182,12 @@ export const SysTenantApiAxiosParamCreator = function (configuration?: Configura
         },
         /**
          * 
-         * @summary 授权租户管理员角色菜单 🔖
-         * @param {RoleMenuInput} [body] 
+         * @summary 授权租户菜单 🔖
+         * @param {TenantMenuInput} [body] 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        apiSysTenantGrantMenuPost: async (body?: RoleMenuInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+        apiSysTenantGrantMenuPost: async (body?: TenantMenuInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
             const localVarPath = `/api/sysTenant/grantMenu`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, 'https://example.com');
@@ -229,13 +230,12 @@ export const SysTenantApiAxiosParamCreator = function (configuration?: Configura
         },
         /**
          * 
-         * @summary 获取租户管理员角色拥有菜单Id集合 🔖
-         * @param {number} [userId] 用户Id
+         * @summary 获取租户列表
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        apiSysTenantOwnMenuListGet: async (userId?: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysTenant/ownMenuList`;
+        apiSysTenantListGet: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysTenant/list`;
             // use dummy base URL string because the URL constructor only accepts absolute URLs.
             const localVarUrlObj = new URL(localVarPath, 'https://example.com');
             let baseOptions;
@@ -255,10 +255,6 @@ export const SysTenantApiAxiosParamCreator = function (configuration?: Configura
                 localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
             }
 
-            if (userId !== undefined) {
-                localVarQueryParameter['UserId'] = userId;
-            }
-
             const query = new URLSearchParams(localVarUrlObj.search);
             for (const key in localVarQueryParameter) {
                 query.set(key, localVarQueryParameter[key]);
@@ -421,6 +417,58 @@ export const SysTenantApiAxiosParamCreator = function (configuration?: Configura
         },
         /**
          * 
+         * @summary 获取租户菜单Id集合 🔖
+         * @param {number} id 主键Id
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysTenantTenantMenuListGet: async (id: number, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            // verify required parameter 'id' is not null or undefined
+            if (id === null || id === undefined) {
+                throw new RequiredError('id','Required parameter id was null or undefined when calling apiSysTenantTenantMenuListGet.');
+            }
+            const localVarPath = `/api/sysTenant/tenantMenuList`;
+            // use dummy base URL string because the URL constructor only accepts absolute URLs.
+            const localVarUrlObj = new URL(localVarPath, 'https://example.com');
+            let baseOptions;
+            if (configuration) {
+                baseOptions = configuration.baseOptions;
+            }
+            const localVarRequestOptions :AxiosRequestConfig = { method: 'GET', ...baseOptions, ...options};
+            const localVarHeaderParameter = {} as any;
+            const localVarQueryParameter = {} as any;
+
+            // authentication Bearer required
+            // http bearer authentication required
+            if (configuration && configuration.accessToken) {
+                const accessToken = typeof configuration.accessToken === 'function'
+                    ? await configuration.accessToken()
+                    : await configuration.accessToken;
+                localVarHeaderParameter["Authorization"] = "Bearer " + accessToken;
+            }
+
+            if (id !== undefined) {
+                localVarQueryParameter['Id'] = id;
+            }
+
+            const query = new URLSearchParams(localVarUrlObj.search);
+            for (const key in localVarQueryParameter) {
+                query.set(key, localVarQueryParameter[key]);
+            }
+            for (const key in options.params) {
+                query.set(key, options.params[key]);
+            }
+            localVarUrlObj.search = (new URLSearchParams(query)).toString();
+            let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
+            localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
+
+            return {
+                url: localVarUrlObj.pathname + localVarUrlObj.search + localVarUrlObj.hash,
+                options: localVarRequestOptions,
+            };
+        },
+        /**
+         * 
          * @summary 更新租户 🔖
          * @param {UpdateTenantInput} [body] 
          * @param {*} [options] Override http request option.
@@ -568,12 +616,12 @@ export const SysTenantApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
-         * @summary 授权租户管理员角色菜单 🔖
-         * @param {RoleMenuInput} [body] 
+         * @summary 授权租户菜单 🔖
+         * @param {TenantMenuInput} [body] 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysTenantGrantMenuPost(body?: RoleMenuInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
+        async apiSysTenantGrantMenuPost(body?: TenantMenuInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
             const localVarAxiosArgs = await SysTenantApiAxiosParamCreator(configuration).apiSysTenantGrantMenuPost(body, options);
             return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
                 const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
@@ -582,13 +630,12 @@ export const SysTenantApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
-         * @summary 获取租户管理员角色拥有菜单Id集合 🔖
-         * @param {number} [userId] 用户Id
+         * @summary 获取租户列表
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysTenantOwnMenuListGet(userId?: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListInt64>>> {
-            const localVarAxiosArgs = await SysTenantApiAxiosParamCreator(configuration).apiSysTenantOwnMenuListGet(userId, options);
+        async apiSysTenantListGet(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultObject>>> {
+            const localVarAxiosArgs = await SysTenantApiAxiosParamCreator(configuration).apiSysTenantListGet(options);
             return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
                 const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
                 return axios.request(axiosRequestArgs);
@@ -638,6 +685,20 @@ export const SysTenantApiFp = function(configuration?: Configuration) {
         },
         /**
          * 
+         * @summary 获取租户菜单Id集合 🔖
+         * @param {number} id 主键Id
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysTenantTenantMenuListGet(id: number, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultListInt64>>> {
+            const localVarAxiosArgs = await SysTenantApiAxiosParamCreator(configuration).apiSysTenantTenantMenuListGet(id, options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
+        /**
+         * 
          * @summary 更新租户 🔖
          * @param {UpdateTenantInput} [body] 
          * @param {*} [options] Override http request option.
@@ -705,23 +766,22 @@ export const SysTenantApiFactory = function (configuration?: Configuration, base
         },
         /**
          * 
-         * @summary 授权租户管理员角色菜单 🔖
-         * @param {RoleMenuInput} [body] 
+         * @summary 授权租户菜单 🔖
+         * @param {TenantMenuInput} [body] 
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysTenantGrantMenuPost(body?: RoleMenuInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
+        async apiSysTenantGrantMenuPost(body?: TenantMenuInput, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
             return SysTenantApiFp(configuration).apiSysTenantGrantMenuPost(body, options).then((request) => request(axios, basePath));
         },
         /**
          * 
-         * @summary 获取租户管理员角色拥有菜单Id集合 🔖
-         * @param {number} [userId] 用户Id
+         * @summary 获取租户列表
          * @param {*} [options] Override http request option.
          * @throws {RequiredError}
          */
-        async apiSysTenantOwnMenuListGet(userId?: number, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListInt64>> {
-            return SysTenantApiFp(configuration).apiSysTenantOwnMenuListGet(userId, options).then((request) => request(axios, basePath));
+        async apiSysTenantListGet(options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultObject>> {
+            return SysTenantApiFp(configuration).apiSysTenantListGet(options).then((request) => request(axios, basePath));
         },
         /**
          * 
@@ -755,6 +815,16 @@ export const SysTenantApiFactory = function (configuration?: Configuration, base
         },
         /**
          * 
+         * @summary 获取租户菜单Id集合 🔖
+         * @param {number} id 主键Id
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysTenantTenantMenuListGet(id: number, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultListInt64>> {
+            return SysTenantApiFp(configuration).apiSysTenantTenantMenuListGet(id, options).then((request) => request(axios, basePath));
+        },
+        /**
+         * 
          * @summary 更新租户 🔖
          * @param {UpdateTenantInput} [body] 
          * @param {*} [options] Override http request option.
@@ -818,25 +888,24 @@ export class SysTenantApi extends BaseAPI {
     }
     /**
      * 
-     * @summary 授权租户管理员角色菜单 🔖
-     * @param {RoleMenuInput} [body] 
+     * @summary 授权租户菜单 🔖
+     * @param {TenantMenuInput} [body] 
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof SysTenantApi
      */
-    public async apiSysTenantGrantMenuPost(body?: RoleMenuInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
+    public async apiSysTenantGrantMenuPost(body?: TenantMenuInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
         return SysTenantApiFp(this.configuration).apiSysTenantGrantMenuPost(body, options).then((request) => request(this.axios, this.basePath));
     }
     /**
      * 
-     * @summary 获取租户管理员角色拥有菜单Id集合 🔖
-     * @param {number} [userId] 用户Id
+     * @summary 获取租户列表
      * @param {*} [options] Override http request option.
      * @throws {RequiredError}
      * @memberof SysTenantApi
      */
-    public async apiSysTenantOwnMenuListGet(userId?: number, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListInt64>> {
-        return SysTenantApiFp(this.configuration).apiSysTenantOwnMenuListGet(userId, options).then((request) => request(this.axios, this.basePath));
+    public async apiSysTenantListGet(options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultObject>> {
+        return SysTenantApiFp(this.configuration).apiSysTenantListGet(options).then((request) => request(this.axios, this.basePath));
     }
     /**
      * 
@@ -873,6 +942,17 @@ export class SysTenantApi extends BaseAPI {
     }
     /**
      * 
+     * @summary 获取租户菜单Id集合 🔖
+     * @param {number} id 主键Id
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysTenantApi
+     */
+    public async apiSysTenantTenantMenuListGet(id: number, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultListInt64>> {
+        return SysTenantApiFp(this.configuration).apiSysTenantTenantMenuListGet(id, options).then((request) => request(this.axios, this.basePath));
+    }
+    /**
+     * 
      * @summary 更新租户 🔖
      * @param {UpdateTenantInput} [body] 
      * @param {*} [options] Override http request option.

+ 6 - 0
Web/src/api-services/models/add-menu-input.ts

@@ -176,4 +176,10 @@ export interface AddMenuInput {
      * @memberof AddMenuInput
      */
     title: string;
+    /**
+     * 租户Id
+     * @type {number}
+     * @memberof AddMenuInput
+     */
+    tenantId?: number;
 }

+ 1 - 1
Web/src/api-services/models/add-pos-input.ts

@@ -98,7 +98,7 @@ export interface AddPosInput {
      */
     status?: StatusEnum;
     /**
-     * 在职人
+     * 在职人
      * @type {Array<SysUser>}
      * @memberof AddPosInput
      */

+ 0 - 80
Web/src/api-services/models/add-sys-app-input.ts

@@ -1,80 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-/**
- * 应用增加输入参数
- * @export
- * @interface AddSysAppInput
- */
-export interface AddSysAppInput {
-    /**
-     * 图标
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    logo: string;
-    /**
-     * 名称
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    name: string;
-    /**
-     * 标题
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    title: string;
-    /**
-     * 副标题
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    viceTitle: string;
-    /**
-     * 副描述
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    viceDesc: string;
-    /**
-     * 水印
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    watermark: string;
-    /**
-     * 版权信息
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    copyright: string;
-    /**
-     * ICP备案号
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    icp: string;
-    /**
-     * 排序
-     * @type {number}
-     * @memberof AddSysAppInput
-     */
-    orderNo: number;
-    /**
-     * 备注
-     * @type {string}
-     * @memberof AddSysAppInput
-     */
-    remark?: string | null;
-}

+ 62 - 8
Web/src/api-services/models/add-tenant-input.ts

@@ -73,9 +73,9 @@ export interface AddTenantInput {
      * @type {number}
      * @memberof AddTenantInput
      */
-    appId: number;
+    appId?: number | null;
     /**
-     * 用户Id
+     * 租管用户Id
      * @type {number}
      * @memberof AddTenantInput
      */
@@ -117,6 +117,12 @@ export interface AddTenantInput {
      */
     slaveConnections?: string | null;
     /**
+     * 图标
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    logo: string;
+    /**
      * 排序
      * @type {number}
      * @memberof AddTenantInput
@@ -135,12 +141,6 @@ export interface AddTenantInput {
      */
     status?: StatusEnum;
     /**
-     * 关联应用名称
-     * @type {string}
-     * @memberof AddTenantInput
-     */
-    appName?: string | null;
-    /**
      * 电子邮箱
      * @type {string}
      * @memberof AddTenantInput
@@ -170,4 +170,58 @@ export interface AddTenantInput {
      * @memberof AddTenantInput
      */
     host: string;
+    /**
+     * 系统主标题
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    title: string;
+    /**
+     * 系统副标题
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    viceTitle: string;
+    /**
+     * 系统描述
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    viceDesc: string;
+    /**
+     * 水印内容
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    watermark: string;
+    /**
+     * 版权说明
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    copyright: string;
+    /**
+     * ICP备案号
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    icp: string;
+    /**
+     * ICP地址
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    icpUrl: string;
+    /**
+     * Logo图片Base64码
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    logoBase64: string;
+    /**
+     * Logo文件名
+     * @type {string}
+     * @memberof AddTenantInput
+     */
+    logoFileName?: string | null;
 }

+ 0 - 57
Web/src/api-services/models/admin-result-sql-sugar-paged-list-sys-app-output.ts

@@ -1,57 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-import { SqlSugarPagedListSysAppOutput } from './sql-sugar-paged-list-sys-app-output';
-/**
- * 全局返回结果
- * @export
- * @interface AdminResultSqlSugarPagedListSysAppOutput
- */
-export interface AdminResultSqlSugarPagedListSysAppOutput {
-    /**
-     * 状态码
-     * @type {number}
-     * @memberof AdminResultSqlSugarPagedListSysAppOutput
-     */
-    code?: number;
-    /**
-     * 类型success、warning、error
-     * @type {string}
-     * @memberof AdminResultSqlSugarPagedListSysAppOutput
-     */
-    type?: string | null;
-    /**
-     * 错误信息
-     * @type {string}
-     * @memberof AdminResultSqlSugarPagedListSysAppOutput
-     */
-    message?: string | null;
-    /**
-     * 
-     * @type {SqlSugarPagedListSysAppOutput}
-     * @memberof AdminResultSqlSugarPagedListSysAppOutput
-     */
-    result?: SqlSugarPagedListSysAppOutput;
-    /**
-     * 附加数据
-     * @type {any}
-     * @memberof AdminResultSqlSugarPagedListSysAppOutput
-     */
-    extras?: any | null;
-    /**
-     * 时间
-     * @type {Date}
-     * @memberof AdminResultSqlSugarPagedListSysAppOutput
-     */
-    time?: Date;
-}

+ 0 - 26
Web/src/api-services/models/base-id-input.ts

@@ -1,26 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-/**
- * 主键Id输入参数
- * @export
- * @interface BaseIdInput
- */
-export interface BaseIdInput {
-    /**
-     * 主键Id
-     * @type {number}
-     * @memberof BaseIdInput
-     */
-    id: number;
-}

+ 0 - 70
Web/src/api-services/models/base-page-input.ts

@@ -1,70 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-import { Filter } from './filter';
-import { Search } from './search';
-/**
- * 全局分页查询输入参数
- * @export
- * @interface BasePageInput
- */
-export interface BasePageInput {
-    /**
-     * 
-     * @type {Search}
-     * @memberof BasePageInput
-     */
-    search?: Search;
-    /**
-     * 模糊查询关键字
-     * @type {string}
-     * @memberof BasePageInput
-     */
-    keyword?: string | null;
-    /**
-     * 
-     * @type {Filter}
-     * @memberof BasePageInput
-     */
-    filter?: Filter;
-    /**
-     * 当前页码
-     * @type {number}
-     * @memberof BasePageInput
-     */
-    page?: number;
-    /**
-     * 页码容量
-     * @type {number}
-     * @memberof BasePageInput
-     */
-    pageSize?: number;
-    /**
-     * 排序字段
-     * @type {string}
-     * @memberof BasePageInput
-     */
-    field?: string | null;
-    /**
-     * 排序方向
-     * @type {string}
-     * @memberof BasePageInput
-     */
-    order?: string | null;
-    /**
-     * 降序排序
-     * @type {string}
-     * @memberof BasePageInput
-     */
-    descStr?: string | null;
-}

+ 0 - 32
Web/src/api-services/models/change-app-input.ts

@@ -1,32 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-/**
- * 租户iId
- * @export
- * @interface ChangeAppInput
- */
-export interface ChangeAppInput {
-    /**
-     * 主键Id
-     * @type {number}
-     * @memberof ChangeAppInput
-     */
-    id: number;
-    /**
-     * 租户Id
-     * @type {number}
-     * @memberof ChangeAppInput
-     */
-    tenantId: number;
-}

+ 1 - 9
Web/src/api-services/models/index.ts

@@ -16,7 +16,6 @@ export * from './add-region-input';
 export * from './add-role-input';
 export * from './add-schedule-input';
 export * from './add-subscribe-message-template-input';
-export * from './add-sys-app-input';
 export * from './add-sys-ldap-input';
 export * from './add-tenant-input';
 export * from './add-user-input';
@@ -68,7 +67,6 @@ export * from './admin-result-object';
 export * from './admin-result-sm-key-pair-output';
 export * from './admin-result-sql-sugar-paged-list-job-detail-output';
 export * from './admin-result-sql-sugar-paged-list-open-access-output';
-export * from './admin-result-sql-sugar-paged-list-sys-app-output';
 export * from './admin-result-sql-sugar-paged-list-sys-code-gen';
 export * from './admin-result-sql-sugar-paged-list-sys-config';
 export * from './admin-result-sql-sugar-paged-list-sys-dict-data';
@@ -115,8 +113,6 @@ export * from './admin-result-wx-open-id-output';
 export * from './admin-result-wx-phone-output';
 export * from './api-output';
 export * from './assembly';
-export * from './base-id-input';
-export * from './base-page-input';
 export * from './base-proc-input';
 export * from './batch-config-input';
 export * from './calendar';
@@ -124,7 +120,6 @@ export * from './calendar-algorithm-type';
 export * from './calendar-week-rule';
 export * from './calling-conventions';
 export * from './card-type-enum';
-export * from './change-app-input';
 export * from './change-pwd-input';
 export * from './cluster-status';
 export * from './code-gen-config';
@@ -291,7 +286,6 @@ export * from './sms-verify-code-input';
 export * from './sort-version';
 export * from './sql-sugar-paged-list-job-detail-output';
 export * from './sql-sugar-paged-list-open-access-output';
-export * from './sql-sugar-paged-list-sys-app-output';
 export * from './sql-sugar-paged-list-sys-code-gen';
 export * from './sql-sugar-paged-list-sys-config';
 export * from './sql-sugar-paged-list-sys-dict-data';
@@ -318,7 +312,6 @@ export * from './status-enum';
 export * from './struct-layout-attribute';
 export * from './swagger-submit-url-body';
 export * from './sync-sys-ldap-input';
-export * from './sys-app-output';
 export * from './sys-code-gen';
 export * from './sys-code-gen-config';
 export * from './sys-config';
@@ -362,6 +355,7 @@ export * from './table-output';
 export * from './table-unique-config-item';
 export * from './tenant-id-input';
 export * from './tenant-input';
+export * from './tenant-menu-input';
 export * from './tenant-output';
 export * from './tenant-type-enum';
 export * from './tenant-user-input';
@@ -371,7 +365,6 @@ export * from './type';
 export * from './type-attributes';
 export * from './type-info';
 export * from './unlock-login-input';
-export * from './update-app-menu-input';
 export * from './update-code-gen-input';
 export * from './update-config-input';
 export * from './update-db-column-input';
@@ -390,7 +383,6 @@ export * from './update-print-input';
 export * from './update-region-input';
 export * from './update-role-input';
 export * from './update-schedule-input';
-export * from './update-sys-app-input';
 export * from './update-sys-ldap-input';
 export * from './update-tenant-input';
 export * from './update-user-input';

+ 2 - 2
Web/src/api-services/models/info-save-input.ts

@@ -76,11 +76,11 @@ export interface InfoSaveInput {
      * @type {boolean}
      * @memberof InfoSaveInput
      */
-    sysSecondVer?: boolean | null;
+    sysSecondVer?: boolean;
     /**
      * 图形验证码
      * @type {boolean}
      * @memberof InfoSaveInput
      */
-    sysCaptcha?: boolean | null;
+    sysCaptcha?: boolean;
 }

+ 3 - 3
Web/src/api-services/models/login-input.ts

@@ -30,11 +30,11 @@ export interface LoginInput {
      */
     password: string;
     /**
-     * 租户域名
-     * @type {string}
+     * 租户
+     * @type {number}
      * @memberof LoginInput
      */
-    host: string;
+    tenantId: number;
     /**
      * 验证码Id
      * @type {number}

+ 3 - 3
Web/src/api-services/models/login-phone-input.ts

@@ -30,9 +30,9 @@ export interface LoginPhoneInput {
      */
     code: string;
     /**
-     * 租户域名
-     * @type {string}
+     * 租户
+     * @type {number}
      * @memberof LoginPhoneInput
      */
-    host: string;
+    tenantId: number;
 }

+ 0 - 6
Web/src/api-services/models/page-dict-data-input.ts

@@ -79,10 +79,4 @@ export interface PageDictDataInput {
      * @memberof PageDictDataInput
      */
     label?: string | null;
-    /**
-     * 租户Id
-     * @type {number}
-     * @memberof PageDictDataInput
-     */
-    tenantId?: number;
 }

+ 1 - 1
Web/src/api-services/models/role-menu-input.ts

@@ -12,7 +12,7 @@
  * Do not edit the class manually.
  */
 /**
- * 授权角色菜单
+ * 系统角色菜单
  * @export
  * @interface RoleMenuInput
  */

+ 0 - 63
Web/src/api-services/models/sql-sugar-paged-list-sys-app-output.ts

@@ -1,63 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-import { SysAppOutput } from './sys-app-output';
-/**
- * 分页泛型集合
- * @export
- * @interface SqlSugarPagedListSysAppOutput
- */
-export interface SqlSugarPagedListSysAppOutput {
-    /**
-     * 页码
-     * @type {number}
-     * @memberof SqlSugarPagedListSysAppOutput
-     */
-    page?: number;
-    /**
-     * 页容量
-     * @type {number}
-     * @memberof SqlSugarPagedListSysAppOutput
-     */
-    pageSize?: number;
-    /**
-     * 总条数
-     * @type {number}
-     * @memberof SqlSugarPagedListSysAppOutput
-     */
-    total?: number;
-    /**
-     * 总页数
-     * @type {number}
-     * @memberof SqlSugarPagedListSysAppOutput
-     */
-    totalPages?: number;
-    /**
-     * 当前页集合
-     * @type {Array<SysAppOutput>}
-     * @memberof SqlSugarPagedListSysAppOutput
-     */
-    items?: Array<SysAppOutput> | null;
-    /**
-     * 是否有上一页
-     * @type {boolean}
-     * @memberof SqlSugarPagedListSysAppOutput
-     */
-    hasPrevPage?: boolean;
-    /**
-     * 是否有下一页
-     * @type {boolean}
-     * @memberof SqlSugarPagedListSysAppOutput
-     */
-    hasNextPage?: boolean;
-}

+ 0 - 128
Web/src/api-services/models/sys-app-output.ts

@@ -1,128 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-/**
- * 应用输出参数
- * @export
- * @interface SysAppOutput
- */
-export interface SysAppOutput {
-    /**
-     * 主键Id
-     * @type {number}
-     * @memberof SysAppOutput
-     */
-    id?: number;
-    /**
-     * 名称
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    name?: string | null;
-    /**
-     * 图标
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    logo?: string | null;
-    /**
-     * 标题
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    title?: string | null;
-    /**
-     * 副标题
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    viceTitle?: string | null;
-    /**
-     * 副描述
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    viceDesc?: string | null;
-    /**
-     * 水印
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    watermark?: string | null;
-    /**
-     * 版权信息
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    copyright?: string | null;
-    /**
-     * ICP备案号
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    icp?: string | null;
-    /**
-     * 排序
-     * @type {number}
-     * @memberof SysAppOutput
-     */
-    orderNo?: number;
-    /**
-     * 备注
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    remark?: string | null;
-    /**
-     * 创建时间
-     * @type {Date}
-     * @memberof SysAppOutput
-     */
-    createTime?: Date | null;
-    /**
-     * 更新时间
-     * @type {Date}
-     * @memberof SysAppOutput
-     */
-    updateTime?: Date | null;
-    /**
-     * 创建者Id
-     * @type {number}
-     * @memberof SysAppOutput
-     */
-    createUserId?: number | null;
-    /**
-     * 创建者姓名
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    createUserName?: string | null;
-    /**
-     * 修改者Id
-     * @type {number}
-     * @memberof SysAppOutput
-     */
-    updateUserId?: number | null;
-    /**
-     * 修改者姓名
-     * @type {string}
-     * @memberof SysAppOutput
-     */
-    updateUserName?: string | null;
-    /**
-     * 软删除
-     * @type {boolean}
-     * @memberof SysAppOutput
-     */
-    isDelete?: boolean;
-}

+ 1 - 1
Web/src/api-services/models/sys-pos.ts

@@ -104,7 +104,7 @@ export interface SysPos {
      */
     status?: StatusEnum;
     /**
-     * 在职人
+     * 在职人
      * @type {Array<SysUser>}
      * @memberof SysPos
      */

+ 5 - 5
Web/src/api-services/models/update-app-menu-input.ts → Web/src/api-services/models/tenant-menu-input.ts

@@ -12,21 +12,21 @@
  * Do not edit the class manually.
  */
 /**
- * 授权应用菜单
+ * 租户菜单
  * @export
- * @interface UpdateAppMenuInput
+ * @interface TenantMenuInput
  */
-export interface UpdateAppMenuInput {
+export interface TenantMenuInput {
     /**
      * 主键Id
      * @type {number}
-     * @memberof UpdateAppMenuInput
+     * @memberof TenantMenuInput
      */
     id: number;
     /**
      * 菜单Id集合
      * @type {Array<number>}
-     * @memberof UpdateAppMenuInput
+     * @memberof TenantMenuInput
      */
     menuIdList?: Array<number> | null;
 }

+ 50 - 8
Web/src/api-services/models/tenant-output.ts

@@ -73,9 +73,9 @@ export interface TenantOutput {
      * @type {number}
      * @memberof TenantOutput
      */
-    appId?: number;
+    appId?: number | null;
     /**
-     * 用户Id
+     * 租管用户Id
      * @type {number}
      * @memberof TenantOutput
      */
@@ -123,6 +123,54 @@ export interface TenantOutput {
      */
     slaveConnections?: string | null;
     /**
+     * 图标
+     * @type {string}
+     * @memberof TenantOutput
+     */
+    logo: string;
+    /**
+     * 标题
+     * @type {string}
+     * @memberof TenantOutput
+     */
+    title?: string | null;
+    /**
+     * 副标题
+     * @type {string}
+     * @memberof TenantOutput
+     */
+    viceTitle?: string | null;
+    /**
+     * 副描述
+     * @type {string}
+     * @memberof TenantOutput
+     */
+    viceDesc?: string | null;
+    /**
+     * 水印
+     * @type {string}
+     * @memberof TenantOutput
+     */
+    watermark?: string | null;
+    /**
+     * 版权信息
+     * @type {string}
+     * @memberof TenantOutput
+     */
+    copyright?: string | null;
+    /**
+     * ICP备案号
+     * @type {string}
+     * @memberof TenantOutput
+     */
+    icp?: string | null;
+    /**
+     * ICP地址
+     * @type {string}
+     * @memberof TenantOutput
+     */
+    icpUrl?: string | null;
+    /**
      * 排序
      * @type {number}
      * @memberof TenantOutput
@@ -147,12 +195,6 @@ export interface TenantOutput {
      */
     name?: string | null;
     /**
-     * 关联应用名称
-     * @type {string}
-     * @memberof TenantOutput
-     */
-    appName?: string | null;
-    /**
      * 管理员账号
      * @type {string}
      * @memberof TenantOutput

+ 6 - 0
Web/src/api-services/models/update-menu-input.ts

@@ -176,4 +176,10 @@ export interface UpdateMenuInput {
      * @memberof UpdateMenuInput
      */
     title: string;
+    /**
+     * 租户Id
+     * @type {number}
+     * @memberof UpdateMenuInput
+     */
+    tenantId?: number;
 }

+ 1 - 1
Web/src/api-services/models/update-pos-input.ts

@@ -98,7 +98,7 @@ export interface UpdatePosInput {
      */
     status?: StatusEnum;
     /**
-     * 在职人
+     * 在职人
      * @type {Array<SysUser>}
      * @memberof UpdatePosInput
      */

+ 0 - 86
Web/src/api-services/models/update-sys-app-input.ts

@@ -1,86 +0,0 @@
-/* tslint:disable */
-/* eslint-disable */
-/**
- * Admin.NET 通用权限开发平台
- * 让 .NET 开发更简单、更通用、更流行。整合最新技术,模块插件式开发,前后端分离,开箱即用。<br/><u><b><font color='FF0000'> 👮不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!</font></b></u>
- *
- * OpenAPI spec version: 1.0.0
- * 
- *
- * NOTE: This class is auto generated by the swagger code generator program.
- * https://github.com/swagger-api/swagger-codegen.git
- * Do not edit the class manually.
- */
-/**
- * 应用更新输入参数
- * @export
- * @interface UpdateSysAppInput
- */
-export interface UpdateSysAppInput {
-    /**
-     * 图标
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    logo: string;
-    /**
-     * 名称
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    name: string;
-    /**
-     * 标题
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    title: string;
-    /**
-     * 副标题
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    viceTitle: string;
-    /**
-     * 副描述
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    viceDesc: string;
-    /**
-     * 水印
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    watermark: string;
-    /**
-     * 版权信息
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    copyright: string;
-    /**
-     * ICP备案号
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    icp: string;
-    /**
-     * 排序
-     * @type {number}
-     * @memberof UpdateSysAppInput
-     */
-    orderNo: number;
-    /**
-     * 备注
-     * @type {string}
-     * @memberof UpdateSysAppInput
-     */
-    remark?: string | null;
-    /**
-     * 主键Id
-     * @type {number}
-     * @memberof UpdateSysAppInput
-     */
-    id: number;
-}

+ 62 - 8
Web/src/api-services/models/update-tenant-input.ts

@@ -73,9 +73,9 @@ export interface UpdateTenantInput {
      * @type {number}
      * @memberof UpdateTenantInput
      */
-    appId: number;
+    appId?: number | null;
     /**
-     * 用户Id
+     * 租管用户Id
      * @type {number}
      * @memberof UpdateTenantInput
      */
@@ -117,6 +117,12 @@ export interface UpdateTenantInput {
      */
     slaveConnections?: string | null;
     /**
+     * 图标
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    logo: string;
+    /**
      * 排序
      * @type {number}
      * @memberof UpdateTenantInput
@@ -135,12 +141,6 @@ export interface UpdateTenantInput {
      */
     status?: StatusEnum;
     /**
-     * 关联应用名称
-     * @type {string}
-     * @memberof UpdateTenantInput
-     */
-    appName?: string | null;
-    /**
      * 电子邮箱
      * @type {string}
      * @memberof UpdateTenantInput
@@ -170,4 +170,58 @@ export interface UpdateTenantInput {
      * @memberof UpdateTenantInput
      */
     host: string;
+    /**
+     * 系统主标题
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    title: string;
+    /**
+     * 系统副标题
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    viceTitle: string;
+    /**
+     * 系统描述
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    viceDesc: string;
+    /**
+     * 水印内容
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    watermark: string;
+    /**
+     * 版权说明
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    copyright: string;
+    /**
+     * ICP备案号
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    icp: string;
+    /**
+     * ICP地址
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    icpUrl: string;
+    /**
+     * Logo文件名
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    logoFileName?: string | null;
+    /**
+     * Logo图片Base64码
+     * @type {string}
+     * @memberof UpdateTenantInput
+     */
+    logoBase64?: string | null;
 }

+ 0 - 1
Web/src/i18n/lang/zh-cn.ts

@@ -152,7 +152,6 @@ export default {
 		threeIsFixedHeader: '固定 Header',
 		threeIsClassicSplitMenu: '经典布局分割菜单',
 		threeIsLockScreen: '开启锁屏',
-		changeApp: '切换应用',
 		threeLockScreenTime: '自动锁屏(s/秒)',
 		fourTitle: '界面显示',
 		fourIsShowLogo: '侧边栏 Logo',

+ 0 - 1
Web/src/i18n/lang/zh-tw.ts

@@ -152,7 +152,6 @@ export default {
 		threeIsFixedHeader: '固定 Header',
 		threeIsClassicSplitMenu: '經典佈局分割選單',
 		threeIsLockScreen: '開啟鎖屏',
-		changeApp: '切换应用',
 		threeLockScreenTime: '自動鎖屏(s/秒)',
 		fourTitle: '介面顯示',
 		fourIsShowLogo: '側邊欄 Logo',

+ 6 - 3
Web/src/i18n/pages/login/en.ts

@@ -11,18 +11,21 @@ export default {
 	account: {
 		accountPlaceholder1: 'The user name admin or not is common',
 		accountPlaceholder2: 'Password: 123456',
-		accountPlaceholder3: 'Please enter the verification code',
+		accountPlaceholder3: 'Please select a tenant',
+		accountPlaceholder4: 'Please enter the verification code',
 		accountBtnText: 'Sign in',
 	},
 	mobile: {
-		placeholder1: 'Please input mobile phone number',
-		placeholder2: 'Please enter the verification code',
+		placeholder1: 'Please select a tenant',
+		placeholder2: 'Please input mobile phone number',
+		placeholder3: 'Please enter the verification code',
 		codeText: 'Get code',
 		btnText: 'Sign in',
 		msgText:
 			'Warm tip: it is recommended to use Google, Microsoft edge, version 79.0.1072.62 and above browsers, and 360 browser, please use speed mode',
 	},
 	scan: {
+		placeholder1: 'Please select a tenant',
 		text: 'Open the mobile phone to scan and quickly log in / register',
 	},
 	signInText: 'welcome back!',

+ 6 - 3
Web/src/i18n/pages/login/zh-cn.ts

@@ -11,17 +11,20 @@ export default {
 	account: {
 		accountPlaceholder1: '用户名 admin 或不输均为 common',
 		accountPlaceholder2: '密码:123456',
-		accountPlaceholder3: '请输入验证码',
+		accountPlaceholder3: '请选择租户',
+		accountPlaceholder4: '请输入验证码',
 		accountBtnText: '登 录',
 	},
 	mobile: {
-		placeholder1: '请输入手机号',
-		placeholder2: '请输入验证码',
+		placeholder1: '请选择租户',
+		placeholder2: '请输入手机号',
+		placeholder3: '请输入验证码',
 		codeText: '获取验证码',
 		btnText: '登 录',
 		msgText: '* 温馨提示:建议使用谷歌、Microsoft Edge,版本 79.0.1072.62 及以上浏览器,360浏览器请使用极速模式',
 	},
 	scan: {
+		placeholder1: '请选择租户',
 		text: '打开手机扫一扫,快速登录/注册',
 	},
 	signInText: '欢迎回来!',

+ 6 - 3
Web/src/i18n/pages/login/zh-tw.ts

@@ -11,17 +11,20 @@ export default {
 	account: {
 		accountPlaceholder1: '用戶名admin或不輸均為common',
 		accountPlaceholder2: '密碼:123456',
-		accountPlaceholder3: '請輸入驗證碼',
+		accountPlaceholder3: '請選擇租戶',
+		accountPlaceholder4: '請輸入驗證碼',
 		accountBtnText: '登入',
 	},
 	mobile: {
-		placeholder1: '請輸入手機號',
-		placeholder2: '請輸入驗證碼',
+		placeholder1: '請選擇租戶',
+		placeholder2: '請輸入手機號',
+		placeholder3: '請輸入驗證碼',
 		codeText: '獲取驗證碼',
 		btnText: '登入',
 		msgText: '* 溫馨提示:建議使用穀歌、Microsoft Edge,版本79.0.1072.62及以上瀏覽器,360瀏覽器請使用極速模式',
 	},
 	scan: {
+		placeholder1: '請選擇租戶',
 		text: '打開手機掃一掃,快速登錄/注册',
 	},
 	signInText: '歡迎回來!',

+ 0 - 91
Web/src/layout/navBars/topBar/changeApp.vue

@@ -1,91 +0,0 @@
-<template>
-  <div class="sys-app-container">
-    <el-dialog v-model="state.isShowDialog" width="300" draggable :close-on-click-modal="false">
-      <template #header>
-        <div style="color: #fff">
-          <el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Switch /> </el-icon>
-          <span>切换应用</span>
-        </div>
-      </template>
-      <el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto">
-        <el-row :gutter="35">
-          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-            <el-form-item label="应用" prop="id" :rules="[{ required: true, message: '应用不能为空', trigger: 'blur' }]">
-              <el-select v-model="state.ruleForm.id" @change="changeApp" value-key="id" placeholder="应用" class="w100" clearable>
-                <el-option v-for="item in state.appList" :key="item.id" :label="item.label" :value="item.id" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-            <el-form-item label="租户" prop="tenantId" :rules="[{ required: true, message: '租户不能为空', trigger: 'blur' }]">
-              <el-select v-model="state.ruleForm.tenantId" value-key="id" placeholder="租户" class="w100" clearable>
-                <el-option v-for="item in state.tenantList" :key="item.id" :label="item.label" :value="item.id" />
-              </el-select>
-            </el-form-item>
-          </el-col>
-        </el-row>
-      </el-form>
-      <template #footer>
-				<span class="dialog-footer">
-					<el-button @click="() => state.isShowDialog = false">取 消</el-button>
-					<el-button type="primary" @click="submit">确 定</el-button>
-				</span>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script lang="ts" setup name="sysEditApp">
-import { reactive, ref } from 'vue';
-import { getAPI } from '/@/utils/axios-utils';
-import { SysAppApi } from '/@/api-services/api';
-import {Local} from "/@/utils/storage";
-import {accessTokenKey, refreshAccessTokenKey} from "/@/utils/request";
-
-const ruleFormRef = ref();
-const state = reactive({
-  loading: false,
-  isShowDialog: false,
-  ruleForm: {} as any,
-  appList: [] as Array<any>,
-  tenantList: [] as Array<any>
-});
-
-// 打开弹窗
-const openDialog = async () => {
-  const result = await getAPI(SysAppApi).apiSysAppChangeAppGet().then(res => res.data.result ?? {});
-  state.appList = result.selectList ?? [];
-  state.tenantList = state.appList.find((u: any) => u.id === result.appId)?.children ?? [];
-  state.ruleForm.tenantId = result.tenantId;
-  state.ruleForm.id = result.appId;
-
-  ruleFormRef.value?.resetFields();
-  state.isShowDialog = true;
-  state.loading = false;
-};
-
-// 应用id改变事件
-const changeApp = (val: any) => {
-  state.tenantList = state.appList.find(u => u.id === val)?.children ?? [];
-  state.ruleForm.tenantId = undefined;
-}
-
-// 提交
-const submit = () => {
-  ruleFormRef.value.validate(async (valid: boolean) => {
-    if (!valid) return;
-    state.loading = true;
-    const newToken = await getAPI(SysAppApi).apiSysAppChangeAppPost(state.ruleForm).then(res => res.data.result);
-    if (newToken) {
-      Local.set(accessTokenKey, newToken.accessToken);
-      Local.set(refreshAccessTokenKey, newToken.refreshToken);
-      location.reload();
-    }
-    state.loading = false;
-    state.isShowDialog = false;
-  });
-};
-
-// 导出对象
-defineExpose({ openDialog });
-</script>

+ 2 - 9
Web/src/layout/navBars/topBar/user.vue

@@ -75,14 +75,12 @@
 					<!-- <el-dropdown-item command="/dashboard/home">{{ $t('message.user.dropdown1') }}</el-dropdown-item> -->
 					<el-dropdown-item :icon="Avatar" command="/system/userCenter">{{ $t('message.user.dropdown2') }}</el-dropdown-item>
 					<el-dropdown-item :icon="Loading" command="clearCache">{{ $t('message.user.dropdown3') }}</el-dropdown-item>
-					<el-dropdown-item :icon="Switch" divided command="changeApp" v-if="auth('sysApp:changeApp')">{{ $t('message.layout.changeApp') }}</el-dropdown-item>
 					<el-dropdown-item :icon="Lock" divided command="lockScreen">{{ $t('message.layout.threeIsLockScreen') }}</el-dropdown-item>
 					<el-dropdown-item :icon="CircleCloseFilled" divided command="logOut">{{ $t('message.user.dropdown5') }}</el-dropdown-item>
 				</el-dropdown-menu>
 			</template>
 		</el-dropdown>
 		<Search ref="searchRef" />
-    <ChangeApp ref="changeAppRef" />
 		<OnlineUser ref="onlineUserRef" />
 	</div>
 </template>
@@ -105,13 +103,11 @@ import { Avatar, CircleCloseFilled, Loading, Lock, Switch } from '@element-plus/
 
 import { clearAccessTokens, getAPI } from '/@/utils/axios-utils';
 import { SysAuthApi, SysNoticeApi } from '/@/api-services/api';
-import {auth} from "/@/utils/authFunction";
 
 // 引入组件
 const UserNews = defineAsyncComponent(() => import('/@/layout/navBars/topBar/userNews.vue'));
 const Search = defineAsyncComponent(() => import('/@/layout/navBars/topBar/search.vue'));
 const OnlineUser = defineAsyncComponent(() => import('/@/views/system/onlineUser/index.vue'));
-const ChangeApp = defineAsyncComponent(() => import('./changeApp.vue'));
 
 // 定义变量内容
 const { locale, t } = useI18n();
@@ -121,7 +117,6 @@ const storesThemeConfig = useThemeConfig();
 const { userInfos } = storeToRefs(stores);
 const { themeConfig } = storeToRefs(storesThemeConfig);
 const searchRef = ref();
-const changeAppRef = ref();
 const onlineUserRef = ref();
 const state = reactive({
 	isScreenfull: false,
@@ -196,9 +191,7 @@ const onHandleCommandClick = (path: string) => {
 				clearAccessTokens();
 			})
 			.catch(() => {});
-	} else if (path === 'changeApp') {
-    changeAppRef.value.openDialog();
-  } else {
+	} else {
 		router.push(path);
 	}
 };
@@ -240,7 +233,7 @@ onMounted(async () => {
 	// 手动获取用户桌面通知权限
 	if (Push.Permission.GRANTED) {
 		// 判断当前是否有权限,没有则手动获取
-		Push.Permission.request(null, null);
+		Push.Permission.request(undefined, undefined);
 	}
 	// 监听浏览器 当前系统是否在当前页
 	document.addEventListener('visibilitychange', () => {

+ 18 - 8
Web/src/views/login/component/account.vue

@@ -2,7 +2,7 @@
 	<el-tooltip :visible="state.capsLockVisible" effect="light" content="大写锁定已打开" placement="top">
 		<el-form ref="ruleFormRef" :model="state.ruleForm" size="large" :rules="state.rules" class="login-content-form">
 			<el-form-item class="login-animation1" prop="account">
-				<el-input ref="accountRef" text placeholder="请输入账号" v-model="state.ruleForm.account" clearable autocomplete="off" @keyup.enter.native="handleSignIn">
+				<el-input ref="accountRef" text :placeholder="$t('message.account.accountPlaceholder1')" v-model="state.ruleForm.account" clearable autocomplete="off" @keyup.enter.native="handleSignIn">
 					<template #prefix>
 						<el-icon>
 							<ele-User />
@@ -11,7 +11,7 @@
 				</el-input>
 			</el-form-item>
 			<el-form-item class="login-animation2" prop="password">
-				<el-input ref="passwordRef" :type="state.isShowPassword ? 'text' : 'password'" placeholder="请输入密码" v-model="state.ruleForm.password" autocomplete="off" @keyup.enter.native="handleSignIn">
+				<el-input ref="passwordRef" :type="state.isShowPassword ? 'text' : 'password'" :placeholder="$t('message.account.accountPlaceholder2')" v-model="state.ruleForm.password" autocomplete="off" @keyup.enter.native="handleSignIn">
 					<template #prefix>
 						<el-icon>
 							<ele-Unlock />
@@ -23,13 +23,21 @@
 					</template>
 				</el-input>
 			</el-form-item>
+			<el-form-item class="login-animation2" prop="tenantId" clearable v-if="!tenantInfo.list.some(e => e.host === tenantInfo.host)">
+				<el-select v-model="state.ruleForm.tenantId" :placeholder="$t('message.account.accountPlaceholder3')" style="width: 100%">
+					<template #prefix>
+						<i class="iconfont icon-shuxingtu el-input__icon"></i>
+					</template>
+					<el-option :value="item.value" :label="`${item.label} (${item.host})`" v-for="(item, index) in tenantInfo.list" :key="index" />
+				</el-select>
+			</el-form-item>
 			<el-form-item class="login-animation3" prop="captcha" v-if="state.captchaEnabled">
 				<el-col :span="15">
 					<el-input
 						ref="codeRef"
 						text
 						maxlength="4"
-						:placeholder="$t('message.account.accountPlaceholder3')"
+						:placeholder="$t('message.account.accountPlaceholder4')"
 						v-model="state.ruleForm.code"
 						clearable
 						autocomplete="off"
@@ -75,7 +83,7 @@
 </template>
 
 <script lang="ts" setup name="loginAccount">
-import { reactive, computed, ref, onMounted, defineAsyncComponent, onUnmounted, watch } from 'vue';
+import {reactive, computed, ref, onMounted, defineAsyncComponent, onUnmounted, watch } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { ElMessage, InputInstance } from 'element-plus';
 import { useI18n } from 'vue-i18n';
@@ -90,6 +98,10 @@ import { storeToRefs } from 'pinia';
 import { accessTokenKey, clearTokens, feature, getAPI } from '/@/utils/axios-utils';
 import { SysAuthApi } from '/@/api-services/api';
 
+const props = defineProps({
+	tenantInfo: {},
+});
+
 // 旋转图片滑块组件
 // import verifyImg from '/@/assets/logo-mini.svg';
 const DragVerifyImgRotate = defineAsyncComponent(() => import('/@/components/dragVerify/dragVerifyImgRotate.vue'));
@@ -112,6 +124,7 @@ const state = reactive({
 	ruleForm: {
 		account: window.__env__.VITE_DEFAULT_USER,
 		password: window.__env__.VITE_DEFAULT_USER_PASSWORD,
+		tenantId: props.tenantInfo.id,
 		code: '',
 		codeId: 0,
 	},
@@ -142,7 +155,6 @@ onMounted(async () => {
 	// 若URL带有Token参数(第三方登录)
 	const accessToken = route.query.token;
 	if (accessToken) await saveTokenAndInitRoutes(accessToken);
-
 	watch(
 		() => themeConfig.value.isLoaded,
 		(isLoaded) => {
@@ -211,9 +223,7 @@ const onSignIn = async () => {
 			// const keys = SM2.generateKeyPair();
 			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 }));
+			const [err, res] = await feature(getAPI(SysAuthApi).apiSysAuthLoginPost({ ...state.ruleForm, password: password } as any));
 			if (err) {
 				getCaptcha(); // 重新获取验证码
 				return;

+ 13 - 0
Web/src/views/login/component/mobile.vue

@@ -1,5 +1,13 @@
 <template>
 	<el-form size="large" class="login-content-form">
+		<el-form-item class="login-animation1" v-if="!tenantInfo.list.some(e => e.host === tenantInfo.host)">
+			<el-select v-model="state.ruleForm.tenantId" :placeholder="$t('message.mobile.placeholder1')" clearable style="width: 100%">
+				<template #prefix>
+					<i class="iconfont icon-shuxingtu el-input__icon"></i>
+				</template>
+				<el-option :value="item.value" :label="`${item.label} (${item.host})`" v-for="(item, index) in tenantInfo.list" :key="index" />
+			</el-select>
+		</el-form-item>
 		<el-form-item class="login-animation1">
 			<el-input text :placeholder="$t('message.mobile.placeholder1')" v-model="state.ruleForm.phone" clearable autocomplete="off">
 				<template #prefix>
@@ -39,9 +47,14 @@ import { useRoute } from "vue-router";
 import { getAPI } from '/@/utils/axios-utils';
 import { SysSmsApi, SysAuthApi } from '/@/api-services/api';
 
+const props = defineProps({
+	tenantInfo: {},
+});
+
 const route = useRoute();
 const state = reactive({
 	ruleForm: {
+		tenantId: props.tenantInfo.id,
 		phone: '',
 		code: '',
 	},

+ 15 - 1
Web/src/views/login/component/scan.vue

@@ -1,5 +1,11 @@
 <template>
 	<div class="login-scan-container">
+		<el-select v-model="state.tenantId" v-if="!tenantInfo.list.some(e => e.host === tenantInfo.host)" :placeholder="$t('message.scan.placeholder1')" clearable class="mb30" style="width: 260px; align-self: center;">
+			<template #prefix>
+				<i class="iconfont icon-shuxingtu el-input__icon"></i>
+			</template>
+			<el-option :value="item.value" :label="`${item.label} (${item.host})`" v-for="(item, index) in tenantInfo.list" :key="index" />
+		</el-select>
 		<div ref="qrcodeRef"></div>
 		<div class="font12 mt20 login-msg">
 			<i class="iconfont icon-saoyisao mr5"></i>
@@ -9,9 +15,17 @@
 </template>
 
 <script setup lang="ts" name="loginScan">
-import { ref, onMounted, nextTick } from 'vue';
+import {ref, onMounted, nextTick, reactive} from 'vue';
 import QRCode from 'qrcodejs2-fixes';
 
+const props = defineProps({
+	tenantInfo: {},
+});
+
+const state = reactive({
+	tenantId: props.tenantInfo.id,
+});
+
 // 定义变量内容
 const qrcodeRef = ref<HTMLElement | null>(null);
 

+ 23 - 5
Web/src/views/login/index.vue

@@ -30,14 +30,14 @@
 						<div v-if="!state.isScan">
 							<el-tabs v-model="state.tabsActiveName">
 								<el-tab-pane :label="$t('message.label.one1')" name="account">
-									<Account />
+									<Account :tenant-info="state.tenantInfo" />
 								</el-tab-pane>
 								<el-tab-pane :label="$t('message.label.two2')" name="mobile">
-									<Mobile />
+									<Mobile :tenant-info="state.tenantInfo" />
 								</el-tab-pane>
 							</el-tabs>
 						</div>
-						<Scan v-if="state.isScan" />
+						<Scan v-if="state.isScan" :tenant-info="state.tenantInfo" />
 						<div class="login-content-main-scan" @click="state.isScan = !state.isScan">
 							<i class="iconfont" :class="state.isScan ? 'icon-diannao1' : 'icon-barcode-qr'"></i>
 							<div class="login-content-main-scan-delta"></div>
@@ -58,20 +58,28 @@ import { defineAsyncComponent, onMounted, reactive, computed } from 'vue';
 import { storeToRefs } from 'pinia';
 import { useThemeConfig } from '/@/stores/themeConfig';
 import { NextLoading } from '/@/utils/loading';
-// import logoMini from '/@/assets/logo-mini.svg';
 import loginIconTwo from '/@/assets/login-icon-two.svg';
 import loginIconTwo1 from '/@/assets/login-icon-two1.svg';
 import loginIconTwo2 from '/@/assets/login-icon-two2.svg';
+import {getAPI} from "/@/utils/axios-utils";
+import {SysTenantApi} from "/@/api-services";
+import {useRoute} from "vue-router";
 
 // 引入组件
 const Account = defineAsyncComponent(() => import('/@/views/login/component/account.vue'));
 const Mobile = defineAsyncComponent(() => import('/@/views/login/component/mobile.vue'));
 const Scan = defineAsyncComponent(() => import('/@/views/login/component/scan.vue'));
 
+const route = useRoute();
 const storesThemeConfig = useThemeConfig();
 const { themeConfig } = storeToRefs(storesThemeConfig);
 const state = reactive({
 	tabsActiveName: 'account',
+	tenantInfo: {
+		host: location.hostname.toLowerCase(),
+		id: undefined,
+		list: [],
+	},
 	isScan: false,
 });
 // 获取布局配置信息
@@ -81,7 +89,17 @@ const getThemeConfig = computed(() => {
 // 页面加载时
 onMounted(() => {
 	NextLoading.done();
+	getTenantInfo();
 });
+
+// 处理租户信息
+const getTenantInfo = async () => {
+	const list = await getAPI(SysTenantApi).apiSysTenantListGet().then(res => res.data.result ?? []);
+	const tenant = list.find((item) => item.host === state.host);
+	state.tenantInfo.id = parseInt(route.query.t ?? (tenant ?? list[0])?.value);
+	state.tenantInfo.list = list;
+	return state.tenantInfo;
+}
 </script>
 
 <style scoped lang="scss">
@@ -147,7 +165,7 @@ onMounted(() => {
 		.login-right-warp {
 			border: 1px solid var(--el-color-primary-light-3);
 			border-radius: 3px;
-			height: 550px;
+			height: 600px;
 			position: relative;
 			overflow: hidden;
 			background-color: var(--el-color-white);

+ 0 - 198
Web/src/views/system/app/component/editApp.vue

@@ -1,198 +0,0 @@
-<template>
-	<div class="sys-app-container">
-		<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false">
-			<template #header>
-				<div style="color: #fff">
-					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Edit /> </el-icon>
-					<span>{{ props.title }}</span>
-				</div>
-			</template>
-			<el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto">
-				<el-row :gutter="35">
-					<el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" class="mb20">
-						<el-form-item label="名称" prop="name" :rules="[{ required: true, message: '名称不能为空', trigger: 'blur' }]">
-							<el-input v-model="state.ruleForm.name" maxlength="32" placeholder="应用名称" show-word-limit clearable />
-						</el-form-item>
-					</el-col>
-          <el-col :xs="24" :sm="14" :md="14" :lg="14" :xl="14" class="mb20">
-            <el-form-item label="Logo" prop="logo" :rules="[{ required: true, message: '应用Logo不能为空', trigger: 'blur' }]">
-              <el-input v-model="state.ruleForm.logo" class="w80" maxlength="256" placeholder="应用Logo" show-word-limit clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :xs="24" :sm="10" :md="10" :lg="10" :xl="10" class="mb20">
-            <el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题不能为空', trigger: 'blur' }]">
-              <el-input v-model="state.ruleForm.title" placeholder="应用标题" maxlength="32" show-word-limit clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :xs="24" :sm="14" :md="14" :lg="14" :xl="14" class="mb20">
-            <el-form-item label="副标题" prop="viceTitle" :rules="[{ required: true, message: '副标题不能为空', trigger: 'blur' }]">
-              <el-input v-model="state.ruleForm.viceTitle" placeholder="应用副标题" maxlength="32" show-word-limit clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-            <el-form-item label="副标题描述" prop="viceDesc" :rules="[{ required: true, message: '副标题描述不能为空', trigger: 'blur' }]">
-              <el-input v-model="state.ruleForm.viceDesc" placeholder="应用副标题描述" maxlength="64" show-word-limit clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24" class="mb20">
-            <el-form-item label="版权信息" prop="copyright" :rules="[{ required: true, message: '版权信息不能为空', trigger: 'blur' }]">
-              <el-input v-model="state.ruleForm.copyright" placeholder="版权信息" maxlength="64" show-word-limit clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :xs="24" :sm="9" :md="9" :lg="9" :xl="9" class="mb20">
-            <el-form-item label="备案号" prop="icp" :rules="[{ required: true, message: '备案号不能为空', trigger: 'blur' }]">
-              <el-input v-model="state.ruleForm.icp" placeholder="备案号" maxlength="32" show-word-limit clearable />
-            </el-form-item>
-          </el-col>
-          <el-col :xs="24" :sm="9" :md="9" :lg="9" :xl="9" class="mb20">
-            <el-form-item label="水印" prop="watermark" :rules="[{ required: true, message: '水印不能为空', trigger: 'blur' }]">
-              <el-input v-model="state.ruleForm.watermark" placeholder="应用水印" maxlength="32" show-word-limit clearable />
-            </el-form-item>
-          </el-col>
-					<el-col :xs="24" :sm="6" :md="6" :lg="6" :xl="6" class="mb20">
-						<el-form-item label="排序">
-							<el-input-number v-model="state.ruleForm.orderNo" placeholder="排序" class="w100" />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="备注">
-							<el-input v-model="state.ruleForm.remark" placeholder="请输入备注内容" show-word-limit clearable type="textarea" />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20" v-auth="'sysApp:grantMenu'">
-						<el-form-item label="菜单权限" v-loading="state.loading">
-							<el-tree
-								ref="treeRef"
-								:data="state.menuData"
-								node-key="id"
-								show-checkbox
-								:props="{ children: 'children', label: 'title', class: treeNodeClass }"
-								icon="ele-Menu"
-								highlight-current
-								default-expand-all
-								style="height: 600px;overflow-y: auto;">
-                <template #default="{ node, data }">
-                  <span>{{ node.label }}</span>
-                  <span v-if="data.path" style="margin-left: 5px!important;">
-                    <el-tag effect="plain" type="warning">{{data.path}}</el-tag>
-                  </span>
-                </template>
-              </el-tree>
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="cancel">取 消</el-button>
-					<el-button type="primary" @click="submit">确 定</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script lang="ts" setup name="sysEditApp">
-import { reactive, ref } from 'vue';
-import type { ElTree } from 'element-plus';
-import { auth } from "/@/utils/authFunction";
-import { getAPI } from '/@/utils/axios-utils';
-import { SysMenu } from '/@/api-services/models';
-import { SysMenuApi, SysAppApi } from '/@/api-services/api';
-
-const props = defineProps({
-	title: String,
-});
-const emits = defineEmits(['handleQuery']);
-const ruleFormRef = ref();
-const treeRef = ref<InstanceType<typeof ElTree>>();
-const state = reactive({
-	loading: false,
-	isShowDialog: false,
-	ruleForm: {} as any,
-	menuData: [] as Array<SysMenu>, // 菜单数据
-});
-
-// 打开弹窗
-const openDialog = async (row: any) => {
-	ruleFormRef.value?.resetFields();
-	treeRef.value?.setCheckedKeys([]); // 清空选中值
-	state.ruleForm = JSON.parse(JSON.stringify(row));
-	if (row.id && auth('sysApp:grantMenu')) {
-    state.menuData = await getAPI(SysMenuApi).apiSysMenuListGet(undefined, undefined, true).then(res => res.data.result ?? []);
-    const menuIds = await getAPI(SysAppApi).apiSysAppGrantMenuGet(row.id).then(res => res.data.result ?? []);
-    setTimeout(() => treeRef.value?.setCheckedKeys(menuIds), 100);
-	}
-	state.isShowDialog = true;
-};
-
-// 关闭弹窗
-const closeDialog = () => {
-	emits('handleQuery');
-	state.isShowDialog = false;
-};
-
-// 取消
-const cancel = () => {
-	state.isShowDialog = false;
-};
-
-// 提交
-const submit = () => {
-	ruleFormRef.value.validate(async (valid: boolean) => {
-		if (!valid) return;
-		const menuIdList = treeRef.value?.getCheckedKeys() as Array<number> ?? []; //.concat(treeRef.value?.getHalfCheckedKeys());
-		if (state.ruleForm.id != undefined && state.ruleForm.id > 0) {
-			await getAPI(SysAppApi).apiSysAppUpdatePost(state.ruleForm);
-		} else {
-      state.ruleForm.id = await getAPI(SysAppApi).apiSysAppAddPost(state.ruleForm).then(res => res.data.result);
-		}
-    if (state.ruleForm.id && auth('sysApp:grantMenu')) {
-      await getAPI(SysAppApi).apiSysAppGrantMenuPost({ id: state.ruleForm.id, menuIdList: menuIdList });
-    }
-		closeDialog();
-	});
-};
-
-// 叶子节点同行显示样式
-const treeNodeClass = (node: SysMenu) => {
-	let addClass = true; // 添加叶子节点同行显示样式
-	for (const index in node.children) {
-		// 如果存在子节点非叶子节点,不添加样式
-		if (node.children[index].children?.length ?? 0 > 0) {
-			addClass = false;
-			break;
-		}
-	}
-	return addClass ? 'penultimate-node' : '';
-};
-
-// 导出对象
-defineExpose({ openDialog });
-</script>
-
-<style lang="scss" scoped>
-.menu-data-tree {
-	width: 100%;
-	border: 1px solid var(--el-border-color);
-	border-radius: var(--el-input-border-radius, var(--el-border-radius-base));
-	padding: 5px;
-}
-
-:deep(.penultimate-node) {
-	.el-tree-node__children {
-		padding-left: 40px;
-		white-space: pre-wrap;
-		line-height: 100%;
-
-		.el-tree-node {
-			display: inline-block;
-		}
-
-		.el-tree-node__content {
-			padding-left: 5px !important;
-			padding-right: 5px;
-		}
-	}
-}
-</style>

+ 0 - 147
Web/src/views/system/app/index.vue

@@ -1,147 +0,0 @@
-<template>
-	<div class="sys-app-container">
-		<el-card shadow="hover" :body-style="{ paddingBottom: '0' }">
-			<el-form :model="state.queryParams" ref="queryForm" :inline="true">
-				<el-form-item label="关键字">
-					<el-input v-model="state.queryParams.keyword" placeholder="关键字" clearable />
-				</el-form-item>
-				<el-form-item>
-					<el-button-group>
-						<el-button type="primary" icon="ele-Search" @click="handleQuery" v-auth="'sysApp:page'"> 查询 </el-button>
-						<el-button icon="ele-Refresh" @click="resetQuery"> 重置 </el-button>
-					</el-button-group>
-				</el-form-item>
-				<el-form-item>
-					<el-button type="primary" icon="ele-Plus" @click="openAddApp" v-auth="'sysApp:add'"> 新增 </el-button>
-				</el-form-item>
-			</el-form>
-		</el-card>
-
-		<el-card class="full-table" shadow="hover" style="margin-top: 5px">
-			<el-table :data="state.appData" style="width: 100%" v-loading="state.loading" border>
-				<el-table-column type="index" label="序号" width="55" align="center" fixed />
-        <el-table-column prop="logo" label="图标" width="55" align="center" show-overflow-tooltip>
-          <template #default="scope">
-            <el-avatar shape="square" :src="scope.row.logo" size="small" />
-          </template>
-        </el-table-column>
-				<el-table-column prop="name" label="名称" width="180" align="center" show-overflow-tooltip />
-				<el-table-column prop="title" label="标题" width="180" align="center" show-overflow-tooltip />
-				<el-table-column prop="viceTitle" label="副标题" width="180" align="center" show-overflow-tooltip />
-				<el-table-column prop="viceDesc" label="描述" width="300" align="center" show-overflow-tooltip />
-				<el-table-column prop="watermark" label="水印" width="130" align="center" show-overflow-tooltip />
-				<el-table-column prop="copyright" label="版权信息" width="350" align="center" show-overflow-tooltip />
-				<el-table-column prop="icp" label="备案号" width="130" align="center" show-overflow-tooltip />
-				<el-table-column prop="orderNo" label="排序" width="70" align="center" show-overflow-tooltip />
-				<el-table-column label="修改记录" width="100" align="center" show-overflow-tooltip>
-					<template #default="scope">
-						<ModifyRecord :data="scope.row" />
-					</template>
-				</el-table-column>
-				<el-table-column label="操作" width="130" fixed="right" align="center" show-overflow-tooltip>
-					<template #default="scope">
-						<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditApp(scope.row)" v-auth="'sysApp:update'"> 编辑 </el-button>
-						<el-button icon="ele-Delete" size="small" text type="danger" @click="delApp(scope.row)" v-auth="'sysApp:delete'"> 删除 </el-button>
-					</template>
-				</el-table-column>
-			</el-table>
-			<el-pagination
-				v-model:currentPage="state.tableParams.page"
-				v-model:page-size="state.tableParams.pageSize"
-				:total="state.tableParams.total"
-				:page-sizes="[10, 20, 50, 100]"
-				size="small"
-				background
-				@size-change="handleSizeChange"
-				@current-change="handleCurrentChange"
-				layout="total, sizes, prev, pager, next, jumper"
-			/>
-		</el-card>
-
-		<EditApp ref="editAppRef" :title="state.editAppTitle" @handleQuery="handleQuery" />
-	</div>
-</template>
-
-<script lang="ts" setup name="sysApp">
-import { onMounted, reactive, ref } from 'vue';
-import { ElMessageBox, ElMessage } from 'element-plus';
-import EditApp from '/@/views/system/app/component/editApp.vue';
-import ModifyRecord from '/@/components/table/modifyRecord.vue';
-
-import { getAPI } from '/@/utils/axios-utils';
-import { SysAppApi } from '/@/api-services/api';
-
-const editAppRef = ref<InstanceType<typeof EditApp>>();
-const state = reactive({
-	loading: false,
-	appData: [] as any[],
-	queryParams: {
-		keyword: undefined,
-	},
-	tableParams: {
-		page: 1,
-		pageSize: 50,
-		total: 0 as any,
-	},
-	editAppTitle: '',
-});
-
-onMounted(async () => {
-	await handleQuery();
-});
-
-// 查询操作
-const handleQuery = async () => {
-	state.loading = true;
-	let params = Object.assign(state.queryParams, state.tableParams);
-	let res = await getAPI(SysAppApi).apiSysAppPagePost(params);
-	state.appData = res.data.result?.items ?? [];
-	state.tableParams.total = res.data.result?.total;
-	state.loading = false;
-};
-
-// 重置操作
-const resetQuery = async () => {
-	state.queryParams.keyword = undefined;
-	await handleQuery();
-};
-
-// 打开新增页面
-const openAddApp = () => {
-	state.editAppTitle = '添加应用';
-	editAppRef.value?.openDialog({ id: undefined, status: 1, orderNo: 100 });
-};
-
-// 打开编辑页面
-const openEditApp = async (row: any) => {
-	state.editAppTitle = '编辑应用';
-	editAppRef.value?.openDialog(row);
-};
-
-// 删除
-const delApp = (row: any) => {
-	ElMessageBox.confirm(`确定删除应用:【${row.name}】?`, '提示', {
-		confirmButtonText: '确定',
-		cancelButtonText: '取消',
-		type: 'warning',
-	})
-		.then(async () => {
-			await getAPI(SysAppApi).apiSysAppDeletePost({ id: row.id });
-			await handleQuery();
-			ElMessage.success('删除成功');
-		})
-		.catch(() => {});
-};
-
-// 改变页面容量
-const handleSizeChange = async (val: number) => {
-	state.tableParams.pageSize = val;
-	await handleQuery();
-};
-
-// 改变页码序号
-const handleCurrentChange = async (val: number) => {
-	state.tableParams.page = val;
-	await handleQuery();
-};
-</script>

+ 4 - 4
Web/src/views/system/infoSetting/index.vue

@@ -39,14 +39,14 @@
 				</el-descriptions-item>
 				<el-descriptions-item label="登录二次验证">
 					<el-radio-group v-model="state.formData.sysSecondVer">
-						<el-radio :value="true">启用</el-radio>
-						<el-radio :value="false">禁用</el-radio>
+						<el-radio :value="1">启用</el-radio>
+						<el-radio :value="2">禁用</el-radio>
 					</el-radio-group>
 				</el-descriptions-item>
 				<el-descriptions-item label="图形验证码">
 					<el-radio-group v-model="state.formData.sysCaptcha">
-						<el-radio :value="true">启用</el-radio>
-						<el-radio :value="false">禁用</el-radio>
+						<el-radio :value="1">启用</el-radio>
+						<el-radio :value="2">禁用</el-radio>
 					</el-radio-group>
 				</el-descriptions-item>
 

+ 19 - 3
Web/src/views/system/menu/index.vue

@@ -2,6 +2,11 @@
 	<div class="sys-menu-container">
 		<el-card shadow="hover" :body-style="{ paddingBottom: '0' }">
 			<el-form :model="state.queryParams" ref="queryForm" :inline="true">
+				<el-form-item label="租户" v-if="userStore.userInfos.accountType == 999">
+					<el-select v-model="state.queryParams.tenantId" placeholder="租户" style="width: 100%">
+						<el-option :value="item.value" :label="`${item.label} (${item.host})`" v-for="(item, index) in state.tenantList" :key="index" />
+					</el-select>
+				</el-form-item>
 				<el-form-item label="菜单名称">
 					<el-input v-model="state.queryParams.title" placeholder="菜单名称" clearable />
 				</el-form-item>
@@ -68,14 +73,18 @@ import EditMenu from '/@/views/system/menu/component/editMenu.vue';
 import ModifyRecord from '/@/components/table/modifyRecord.vue';
 
 import { getAPI } from '/@/utils/axios-utils';
-import { SysMenuApi } from '/@/api-services/api';
+import {SysMenuApi, SysTenantApi} from '/@/api-services/api';
 import { SysMenu, UpdateMenuInput } from '/@/api-services/models';
+import {useUserInfo} from "/@/stores/userInfo";
 
+const userStore = useUserInfo();
 const editMenuRef = ref<InstanceType<typeof EditMenu>>();
 const state = reactive({
 	loading: false,
+	tenantList: [] as Array<any>,
 	menuData: [] as Array<SysMenu>,
 	queryParams: {
+		tenantId: undefined,
 		title: undefined,
 		type: undefined,
 	},
@@ -83,13 +92,17 @@ const state = reactive({
 });
 
 onMounted(async () => {
+	if (userStore.userInfos.accountType == 999) {
+		state.tenantList = await getAPI(SysTenantApi).apiSysTenantListGet().then(res => res.data.result ?? []);
+		state.queryParams.tenantId = state.tenantList[0].value;
+	}
 	handleQuery();
 });
 
 // 查询操作
 const handleQuery = async () => {
 	state.loading = true;
-	var res = await getAPI(SysMenuApi).apiSysMenuListGet(state.queryParams.title, state.queryParams.type);
+	var res = await getAPI(SysMenuApi).apiSysMenuListGet(state.queryParams.title, state.queryParams.type, state.queryParams.tenantId);
 	state.menuData = res.data.result ?? [];
 	state.loading = false;
 };
@@ -103,13 +116,16 @@ const resetQuery = () => {
 
 // 打开新增页面
 const openAddMenu = () => {
+	const data = { type: 2, isHide: false, isKeepAlive: true, isAffix: false, isIframe: false, tenantId: undefined, status: 1, orderNo: 100 };
+	if (userStore.userInfos.accountType == 999) data.tenantId = state.queryParams.tenantId;
 	state.editMenuTitle = '添加菜单';
-	editMenuRef.value?.openDialog({ type: 2, isHide: false, isKeepAlive: true, isAffix: false, isIframe: false, status: 1, orderNo: 100 });
+	editMenuRef.value?.openDialog(data);
 };
 
 // 打开编辑页面
 const openEditMenu = (row: any) => {
 	state.editMenuTitle = '编辑菜单';
+	if (userStore.userInfos.accountType == 999) row.tenantId = state.queryParams.tenantId;
 	editMenuRef.value?.openDialog(row);
 };
 

+ 167 - 105
Web/src/views/system/tenant/component/editTenant.vue

@@ -8,107 +8,146 @@
 				</div>
 			</template>
 			<el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto">
-				<el-row :gutter="35">
-          <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-            <el-form-item label="应用" prop="appId" :rules="[{ required: true, message: '应用不能为空', trigger: 'blur' }]">
-              <el-select v-model="state.ruleForm.appId" :disabled="(state.ruleForm?.id ?? 0) > 0" value-key="appId" placeholder="应用" class="w100" clearable>
-                <el-option v-for="item in state.appList" :key="item.id" :label="item.label" :value="item.id" />
-              </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="租户类型" :rules="[{ required: true, message: '租户类型不能为空', trigger: 'blur' }]">
-              <g-sys-dict v-model="state.ruleForm.tenantType" code="TenantTypeEnum" render-as="radio" :disabled="state.ruleForm.id != undefined" />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						<el-form-item label="租户名称" prop="name" :rules="[{ required: true, message: '租户名称不能为空', trigger: 'blur' }]">
-							<el-input v-model="state.ruleForm.name" placeholder="租户名称" clearable />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						<el-form-item label="租管账号" prop="adminAccount" :rules="[{ required: true, message: '租管账号不能为空', trigger: 'blur' }]">
-							<el-input v-model="state.ruleForm.adminAccount" placeholder="租管账号" clearable />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						<el-form-item label="电话" prop="phone" :rules="[{ required: true, message: '电话号码不能为空', trigger: 'blur' }]">
-							<el-input v-model="state.ruleForm.phone" placeholder="电话" clearable />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						<el-form-item label="邮箱">
-							<el-input v-model="state.ruleForm.email" placeholder="邮箱" clearable />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						<el-form-item label="排序">
-							<el-input-number v-model="state.ruleForm.orderNo" placeholder="排序" class="w100" />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="绑定域名" :rules="[{ required: true, message: '绑定域名不能为空', trigger: 'blur' }]">
-							<el-input v-model="state.ruleForm.host" placeholder="例:gitee.com" clearable />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						<el-form-item label="数据库类型">
-							<el-select v-model="state.ruleForm.dbType" placeholder="数据库类型" clearable class="w100" :disabled="state.ruleForm.tenantType == 0 && state.ruleForm.tenantType != undefined">
-								<el-option label="MySql" :value="0" />
-								<el-option label="SqlServer" :value="1" />
-								<el-option label="Sqlite" :value="2" />
-								<el-option label="Oracle" :value="3" />
-								<el-option label="PostgreSQL" :value="4" />
-								<el-option label="Dm" :value="5" />
-								<el-option label="Kdbndp" :value="6" />
-								<el-option label="Oscar" :value="7" />
-								<el-option label="MySqlConnector" :value="8" />
-								<el-option label="Access" :value="9" />
-								<el-option label="OpenGauss" :value="10" />
-								<el-option label="QuestDB" :value="11" />
-								<el-option label="HG" :value="12" />
-								<el-option label="ClickHouse" :value="13" />
-								<el-option label="GBase" :value="14" />
-								<el-option label="Odbc" :value="'15'" />
-								<el-option label="OceanBaseForOracle" :value="'16'" />
-								<el-option label="TDengine" :value="'17'" />
-								<el-option label="GaussDB" :value="'18'" />
-								<el-option label="OceanBase" :value="'19'" />
-								<el-option label="Tidb" :value="'20'" />
-								<el-option label="Vastbase" :value="'21'" />
-								<el-option label="PolarDB" :value="'22'" />
-								<el-option label="Doris" :value="'23'" />
-								<el-option label="Custom" :value="900" />
-							</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="数据库标识">
-							<el-input v-model="ruleForm.configId" placeholder="数据库标识" clearable :disabled="ruleForm.tenantType == 0 && ruleForm.tenantType != undefined" />
-						</el-form-item>
-					</el-col> -->
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="连接字符串">
-							<el-input v-model="state.ruleForm.connection" placeholder="连接字符串" clearable type="textarea" :disabled="state.ruleForm.tenantType == 0 && state.ruleForm.tenantType != undefined" />
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="从库连接串">
-							<el-input
-								v-model="state.ruleForm.slaveConnections"
-								placeholder="格式:[{'HitRate':10, 'ConnectionString':'xxx'},{'HitRate':10, 'ConnectionString':'xxx'}]"
-								clearable
-								type="textarea"
-								:disabled="state.ruleForm.tenantType == 0 && state.ruleForm.tenantType != undefined"
-							/>
-						</el-form-item>
-					</el-col>
-					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="备注">
-							<el-input v-model="state.ruleForm.remark" placeholder="请输入备注内容" clearable type="textarea" />
-						</el-form-item>
-					</el-col>
-				</el-row>
+				<el-tabs v-loading="state.loading" v-model="state.selectedTabName">
+					<el-tab-pane label="基本信息" style="height: 550px; overflow-y: auto; overflow-x: hidden">
+						<el-row :gutter="35">
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="租户类型" :rules="[{ required: true, message: '租户类型不能为空', trigger: 'blur' }]">
+									<g-sys-dict v-model="state.ruleForm.tenantType" code="TenantTypeEnum" render-as="radio" :disabled="state.ruleForm.id != undefined" />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="租户名称" prop="name" :rules="[{ required: true, message: '租户名称不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.name" placeholder="租户名称" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="租管账号" prop="adminAccount" :rules="[{ required: true, message: '租管账号不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.adminAccount" placeholder="租管账号" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="电话" prop="phone" :rules="[{ required: true, message: '电话号码不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.phone" placeholder="电话" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="数据库类型">
+									<el-select v-model="state.ruleForm.dbType" placeholder="数据库类型" clearable class="w100" :disabled="state.ruleForm.tenantType == 0 && state.ruleForm.tenantType != undefined">
+										<el-option label="MySql" :value="0" />
+										<el-option label="SqlServer" :value="1" />
+										<el-option label="Sqlite" :value="2" />
+										<el-option label="Oracle" :value="3" />
+										<el-option label="PostgreSQL" :value="4" />
+										<el-option label="Dm" :value="5" />
+										<el-option label="Kdbndp" :value="6" />
+										<el-option label="Oscar" :value="7" />
+										<el-option label="MySqlConnector" :value="8" />
+										<el-option label="Access" :value="9" />
+										<el-option label="OpenGauss" :value="10" />
+										<el-option label="QuestDB" :value="11" />
+										<el-option label="HG" :value="12" />
+										<el-option label="ClickHouse" :value="13" />
+										<el-option label="GBase" :value="14" />
+										<el-option label="Odbc" :value="'15'" />
+										<el-option label="OceanBaseForOracle" :value="'16'" />
+										<el-option label="TDengine" :value="'17'" />
+										<el-option label="GaussDB" :value="'18'" />
+										<el-option label="OceanBase" :value="'19'" />
+										<el-option label="Tidb" :value="'20'" />
+										<el-option label="Vastbase" :value="'21'" />
+										<el-option label="PolarDB" :value="'22'" />
+										<el-option label="Doris" :value="'23'" />
+										<el-option label="Custom" :value="900" />
+									</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="绑定域名" :rules="[{ required: true, message: '绑定域名不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.host" placeholder="例:gitee.com" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="连接字符串">
+									<el-input v-model="state.ruleForm.connection" placeholder="连接字符串" clearable type="textarea" :disabled="state.ruleForm.tenantType == 0 && state.ruleForm.tenantType != undefined" />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="从库连接串">
+									<el-input
+											v-model="state.ruleForm.slaveConnections"
+											placeholder="格式:[{'HitRate':10, 'ConnectionString':'xxx'},{'HitRate':10, 'ConnectionString':'xxx'}]"
+											clearable
+											type="textarea"
+											:disabled="state.ruleForm.tenantType == 0 && state.ruleForm.tenantType != undefined"
+									/>
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="邮箱">
+									<el-input v-model="state.ruleForm.email" placeholder="邮箱" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="排序">
+									<el-input-number v-model="state.ruleForm.orderNo" placeholder="排序" class="w100" />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="备注">
+									<el-input v-model="state.ruleForm.remark" placeholder="请输入备注内容" clearable type="textarea" />
+								</el-form-item>
+							</el-col>
+						</el-row>
+					</el-tab-pane>
+					<el-tab-pane label="站点信息" style="height: 550px; overflow: auto; overflow-x: hidden;">
+						<el-row :gutter="35">
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="Logo" prop="logo" :rules="[{ required: true, message: '应用Logo不能为空', trigger: 'blur' }]">
+									<el-upload ref="uploadRef" class="avatar-uploader" :showFileList="false" :autoUpload="false" accept=".jpg,.png,.svg" action :limit="1" :onChange="handleUploadChange">
+										<img v-if="state.ruleForm.logo" :src="state.ruleForm.logo" class="avatar" />
+										<SvgIcon v-else class="avatar-uploader-icon" name="ele-Plus" :size="28" />
+									</el-upload>
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="标题" prop="title" :rules="[{ required: true, message: '标题不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.title" placeholder="应用标题" maxlength="32" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="副标题" prop="viceTitle" :rules="[{ required: true, message: '副标题不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.viceTitle" placeholder="应用副标题" maxlength="32" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="副标题描述" prop="viceDesc" :rules="[{ required: true, message: '副标题描述不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.viceDesc" placeholder="应用副标题描述" maxlength="64" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="24" :lg="24" :xl="24" class="mb20">
+								<el-form-item label="版权信息" prop="copyright" :rules="[{ required: true, message: '版权信息不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.copyright" placeholder="版权信息" maxlength="64" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="备案号" prop="icp" :rules="[{ required: true, message: '备案号不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.icp" placeholder="备案号" maxlength="32" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="ICP地址" prop="icpUrl" :rules="[{ required: true, message: 'ICP地址不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.icpUrl" placeholder="ICP地址" maxlength="32" clearable />
+								</el-form-item>
+							</el-col>
+							<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+								<el-form-item label="水印" prop="watermark" :rules="[{ required: true, message: '水印不能为空', trigger: 'blur' }]">
+									<el-input v-model="state.ruleForm.watermark" placeholder="应用水印" maxlength="32" clearable />
+								</el-form-item>
+							</el-col>
+						</el-row>
+					</el-tab-pane>
+				</el-tabs>
 			</el-form>
 			<template #footer>
 				<span class="dialog-footer">
@@ -123,24 +162,40 @@
 <script lang="ts" setup name="sysEditTenant">
 import { reactive, ref } from 'vue';
 import { getAPI } from '/@/utils/axios-utils';
-import {SysAppApi, SysTenantApi} from '/@/api-services/api';
+import { SysTenantApi } from '/@/api-services/api';
 import { UpdateTenantInput } from '/@/api-services/models';
+import {UploadInstance} from "element-plus";
+import {fileToBase64} from "/@/utils/base64Conver";
 
 const props = defineProps({
 	title: String,
 });
 const emits = defineEmits(['handleQuery']);
+const uploadRef = ref<UploadInstance>();
 const ruleFormRef = ref();
 const state = reactive({
+	loading: false,
+	selectedTabName: '0',
 	isShowDialog: false,
-  appList: [] as Array<any>,
+	file: undefined as any,
 	ruleForm: {} as UpdateTenantInput,
 });
 
+// 通过onChange方法获得文件列表
+const handleUploadChange = (file: any) => {
+	uploadRef.value!.clearFiles();
+
+	state.file = file;
+	state.ruleForm.logo = URL.createObjectURL(state.file.raw); // 显示预览logo
+};
+
 // 打开弹窗
 const openDialog = async (row: any) => {
-  if (state.appList.length == 0) state.appList = await getAPI(SysAppApi).apiSysAppChangeAppGet().then(res => res.data.result?.selectList ?? []);
+	state.selectedTabName = '0';
 	state.ruleForm = JSON.parse(JSON.stringify(row));
+	state.ruleForm.icp ??= '省ICP备12345678号';
+	state.ruleForm.icpUrl ??= 'https://beian.miit.gov.cn';
+	state.ruleForm.copyright ??= 'Copyright \u00a9 2024-present xxxxx All rights reserved.';
 	state.isShowDialog = true;
 	ruleFormRef.value?.resetFields();
 };
@@ -157,7 +212,14 @@ const cancel = () => {
 };
 
 // 提交
-const submit = () => {
+const submit = async () => {
+	// 如果有选择图标,则转换为 base64
+	let sysLogoBase64 = '';
+	let sysLogoFileName = '';
+	if (state.file) {
+		state.ruleForm.logoBase64 = (await fileToBase64(state.file.raw)) as string;
+		state.ruleForm.logoFileName = state.file.raw.name;
+	}
 	ruleFormRef.value.validate(async (valid: boolean) => {
 		if (!valid) return;
 		if (state.ruleForm.id != undefined && state.ruleForm.id > 0) {

+ 11 - 5
Web/src/views/system/tenant/component/grantMenu.vue

@@ -19,8 +19,14 @@
 								:props="{ children: 'children', label: 'title', class: treeNodeClass }"
 								icon="ele-Menu"
 								highlight-current
-								default-expand-all
-							/>
+								default-expand-all>
+								<template #default="{ node, data }">
+									<span>{{ node.label }}</span>
+									<span v-if="data.path" style="margin-left: 5px!important;">
+                    <el-tag effect="plain" type="warning">{{data.path}}</el-tag>
+                  </span>
+								</template>
+							</el-tree>
 						</el-form-item>
 					</el-col>
 				</el-row>
@@ -59,11 +65,11 @@ const state = reactive({
 const openDialog = async (row: any) => {
 	treeRef.value?.setCheckedKeys([]); // 先清空已选择节点
 	state.ruleForm = row;
-  state.menuData = await getAPI(SysMenuApi).apiSysMenuListGet(undefined,undefined, undefined, state.ruleForm.appId).then(res => res.data.result);
-	const res = await getAPI(SysTenantApi).apiSysTenantOwnMenuListGet(row.userId);
+  state.menuData = await getAPI(SysMenuApi).apiSysMenuListGet().then(res => res.data.result);
+	const menuIds = await getAPI(SysTenantApi).apiSysTenantTenantMenuListGet(row.id).then(res => res.data.result);
 	setTimeout(() => {
 		// 延迟传递数据
-		treeRef.value?.setCheckedKeys(res.data.result);
+		treeRef.value?.setCheckedKeys(menuIds ?? []);
 	}, 100);
 	state.isShowDialog = true;
 };

+ 14 - 3
Web/src/views/system/tenant/index.vue

@@ -25,8 +25,12 @@
 		<el-card class="full-table" shadow="hover" style="margin-top: 5px">
 			<el-table :data="state.tenantData" style="width: 100%" v-loading="state.loading" border>
 				<el-table-column type="index" label="序号" width="55" align="center" fixed />
-				<el-table-column prop="name" label="租户名称" width="160" align="center" show-overflow-tooltip />
-				<el-table-column prop="appName" label="关联应用" width="160" align="center" show-overflow-tooltip />
+				<el-table-column prop="logo" label="图标" width="55" align="center" show-overflow-tooltip>
+					<template #default="scope">
+						<el-avatar shape="square" :src="scope.row.logo" size="small" />
+					</template>
+				</el-table-column>
+				<el-table-column prop="name" label="名称" width="180" align="center" show-overflow-tooltip />
 				<el-table-column prop="adminAccount" label="租管账号" align="center" width="120" show-overflow-tooltip />
 				<el-table-column prop="phone" label="电话" width="120" align="center" show-overflow-tooltip />
 				<el-table-column prop="host" label="域名" width="150" show-overflow-tooltip />
@@ -76,7 +80,14 @@
 					show-overflow-tooltip />
 				<el-table-column prop="slaveConnections" label="从库连接" min-width="300" header-align="center"
 					show-overflow-tooltip />
-				<el-table-column prop="orderNo" label="排序" width="70" align="center" show-overflow-tooltip />
+				<el-table-column prop="title" label="标题" width="180" show-overflow-tooltip />
+				<el-table-column prop="viceTitle" label="副标题" width="180" show-overflow-tooltip />
+				<el-table-column prop="viceDesc" label="描述" width="300" show-overflow-tooltip />
+				<el-table-column prop="watermark" label="水印" width="130" show-overflow-tooltip />
+				<el-table-column prop="copyright" label="版权信息" width="350" show-overflow-tooltip />
+				<el-table-column prop="icp" label="备案号" width="130" show-overflow-tooltip />
+				<el-table-column prop="icpUrl" label="icp地址" width="280" show-overflow-tooltip />
+				<el-table-column prop="orderNo" label="排序" width="70" show-overflow-tooltip />
 				<el-table-column label="修改记录" width="100" align="center" show-overflow-tooltip>
 					<template #default="scope">
 						<ModifyRecord :data="scope.row" />