Просмотр исходного кода

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

ccjungle 1 год назад
Родитель
Сommit
12ec3918ee
38 измененных файлов с 476 добавлено и 356 удалено
  1. 23 37
      Admin.NET/Admin.NET.Application/Configuration/Limit.json
  2. 2 1
      Admin.NET/Admin.NET.Application/Configuration/Logging.json
  3. 7 6
      Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
  4. 6 1
      Admin.NET/Admin.NET.Core/Const/ConfigConst.cs
  5. 2 2
      Admin.NET/Admin.NET.Core/EventBus/AppEventSubscriber.cs
  6. 1 1
      Admin.NET/Admin.NET.Core/Extension/SqlSugarExtension.cs
  7. 26 0
      Admin.NET/Admin.NET.Core/Extension/UseApplicationBuilder.cs
  8. 22 22
      Admin.NET/Admin.NET.Core/SeedData/SysConfigSeedData.cs
  9. 3 6
      Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs
  10. 13 11
      Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs
  11. 0 11
      Admin.NET/Admin.NET.Core/Service/Log/SysLogDiffService.cs
  12. 22 2
      Admin.NET/Admin.NET.Core/Service/Message/Dto/MessageInput.cs
  13. 25 0
      Admin.NET/Admin.NET.Core/Service/Message/Dto/SmsInput.cs
  14. 9 57
      Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs
  15. 18 1
      Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs
  16. 21 30
      Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs
  17. 12 12
      Admin.NET/Admin.NET.Core/Util/ComputerUtil.cs
  18. 1 0
      Admin.NET/Admin.NET.Web.Core/Startup.cs
  19. 1 1
      Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Service.cs.vm
  20. 13 13
      Web/package.json
  21. 0 75
      Web/src/api-services/apis/sys-log-diff-api.ts
  22. 85 0
      Web/src/api-services/apis/sys-sms-api.ts
  23. 1 0
      Web/src/api-services/models/index.ts
  24. 34 2
      Web/src/api-services/models/message-input.ts
  25. 40 0
      Web/src/api-services/models/sms-verify-code-input.ts
  26. 6 0
      Web/src/components/iconSelector/index.vue
  27. 1 1
      Web/src/i18n/lang/zh-cn.ts
  28. 3 1
      Web/src/stores/keepAliveNames.ts
  29. 1 1
      Web/src/utils/toolsValidate.ts
  30. 1 1
      Web/src/views/system/cache/index.vue
  31. 3 3
      Web/src/views/system/dict/index.vue
  32. 0 14
      Web/src/views/system/log/difflog/index.vue
  33. 9 9
      Web/src/views/system/menu/index.vue
  34. 10 10
      Web/src/views/system/org/index.vue
  35. 14 4
      Web/src/views/system/pos/index.vue
  36. 1 0
      Web/src/views/system/print/component/editPrint.vue
  37. 18 8
      Web/src/views/system/print/component/hiprint/index.vue
  38. 22 13
      Web/src/views/system/user/index.vue

+ 23 - 37
Admin.NET/Admin.NET.Application/Configuration/Limit.json

@@ -21,7 +21,7 @@
     // 客户端白名单
     "ClientWhitelist": [],
     "QuotaExceededResponse": {
-      "Content": "{{\"code\":429,\"type\":\"error\",\"message\":\"访问过于频繁,请稍后重试!\",\"result\":null,\"extras\":null}}",
+      "Content": "{{\"code\":429,\"type\":\"error\",\"message\":\"访问过于频繁,请稍后重试!禁止违法行为否则110 👮\",\"result\":null,\"extras\":null}}",
       "ContentType": "application/json",
       "StatusCode": 429
     },
@@ -29,46 +29,42 @@
     "HttpStatusCode": 429,
     // API规则,结尾一定要带*
     "GeneralRules": [
-      // 1秒钟只能调用10次
+      // 1秒钟只能调用1000
       {
         "Endpoint": "*",
         "Period": "1s",
-        "Limit": 10
+        "Limit": 1000
       },
-      // 1分钟只能调用600次
+      // 1分钟只能调用60000
       {
         "Endpoint": "*",
         "Period": "1m",
-        "Limit": 600
-      },
-      // 1小时只能调用3600
-      {
-        "Endpoint": "*",
-        "Period": "1h",
-        "Limit": 3600
-      },
-      // 1天只能调用86400次
-      {
-        "Endpoint": "*",
-        "Period": "1d",
-        "Limit": 86400
+        "Limit": 60000
       }
+      //// 1小时只能调用3600000次
+      //{
+      //  "Endpoint": "*",
+      //  "Period": "1h",
+      //  "Limit": 3600000
+      //},
+      //// 1天只能调用86400000次
+      //{
+      //  "Endpoint": "*",
+      //  "Period": "1d",
+      //  "Limit": 86400000
+      //}
     ]
   },
+  // IP 黑名单
   "IpRateLimitPolicies": {
     "IpRules": [
       {
-        "Ip": "XXX.XXX.XXX.XXX",
+        "Ip": "0.0.0.0", // IP可用:"::1/10" 
         "Rules": [
           {
             "Endpoint": "*",
             "Period": "1s",
-            "Limit": 10
-          },
-          {
-            "Endpoint": "*",
-            "Period": "1m",
-            "Limit": 600
+            "Limit": 0 // 设置为0就是1次都不能请求,完全屏蔽
           }
         ]
       }
@@ -90,29 +86,19 @@
       {
         "Endpoint": "*",
         "Period": "1s",
-        "Limit": 10
-      },
-      {
-        "Endpoint": "*",
-        "Period": "1m",
-        "Limit": 600
+        "Limit": 2000
       }
     ]
   },
   "ClientRateLimitPolicies": {
     "ClientRules": [
       {
-        "ClientId": "xxx-xxx",
+        "ClientId": "",
         "Rules": [
           {
             "Endpoint": "*",
             "Period": "1s",
-            "Limit": 10
-          },
-          {
-            "Endpoint": "*",
-            "Period": "1m",
-            "Limit": 600
+            "Limit": 2000
           }
         ]
       }

+ 2 - 1
Admin.NET/Admin.NET.Application/Configuration/Logging.json

@@ -5,7 +5,8 @@
     "LogLevel": {
       "Default": "Information",
       "Microsoft.AspNetCore": "Warning",
-      "Microsoft.EntityFrameworkCore": "Information"
+      "Microsoft.EntityFrameworkCore": "Information",
+      "AspNetCoreRateLimit": "None"
     },
     "File": {
       "Enabled": false, // 启用文件日志

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

@@ -17,10 +17,11 @@
     <PackageReference Include="AngleSharp" Version="1.1.2" />
     <PackageReference Include="AspectCore.Extensions.Reflection" Version="2.4.0" />
     <PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
-    <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.15.1" />
-    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.5.4" />
-    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.5.4" />
-    <PackageReference Include="Furion.Pure" Version="4.9.5.4" />
+    <PackageReference Include="Elastic.Clients.Elasticsearch" Version="8.15.4" />
+    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.5.5" />
+    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.5.5" />
+    <PackageReference Include="Furion.Pure" Version="4.9.5.5" />
+    <PackageReference Include="Hashids.net" Version="1.7.0" />
     <PackageReference Include="IPTools.China" Version="1.6.0" />
     <PackageReference Include="IPTools.International" Version="1.6.0" />
     <PackageReference Include="Magicodes.IE.Excel" Version="2.7.5.1" />
@@ -37,8 +38,8 @@
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.7.0" />
     <PackageReference Include="SqlSugarCore" Version="5.1.4.167" />
     <PackageReference Include="SSH.NET" Version="2024.1.0" />
-    <PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.4" />
-    <PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1071" />
+    <PackageReference Include="System.Linq.Dynamic.Core" Version="1.4.5" />
+    <PackageReference Include="TencentCloudSDK.Sms" Version="3.0.1079" />
     <PackageReference Include="UAParser" Version="3.1.47" />
     <PackageReference Include="Yitter.IdGenerator" Version="1.0.14" />
   </ItemGroup>

+ 6 - 1
Admin.NET/Admin.NET.Core/Const/ConfigConst.cs

@@ -1,4 +1,4 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
 //
 // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
 //
@@ -81,6 +81,11 @@ public class ConfigConst
     /// </summary>
     public const string SysRegionSyncLevel = "sys_region_sync_level";
 
+    /// <summary>
+    /// Default 分组
+    /// </summary>
+    public const string SysDefaultGroup = "Default";
+
     /// <summary>
     /// WebConfig 分组
     /// </summary>

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

@@ -26,8 +26,8 @@ public class AppEventSubscriber : IEventSubscriber, ISingleton, IDisposable
     [EventSubscribe(CommonConst.AddExLog)]
     public async Task CreateExLog(EventHandlerExecutingContext context)
     {
-        var rep = _serviceScope.ServiceProvider.GetRequiredService<SqlSugarRepository<SysLogEx>>();
-        await rep.InsertAsync((SysLogEx)context.Source.Payload);
+        var db = _serviceScope.ServiceProvider.GetRequiredService<ISqlSugarClient>();
+        await db.CopyNew().Insertable((SysLogEx)context.Source.Payload).ExecuteCommandAsync();
     }
 
     /// <summary>

+ 1 - 1
Admin.NET/Admin.NET.Core/Extension/SqlSugarExtension.cs

@@ -197,7 +197,7 @@ public static class SqlSugarExtension
 
             return Expression.Constant(valueparsed, propertyType);
         }
-        if (propertyType == typeof(long))
+        if (propertyType == typeof(long) || propertyType == typeof(long?))
         {
             string? stringLong = GetStringFromJsonElement(value);
 

+ 26 - 0
Admin.NET/Admin.NET.Core/Extension/UseApplicationBuilder.cs

@@ -0,0 +1,26 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+using AspNetCoreRateLimit;
+using Microsoft.AspNetCore.Builder;
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 配置中间件扩展
+/// </summary>
+public static class UseApplicationBuilder
+{
+    // 配置限流中间件策略
+    public static void UsePolicyRateLimit(this IApplicationBuilder app)
+    {
+        var ipPolicyStore = app.ApplicationServices.GetRequiredService<IIpPolicyStore>();
+        ipPolicyStore.SeedAsync().GetAwaiter().GetResult();
+
+        var clientPolicyStore = app.ApplicationServices.GetRequiredService<IClientPolicyStore>();
+        clientPolicyStore.SeedAsync().GetAwaiter().GetResult();
+    }
+}

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

@@ -19,28 +19,28 @@ public class SysConfigSeedData : ISqlSugarEntitySeedData<SysConfig>
     {
         return new[]
         {
-            new SysConfig{ Id=1300000000101, Name="演示环境", Code=ConfigConst.SysDemoEnv, Value="False", SysFlag=YesNoEnum.Y, Remark="演示环境", OrderNo=10, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000111, Name="默认密码", Code=ConfigConst.SysPassword, Value="123456", SysFlag=YesNoEnum.Y, Remark="默认密码", OrderNo=20, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000121, Name="密码最大错误次数", Code=ConfigConst.SysPasswordMaxErrorTimes, Value="5", SysFlag=YesNoEnum.Y, Remark="允许密码最大输入错误次数", OrderNo=30, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000131, Name="日志保留天数", Code=ConfigConst.SysLogRetentionDays, Value="180", SysFlag=YesNoEnum.Y, Remark="日志保留天数(天)", OrderNo=40, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000141, Name="记录操作日志", Code=ConfigConst.SysOpLog, Value="True", SysFlag=YesNoEnum.Y, Remark="是否记录操作日志", OrderNo=50, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000151, Name="单设备登录", Code=ConfigConst.SysSingleLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启单设备登录", OrderNo=60, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000161, Name="登录二次验证", Code=ConfigConst.SysSecondVer, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启登录二次验证", OrderNo=70, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000171, Name="图形验证码", Code=ConfigConst.SysCaptcha, Value="True", SysFlag=YesNoEnum.Y, Remark="是否开启图形验证码", OrderNo=80, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000181, Name="Token过期时间", Code=ConfigConst.SysTokenExpire, Value="10080", SysFlag=YesNoEnum.Y, Remark="Token过期时间(分钟)", OrderNo=90, GroupCode="Default", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000191, Name="RefreshToken过期时间", Code=ConfigConst.SysRefreshTokenExpire, Value="20160", SysFlag=YesNoEnum.Y, Remark="刷新Token过期时间(分钟)(一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)", OrderNo=100, GroupCode="Default", 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="Default", 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="Default", 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="Default", 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="Default", 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="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000311, Name="系统副标题", Code=ConfigConst.SysWebViceTitle, Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="系统副标题", OrderNo=310, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000321, Name="系统描述", Code=ConfigConst.SysWebViceDesc, Value="站在巨人肩膀上的 .NET 通用权限开发框架", SysFlag=YesNoEnum.Y, Remark="系统描述", OrderNo=320, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000331, Name="水印内容", Code=ConfigConst.SysWebWatermark, Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="水印内容", OrderNo=330, GroupCode="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
-            new SysConfig{ Id=1300000000341, Name="版权说明", Code=ConfigConst.SysWebCopyright, Value="Copyright © 2021-present Admin.NET All rights reserved.", SysFlag=YesNoEnum.Y, Remark="版权说明", OrderNo=340, GroupCode="WebConfig", 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="WebConfig", 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="WebConfig", 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="WebConfig", CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000101, Name="演示环境", Code=ConfigConst.SysDemoEnv, Value="False", SysFlag=YesNoEnum.Y, Remark="演示环境", OrderNo=10, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000111, Name="默认密码", Code=ConfigConst.SysPassword, Value="123456", SysFlag=YesNoEnum.Y, Remark="默认密码", OrderNo=20, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000121, Name="密码最大错误次数", Code=ConfigConst.SysPasswordMaxErrorTimes, Value="5", SysFlag=YesNoEnum.Y, Remark="允许密码最大输入错误次数", OrderNo=30, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000131, Name="日志保留天数", Code=ConfigConst.SysLogRetentionDays, Value="180", SysFlag=YesNoEnum.Y, Remark="日志保留天数(天)", OrderNo=40, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000141, Name="记录操作日志", Code=ConfigConst.SysOpLog, Value="True", SysFlag=YesNoEnum.Y, Remark="是否记录操作日志", OrderNo=50, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000151, Name="单设备登录", Code=ConfigConst.SysSingleLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启单设备登录", OrderNo=60, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000161, Name="登录二次验证", Code=ConfigConst.SysSecondVer, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启登录二次验证", OrderNo=70, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000171, Name="图形验证码", Code=ConfigConst.SysCaptcha, Value="True", SysFlag=YesNoEnum.Y, Remark="是否开启图形验证码", OrderNo=80, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000181, Name="Token过期时间", Code=ConfigConst.SysTokenExpire, Value="10080", SysFlag=YesNoEnum.Y, Remark="Token过期时间(分钟)", OrderNo=90, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000191, Name="RefreshToken过期时间", Code=ConfigConst.SysRefreshTokenExpire, Value="20160", SysFlag=YesNoEnum.Y, Remark="刷新Token过期时间(分钟)(一般 refresh_token 的有效时间 > 2 * access_token 的有效时间)", OrderNo=100, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000201, Name="发送异常日志邮件", Code=ConfigConst.SysErrorMail, Value="False", SysFlag=YesNoEnum.Y, Remark="是否发送异常日志邮件", OrderNo=110, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000211, Name="域登录验证", Code=ConfigConst.SysDomainLogin, Value="False", SysFlag=YesNoEnum.Y, Remark="是否开启域登录验证", OrderNo=120, GroupCode=ConfigConst.SysDefaultGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000221, Name="数据校验日志", Code=ConfigConst.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") },
+            new SysConfig{ Id=1300000000311, Name="系统副标题", Code=ConfigConst.SysWebViceTitle, Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="系统副标题", OrderNo=310, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000321, Name="系统描述", Code=ConfigConst.SysWebViceDesc, Value="站在巨人肩膀上的 .NET 通用权限开发框架", SysFlag=YesNoEnum.Y, Remark="系统描述", OrderNo=320, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            new SysConfig{ Id=1300000000331, Name="水印内容", Code=ConfigConst.SysWebWatermark, Value="Admin.NET", SysFlag=YesNoEnum.Y, Remark="水印内容", OrderNo=330, GroupCode=ConfigConst.SysWebConfigGroup, CreateTime=DateTime.Parse("2022-02-10 00:00:00") },
+            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") },
         };
     }
 }

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

@@ -192,14 +192,11 @@ public class SysAuthService : IDynamicApiController, ITransient
     [DisplayName("手机号登录")]
     public virtual async Task<LoginOutput> LoginPhone([Required] LoginPhoneInput input)
     {
-        var verifyCode = _sysCacheService.Get<string>($"{CacheConst.KeyPhoneVerCode}{input.Phone}");
-        if (string.IsNullOrWhiteSpace(verifyCode))
-            throw Oops.Oh("验证码不存在或已失效,请重新获取!");
-        if (verifyCode != input.Code)
-            throw Oops.Oh("验证码错误!");
+        // 校验短信验证码
+        App.GetRequiredService<SysSmsService>().VerifyCode(new SmsVerifyCodeInput { Phone = input.Phone, Code = input.Code });
 
         // 账号是否存在
-        var user = await _sysUserRep.AsQueryable().Includes(t => t.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone));
+        var user = await _sysUserRep.AsQueryable().Includes(u => u.SysOrg).ClearFilter().FirstAsync(u => u.Phone.Equals(input.Phone));
         _ = user ?? throw Oops.Oh(ErrorCodeEnum.D0009);
 
         return await CreateToken(user);

+ 13 - 11
Admin.NET/Admin.NET.Core/Service/Const/SysConstService.cs

@@ -1,4 +1,4 @@
-// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
 //
 // 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
 //
@@ -56,16 +56,18 @@ public class SysConstService : IDynamicApiController, ITransient
         {
             var typeList = GetConstAttributeList();
             var type = typeList.FirstOrDefault(u => u.Name == typeName);
-
-            var isEnum = type.BaseType.Name == "Enum";
-            constlist = type.GetFields()?
-                .Where(isEnum, u => u.FieldType.Name == typeName)
-                .Select(u => new ConstOutput
-                {
-                    Name = u.Name,
-                    Code = isEnum ? (int)u.GetValue(BindingFlags.Instance) : u.GetValue(BindingFlags.Instance)
-                }).ToList();
-            _sysCacheService.Set(key, constlist);
+            if (type != null)
+            {
+                var isEnum = type.BaseType.Name == "Enum";
+                constlist = type.GetFields()?
+                    .Where(isEnum, u => u.FieldType.Name == typeName)
+                    .Select(u => new ConstOutput
+                    {
+                        Name = u.Name,
+                        Code = isEnum ? (int)u.GetValue(BindingFlags.Instance) : u.GetValue(BindingFlags.Instance)
+                    }).ToList();
+                _sysCacheService.Set(key, constlist);
+            }
         }
         return await Task.FromResult(constlist);
     }

+ 0 - 11
Admin.NET/Admin.NET.Core/Service/Log/SysLogDiffService.cs

@@ -44,15 +44,4 @@ public class SysLogDiffService : IDynamicApiController, ITransient
     {
         return await _sysLogDiffRep.GetFirstAsync(u => u.Id == id);
     }
-
-    /// <summary>
-    /// 清空差异日志 🔖
-    /// </summary>
-    /// <returns></returns>
-    [ApiDescriptionSettings(Name = "Clear"), HttpPost]
-    [DisplayName("清空差异日志")]
-    public void Clear()
-    {
-        _sysLogDiffRep.AsSugarClient().DbMaintenance.TruncateTable<SysLogDiff>();
-    }
 }

+ 22 - 2
Admin.NET/Admin.NET.Core/Service/Message/Dto/MessageInput.cs

@@ -9,9 +9,14 @@ namespace Admin.NET.Core;
 public class MessageInput
 {
     /// <summary>
-    /// 用户ID
+    /// 接收者用户Id
     /// </summary>
-    public long UserId { get; set; }
+    public long ReceiveUserId { get; set; }
+
+    /// <summary>
+    /// 接收者名称
+    /// </summary>
+    public string ReceiveUserName { get; set; }
 
     /// <summary>
     /// 用户ID列表
@@ -32,4 +37,19 @@ public class MessageInput
     /// 消息内容
     /// </summary>
     public string Message { get; set; }
+
+    /// <summary>
+    /// 发送者Id
+    /// </summary>
+    public string SendUserId { get; set; }
+
+    /// <summary>
+    /// 发送者名称
+    /// </summary>
+    public string SendUserName { get; set; }
+
+    /// <summary>
+    /// 发送时间
+    /// </summary>
+    public DateTime SendTime { get; set; }
 }

+ 25 - 0
Admin.NET/Admin.NET.Core/Service/Message/Dto/SmsInput.cs

@@ -0,0 +1,25 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+public class SmsVerifyCodeInput
+{
+    /// <summary>
+    /// 手机号码
+    /// </summary>
+    /// <example>admin</example>
+    [Required(ErrorMessage = "手机号码不能为空")]
+    [DataValidation(ValidationTypes.PhoneNumber, ErrorMessage = "手机号码不正确")]
+    public string Phone { get; set; }
+
+    /// <summary>
+    /// 验证码
+    /// </summary>
+    /// <example>123456</example>
+    [Required(ErrorMessage = "验证码不能为空"), MinLength(4, ErrorMessage = "验证码不能少于4个字符")]
+    public string Code { get; set; }
+}

+ 9 - 57
Admin.NET/Admin.NET.Core/Service/Message/SysMessageService.cs

@@ -46,24 +46,9 @@ public class SysMessageService : IDynamicApiController, ITransient
     [DisplayName("发送消息给除了发送人的其他人")]
     public async Task SendOtherUser(MessageInput input)
     {
-        var cacheKey = CacheConst.KeyUserOnline + input.UserId;
-        // 是否开启单用户登录
-        if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin))
-        {
-            var user = _sysCacheService.Get<SysOnlineUser>(cacheKey);
-            if (user == null) return;
-            await _chatHubContext.Clients.AllExcept(user.ConnectionId).ReceiveMessage(input);
-        }
-        else
-        {
-            var _cacheKeys = _sysCacheService.GetKeyList().Where(u => u.StartsWith(cacheKey)).ToArray();
-            foreach (var _cacheKey in _cacheKeys)
-            {
-                var user = _sysCacheService.Get<SysOnlineUser>(_cacheKey);
-                if (user == null) return;
-                await _chatHubContext.Clients.AllExcept(user.ConnectionId).ReceiveMessage(input);
-            }
-        }
+        var hashKey = _sysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
+        var exceptRecevieUsers = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).ToList();
+        await _chatHubContext.Clients.AllExcept(exceptRecevieUsers.Select(t => t.ConnectionId)).ReceiveMessage(input);
     }
 
     /// <summary>
@@ -74,24 +59,9 @@ public class SysMessageService : IDynamicApiController, ITransient
     [DisplayName("发送消息给某个人")]
     public async Task SendUser(MessageInput input)
     {
-        var cacheKey = CacheConst.KeyUserOnline + input.UserId;
-        // 是否开启单用户登录
-        if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin))
-        {
-            var user = _sysCacheService.Get<SysOnlineUser>(cacheKey);
-            if (user == null) return;
-            await _chatHubContext.Clients.Client(user.ConnectionId).ReceiveMessage(input);
-        }
-        else
-        {
-            var _cacheKeys = _sysCacheService.GetKeyList().Where(u => u.StartsWith(cacheKey)).ToArray();
-            foreach (var _cacheKey in _cacheKeys)
-            {
-                var user = _sysCacheService.Get<SysOnlineUser>(_cacheKey);
-                if (user == null) return;
-                await _chatHubContext.Clients.Client(user.ConnectionId).ReceiveMessage(input);
-            }
-        }
+        var hashKey = _sysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
+        var recevieUsers = hashKey.Where(u => u.Value.UserId == input.ReceiveUserId).Select(u => u.Value).ToList();
+        await recevieUsers.ForEachAsync(u => _chatHubContext.Clients.Client(u.ConnectionId).ReceiveMessage(input));
     }
 
     /// <summary>
@@ -102,26 +72,8 @@ public class SysMessageService : IDynamicApiController, ITransient
     [DisplayName("发送消息给某些人")]
     public async Task SendUsers(MessageInput input)
     {
-        var userList = new List<string>();
-        foreach (var userId in input.UserIds)
-        {
-            var cacheKey = CacheConst.KeyUserOnline + userId;
-            // 是否开启单用户登录
-            if (await _sysConfigService.GetConfigValue<bool>(ConfigConst.SysSingleLogin))
-            {
-                var user = _sysCacheService.Get<SysOnlineUser>(cacheKey);
-                if (user != null) userList.Add(user.ConnectionId);
-            }
-            else
-            {
-                var _cacheKeys = _sysCacheService.GetKeyList().Where(u => u.StartsWith(cacheKey)).ToArray();
-                foreach (var _cacheKey in _cacheKeys)
-                {
-                    var user = _sysCacheService.Get<SysOnlineUser>(_cacheKey);
-                    if (user != null) userList.Add(user.ConnectionId);
-                }
-            }
-        }
-        await _chatHubContext.Clients.Clients(userList).ReceiveMessage(input);
+        var hashKey = _sysCacheService.HashGetAll<SysOnlineUser>(CacheConst.KeyUserOnline);
+        var recevieUsers = hashKey.Where(u => input.UserIds.Any(a => a == u.Value.UserId)).Select(u => u.Value).ToList();
+        await recevieUsers.ForEachAsync(u => _chatHubContext.Clients.Client(u.ConnectionId).ReceiveMessage(input));
     }
 }

+ 18 - 1
Admin.NET/Admin.NET.Core/Service/Message/SysSmsService.cs

@@ -43,6 +43,24 @@ public class SysSmsService : IDynamicApiController, ITransient
             await TencentSendSms(phoneNumber);
     }
 
+    /// <summary>
+    /// 校验短信验证码
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [DisplayName("校验短信验证码")]
+    public bool VerifyCode(SmsVerifyCodeInput input)
+    {
+        var verifyCode = _sysCacheService.Get<string>($"{CacheConst.KeyPhoneVerCode}{input.Phone}");
+        if (string.IsNullOrWhiteSpace(verifyCode))
+            throw Oops.Oh("验证码不存在或已失效,请重新获取!");
+        if (verifyCode != input.Code)
+            throw Oops.Oh("验证码错误!");
+
+        return true;
+    }
+
     /// <summary>
     /// 阿里云发送短信 📨
     /// </summary>
@@ -197,7 +215,6 @@ public class SysSmsService : IDynamicApiController, ITransient
             SecretId = _smsOptions.Tencentyun.AccessKeyId,
             SecretKey = _smsOptions.Tencentyun.AccessKeySecret
         };
-
         return cred;
     }
 }

+ 21 - 30
Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs

@@ -4,21 +4,6 @@
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
-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;
 
 /// <summary>
@@ -102,25 +87,28 @@ public class SysServerService : IDynamicApiController, ITransient
         var redisAssembly = typeof(Redis).Assembly.GetName();
         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 pdfAssembly = typeof(Magicodes.ExporterAndImporter.Pdf.IPdfExporter).Assembly.GetName();
+        var wordAssembly = typeof(Magicodes.ExporterAndImporter.Word.IWordExporter).Assembly.GetName();
+        var captchaAssembly = typeof(Lazy.Captcha.Core.ICaptcha).Assembly.GetName();
         var wechatApiAssembly = typeof(WechatApiClient).Assembly.GetName();
         var wechatTenpayAssembly = typeof(WechatTenpayClient).Assembly.GetName();
-        var ossAssembly = typeof(IOSSServiceFactory).Assembly.GetName();
+        var ossAssembly = typeof(OnceMi.AspNetCore.OSS.IOSSServiceFactory).Assembly.GetName();
         var parserAssembly = typeof(Parser).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 elasticsearchClientAssembly = typeof(Elastic.Clients.Elasticsearch.ElasticsearchClient).Assembly.GetName();
+        var limitAssembly = typeof(AspNetCoreRateLimit.IpRateLimitMiddleware).Assembly.GetName();
+        var htmlParserAssembly = typeof(AngleSharp.Html.Parser.HtmlParser).Assembly.GetName();
+        var fluentEmailAssembly = typeof(MailKit.Net.Smtp.SmtpClient).Assembly.GetName();
+        var qRCodeGeneratorAssembly = typeof(QRCoder.QRCodeGenerator).Assembly.GetName();
+        var alibabaSendSmsRequestAssembly = typeof(AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest).Assembly.GetName();
+        var tencentSendSmsRequestAssembly = typeof(TencentCloud.Sms.V20190711.Models.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();
+        var ldapConnectionAssembly = typeof(Novell.Directory.Ldap.LdapConnection).Assembly.GetName();
+        var ipToolAssembly = typeof(IPTools.Core.IpTool).Assembly.GetName();
+        var weixinAuthenticationOptionsAssembly = typeof(AspNet.Security.OAuth.Weixin.WeixinAuthenticationOptions).Assembly.GetName();
+        var giteeAuthenticationOptionsAssembly = typeof(AspNet.Security.OAuth.Gitee.GiteeAuthenticationOptions).Assembly.GetName();
+        var hashidsAssembly = typeof(HashidsNet.Hashids).Assembly.GetName();
+        var sftpClientAssembly = typeof(Renci.SshNet.SftpClient).Assembly.GetName();
 
         return new[]
         {
@@ -142,13 +130,16 @@ public class SysServerService : IDynamicApiController, ITransient
             new { htmlParserAssembly.Name, htmlParserAssembly.Version },
             new { fluentEmailAssembly.Name, fluentEmailAssembly.Version },
             new { qRCodeGeneratorAssembly.Name, qRCodeGeneratorAssembly.Version },
-            new { sendSmsRequestAssembly.Name, sendSmsRequestAssembly.Version },
+            new { alibabaSendSmsRequestAssembly.Name, alibabaSendSmsRequestAssembly.Version },
+            new { tencentSendSmsRequestAssembly.Name, tencentSendSmsRequestAssembly.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 },
+            new { hashidsAssembly.Name, hashidsAssembly.Version },
+            new { sftpClientAssembly.Name, sftpClientAssembly.Version },
         };
     }
 }

+ 12 - 12
Admin.NET/Admin.NET.Core/Util/ComputerUtil.cs

@@ -84,9 +84,9 @@ public static class ComputerUtil
                 {
                     DiskName = disk[0],
                     TypeName = ShellHelper.Bash("diskutil info " + disk[0] + " | awk '/File System Personality/ {print $4}'").Replace("\n", string.Empty),
-                    TotalSize = long.Parse(disk[1]) / 1024,
-                    Used = long.Parse(disk[2]) / 1024,
-                    AvailableFreeSpace = long.Parse(disk[3]) / 1024,
+                    TotalSize = Math.Round(long.Parse(disk[1]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
+                    Used = Math.Round(long.Parse(disk[2]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
+                    AvailableFreeSpace = Math.Round(long.Parse(disk[3]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
                     AvailablePercent = decimal.Parse(disk[4].Replace("%", ""))
                 };
                 diskInfos.Add(diskInfo);
@@ -112,9 +112,9 @@ public static class ComputerUtil
                 {
                     DiskName = disk[0],
                     TypeName = disk[1],
-                    TotalSize = long.Parse(disk[2]) / 1024,
-                    Used = long.Parse(disk[3]) / 1024,
-                    AvailableFreeSpace = long.Parse(disk[4]) / 1024,
+                    TotalSize = Math.Round(long.Parse(disk[2]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
+                    Used = Math.Round(long.Parse(disk[3]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
+                    AvailableFreeSpace = Math.Round(long.Parse(disk[4]) / 1024.0m, 2, MidpointRounding.AwayFromZero),
                     AvailablePercent = decimal.Parse(disk[5].Replace("%", ""))
                 };
                 diskInfos.Add(diskInfo);
@@ -130,8 +130,8 @@ public static class ComputerUtil
                 {
                     DiskName = item.Name,
                     TypeName = item.DriveType.ToString(),
-                    TotalSize = item.TotalSize / 1024 / 1024 / 1024,
-                    AvailableFreeSpace = item.AvailableFreeSpace / 1024 / 1024 / 1024,
+                    TotalSize = Math.Round(item.TotalSize / 1024 / 1024 / 1024.0m, 2, MidpointRounding.AwayFromZero),
+                    AvailableFreeSpace = Math.Round(item.AvailableFreeSpace / 1024 / 1024 / 1024.0m, 2, MidpointRounding.AwayFromZero),
                 };
                 obj.Used = obj.TotalSize - obj.AvailableFreeSpace;
                 obj.AvailablePercent = decimal.Ceiling(obj.Used / (decimal)obj.TotalSize * 100);
@@ -294,22 +294,22 @@ public class DiskInfo
     /// <summary>
     /// 总剩余
     /// </summary>
-    public long TotalFree { get; set; }
+    public decimal TotalFree { get; set; }
 
     /// <summary>
     /// 总量
     /// </summary>
-    public long TotalSize { get; set; }
+    public decimal TotalSize { get; set; }
 
     /// <summary>
     /// 已使用
     /// </summary>
-    public long Used { get; set; }
+    public decimal Used { get; set; }
 
     /// <summary>
     /// 可使用
     /// </summary>
-    public long AvailableFreeSpace { get; set; }
+    public decimal AvailableFreeSpace { get; set; }
 
     /// <summary>
     /// 使用百分比

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

@@ -259,6 +259,7 @@ public class Startup : AppStartup
         // 限流组件(在跨域之后)
         app.UseIpRateLimiting();
         app.UseClientRateLimiting();
+        app.UsePolicyRateLimit();
 
         // 任务调度看板
         app.UseScheduleUI(options =>

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

@@ -109,7 +109,7 @@ if (@column.QueryWhether == "Y"){
 } else {
             @:.Select<@(@Model.ClassName)Output>();
 }
-		return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
+		return await query.OrderBuilder(input, "u.").ToPagedListAsync(input.Page, input.PageSize);
     }
 
     /// <summary>

+ 13 - 13
Web/package.json

@@ -2,7 +2,7 @@
 	"name": "admin.net",
 	"type": "module",
 	"version": "2.4.33",
-	"lastBuildTime": "2024.08.19",
+	"lastBuildTime": "2024.08.30",
 	"description": "Admin.NET 站在巨人肩膀上的 .NET 通用权限开发框架",
 	"author": "zuohuaijun",
 	"license": "MIT",
@@ -21,27 +21,27 @@
 		"@vue-office/docx": "^1.6.2",
 		"@vue-office/excel": "^1.7.11",
 		"@vue-office/pdf": "^2.0.2",
-		"@vueuse/core": "^10.11.1",
+		"@vueuse/core": "^11.0.3",
 		"@wangeditor/editor": "^5.1.23",
 		"@wangeditor/editor-for-vue": "^5.1.12",
 		"animate.css": "^4.1.1",
 		"async-validator": "^4.2.5",
-		"axios": "^1.7.4",
+		"axios": "^1.7.5",
 		"countup.js": "^2.8.0",
 		"cropperjs": "^1.6.2",
 		"echarts": "^5.5.1",
 		"echarts-gl": "^2.0.9",
 		"echarts-wordcloud": "^2.1.0",
-		"element-plus": "^2.8.0",
+		"element-plus": "^2.8.1",
 		"ezuikit": "^1.0.0",
-		"ezuikit-js": "^8.0.9",
+		"ezuikit-js": "^8.0.11",
 		"js-cookie": "^3.0.5",
 		"js-table2excel": "^1.1.2",
 		"jsplumb": "^2.15.6",
 		"lodash-es": "^4.17.21",
-		"md-editor-v3": "^4.18.1",
+		"md-editor-v3": "^4.19.2",
 		"mitt": "^3.0.1",
-		"monaco-editor": "^0.50.0",
+		"monaco-editor": "^0.51.0",
 		"mqtt": "^4.3.8",
 		"nprogress": "^0.2.0",
 		"pinia": "^2.2.2",
@@ -75,13 +75,13 @@
 		"@types/node": "^20.14.15",
 		"@types/nprogress": "^0.2.3",
 		"@types/sortablejs": "^1.15.8",
-		"@typescript-eslint/eslint-plugin": "^8.1.0",
-		"@typescript-eslint/parser": "^8.1.0",
-		"@vitejs/plugin-vue": "^5.1.2",
+		"@typescript-eslint/eslint-plugin": "^8.3.0",
+		"@typescript-eslint/parser": "^8.3.0",
+		"@vitejs/plugin-vue": "^5.1.3",
 		"@vitejs/plugin-vue-jsx": "^4.0.1",
 		"@vue/compiler-sfc": "^3.4.38",
-		"code-inspector-plugin": "^0.15.2",
-		"eslint": "^8.57.0",
+		"code-inspector-plugin": "^0.16.0",
+		"eslint": "^9.9.1",
 		"eslint-plugin-vue": "^9.27.0",
 		"globals": "^15.9.0",
 		"less": "^4.2.0",
@@ -90,7 +90,7 @@
 		"sass": "^1.77.8",
 		"terser": "^5.31.6",
 		"typescript": "^5.5.4",
-		"vite": "^5.4.1",
+		"vite": "^5.4.2",
 		"vite-plugin-cdn-import": "^1.0.1",
 		"vite-plugin-compression2": "^1.2.0",
 		"vite-plugin-vue-setup-extend": "^0.4.0",

+ 0 - 75
Web/src/api-services/apis/sys-log-diff-api.ts

@@ -26,49 +26,6 @@ import { PageLogInput } from '../models';
  */
 export const SysLogDiffApiAxiosParamCreator = function (configuration?: Configuration) {
     return {
-        /**
-         * 
-         * @summary 清空差异日志 🔖
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        apiSysLogDiffClearPost: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
-            const localVarPath = `/api/sysLogDiff/clear`;
-            // 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;
-            }
-
-            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 获取差异日志详情 🔖
@@ -175,19 +132,6 @@ export const SysLogDiffApiAxiosParamCreator = function (configuration?: Configur
  */
 export const SysLogDiffApiFp = function(configuration?: Configuration) {
     return {
-        /**
-         * 
-         * @summary 清空差异日志 🔖
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysLogDiffClearPost(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<void>>> {
-            const localVarAxiosArgs = await SysLogDiffApiAxiosParamCreator(configuration).apiSysLogDiffClearPost(options);
-            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
-                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
-                return axios.request(axiosRequestArgs);
-            };
-        },
         /**
          * 
          * @summary 获取差异日志详情 🔖
@@ -225,15 +169,6 @@ export const SysLogDiffApiFp = function(configuration?: Configuration) {
  */
 export const SysLogDiffApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
     return {
-        /**
-         * 
-         * @summary 清空差异日志 🔖
-         * @param {*} [options] Override http request option.
-         * @throws {RequiredError}
-         */
-        async apiSysLogDiffClearPost(options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
-            return SysLogDiffApiFp(configuration).apiSysLogDiffClearPost(options).then((request) => request(axios, basePath));
-        },
         /**
          * 
          * @summary 获取差异日志详情 🔖
@@ -264,16 +199,6 @@ export const SysLogDiffApiFactory = function (configuration?: Configuration, bas
  * @extends {BaseAPI}
  */
 export class SysLogDiffApi extends BaseAPI {
-    /**
-     * 
-     * @summary 清空差异日志 🔖
-     * @param {*} [options] Override http request option.
-     * @throws {RequiredError}
-     * @memberof SysLogDiffApi
-     */
-    public async apiSysLogDiffClearPost(options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
-        return SysLogDiffApiFp(this.configuration).apiSysLogDiffClearPost(options).then((request) => request(this.axios, this.basePath));
-    }
     /**
      * 
      * @summary 获取差异日志详情 🔖

+ 85 - 0
Web/src/api-services/apis/sys-sms-api.ts

@@ -17,6 +17,8 @@ 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 { AdminResultBoolean } from '../models';
+import { SmsVerifyCodeInput } from '../models';
 /**
  * SysSmsApi - axios parameter creator
  * @export
@@ -223,6 +225,54 @@ export const SysSmsApiAxiosParamCreator = function (configuration?: Configuratio
             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 {SmsVerifyCodeInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        apiSysSmsVerifyCodePost: async (body?: SmsVerifyCodeInput, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
+            const localVarPath = `/api/sysSms/verifyCode`;
+            // 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,
@@ -294,6 +344,20 @@ export const SysSmsApiFp = function(configuration?: Configuration) {
                 return axios.request(axiosRequestArgs);
             };
         },
+        /**
+         * 
+         * @summary 校验短信验证码
+         * @param {SmsVerifyCodeInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysSmsVerifyCodePost(body?: SmsVerifyCodeInput, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => Promise<AxiosResponse<AdminResultBoolean>>> {
+            const localVarAxiosArgs = await SysSmsApiAxiosParamCreator(configuration).apiSysSmsVerifyCodePost(body, options);
+            return (axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
+                const axiosRequestArgs :AxiosRequestConfig = {...localVarAxiosArgs.options, url: basePath + localVarAxiosArgs.url};
+                return axios.request(axiosRequestArgs);
+            };
+        },
     }
 };
 
@@ -344,6 +408,16 @@ export const SysSmsApiFactory = function (configuration?: Configuration, basePat
         async apiSysSmsTencentSendSmsPhoneNumberPost(phoneNumber: string, options?: AxiosRequestConfig): Promise<AxiosResponse<void>> {
             return SysSmsApiFp(configuration).apiSysSmsTencentSendSmsPhoneNumberPost(phoneNumber, options).then((request) => request(axios, basePath));
         },
+        /**
+         * 
+         * @summary 校验短信验证码
+         * @param {SmsVerifyCodeInput} [body] 
+         * @param {*} [options] Override http request option.
+         * @throws {RequiredError}
+         */
+        async apiSysSmsVerifyCodePost(body?: SmsVerifyCodeInput, options?: AxiosRequestConfig): Promise<AxiosResponse<AdminResultBoolean>> {
+            return SysSmsApiFp(configuration).apiSysSmsVerifyCodePost(body, options).then((request) => request(axios, basePath));
+        },
     };
 };
 
@@ -399,4 +473,15 @@ export class SysSmsApi extends BaseAPI {
     public async apiSysSmsTencentSendSmsPhoneNumberPost(phoneNumber: string, options?: AxiosRequestConfig) : Promise<AxiosResponse<void>> {
         return SysSmsApiFp(this.configuration).apiSysSmsTencentSendSmsPhoneNumberPost(phoneNumber, options).then((request) => request(this.axios, this.basePath));
     }
+    /**
+     * 
+     * @summary 校验短信验证码
+     * @param {SmsVerifyCodeInput} [body] 
+     * @param {*} [options] Override http request option.
+     * @throws {RequiredError}
+     * @memberof SysSmsApi
+     */
+    public async apiSysSmsVerifyCodePost(body?: SmsVerifyCodeInput, options?: AxiosRequestConfig) : Promise<AxiosResponse<AdminResultBoolean>> {
+        return SysSmsApiFp(this.configuration).apiSysSmsVerifyCodePost(body, options).then((request) => request(this.axios, this.basePath));
+    }
 }

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

@@ -283,6 +283,7 @@ export * from './send-subscribe-message-input';
 export * from './serialization-format';
 export * from './signature-input';
 export * from './sm-key-pair-output';
+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';

+ 34 - 2
Web/src/api-services/models/message-input.ts

@@ -22,12 +22,20 @@ import { MessageTypeEnum } from './message-type-enum';
 export interface MessageInput {
 
     /**
-     * 用户ID
+     * 接收者用户Id
      *
      * @type {number}
      * @memberof MessageInput
      */
-    userId?: number;
+    receiveUserId?: number;
+
+    /**
+     * 接收者名称
+     *
+     * @type {string}
+     * @memberof MessageInput
+     */
+    receiveUserName?: string | null;
 
     /**
      * 用户ID列表
@@ -58,4 +66,28 @@ export interface MessageInput {
      * @memberof MessageInput
      */
     message?: string | null;
+
+    /**
+     * 发送者Id
+     *
+     * @type {string}
+     * @memberof MessageInput
+     */
+    sendUserId?: string | null;
+
+    /**
+     * 发送者名称
+     *
+     * @type {string}
+     * @memberof MessageInput
+     */
+    sendUserName?: string | null;
+
+    /**
+     * 发送时间
+     *
+     * @type {Date}
+     * @memberof MessageInput
+     */
+    sendTime?: Date;
 }

+ 40 - 0
Web/src/api-services/models/sms-verify-code-input.ts

@@ -0,0 +1,40 @@
+/* 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 SmsVerifyCodeInput
+ */
+export interface SmsVerifyCodeInput {
+
+    /**
+     * 手机号码
+     *
+     * @type {string}
+     * @memberof SmsVerifyCodeInput
+     * @example admin
+     */
+    phone: string;
+
+    /**
+     * 验证码
+     *
+     * @type {string}
+     * @memberof SmsVerifyCodeInput
+     * @example 123456
+     */
+    code: string;
+}

+ 6 - 0
Web/src/components/iconSelector/index.vue

@@ -210,3 +210,9 @@ watch(
 	}
 );
 </script>
+
+<style scoped>
+:deep(.el-tabs__nav) {
+	float: right !important;
+}
+</style>

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

@@ -93,7 +93,7 @@ export default {
 		dropdownSmall: '小型',
 		dropdown1: '首页',
 		dropdown2: '个人中心',
-		dropdown3: '404',
+		dropdown3: '清理缓存',
 		dropdown4: '401',
 		dropdown5: '退出登录',
 		dropdown6: '代码仓库',

+ 3 - 1
Web/src/stores/keepAliveNames.ts

@@ -22,7 +22,9 @@ export const useKeepALiveNames = defineStore('keepALiveNames', {
 		},
 		async delCachedView(view: any) {
 			const index = this.cachedViews.indexOf(view.name);
-			index > -1 && this.cachedViews.splice(index, 1);
+			setTimeout(() => {
+				index > -1 && this.cachedViews.splice(index, 1);
+			}, 20);
 		},
 		async delOthersCachedViews(view: any) {
 			if (view.meta.isKeepAlive) this.cachedViews = [view.name];

+ 1 - 1
Web/src/utils/toolsValidate.ts

@@ -190,7 +190,7 @@ export function verifyNumberCnUppercase(val: any, unit = '仟佰拾亿仟佰拾
  */
 export function verifyPhone(val: string) {
 	// false: 手机号码不正确
-	if (!/^((12[0-9])|(13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0|1,5-9]))\d{8}$/.test(val)) return false;
+	if (!/^1[3456789][0-9]{9}$/.test(val)) return false;
 	// true: 手机号码正确
 	else return true;
 }

+ 1 - 1
Web/src/views/system/cache/index.vue

@@ -134,7 +134,7 @@ const clearCache = () => {
 		type: 'warning',
 	})
 		.then(async () => {
-			await getAPI(SysCacheApi).apiSysCacheClearDelete();
+			await getAPI(SysCacheApi).apiSysCacheClearPost();
 			await handleQuery();
 			state.cacheValue = undefined;
 			state.cacheKey = undefined;

+ 3 - 3
Web/src/views/system/dict/index.vue

@@ -120,15 +120,15 @@
 						</el-table-column>
 						<el-table-column label="操作" width="120" fixed="right" align="center" show-overflow-tooltip>
 							<template #default="scope">
-								<el-tooltip content="复制">
-									<el-button icon="ele-CopyDocument" size="small" text type="primary" @click="openCopyDictData(scope.row)"> </el-button>
-								</el-tooltip>
 								<el-tooltip content="编辑">
 									<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditDictData(scope.row)"> </el-button>
 								</el-tooltip>
 								<el-tooltip content="删除">
 									<el-button icon="ele-Delete" size="small" text type="danger" @click="delDictData(scope.row)"> </el-button>
 								</el-tooltip>
+								<el-tooltip content="复制">
+									<el-button icon="ele-CopyDocument" size="small" text type="primary" @click="openCopyDictData(scope.row)"> </el-button>
+								</el-tooltip>
 							</template>
 						</el-table-column>
 					</el-table>

+ 0 - 14
Web/src/views/system/log/difflog/index.vue

@@ -14,9 +14,6 @@
 						<el-button icon="ele-Refresh" @click="resetQuery"> 重置 </el-button>
 					</el-button-group>
 				</el-form-item>
-				<el-form-item>
-					<el-button icon="ele-DeleteFilled" type="danger" @click="clearLog" v-auth="'sysDifflog:clear'"> 清空 </el-button>
-				</el-form-item>
 			</el-form>
 		</el-card>
 
@@ -50,7 +47,6 @@
 
 <script lang="ts" setup name="sysDiffLog">
 import { onMounted, reactive } from 'vue';
-import { ElMessage } from 'element-plus';
 
 import { getAPI } from '/@/utils/axios-utils';
 import { SysLogDiffApi } from '/@/api-services/api';
@@ -94,16 +90,6 @@ const resetQuery = () => {
 	handleQuery();
 };
 
-// 清空日志
-const clearLog = async () => {
-	state.loading = true;
-	await getAPI(SysLogDiffApi).apiSysLogDiffClearPost();
-	state.loading = false;
-
-	ElMessage.success('清空成功');
-	handleQuery();
-};
-
 // 改变页面容量
 const handleSizeChange = (val: number) => {
 	state.tableParams.pageSize = val;

+ 9 - 9
Web/src/views/system/menu/index.vue

@@ -56,9 +56,9 @@
 				</el-table-column>
 				<el-table-column label="操作" width="210" fixed="right" align="center" show-overflow-tooltip>
 					<template #default="scope">
-						<el-button icon="ele-CopyDocument" size="small" text type="primary" @click="openCopyMenu(scope.row)" v-auth="'sysMenu:add'"> 复制 </el-button>
-						<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditMenu(scope.row)" v-auth="'sysMenu:update'"> 编辑 </el-button>
-						<el-button icon="ele-Delete" size="small" text type="danger" @click="delMenu(scope.row)" v-auth="'sysMenu:delete'"> 删除 </el-button>
+						<el-button icon="ele-Edit" text type="primary" @click="openEditMenu(scope.row)" v-auth="'sysMenu:update'"> 编辑 </el-button>
+						<el-button icon="ele-Delete" text type="danger" @click="delMenu(scope.row)" v-auth="'sysMenu:delete'"> 删除 </el-button>
+						<el-button icon="ele-CopyDocument" text type="primary" @click="openCopyMenu(scope.row)" v-auth="'sysMenu:add'"> 复制 </el-button>
 					</template>
 				</el-table-column>
 			</el-table>
@@ -114,6 +114,12 @@ const openAddMenu = () => {
 	editMenuRef.value?.openDialog({ type: 2, isHide: false, isKeepAlive: true, isAffix: false, isIframe: false, status: 1, orderNo: 100 });
 };
 
+// 打开编辑页面
+const openEditMenu = (row: any) => {
+	state.editMenuTitle = '编辑菜单';
+	editMenuRef.value?.openDialog(row);
+};
+
 // 打开复制页面
 const openCopyMenu = (row: any) => {
 	state.editMenuTitle = '复制菜单';
@@ -123,12 +129,6 @@ const openCopyMenu = (row: any) => {
 	editMenuRef.value?.openDialog(copyRow);
 };
 
-// 打开编辑页面
-const openEditMenu = (row: any) => {
-	state.editMenuTitle = '编辑菜单';
-	editMenuRef.value?.openDialog(row);
-};
-
 // 删除当前行
 const delMenu = (row: any) => {
 	ElMessageBox.confirm(`确定删除菜单:【${row.title}】?`, '提示', {

+ 10 - 10
Web/src/views/system/org/index.vue

@@ -50,9 +50,9 @@
 						</el-table-column>
 						<el-table-column label="操作" width="210" fixed="right" align="center" show-overflow-tooltip>
 							<template #default="scope">
-								<el-button icon="ele-CopyDocument" size="small" text type="primary" @click="openCopyOrg(scope.row)" v-auth="'sysOrg:add'"> 复制 </el-button>
-								<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditOrg(scope.row)" v-auth="'sysOrg:update'"> 编辑 </el-button>
-								<el-button icon="ele-Delete" size="small" text type="danger" @click="delOrg(scope.row)" v-auth="'sysOrg:delete'"> 删除 </el-button>
+								<el-button icon="ele-Edit" text type="primary" @click="openEditOrg(scope.row)" v-auth="'sysOrg:update'"> 编辑 </el-button>
+								<el-button icon="ele-Delete" text type="danger" @click="delOrg(scope.row)" v-auth="'sysOrg:delete'"> 删除 </el-button>
+								<el-button icon="ele-CopyDocument" text type="primary" @click="openCopyOrg(scope.row)" v-auth="'sysOrg:add'"> 复制 </el-button>
 							</template>
 						</el-table-column>
 					</el-table>
@@ -135,21 +135,21 @@ const openAddOrg = () => {
 	editOrgRef.value?.openDialog({ status: 1, orderNo: 100 });
 };
 
+// 打开编辑页面
+const openEditOrg = (row: any) => {
+	state.editOrgTitle = '编辑机构';
+	editOrgRef.value?.openDialog(row);
+};
+
 // 打开复制页面
 const openCopyOrg = (row: any) => {
-	state.editOrgTitle = '复制菜单';
+	state.editOrgTitle = '复制机构';
 	var copyRow = JSON.parse(JSON.stringify(row)) as UpdateOrgInput;
 	copyRow.id = 0;
 	copyRow.name = '';
 	editOrgRef.value?.openDialog(copyRow);
 };
 
-// 打开编辑页面
-const openEditOrg = (row: any) => {
-	state.editOrgTitle = '编辑机构';
-	editOrgRef.value?.openDialog(row);
-};
-
 // 删除
 const delOrg = (row: any) => {
 	ElMessageBox.confirm(`确定删除机构:【${row.name}】?`, '提示', {

+ 14 - 4
Web/src/views/system/pos/index.vue

@@ -37,10 +37,11 @@
 						<ModifyRecord :data="scope.row" />
 					</template>
 				</el-table-column>
-				<el-table-column label="操作" width="140" fixed="right" align="center" show-overflow-tooltip>
+				<el-table-column label="操作" width="210" fixed="right" align="center" show-overflow-tooltip>
 					<template #default="scope">
-						<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditPos(scope.row)" v-auth="'sysPos:update'"> 编辑 </el-button>
-						<el-button icon="ele-Delete" size="small" text type="danger" @click="delPos(scope.row)" v-auth="'sysPos:delete'"> 删除 </el-button>
+						<el-button icon="ele-Edit" text type="primary" @click="openEditPos(scope.row)" v-auth="'sysPos:update'"> 编辑 </el-button>
+						<el-button icon="ele-Delete" text type="danger" @click="delPos(scope.row)" v-auth="'sysPos:delete'"> 删除 </el-button>
+						<el-button icon="ele-CopyDocument" text type="primary" @click="openCopyMenu(scope.row)" v-auth="'sysPos:add'"> 复制 </el-button>
 					</template>
 				</el-table-column>
 			</el-table>
@@ -58,7 +59,7 @@ import ModifyRecord from '/@/components/table/modifyRecord.vue';
 
 import { getAPI } from '/@/utils/axios-utils';
 import { SysPosApi } from '/@/api-services/api';
-import { SysPos } from '/@/api-services/models';
+import { SysPos, UpdatePosInput } from '/@/api-services/models';
 
 const editPosRef = ref<InstanceType<typeof EditPos>>();
 const state = reactive({
@@ -102,6 +103,15 @@ const openEditPos = (row: any) => {
 	editPosRef.value?.openDialog(row);
 };
 
+// 打开复制页面
+const openCopyMenu = (row: any) => {
+	state.title = '复制职位';
+	var copyRow = JSON.parse(JSON.stringify(row)) as UpdatePosInput;
+	copyRow.id = 0;
+	copyRow.name = '';
+	editPosRef.value?.openDialog(copyRow);
+};
+
 // 删除
 const delPos = (row: any) => {
 	ElMessageBox.confirm(`确定删除职位:【${row.name}】?`, '提示', {

+ 1 - 0
Web/src/views/system/print/component/editPrint.vue

@@ -122,6 +122,7 @@ const loadTemplate = () => {
 	hiprintDesignRef.value?.hiprintTemplate.clear();
 	if (JSON.stringify(state.ruleForm) !== '{}') {
 		hiprintDesignRef.value?.hiprintTemplate.update(JSON.parse(state.ruleForm.template));
+		hiprintDesignRef.value?.initPaper();
 	}
 };
 

+ 18 - 8
Web/src/views/system/print/component/hiprint/index.vue

@@ -70,19 +70,19 @@
 
 	<el-row :gutter="8">
 		<el-col :span="4">
-			<el-card style="height: 100%" shadow="never">
+			<el-card style="height: 100%" shadow="never" :body-style="{ padding: '0px 0 5px 7px' }">
 				<el-row>
 					<el-col :span="24" id="hiprintEpContainer" class="rect-printElement-types hiprintEpContainer"> </el-col>
 				</el-row>
 			</el-card>
 		</el-col>
 		<el-col :span="14">
-			<el-card shadow="never" class="card-design">
+			<el-card shadow="never" class="card-design" :body-style="{ padding: '18px' }">
 				<div id="hiprint-printTemplate" class="hiprint-printTemplate"></div>
 			</el-card>
 		</el-col>
 		<el-col :span="6" class="params_setting_container">
-			<el-card shadow="never">
+			<el-card shadow="never" :body-style="{ padding: '0px' }">
 				<el-row class="hinnn-layout-sider">
 					<div id="PrintElementOptionSetting"></div>
 				</el-row>
@@ -163,13 +163,14 @@ const state = reactive({
 
 // 计算当前纸张类型
 const curPaperType = computed(() => {
+	let { width, height } = state.curPaper;
 	let type = 'other';
 	let types: any = state.paperTypes;
 	for (const key in types) {
 		let item = types[key];
-		let { width, height } = state.curPaper;
 		if (item.width === width && item.height === height) {
 			type = key;
+			break;
 		}
 	}
 	return type;
@@ -181,7 +182,6 @@ const changeMode = () => {
 	hiprint.init({
 		providers: [provider.f],
 	});
-
 	// 渲染自定义选项
 	const hiprintEpContainerEl = document.getElementById('hiprintEpContainer');
 	if (hiprintEpContainerEl) {
@@ -212,7 +212,7 @@ const changeMode = () => {
 	});
 	hiprintTemplate.value.design('#hiprint-printTemplate');
 	// 获取当前放大比例, 当zoom时传true才会有
-	state.scaleValue = hiprintTemplate.value.editingPanel.scale || 1;
+	state.scaleValue = hiprintTemplate.value.editingPanel?.scale ?? 1;
 };
 
 /**
@@ -239,7 +239,7 @@ const changeScale = (currentValue: number, oldValue: number) => {
 	let big = false;
 	currentValue <= oldValue ? (big = false) : (big = true);
 
-	let scaleVal = state.scaleValue;
+	let scaleVal = currentValue;
 	if (big) {
 		if (scaleVal > state.scaleMax) scaleVal = 5;
 	} else {
@@ -322,8 +322,18 @@ onMounted(() => {
 	// otherPaper(); // 默认纸张
 });
 
+// 初始化纸张大小
+const initPaper = () => {
+	var template = hiprintTemplate.value.getJson();
+	var width = template.panels[0].width;
+	var height = template.panels[0].height;
+
+	state.curPaper = { type: '', width: width, height: height }; // 计算纸张类型和状态
+	hiprintTemplate.value.setPaper(width, height); // 设置纸张大小
+};
+
 // 导出对象
-defineExpose({ hiprintTemplate });
+defineExpose({ hiprintTemplate, initPaper });
 </script>
 
 <style lang="scss" scoped>

+ 22 - 13
Web/src/views/system/user/index.vue

@@ -77,19 +77,19 @@
 								<ModifyRecord :data="scope.row" />
 							</template>
 						</el-table-column>
-						<el-table-column label="操作" width="130" align="center" fixed="right" show-overflow-tooltip>
+						<el-table-column label="操作" width="300" align="center" fixed="right" show-overflow-tooltip>
 							<template #default="scope">
-								<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditUser(scope.row)" v-auth="'sysUser:update'"> 编辑 </el-button>
-								<el-dropdown>
-									<el-button icon="ele-MoreFilled" size="small" text type="primary" style="padding-left: 12px" />
-									<template #dropdown>
-										<el-dropdown-menu>
-											<el-dropdown-item icon="ele-RefreshLeft" @click="resetUserPwd(scope.row)" :disabled="!auth('sysUser:resetPwd')"> 重置密码 </el-dropdown-item>
-											<el-dropdown-item icon="ele-Unlock" @click="unlockLogin(scope.row)" divided :disabled="!auth('sysUser:unlockLogin')"> 解除锁定 </el-dropdown-item>
-											<el-dropdown-item icon="ele-Delete" @click="delUser(scope.row)" divided :disabled="!auth('sysUser:delete')"> 删除账号 </el-dropdown-item>
-										</el-dropdown-menu>
-									</template>
-								</el-dropdown>
+								<el-tooltip content="编辑" placement="top">
+									<el-button icon="ele-Edit" text type="primary" v-auth="'sysUser:update'" @click="openEditUser(scope.row)"> </el-button>
+								</el-tooltip>
+								<el-tooltip content="删除" placement="top">
+									<el-button icon="ele-Delete" text type="danger" v-auth="'sysUser:delete'" @click="delUser(scope.row)"> </el-button>
+								</el-tooltip>
+								<el-tooltip content="复制" placement="top">
+									<el-button icon="ele-CopyDocument" text type="primary" v-auth="'sysUser:add'" @click="openCopyMenu(scope.row)"> </el-button>
+								</el-tooltip>
+								<el-button icon="ele-RefreshLeft" text type="danger" v-auth="'sysUser:resetPwd'" @click="resetUserPwd(scope.row)">重置密码</el-button>
+								<el-button icon="ele-Unlock" text type="primary" v-auth="'sysUser:unlockLogin'" @click="unlockLogin(scope.row)">解除锁定</el-button>
 							</template>
 						</el-table-column>
 					</el-table>
@@ -126,7 +126,7 @@ import 'splitpanes/dist/splitpanes.css';
 
 import { getAPI } from '/@/utils/axios-utils';
 import { SysUserApi, SysOrgApi } from '/@/api-services/api';
-import { SysUser, SysOrg } from '/@/api-services/models';
+import { SysUser, SysOrg, UpdateUserInput } from '/@/api-services/models';
 
 const orgTreeRef = ref<InstanceType<typeof OrgTree>>();
 const editUserRef = ref<InstanceType<typeof EditUser>>();
@@ -194,6 +194,15 @@ const openEditUser = (row: any) => {
 	editUserRef.value?.openDialog(row);
 };
 
+// 打开复制页面
+const openCopyMenu = (row: any) => {
+	state.editUserTitle = '复制账号';
+	var copyRow = JSON.parse(JSON.stringify(row)) as UpdateUserInput;
+	copyRow.id = 0;
+	copyRow.account = '';
+	editUserRef.value?.openDialog(copyRow);
+};
+
 // 删除
 const delUser = (row: any) => {
 	ElMessageBox.confirm(`确定删除账号:【${row.account}】?`, '提示', {