浏览代码

Merge branch 'next' of https://gitee.com/zuohuaijun/Admin.NET into next

SW 2 年之前
父节点
当前提交
b6018f4dd1
共有 36 个文件被更改,包括 359 次插入184 次删除
  1. 1 0
      Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj
  2. 8 7
      Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
  3. 1 11
      Admin.NET/Admin.NET.Core/Const/CacheConst.cs
  4. 15 0
      Admin.NET/Admin.NET.Core/Const/CommonConst.cs
  5. 6 0
      Admin.NET/Admin.NET.Core/Enum/ErrorCodeEnum.cs
  6. 2 2
      Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs
  7. 1 1
      Admin.NET/Admin.NET.Core/Job/LogJob.cs
  8. 1 1
      Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs
  9. 3 2
      Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs
  10. 2 2
      Admin.NET/Admin.NET.Core/Logging/LoggingSetup.cs
  11. 0 1
      Admin.NET/Admin.NET.Core/SeedData/SysDictDataSeedData.cs
  12. 10 3
      Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs
  13. 47 0
      Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs
  14. 9 4
      Admin.NET/Admin.NET.Core/Service/Org/SysOrgService.cs
  15. 5 3
      Admin.NET/Admin.NET.Core/Service/Role/SysRoleService.cs
  16. 27 2
      Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs
  17. 6 1
      Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs
  18. 5 0
      Admin.NET/Admin.NET.Core/Service/User/UserManager.cs
  19. 4 7
      Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs
  20. 9 3
      Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs
  21. 50 15
      Admin.NET/Admin.NET.Core/Util/CommonUtil.cs
  22. 11 7
      Admin.NET/Admin.NET.Core/Util/VerifyFileExtensionName.cs
  23. 0 1
      Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj
  24. 1 0
      Admin.NET/Admin.NET.Web.Core/Startup.cs
  25. 0 1
      Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Service.cs.vm
  26. 1 1
      Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Job/SyncDingTalkUserJob.cs
  27. 1 1
      Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj
  28. 1 1
      Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Service/SuperApiAop.cs
  29. 18 18
      Web/package.json
  30. 二进制
      Web/src/assets/bg.svg
  31. 5 35
      Web/src/components/iconSelector/index.vue
  32. 5 1
      Web/src/utils/request.ts
  33. 75 53
      Web/src/views/login/component/account.vue
  34. 23 0
      Web/src/views/login/index.vue
  35. 5 0
      Web/src/views/system/dict/component/editDictData.vue
  36. 1 0
      Web/src/views/system/dict/index.vue

+ 1 - 0
Admin.NET/Admin.NET.Application/Admin.NET.Application.csproj

@@ -4,6 +4,7 @@
     <TargetFramework>net6.0</TargetFramework>
     <NoWarn>1701;1702;1591;8632</NoWarn>
     <DocumentationFile></DocumentationFile>
+    <ImplicitUsings>enable</ImplicitUsings>
     <GenerateDocumentationFile>True</GenerateDocumentationFile>
     <Nullable>disable</Nullable>
     <Copyright>© Admin.NET</Copyright>

+ 8 - 7
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj

@@ -19,10 +19,10 @@
     <PackageReference Include="AspNet.Security.OAuth.Gitee" Version="6.0.15" />
     <PackageReference Include="AspNet.Security.OAuth.Weixin" Version="6.0.15" />
     <PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
-    <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.13.8" />
-    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.2.25" />
-    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.2.25" />
-    <PackageReference Include="Furion.Pure" Version="4.9.2.25" />
+    <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.13.12" />
+    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.2.36" />
+    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.2.36" />
+    <PackageReference Include="Furion.Pure" Version="4.9.2.36" />
     <PackageReference Include="IPTools.China" Version="1.6.0" />
     <PackageReference Include="IPTools.International" Version="1.6.0" />
     <PackageReference Include="Lazy.Captcha.Core" Version="2.0.6" />
@@ -35,12 +35,13 @@
     <PackageReference Include="NewLife.Redis" Version="5.6.2024.420-beta0005" />
     <PackageReference Include="Novell.Directory.Ldap.NETStandard" Version="3.6.0" />
     <PackageReference Include="OnceMi.AspNetCore.OSS" Version="1.1.9" />
+    <PackageReference Include="QRCoder" Version="1.5.1" />
     <PackageReference Include="RabbitMQ.Client" Version="6.8.1" />
     <PackageReference Include="SixLabors.ImageSharp.Web" Version="3.1.2" />
-    <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.0.0" />
+    <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.1.0" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.2.0" />
-    <PackageReference Include="SqlSugarCore" Version="5.1.4.152" />
-    <PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.12" />
+    <PackageReference Include="SqlSugarCore" Version="5.1.4.154" />
+    <PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.14" />
     <PackageReference Include="UAParser" Version="3.1.47" />
     <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
     <PackageReference Include="Microsoft.AspNetCore.DataProtection.StackExchangeRedis" Version="6.0.29" />

+ 1 - 11
Admin.NET/Admin.NET.Core/Const/CacheConst.cs

@@ -11,16 +11,6 @@ namespace Admin.NET.Core;
 /// </summary>
 public class CacheConst
 {
-    ///// <summary>
-    ///// 用户缓存
-    ///// </summary>
-    //public const string KeyUser = "sys_user:";
-
-    ///// <summary>
-    ///// 用户菜单缓存
-    ///// </summary>
-    //public const string KeyUserMenu = "sys_user_menu:";
-
     /// <summary>
     /// 用户权限缓存(按钮集合)
     /// </summary>
@@ -87,7 +77,7 @@ public class CacheConst
     public const string KeyOpenAccessNonce = "sys_open_access_nonce:";
 
     /// <summary>
-    /// 黑名单
+    /// 登录黑名单
     /// </summary>
     public const string KeyBlacklist = "sys_blacklist:";
 }

+ 15 - 0
Admin.NET/Admin.NET.Core/Const/CommonConst.cs

@@ -76,4 +76,19 @@ public class CommonConst
     /// 开启域登录验证
     /// </summary>
     public const string SysDomainLogin = "sys_domain_login";
+
+    /// <summary>
+    /// 日志分组名称
+    /// </summary>
+    public const string SysLogCategoryName = "System.Logging.LoggingMonitor";
+
+    /// <summary>
+    /// 事件-增加异常日志
+    /// </summary>
+    public const string AddExLog = "Add:ExLog";
+
+    /// <summary>
+    /// 事件-发送异常邮件
+    /// </summary>
+    public const string SendErrorMail = "Send:ErrorMail";
 }

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

@@ -25,6 +25,12 @@ public enum ErrorCodeEnum
     [ErrorCodeItemMetadata("账号不存在")]
     D0009,
 
+    /// <summary>
+    /// 密匙不匹配
+    /// </summary>
+    [ErrorCodeItemMetadata("密匙不匹配")]
+    D0010,
+
     /// <summary>
     /// 密码不正确
     /// </summary>

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

@@ -23,7 +23,7 @@ public class AppEventSubscriber : IEventSubscriber, ISingleton, IDisposable
     /// </summary>
     /// <param name="context"></param>
     /// <returns></returns>
-    [EventSubscribe("Add:ExLog")]
+    [EventSubscribe(CommonConst.AddExLog)]
     public async Task CreateExLog(EventHandlerExecutingContext context)
     {
         var rep = _serviceScope.ServiceProvider.GetRequiredService<SqlSugarRepository<SysLogEx>>();
@@ -35,7 +35,7 @@ public class AppEventSubscriber : IEventSubscriber, ISingleton, IDisposable
     /// </summary>
     /// <param name="context"></param>
     /// <returns></returns>
-    [EventSubscribe("Send:ErrorMail")]
+    [EventSubscribe(CommonConst.SendErrorMail)]
     public async Task SendOrderErrorMail(EventHandlerExecutingContext context)
     {
         //var mailTempPath = Path.Combine(App.WebHostEnvironment.WebRootPath, "Temp\\ErrorMail.tp");

+ 1 - 1
Admin.NET/Admin.NET.Core/Job/LogJob.cs

@@ -19,7 +19,7 @@ public class LogJob : IJob
     public LogJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory)
     {
         _scopeFactory = scopeFactory;
-        _logger = loggerFactory.CreateLogger("System.Logging.LoggingMonitor");
+        _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName);
     }
 
     public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)

+ 1 - 1
Admin.NET/Admin.NET.Core/Job/OnlineUserJob.cs

@@ -21,7 +21,7 @@ public class OnlineUserJob : IJob
     public OnlineUserJob(IServiceScopeFactory scopeFactory, ILoggerFactory loggerFactory)
     {
         _scopeFactory = scopeFactory;
-        _logger = loggerFactory.CreateLogger("System.Logging.LoggingMonitor");
+        _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName);
     }
 
     public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)

+ 3 - 2
Admin.NET/Admin.NET.Core/Logging/DatabaseLoggingWriter.cs

@@ -45,7 +45,8 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter, IDisposable
                 TraceId = logMsg.TraceId,
                 Exception = logMsg.Exception == null ? null : JSON.Serialize(logMsg.Exception),
                 Message = logMsg.Message,
-                LogLevel = logMsg.LogLevel
+                LogLevel = logMsg.LogLevel,
+                Status = "200",
             }).ExecuteCommandAsync();
             return;
         }
@@ -125,7 +126,7 @@ public class DatabaseLoggingWriter : IDatabaseLoggingWriter, IDisposable
                 // 将异常日志发送到邮件
                 if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysErrorMail))
                 {
-                    await App.GetRequiredService<IEventPublisher>().PublishAsync("Send:ErrorMail", loggingMonitor.exception);
+                    await App.GetRequiredService<IEventPublisher>().PublishAsync(CommonConst.SendErrorMail, loggingMonitor.exception);
                 }
 
                 return;

+ 2 - 2
Admin.NET/Admin.NET.Core/Logging/LoggingSetup.cs

@@ -75,7 +75,7 @@ public static class LoggingSetup
                 options.MessageFormat = LoggerFormatter.Json;
                 options.WriteFilter = (logMsg) =>
                 {
-                    return logMsg.LogName == "System.Logging.LoggingMonitor"; // 只写LoggingMonitor日志
+                    return logMsg.LogName == CommonConst.SysLogCategoryName; // 只写LoggingMonitor日志
                 };
             });
         }
@@ -90,7 +90,7 @@ public static class LoggingSetup
                 options.IgnoreReferenceLoop = false; // 忽略循环检测
                 options.WriteFilter = (logMsg) =>
                 {
-                    return logMsg.LogName == "System.Logging.LoggingMonitor"; // 只写LoggingMonitor日志
+                    return logMsg.LogName == CommonConst.SysLogCategoryName; // 只写LoggingMonitor日志
                 };
             });
         }

+ 0 - 1
Admin.NET/Admin.NET.Core/SeedData/SysDictDataSeedData.cs

@@ -65,7 +65,6 @@ public class SysDictDataSeedData : ISqlSugarEntitySeedData<SysDictData>
             new SysDictData{ Id=1300000000504, DictTypeId=1300000000105, Value="EntityTenant【租户实体】", Code="EntityTenant", OrderNo=1, Remark="【租户实体】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             new SysDictData{ Id=1300000000505, DictTypeId=1300000000105, Value="EntityBaseData【业务实体】", Code="EntityBaseData", OrderNo=1, Remark="【业务实体】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
             new SysDictData{ Id=1300000000506, DictTypeId=1300000000105, Value="EntityTenantBaseData【租户业务实体】", Code="EntityTenantBaseData", OrderNo=1, Remark="【租户业务实体】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysDictData{ Id=1300000000507, DictTypeId=1300000000105, Value="无基类", Code="", OrderNo=1, Remark="【无基类】", Status=StatusEnum.Disable, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
 
             new SysDictData{ Id=1300000000601, DictTypeId=1300000000106, Value="不需要", Code="off", OrderNo=100, Remark="不需要打印支持", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-12-04 00:00:00") },
             new SysDictData{ Id=1300000000602, DictTypeId=1300000000106, Value="绑定打印模版", Code="custom", OrderNo=101, Remark="绑定打印模版", Status=StatusEnum.Enable, CreateTime=DateTime.Parse("2023-12-04 00:00:00") },

+ 10 - 3
Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs

@@ -90,7 +90,14 @@ public class SysAuthService : IDynamicApiController, ITransient
             throw Oops.Oh(ErrorCodeEnum.Z1003);
 
         // 国密SM2解密(前端密码传输SM2加密后的)
-        input.Password = CryptogramUtil.SM2Decrypt(input.Password);
+        try
+        {
+            input.Password = CryptogramUtil.SM2Decrypt(input.Password);
+        }
+        catch
+        {
+            throw Oops.Oh(ErrorCodeEnum.D0010);
+        }
 
         // 是否开启域登录验证
         if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysDomainLogin))
@@ -248,9 +255,9 @@ public class SysAuthService : IDynamicApiController, ITransient
         var org = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysOrg>>().GetFirstAsync(u => u.Id == user.OrgId);
         // 获取职位
         var pos = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysPos>>().GetFirstAsync(u => u.Id == user.PosId);
-        // 获取拥有按钮权限集合
+        // 获取按钮集合
         var buttons = await _sysMenuService.GetOwnBtnPermList();
-        // 获取权限集合
+        // 获取角色集合
         var roleIds = await _sysUserRep.ChangeRepository<SqlSugarRepository<SysUserRole>>().AsQueryable()
             .Where(u => u.UserId == user.Id).Select(u => u.RoleId).ToListAsync();
 

+ 47 - 0
Admin.NET/Admin.NET.Core/Service/Job/JobMonitor.cs

@@ -0,0 +1,47 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 作业执行监视器
+/// </summary>
+public class JobMonitor : IJobMonitor, IDisposable
+{
+    private readonly IEventPublisher _eventPublisher;
+    private readonly IServiceScope _serviceScope;
+    private readonly SysConfigService _sysConfigService;
+
+    public JobMonitor(IServiceScopeFactory scopeFactory)
+    {
+        _serviceScope = scopeFactory.CreateScope();
+        _sysConfigService = _serviceScope.ServiceProvider.GetRequiredService<SysConfigService>();
+        _eventPublisher = _serviceScope.ServiceProvider.GetRequiredService<IEventPublisher>(); ;
+    }
+
+    public async Task OnExecutedAsync(JobExecutedContext context, CancellationToken stoppingToken)
+    {
+        // 将异常作业发送到邮件
+        if (await _sysConfigService.GetConfigValue<bool>(CommonConst.SysErrorMail) && context.Exception != null)
+        {
+            var errorInfo = $"【{context.Trigger.Description}】出现错误:{context.Exception.InnerException}";
+            await _eventPublisher.PublishAsync(CommonConst.SendErrorMail, errorInfo);
+        }
+    }
+
+    public Task OnExecutingAsync(JobExecutingContext context, CancellationToken stoppingToken)
+    {
+        return Task.CompletedTask;
+    }
+
+    /// <summary>
+    /// 释放服务作用域
+    /// </summary>
+    public void Dispose()
+    {
+        _serviceScope.Dispose();
+    }
+}

+ 9 - 4
Admin.NET/Admin.NET.Core/Service/Org/SysOrgService.cs

@@ -117,11 +117,16 @@ public class SysOrgService : IDynamicApiController, ITransient
         }
 
         // 删除与此父机构有关的用户机构缓存
-        var pOrg = await _sysOrgRep.GetFirstAsync(u => u.Id == input.Pid);
-        if (pOrg != null)
-            DeleteAllUserOrgCache(pOrg.Id, pOrg.Pid);
-        else if (input.Pid == 0)
+        if (input.Pid == 0)
+        {
             DeleteAllUserOrgCache(0, 0);
+        }
+        else
+        {
+            var pOrg = await _sysOrgRep.GetFirstAsync(u => u.Id == input.Pid);
+            if (pOrg != null)
+                DeleteAllUserOrgCache(pOrg.Id, pOrg.Pid);
+        }
 
         var newOrg = await _sysOrgRep.AsInsertable(input.Adapt<SysOrg>()).ExecuteReturnEntityAsync();
         return newOrg.Id;

+ 5 - 3
Admin.NET/Admin.NET.Core/Service/Role/SysRoleService.cs

@@ -43,7 +43,8 @@ public class SysRoleService : IDynamicApiController, ITransient
     public async Task<SqlSugarPagedList<SysRole>> Page(PageRoleInput input)
     {
         return await _sysRoleRep.AsQueryable()
-            .WhereIF(!_userManager.SuperAdmin, u => u.CreateUserId == _userManager.UserId) // 若非超管,则只能操作自己创建的角色
+            .WhereIF(!_userManager.SuperAdmin, u => u.TenantId == _userManager.TenantId) // 若非超管,则只能操作本租户的角色
+            .WhereIF(!_userManager.SuperAdmin && !_userManager.SysAdmin, u => u.CreateUserId == _userManager.UserId) // 若非超管且非系统管理员,则只能操作自己创建的角色
             .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name))
             .WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code))
             .OrderBy(u => u.OrderNo)
@@ -58,10 +59,11 @@ public class SysRoleService : IDynamicApiController, ITransient
     public async Task<List<RoleOutput>> GetList()
     {
         // 当前用户已拥有的角色集合
-        var roleIdList = _userManager.SuperAdmin ? null : await _sysUserRoleService.GetUserRoleIdList(_userManager.UserId);
+        var roleIdList = _userManager.SuperAdmin ? new List<long>() : await _sysUserRoleService.GetUserRoleIdList(_userManager.UserId);
 
         return await _sysRoleRep.AsQueryable()
-            .WhereIF(roleIdList != null, u => u.CreateUserId == _userManager.UserId || roleIdList.Contains(u.Id)) // 若非超管,则只显示自己创建和已拥有的角色
+            .WhereIF(!_userManager.SuperAdmin, u => u.TenantId == _userManager.TenantId) // 若非超管,则只能操作本租户的角色
+            .WhereIF(!_userManager.SuperAdmin && !_userManager.SysAdmin, u => u.CreateUserId == _userManager.UserId || roleIdList.Contains(u.Id)) // 若非超管且非系统管理员,则只显示自己创建和已拥有的角色
             .OrderBy(u => u.OrderNo).Select<RoleOutput>().ToListAsync();
     }
 

+ 27 - 2
Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs

@@ -4,13 +4,20 @@
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
+using AlibabaCloud.SDK.Dysmsapi20170525.Models;
 using AngleSharp.Html.Parser;
+using AspNet.Security.OAuth.Gitee;
+using AspNet.Security.OAuth.Weixin;
 using AspNetCoreRateLimit;
 using Elastic.Clients.Elasticsearch;
+using IPTools.Core;
 using Lazy.Captcha.Core;
 using Magicodes.ExporterAndImporter.Pdf;
+using Magicodes.ExporterAndImporter.Word;
 using MailKit.Net.Smtp;
+using Novell.Directory.Ldap;
 using OnceMi.AspNetCore.OSS;
+using QRCoder;
 
 namespace Admin.NET.Core.Service;
 
@@ -96,15 +103,24 @@ public class SysServerService : IDynamicApiController, ITransient
         var jsonAssembly = typeof(NewtonsoftJsonMvcCoreBuilderExtensions).Assembly.GetName();
         var excelAssembly = typeof(IExcelImporter).Assembly.GetName();
         var pdfAssembly = typeof(IPdfExporter).Assembly.GetName();
+        var wordAssembly = typeof(IWordExporter).Assembly.GetName();
         var captchaAssembly = typeof(ICaptcha).Assembly.GetName();
         var wechatApiAssembly = typeof(WechatApiClient).Assembly.GetName();
         var wechatTenpayAssembly = typeof(WechatTenpayClient).Assembly.GetName();
         var ossAssembly = typeof(IOSSServiceFactory).Assembly.GetName();
         var parserAssembly = typeof(Parser).Assembly.GetName();
-        var nestAssembly = typeof(ElasticsearchClient).Assembly.GetName();
+        var elasticsearchClientAssembly = typeof(ElasticsearchClient).Assembly.GetName();
         var limitAssembly = typeof(IpRateLimitMiddleware).Assembly.GetName();
         var htmlParserAssembly = typeof(HtmlParser).Assembly.GetName();
         var fluentEmailAssembly = typeof(SmtpClient).Assembly.GetName();
+        var qRCodeGeneratorAssembly = typeof(QRCodeGenerator).Assembly.GetName();
+        var sendSmsRequestAssembly = typeof(SendSmsRequest).Assembly.GetName();
+        var imageAssembly = typeof(Image).Assembly.GetName();
+        var rabbitMQAssembly = typeof(RabbitMQEventSourceStore).Assembly.GetName();
+        var ldapConnectionAssembly = typeof(LdapConnection).Assembly.GetName();
+        var ipToolAssembly = typeof(IpTool).Assembly.GetName();
+        var weixinAuthenticationOptionsAssembly = typeof(WeixinAuthenticationOptions).Assembly.GetName();
+        var giteeAuthenticationOptionsAssembly = typeof(GiteeAuthenticationOptions).Assembly.GetName();
 
         return new[]
         {
@@ -115,15 +131,24 @@ public class SysServerService : IDynamicApiController, ITransient
             new { jsonAssembly.Name, jsonAssembly.Version },
             new { excelAssembly.Name, excelAssembly.Version },
             new { pdfAssembly.Name, pdfAssembly.Version },
+            new { wordAssembly.Name, wordAssembly.Version },
             new { captchaAssembly.Name, captchaAssembly.Version },
             new { wechatApiAssembly.Name, wechatApiAssembly.Version },
             new { wechatTenpayAssembly.Name, wechatTenpayAssembly.Version },
             new { ossAssembly.Name, ossAssembly.Version },
             new { parserAssembly.Name, parserAssembly.Version },
-            new { nestAssembly.Name, nestAssembly.Version },
+            new { elasticsearchClientAssembly.Name, elasticsearchClientAssembly.Version },
             new { limitAssembly.Name, limitAssembly.Version },
             new { htmlParserAssembly.Name, htmlParserAssembly.Version },
             new { fluentEmailAssembly.Name, fluentEmailAssembly.Version },
+            new { qRCodeGeneratorAssembly.Name, qRCodeGeneratorAssembly.Version },
+            new { sendSmsRequestAssembly.Name, sendSmsRequestAssembly.Version },
+            new { imageAssembly.Name, imageAssembly.Version },
+            new { rabbitMQAssembly.Name, rabbitMQAssembly.Version },
+            new { ldapConnectionAssembly.Name, ldapConnectionAssembly.Version },
+            new { ipToolAssembly.Name, ipToolAssembly.Version },
+            new { weixinAuthenticationOptionsAssembly.Name, weixinAuthenticationOptionsAssembly.Version },
+            new { giteeAuthenticationOptionsAssembly.Name, giteeAuthenticationOptionsAssembly.Version },
         };
     }
 }

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

@@ -313,6 +313,11 @@ public class SysUserService : IDynamicApiController, ITransient
         var password = await _sysConfigService.GetConfigValue<string>(CommonConst.SysPassword);
         user.Password = CryptogramUtil.Encrypt(password);
         await _sysUserRep.AsUpdateable(user).UpdateColumns(u => u.Password).ExecuteCommandAsync();
+
+        // 清空密码错误次数
+        var keyErrorPasswordCount = $"{CacheConst.KeyErrorPasswordCount}{user.Account}";
+        _sysCacheService.Remove(keyErrorPasswordCount);
+
         return password;
     }
 
@@ -326,8 +331,8 @@ public class SysUserService : IDynamicApiController, ITransient
     {
         var user = await _sysUserRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D0009);
 
-        var keyErrorPasswordCount = $"{CacheConst.KeyErrorPasswordCount}{user.Account}";
         // 清空密码错误次数
+        var keyErrorPasswordCount = $"{CacheConst.KeyErrorPasswordCount}{user.Account}";
         _sysCacheService.Remove(keyErrorPasswordCount);
     }
 

+ 5 - 0
Admin.NET/Admin.NET.Core/Service/User/UserManager.cs

@@ -38,6 +38,11 @@ public class UserManager : IScoped
     /// </summary>
     public bool SuperAdmin => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SuperAdmin).ToString();
 
+    /// <summary>
+    /// 是否系统管理员
+    /// </summary>
+    public bool SysAdmin => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.AccountType)?.Value == ((int)AccountTypeEnum.SysAdmin).ToString();
+
     /// <summary>
     /// 组织机构Id
     /// </summary>

+ 4 - 7
Admin.NET/Admin.NET.Core/SignatureAuth/SignatureAuthenticationHandler.cs

@@ -16,16 +16,12 @@ namespace Admin.NET.Core;
 /// </summary>
 public sealed class SignatureAuthenticationHandler : AuthenticationHandler<SignatureAuthenticationOptions>
 {
-    private readonly SysCacheService _cacheService;
-
     public SignatureAuthenticationHandler(IOptionsMonitor<SignatureAuthenticationOptions> options,
         ILoggerFactory logger,
         UrlEncoder encoder,
-        ISystemClock clock,
-        SysCacheService cacheService)
+        ISystemClock clock)
         : base(options, logger, encoder, clock)
     {
-        _cacheService = cacheService;
     }
 
     private new SignatureAuthenticationEvent Events
@@ -78,10 +74,11 @@ public sealed class SignatureAuthenticationHandler : AuthenticationHandler<Signa
             return await AuthenticateResultFailAsync("sign 无效的签名");
 
         // 重放检测
+        var cache = App.GetRequiredService<SysCacheService>();
         var cacheKey = $"{CacheConst.KeyOpenAccessNonce}{accessKey}|{nonce}";
-        if (_cacheService.ExistKey(cacheKey))
+        if (cache.ExistKey(cacheKey))
             return await AuthenticateResultFailAsync("重复的请求");
-        _cacheService.Set(cacheKey, null, Options.AllowedDateDrift * 2); // 缓存过期时间为偏差范围时间的2倍
+        cache.Set(cacheKey, null, Options.AllowedDateDrift * 2); // 缓存过期时间为偏差范围时间的2倍
 
         // 已验证成功
         var signatureValidatedContext = new SignatureValidatedContext(Context, Scheme, Options)

+ 9 - 3
Admin.NET/Admin.NET.Core/SqlSugar/SqlSugarSetup.cs

@@ -173,17 +173,22 @@ public static class SqlSugarSetup
         // 数据审计
         db.Aop.DataExecuting = (oldValue, entityInfo) =>
         {
+            // 新增/插入
             if (entityInfo.OperationType == DataFilterType.InsertByObject)
             {
-                // 主键(long类型)且没有值的---赋值雪花Id
+                // 若主键是长整型且空则赋值雪花Id
                 if (entityInfo.EntityColumnInfo.IsPrimarykey && entityInfo.EntityColumnInfo.PropertyInfo.PropertyType == typeof(long))
                 {
                     var id = entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue);
                     if (id == null || (long)id == 0)
                         entityInfo.SetValue(YitIdHelper.NextId());
                 }
-                if (entityInfo.PropertyName == nameof(EntityBase.CreateTime))
+                // 若创建时间为空则赋值当前时间
+                else if (entityInfo.PropertyName == nameof(EntityBase.CreateTime) && entityInfo.EntityColumnInfo.PropertyInfo.GetValue(entityInfo.EntityValue) == null)
+                {
                     entityInfo.SetValue(DateTime.Now);
+                }
+                // 若当前用户非空(web线程时)
                 if (App.User != null)
                 {
                     if (entityInfo.PropertyName == nameof(EntityTenantId.TenantId))
@@ -218,7 +223,8 @@ public static class SqlSugarSetup
                     }
                 }
             }
-            if (entityInfo.OperationType == DataFilterType.UpdateByObject)
+            // 编辑/更新
+            else if (entityInfo.OperationType == DataFilterType.UpdateByObject)
             {
                 if (entityInfo.PropertyName == nameof(EntityBase.UpdateTime))
                     entityInfo.SetValue(DateTime.Now);

+ 50 - 15
Admin.NET/Admin.NET.Core/Util/CommonUtil.cs

@@ -1,9 +1,10 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
 //
 // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
+using Magicodes.ExporterAndImporter.Core.Models;
 using System.Xml;
 using System.Xml.Linq;
 using System.Xml.Serialization;
@@ -103,38 +104,72 @@ public static class CommonUtil
     /// 导出模板Excel
     /// </summary>
     /// <param name="fileName"></param>
-    /// <param name="fileDto"></param>
     /// <returns></returns>
-    public static async Task<IActionResult> ExportExcelTemplate(string fileName, dynamic fileDto)
+    public static async Task<IActionResult> ExportExcelTemplate<T>(string fileName) where T : class, new()
     {
         fileName = $"{fileName}_{DateTime.Now.ToString("yyyyMMddHHmmss")}.xlsx";
-
         IImporter importer = new ExcelImporter();
-        MethodInfo generateTemplateMethod = importer.GetType().GetMethod("GenerateTemplate");
-        MethodInfo closedGenerateTemplateMethod = generateTemplateMethod.MakeGenericMethod(fileDto.GetType());
-        var res = await (Task<dynamic>)closedGenerateTemplateMethod.Invoke(importer, new object[] { Path.Combine(App.WebHostEnvironment.WebRootPath, fileName) });
-
+        var res = await importer.GenerateTemplate<T>(Path.Combine(App.WebHostEnvironment.WebRootPath, fileName));
         return new FileStreamResult(new FileStream(res.FileName, FileMode.Open), "application/octet-stream") { FileDownloadName = fileName };
     }
 
+    /// <summary>
+    /// 导出模板Excel
+    /// </summary>
+    /// <param name="fileName"></param>
+    /// <param name="fileDto"></param>
+    /// <returns></returns>
+    public static async Task<IActionResult> ExportExcelTemplate(string fileName, dynamic fileDto)
+    {
+        MethodInfo generateTemplateMethod = typeof(CommonUtil).GetMethods().FirstOrDefault(p => p.Name == "ExportExcelTemplate" && p.IsGenericMethodDefinition);
+        MethodInfo closedGenerateTemplateMethod = generateTemplateMethod.MakeGenericMethod(fileDto.GetType());
+        return await (Task<IActionResult>)closedGenerateTemplateMethod.Invoke(null, new object[] { fileName });
+    }
+
     /// <summary>
     /// 导入数据Excel
     /// </summary>
+    /// <typeparam name="T"></typeparam>
     /// <param name="file"></param>
-    /// <param name="dataDto"></param>
+    /// <param name="importResultCallback"></param>
     /// <returns></returns>
-    public static async Task<dynamic> ImportExcelData([Required] IFormFile file, dynamic dataDto)
+    public static async Task<ICollection<T>> ImportExcelData<T>([Required] IFormFile file, Func<ImportResult<T>, ImportResult<T>> importResultCallback = null) where T : class, new()
     {
         var newFile = await App.GetRequiredService<SysFileService>().UploadFile(file, "");
-        var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, newFile.FilePath, newFile.Id.ToString(), newFile.Suffix);
+        var filePath = Path.Combine(App.WebHostEnvironment.WebRootPath, newFile.FilePath, newFile.Id.ToString() + newFile.Suffix);
+        var errorFileUrl = Path.Combine(newFile.FilePath, newFile.Id.ToString() + "_" + newFile.Suffix);
 
         IImporter importer = new ExcelImporter();
-        MethodInfo importMethod = importer.GetType().GetMethod("Import");
-        MethodInfo closedImportMethod = importMethod.MakeGenericMethod(dataDto.GetType());
-        var res = await (Task<dynamic>)closedImportMethod.Invoke(importer, new object[] { filePath });
+        var res = await importer.Import<T>(filePath, importResultCallback);
         if (res == null || res.Exception != null)
             throw Oops.Oh("导入异常:" + res.Exception);
-
+        if (res.HasError)
+        {
+            if (res.TemplateErrors.Count > 0)
+            {
+                throw Oops.Oh("导入模板格式错误");
+            }
+            else
+            {
+                throw Oops.Oh($"请下载错误文件,根据提示修改后再次导入,<a href='{errorFileUrl}' target='_blank'>点击下载</a>");
+            }
+        }
         return res.Data;
     }
+
+    /// <summary>
+    /// 导入数据Excel
+    /// </summary>
+    /// <param name="file"></param>
+    /// <param name="dataDto"></param>
+    /// <returns></returns>
+    public static async Task<dynamic> ImportExcelData([Required] IFormFile file, dynamic dataDto)
+    {
+        MethodInfo importMethod = typeof(CommonUtil).GetMethods().FirstOrDefault(p => p.Name == "ImportExcelData" && p.IsGenericMethodDefinition);
+        MethodInfo closedImportMethod = importMethod.MakeGenericMethod(dataDto.GetType());
+        var parameters = importMethod.GetParameters();
+        var task = (Task)closedImportMethod.Invoke(null, new object[] { file, parameters[1].DefaultValue });
+        await task;
+        return task.GetType().GetProperty("Result").GetValue(task);
+    }
 }

+ 11 - 7
Admin.NET/Admin.NET.Core/Util/VerifyFileExtensionName.cs

@@ -1,4 +1,4 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
 //
 // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
 //
@@ -17,6 +17,7 @@ public static class VerifyFileExtensionName
     static VerifyFileExtensionName()
     {
         dics_ext.Add("FFD8FFE0", ".jpg");
+        dics_ext.Add("FFD8FFE1", ".jpg");
         dics_ext.Add("89504E47", ".png");
         dics_ext.Add("47494638", ".gif");
         dics_ext.Add("49492A00", ".tif");
@@ -28,8 +29,8 @@ public static class VerifyFileExtensionName
         dics_ext.Add("252150532D41646F6265", ".ps");
 
         // 办公文档类
-        dics_ext.Add("D0CF11E0", ".doc"); // ppt、doc、xls
-        dics_ext.Add("504B0304", ".docx"); // pptx、docx、xlsx
+        dics_ext.Add("D0CF11E0", ".ppt,.doc,.xls"); // ppt、doc、xls
+        dics_ext.Add("504B0304", ".pptx,.docx,.xlsx"); // pptx、docx、xlsx
 
         /* 注意由于文本文档录入内容过多,则读取文件头时较为多变-START */
         dics_ext.Add("0D0A0D0A", ".txt"); // txt
@@ -77,10 +78,13 @@ public static class VerifyFileExtensionName
 
         foreach (var dics in dics_ext)
         {
-            if (!ext_dics.ContainsKey(dics.Value))
-                ext_dics.Add(dics.Value, new HashSet<int> { dics.Key.Length / 2 });
-            else
-                ext_dics[dics.Value].Add(dics.Key.Length / 2);
+            foreach (var ext in dics.Value.Split(","))
+            {
+                if (!ext_dics.ContainsKey(ext))
+                    ext_dics.Add(ext, new HashSet<int> { dics.Key.Length / 2 });
+                else
+                    ext_dics[ext].Add(dics.Key.Length / 2);
+            }
         }
     }
 

+ 0 - 1
Admin.NET/Admin.NET.Web.Core/Admin.NET.Web.Core.csproj

@@ -16,7 +16,6 @@
 
   <ItemGroup>
     <ProjectReference Include="..\Admin.NET.Application\Admin.NET.Application.csproj" />
-    <ProjectReference Include="..\Admin.NET.Core\Admin.NET.Core.csproj" />
   </ItemGroup>
 
 </Project>

+ 1 - 0
Admin.NET/Admin.NET.Web.Core/Startup.cs

@@ -52,6 +52,7 @@ public class Startup : AppStartup
         services.AddSchedule(options =>
         {
             options.AddPersistence<DbJobPersistence>(); // 添加作业持久化器
+            options.AddMonitor<JobMonitor>(); // 添加作业执行监视器
         });
         // 脱敏检测
         services.AddSensitiveDetection();

+ 0 - 1
Admin.NET/Admin.NET.Web.Entry/wwwroot/Template/Service.cs.vm

@@ -1,5 +1,4 @@
 using Admin.NET.Core.Service;
-using @(@Model.NameSpace).Const;
 using @(@Model.NameSpace).Entity;
 using Microsoft.AspNetCore.Http;
 @{

+ 1 - 1
Admin.NET/Plugins/Admin.NET.Plugin.DingTalk/Job/SyncDingTalkUserJob.cs

@@ -26,7 +26,7 @@ public class SyncDingTalkUserJob : IJob
     {
         _scopeFactory = scopeFactory;
         _dingTalkApi = dingTalkApi;
-        _logger = loggerFactory.CreateLogger("System.Logging.LoggingMonitor");
+        _logger = loggerFactory.CreateLogger(CommonConst.SysLogCategoryName);
     }
 
     public async Task ExecuteAsync(JobExecutingContext context, CancellationToken stoppingToken)

+ 1 - 1
Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Admin.NET.Plugin.ReZero.csproj

@@ -24,7 +24,7 @@
   </ItemGroup>
 
   <ItemGroup>
-    <PackageReference Include="Rezero.Api" Version="1.0.19" />
+    <PackageReference Include="Rezero.Api" Version="1.0.39" />
   </ItemGroup>  
 
   <ItemGroup>

+ 1 - 1
Admin.NET/Plugins/Admin.NET.Plugin.ReZero/Service/SuperApiAop.cs

@@ -102,7 +102,7 @@ public class SuperApiAop : DefaultSuperApiAop
             exception = aopContext.Exception == null ? null : JSON.Serialize(aopContext.Exception)
         });
 
-        var logger = App.GetRequiredService<ILoggerFactory>().CreateLogger("System.Logging.LoggingMonitor");
+        var logger = App.GetRequiredService<ILoggerFactory>().CreateLogger(CommonConst.SysLogCategoryName);
         using var scope = logger.ScopeContext(new Dictionary<object, object> {
             { "loggingMonitor", apiInfo.ToString() }
         });

+ 18 - 18
Web/package.json

@@ -13,40 +13,40 @@
 	"dependencies": {
 		"@element-plus/icons-vue": "^2.3.1",
 		"@microsoft/signalr": "^8.0.0",
-		"@vue-office/docx": "^1.6.0",
-		"@vue-office/excel": "^1.7.6",
-		"@vue-office/pdf": "^2.0.1",
+		"@vue-office/docx": "^1.6.1",
+		"@vue-office/excel": "^1.7.8",
+		"@vue-office/pdf": "^2.0.2",
 		"@vueuse/core": "^10.9.0",
 		"@wangeditor/editor": "^5.1.23",
 		"@wangeditor/editor-for-vue": "^5.1.12",
 		"animate.css": "^4.1.1",
 		"axios": "^1.6.8",
 		"countup.js": "^2.8.0",
-		"cropperjs": "^1.6.1",
+		"cropperjs": "^1.6.2",
 		"echarts": "^5.5.0",
 		"echarts-gl": "^2.0.9",
 		"echarts-wordcloud": "^2.1.0",
-		"element-plus": "^2.7.1",
+		"element-plus": "^2.7.2",
 		"js-cookie": "^3.0.5",
 		"js-table2excel": "^1.1.2",
 		"jsplumb": "^2.15.6",
 		"lodash-es": "^4.17.21",
 		"mitt": "^3.0.1",
-		"monaco-editor": "^0.47.0",
+		"monaco-editor": "^0.48.0",
 		"nprogress": "^0.2.0",
 		"pinia": "^2.1.7",
 		"print-js": "^1.6.0",
 		"push.js": "^1.0.12",
 		"qrcodejs2-fixes": "^0.0.2",
 		"qs": "^6.12.1",
-		"relation-graph": "^2.1.42",
+		"relation-graph": "^2.2.0",
 		"screenfull": "^6.0.2",
 		"sm-crypto-v2": "^1.9.0",
 		"sortablejs": "^1.15.2",
 		"splitpanes": "^3.1.5",
 		"vcrontab-3": "^3.3.22",
 		"vform3-builds": "^3.0.10",
-		"vue": "^3.4.23",
+		"vue": "^3.4.26",
 		"vue-clipboard3": "^2.0.0",
 		"vue-demi": "^0.14.7",
 		"vue-grid-layout": "3.0.0-beta1",
@@ -61,25 +61,25 @@
 	"devDependencies": {
 		"@plugin-web-update-notification/vite": "^1.7.1",
 		"@types/lodash-es": "^4.17.12",
-		"@types/node": "^20.12.7",
+		"@types/node": "^20.12.8",
 		"@types/nprogress": "^0.2.3",
 		"@types/sortablejs": "^1.15.8",
-		"@typescript-eslint/eslint-plugin": "^7.7.0",
-		"@typescript-eslint/parser": "^7.7.0",
+		"@typescript-eslint/eslint-plugin": "^7.8.0",
+		"@typescript-eslint/parser": "^7.8.0",
 		"@vitejs/plugin-vue": "^5.0.4",
 		"@vitejs/plugin-vue-jsx": "^3.1.0",
-		"@vue/compiler-sfc": "^3.4.23",
-		"code-inspector-plugin": "^0.12.2",
-		"eslint": "^9.1.0",
+		"@vue/compiler-sfc": "^3.4.26",
+		"code-inspector-plugin": "^0.13.0",
+		"eslint": "^9.2.0",
 		"eslint-plugin-vue": "^9.25.0",
 		"less": "^4.2.0",
 		"prettier": "^3.2.5",
 		"rollup-plugin-visualizer": "^5.12.0",
-		"sass": "^1.75.0",
-		"terser": "^5.30.3",
+		"sass": "^1.76.0",
+		"terser": "^5.31.0",
 		"typescript": "^5.4.5",
-		"vite": "^5.2.10",
-		"vite-plugin-cdn-import": "^0.3.5",
+		"vite": "^5.2.11",
+		"vite-plugin-cdn-import": "^1.0.1",
 		"vite-plugin-compression2": "^1.1.0",
 		"vite-plugin-vue-setup-extend-plus": "^0.1.0",
 		"vue-eslint-parser": "^9.4.2"

二进制
Web/src/assets/bg.svg


+ 5 - 35
Web/src/components/iconSelector/index.vue

@@ -1,16 +1,6 @@
 <template>
 	<div class="icon-selector w100 h100">
-		<el-input
-			v-model="state.fontIconSearch"
-			:placeholder="state.fontIconPlaceholder"
-			:clearable="clearable"
-			:disabled="disabled"
-			:size="size"
-			ref="inputWidthRef"
-			@clear="onClearFontIcon"
-			@focus="onIconFocus"
-			@blur="onIconBlur"
-		>
+		<el-input v-model="state.fontIconSearch" :placeholder="state.fontIconPlaceholder" :clearable="clearable" :disabled="disabled" :size="size" ref="inputWidthRef" @clear="onClearFontIcon">
 			<template #prepend>
 				<SvgIcon
 					:name="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix"
@@ -20,15 +10,7 @@
 				<i v-else :class="state.fontIconPrefix === '' ? prepend : state.fontIconPrefix" class="font14"></i>
 			</template>
 		</el-input>
-		<el-popover
-			placement="bottom"
-			:width="state.fontIconWidth"
-			transition="el-zoom-in-top"
-			popper-class="icon-selector-popper"
-			trigger="click"
-			:virtual-ref="inputWidthRef"
-			virtual-triggering
-		>
+		<el-popover placement="bottom" :width="state.fontIconWidth" transition="el-zoom-in-top" popper-class="icon-selector-popper" trigger="click" :virtual-ref="inputWidthRef" virtual-triggering>
 			<template #default>
 				<div class="icon-selector-warp">
 					<div class="icon-selector-warp-title">{{ title }}</div>
@@ -119,20 +101,6 @@ const state = reactive({
 	},
 });
 
-// 处理 input 获取焦点时,modelValue 有值时,改变 input 的 placeholder 值
-const onIconFocus = () => {
-	if (!props.modelValue) return false;
-	state.fontIconSearch = '';
-	state.fontIconPlaceholder = props.modelValue;
-};
-// 处理 input 失去焦点时,为空将清空 input 值,为点击选中图标时,将取原先值
-const onIconBlur = () => {
-	const list = fontIconTabNameList();
-	setTimeout(() => {
-		const icon = list.filter((icon: string) => icon === state.fontIconSearch);
-		if (icon.length <= 0) state.fontIconSearch = '';
-	}, 300);
-};
 // 图标搜索及图标数据显示
 const fontIconSheetsFilterList = computed(() => {
 	const list = fontIconTabNameList();
@@ -159,7 +127,7 @@ const initModeValueEcho = () => {
 // 处理 icon 类型,用于回显时,tab 高亮与初始化数据
 const initFontIconName = () => {
 	let name = 'ali';
-    if(props.modelValue == undefined) name = 'ele';
+	if (props.modelValue == undefined) name = 'ele';
 	else if (props.modelValue!.indexOf('iconfont') > -1) name = 'ali';
 	else if (props.modelValue!.indexOf('ele-') > -1) name = 'ele';
 	else if (props.modelValue!.indexOf('fa') > -1) name = 'awe';
@@ -235,8 +203,10 @@ onMounted(() => {
 watch(
 	() => props.modelValue,
 	() => {
+		state.fontIconSearch = '';
 		initModeValueEcho();
 		initFontIconName();
+		getInputWidth();
 	}
 );
 </script>

+ 5 - 1
Web/src/utils/request.ts

@@ -127,7 +127,11 @@ service.interceptors.response.use(
 			} else {
 				message = serve.message;
 			}
-			ElMessage.error(message);
+			ElMessage({
+				dangerouslyUseHTMLString: true,
+				message: message,
+				type:'error'
+			});
 			throw new Error(message);
 		}
 

+ 75 - 53
Web/src/views/login/component/account.vue

@@ -1,61 +1,63 @@
 <template>
-	<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">
-				<template #prefix>
-					<el-icon>
-						<ele-User />
-					</el-icon>
-				</template>
-			</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">
-				<template #prefix>
-					<el-icon>
-						<ele-Unlock />
-					</el-icon>
-				</template>
-				<template #suffix>
-					<i class="iconfont el-input__icon login-content-password" :class="state.isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'" @click="state.isShowPassword = !state.isShowPassword"> </i>
-				</template>
-			</el-input>
-		</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')"
-					v-model="state.ruleForm.code"
-					clearable
-					autocomplete="off"
-					@keyup.enter.native="handleSignIn"
-				>
+	<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">
 					<template #prefix>
 						<el-icon>
-							<ele-Position />
+							<ele-User />
 						</el-icon>
 					</template>
 				</el-input>
-			</el-col>
-			<el-col :span="1"></el-col>
-			<el-col :span="8">
-				<div class="login-content-code">
-					<img class="login-content-code-img" @click="getCaptcha" width="130px" height="38px" :src="state.captchaImage" style="cursor: pointer" />
-				</div>
-			</el-col>
-		</el-form-item>
-		<el-form-item class="login-animation4">
-			<el-button type="primary" class="login-content-submit" round v-waves @click="handleSignIn" :loading="state.loading.signIn">
-				<span>{{ $t('message.account.accountBtnText') }}</span>
-			</el-button>
-		</el-form-item>
-		<div class="font12 mt30 login-animation4 login-msg">{{ $t('message.mobile.msgText') }}</div>
-		<!-- <el-button type="primary" round v-waves @click="weixinSignIn" :loading="state.loading.signIn"></el-button> -->
-	</el-form>
-
+			</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">
+					<template #prefix>
+						<el-icon>
+							<ele-Unlock />
+						</el-icon>
+					</template>
+					<template #suffix>
+						<i class="iconfont el-input__icon login-content-password" :class="state.isShowPassword ? 'icon-yincangmima' : 'icon-xianshimima'" @click="state.isShowPassword = !state.isShowPassword">
+						</i>
+					</template>
+				</el-input>
+			</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')"
+						v-model="state.ruleForm.code"
+						clearable
+						autocomplete="off"
+						@keyup.enter.native="handleSignIn"
+					>
+						<template #prefix>
+							<el-icon>
+								<ele-Position />
+							</el-icon>
+						</template>
+					</el-input>
+				</el-col>
+				<el-col :span="1"></el-col>
+				<el-col :span="8">
+					<div class="login-content-code">
+						<img class="login-content-code-img" @click="getCaptcha" width="130px" height="38px" :src="state.captchaImage" style="cursor: pointer" />
+					</div>
+				</el-col>
+			</el-form-item>
+			<el-form-item class="login-animation4">
+				<el-button type="primary" class="login-content-submit" round v-waves @click="handleSignIn" :loading="state.loading.signIn">
+					<span>{{ $t('message.account.accountBtnText') }}</span>
+				</el-button>
+			</el-form-item>
+			<div class="font12 mt30 login-animation4 login-msg">{{ $t('message.mobile.msgText') }}</div>
+			<!-- <el-button type="primary" round v-waves @click="weixinSignIn" :loading="state.loading.signIn"></el-button> -->
+		</el-form>
+	</el-tooltip>
 	<div class="dialog-header">
 		<el-dialog v-model="state.rotateVerifyVisible" :show-close="false">
 			<DragVerifyImgRotate
@@ -73,7 +75,7 @@
 </template>
 
 <script lang="ts" setup name="loginAccount">
-import { reactive, computed, ref, onMounted, defineAsyncComponent } from 'vue';
+import { reactive, computed, ref, onMounted, defineAsyncComponent, onUnmounted } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import { ElMessage, InputInstance } from 'element-plus';
 import { useI18n } from 'vue-i18n';
@@ -122,7 +124,9 @@ const state = reactive({
 	secondVerEnabled: false,
 	captchaEnabled: false,
 	isPassRotate: false,
+	capsLockVisible: false,
 });
+
 onMounted(async () => {
 	// 若URL带有Token参数(第三方登录)
 	var accessToken = route.query.token;
@@ -135,8 +139,22 @@ onMounted(async () => {
 	state.secondVerEnabled = res1.data.result.secondVerEnabled ?? true;
 	state.captchaEnabled = res1.data.result.captchaEnabled ?? true;
 
+	// 获取验证码
 	getCaptcha();
+
+	// 检测大小写按键/CapsLK
+	document.addEventListener('keyup', handleKeyPress);
 });
+
+onUnmounted(() => {
+	document.removeEventListener('keyup', handleKeyPress);
+});
+
+const handleKeyPress = (e: any) => {
+	const isCapsLockOn = e.getModifierState('CapsLock');
+	state.capsLockVisible = isCapsLockOn;
+};
+
 // 获取验证码
 const getCaptcha = async () => {
 	if (!state.captchaEnabled) return;
@@ -146,10 +164,12 @@ const getCaptcha = async () => {
 	state.captchaImage = 'data:text/html;base64,' + res.data.result?.img;
 	state.ruleForm.codeId = res.data.result?.id;
 };
+
 // 获取时间
 const currentTime = computed(() => {
 	return formatAxis(new Date());
 });
+
 // 登录
 const onSignIn = async () => {
 	ruleFormRef.value.validate(async (valid: boolean) => {
@@ -217,12 +237,14 @@ const signInSuccess = (isNoPower: boolean | undefined) => {
 		NextLoading.start();
 	}
 };
+
 // 打开旋转验证
 const openRotateVerify = () => {
 	state.rotateVerifyVisible = true;
 	state.isPassRotate = false;
 	dragRef.value?.reset();
 };
+
 // 通过旋转验证
 const passRotateVerify = () => {
 	state.rotateVerifyVisible = false;

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

@@ -136,6 +136,11 @@ onMounted(() => {
 		width: 50%;
 		float: right;
 		background: var(--el-color-white);
+		background-image: url('../../assets/bg.svg');
+		background-size: 100% auto;
+		background-position: 50% calc(50% - 15px);
+		background-attachment: fixed;
+		background-repeat: no-repeat;
 		.login-right-warp {
 			border: 1px solid var(--el-color-primary-light-3);
 			border-radius: 3px;
@@ -255,6 +260,24 @@ onMounted(() => {
 				}
 			}
 		}
+		/* 在这里可以添加一个伪元素来覆盖原内容,实现磨砂效果 */
+		.login-right-warp::before {
+			content: '';
+			position: absolute;
+			top: 0;
+			left: 0;
+			width: 100%;
+			height: 100%;
+			background-color: rgba(255, 255, 255, 1);
+			filter: blur(4px); /* 调整模糊半径以改变磨砂效果强度 */
+			z-index: 1;
+		}
+
+		/* 保持原有内容可见,放置在伪元素下方 */
+		.login-right-warp > * {
+			position: relative;
+			z-index: 2;
+		}
 	}
 }
 .copyright {

+ 5 - 0
Web/src/views/system/dict/component/editDictData.vue

@@ -19,6 +19,11 @@
 							<el-input v-model="state.ruleForm.code" 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="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="24" :md="24" :lg="24" :xl="24" class="mb20">
 						<el-form-item label="标签类型">
 							<el-radio-group v-model="state.ruleForm.tagType">

+ 1 - 0
Web/src/views/system/dict/index.vue

@@ -99,6 +99,7 @@
 							</template>
 						</el-table-column>
 						<el-table-column prop="code" label="编码" header-align="center" min-width="120" show-overflow-tooltip />
+						<el-table-column prop="name" label="名称" header-align="center" min-width="120" show-overflow-tooltip />
 						<el-table-column prop="extData" label="拓展数据" width="90" align="center">
 							<template #default="scope">
 								<el-tag type="warning" v-if="scope.row.extData == null || scope.row.extData == ''">空</el-tag>