Ver código fonte

优化样式细节,修复ts错误提示

夜鹰 7 meses atrás
pai
commit
b9f315a6ce
41 arquivos alterados com 2922 adições e 302 exclusões
  1. 9 0
      Admin.NET/Admin.NET.Application/Configuration/DeepSeek.example
  2. 9 9
      Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj
  3. 5 0
      Admin.NET/Admin.NET.Core/Const/ClaimConst.cs
  4. 86 0
      Admin.NET/Admin.NET.Core/Entity/SysLang.cs
  5. 44 0
      Admin.NET/Admin.NET.Core/Entity/SysLangText.cs
  6. 6 0
      Admin.NET/Admin.NET.Core/Entity/SysUser.cs
  7. 26 0
      Admin.NET/Admin.NET.Core/Enum/DirectionEnum.cs
  8. 56 0
      Admin.NET/Admin.NET.Core/Enum/WeekEnum.cs
  9. 40 0
      Admin.NET/Admin.NET.Core/Extension/SqlSugarExtension.cs
  10. 25 0
      Admin.NET/Admin.NET.Core/Option/DeepSeekOptions.cs
  11. 112 0
      Admin.NET/Admin.NET.Core/SeedData/SysLangSeedData.cs
  12. 20 6
      Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs
  13. 5 0
      Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginUserOutput.cs
  14. 3 1
      Admin.NET/Admin.NET.Core/Service/Auth/SysAuthService.cs
  15. 20 6
      Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs
  16. 5 4
      Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs
  17. 23 0
      Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataOutput.cs
  18. 39 4
      Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs
  19. 93 16
      Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs
  20. 108 0
      Admin.NET/Admin.NET.Core/Service/Lang/Dto/SysLangDto.cs
  21. 413 0
      Admin.NET/Admin.NET.Core/Service/Lang/Dto/SysLangInput.cs
  22. 117 0
      Admin.NET/Admin.NET.Core/Service/Lang/Dto/SysLangOutput.cs
  23. 113 0
      Admin.NET/Admin.NET.Core/Service/Lang/SysLangService.cs
  24. 73 0
      Admin.NET/Admin.NET.Core/Service/LangText/Dto/SysLangTextDto.cs
  25. 274 0
      Admin.NET/Admin.NET.Core/Service/LangText/Dto/SysLangTextInput.cs
  26. 82 0
      Admin.NET/Admin.NET.Core/Service/LangText/Dto/SysLangTextOutput.cs
  27. 316 0
      Admin.NET/Admin.NET.Core/Service/LangText/SysLangTextCacheService.cs
  28. 435 0
      Admin.NET/Admin.NET.Core/Service/LangText/SysLangTextService.cs
  29. 84 16
      Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs
  30. 3 1
      Admin.NET/Admin.NET.Core/Service/Org/Dto/OrgTreeOutput.cs
  31. 0 2
      Admin.NET/Admin.NET.Core/Service/Server/SysServerService.cs
  32. 15 0
      Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs
  33. 2 0
      Admin.NET/Admin.NET.Core/Service/User/UserManager.cs
  34. 4 4
      Admin.NET/Admin.NET.Test/Admin.NET.Test.csproj
  35. 1 0
      Admin.NET/Admin.NET.Web.Core/ProjectOptions.cs
  36. 2 2
      Web/src/components/table/index.vue
  37. 3 0
      Web/src/theme/app.scss
  38. 112 114
      Web/src/views/home/notice/index.vue
  39. 23 1
      Web/src/views/system/database/index.vue
  40. 114 114
      Web/src/views/system/notice/index.vue
  41. 2 2
      Web/src/views/system/tenantConfig/index.vue

+ 9 - 0
Admin.NET/Admin.NET.Application/Configuration/DeepSeek.example

@@ -0,0 +1,9 @@
+{
+  "$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
+
+  "DeepSeekSettings": {
+    "SourceLang": "zh-cn",
+    "ApiUrl": "https://api.deepseek.com/v1/chat/completions",
+    "ApiKey": "你的 API KEY"
+  }
+}

+ 9 - 9
Admin.NET/Admin.NET.Core/Admin.NET.Core.csproj

@@ -14,19 +14,19 @@
 
   <ItemGroup>
     <PackageReference Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="4.0.0" />
-    <PackageReference Include="AlipaySDKNet.Standard" Version="4.9.712" />
+    <PackageReference Include="AlipaySDKNet.Standard" Version="4.9.751" />
     <PackageReference Include="AngleSharp" Version="1.3.0" />
     <PackageReference Include="AspectCore.Extensions.Reflection" Version="2.4.0" />
     <PackageReference Include="AspNetCoreRateLimit" Version="5.0.0" />
-    <PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.1.0" />
-    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.108" />
-    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.108" />
-    <PackageReference Include="Furion.Pure" Version="4.9.7.108" />
+    <PackageReference Include="Elastic.Clients.Elasticsearch" Version="9.1.6" />
+    <PackageReference Include="Furion.Extras.Authentication.JwtBearer" Version="4.9.7.114" />
+    <PackageReference Include="Furion.Extras.ObjectMapper.Mapster" Version="4.9.7.114" />
+    <PackageReference Include="Furion.Pure" Version="4.9.7.114" />
     <PackageReference Include="Hardware.Info" Version="101.0.1.1" />
     <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="log4net" Version="3.1.0" />
+    <PackageReference Include="log4net" Version="3.2.0" />
     <PackageReference Include="Magicodes.IE.Excel" Version="2.7.6" />
     <PackageReference Include="Magicodes.IE.Pdf" Version="2.7.6" />
     <PackageReference Include="Magicodes.IE.Word" Version="2.7.6" />
@@ -38,11 +38,11 @@
     <PackageReference Include="OnceMi.AspNetCore.OSS" Version="1.2.0" />
     <PackageReference Include="QRCoder" Version="1.6.0" />
     <PackageReference Include="RabbitMQ.Client" Version="7.1.2" />
-    <PackageReference Include="SixLabors.ImageSharp.Web" Version="3.1.5" />
+    <PackageReference Include="SixLabors.ImageSharp.Web" Version="3.2.0" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.Api" Version="3.11.0" />
     <PackageReference Include="SKIT.FlurlHttpClient.Wechat.TenpayV3" Version="3.13.0" />
-    <PackageReference Include="SqlSugar.MongoDbCore" Version="5.1.4.247" />
-    <PackageReference Include="SqlSugarCore" Version="5.1.4.199" />
+    <PackageReference Include="SqlSugar.MongoDbCore" Version="5.1.4.255" />
+    <PackageReference Include="SqlSugarCore" Version="5.1.4.200" />
     <PackageReference Include="SSH.NET" Version="2025.0.0" />
     <PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.7" />
     <PackageReference Include="System.Net.Http" Version="4.3.4" />

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

@@ -65,4 +65,9 @@ public class ClaimConst
     /// 登录模式PC、APP
     /// </summary>
     public const string LoginMode = "LoginMode";
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public const string LangCode = "LangCode";
 }

+ 86 - 0
Admin.NET/Admin.NET.Core/Entity/SysLang.cs

@@ -0,0 +1,86 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+[SugarTable(null, "语言配置")]
+[SysTable]
+[SugarIndex("index_{table}_N", nameof(Name), OrderByType.Asc)]
+[SugarIndex("index_{table}_C", nameof(Code), OrderByType.Asc)]
+public class SysLang : EntityBase
+{
+    /// <summary>
+    /// 语言名称
+    /// </summary>
+    [SugarColumn(ColumnDescription = "语言名称")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 语言代码(如 zh_CN)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "语言代码")]
+    public string Code { get; set; }
+
+    /// <summary>
+    /// ISO 语言代码
+    /// </summary>
+    [SugarColumn(ColumnDescription = "ISO 语言代码")]
+    public string IsoCode { get; set; }
+
+    /// <summary>
+    /// URL 语言代码
+    /// </summary>
+    [SugarColumn(ColumnDescription = "URL 语言代码")]
+    public string UrlCode { get; set; }
+
+    /// <summary>
+    /// 书写方向(1=从左到右,2=从右到左)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "书写方向", DefaultValue = "1")]
+    public DirectionEnum Direction { get; set; } = DirectionEnum.Ltr;
+
+    /// <summary>
+    /// 日期格式(如 YYYY-MM-DD)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "日期格式")]
+    public string DateFormat { get; set; }
+
+    /// <summary>
+    /// 时间格式(如 HH:MM:SS)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "时间格式")]
+    public string TimeFormat { get; set; }
+
+    /// <summary>
+    /// 每周起始日(如 0=星期日,1=星期一)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "每周起始日", DefaultValue = "7")]
+    public WeekEnum WeekStart { get; set; } = WeekEnum.Sunday;
+
+    /// <summary>
+    /// 分组符号(如 ,)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "分组符号")]
+    public string Grouping { get; set; }
+
+    /// <summary>
+    /// 小数点符号
+    /// </summary>
+    [SugarColumn(ColumnDescription = "小数点符号")]
+    public string DecimalPoint { get; set; }
+
+    /// <summary>
+    /// 千分位分隔符
+    /// </summary>
+    [SugarColumn(ColumnDescription = "千分位分隔符")]
+    public string? ThousandsSep { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    [SugarColumn(ColumnDescription = "是否启用")]
+    public bool Active { get; set; }
+}

+ 44 - 0
Admin.NET/Admin.NET.Core/Entity/SysLangText.cs

@@ -0,0 +1,44 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+[SugarTable(null, "翻译表")]
+[SysTable]
+[SugarIndex("index_{table}_N", nameof(EntityName), OrderByType.Asc)]
+[SugarIndex("index_{table}_F", nameof(FieldName), OrderByType.Asc)]
+public class SysLangText : EntityBase
+{
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "所属实体名")]
+    public string EntityName { get; set; }
+
+    /// <summary>
+    /// 语言代码(如 zh_CN)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "所属实体ID")]
+    public long EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    [SugarColumn(ColumnDescription = "字段名")]
+    public string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [SugarColumn(ColumnDescription = "语言代码")]
+    public string LangCode { get; set; }
+
+    /// <summary>
+    /// 多语言内容
+    /// </summary>
+    [SugarColumn(ColumnDescription = "翻译内容")]
+    public string Content { get; set; }
+}

+ 6 - 0
Admin.NET/Admin.NET.Core/Entity/SysUser.cs

@@ -314,6 +314,12 @@ public partial class SysUser : EntityBaseTenantOrg
     public string? Signature { get; set; }
 
     /// <summary>
+    /// 语言代码(如 zh_CN)
+    /// </summary>
+    [SugarColumn(ColumnDescription = "语言代码")]
+    public string LangCode { get; set; } = "zh_CN";
+
+    /// <summary>
     /// 验证超级管理员类型,若账号类型为超级管理员则报错
     /// </summary>
     /// <param name="errorMsg">自定义错误消息</param>

+ 26 - 0
Admin.NET/Admin.NET.Core/Enum/DirectionEnum.cs

@@ -0,0 +1,26 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 书写方向枚举
+/// </summary>
+[Description("书写方向枚举")]
+public enum DirectionEnum
+{
+    /// <summary>
+    /// 从左到右
+    /// </summary>
+    [Description("从左到右")]
+    Ltr = 1,
+
+    /// <summary>
+    /// 从右到左
+    /// </summary>
+    [Description("从右到左")]
+    Rtl = 2
+}

+ 56 - 0
Admin.NET/Admin.NET.Core/Enum/WeekEnum.cs

@@ -0,0 +1,56 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 周枚举
+/// </summary>
+[Description("周枚举")]
+public enum WeekEnum
+{
+    /// <summary>
+    /// 周一
+    /// </summary>
+    [Description("周一")]
+    Monday = 1,
+
+    /// <summary>
+    /// 周二
+    /// </summary>
+    [Description("周二")]
+    Tuesday = 2,
+
+    /// <summary>
+    /// 周三
+    /// </summary>
+    [Description("周三")]
+    Wednesday = 3,
+
+    /// <summary>
+    /// 周四
+    /// </summary>
+    [Description("周四")]
+    Thursday = 4,
+
+    /// <summary>
+    /// 周五
+    /// </summary>
+    [Description("周五")]
+    Friday = 5,
+
+    /// <summary>
+    /// 周六
+    /// </summary>
+    [Description("周六")]
+    Saturday = 6,
+
+    /// <summary>
+    /// 周日
+    /// </summary>
+    [Description("周日")]
+    Sunday = 7,
+}

+ 40 - 0
Admin.NET/Admin.NET.Core/Extension/SqlSugarExtension.cs

@@ -324,4 +324,44 @@ public static class SqlSugarExtension
     }
 
     #endregion 视图操作
+
+    /// <summary>
+    /// 列表转换为树形结构
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    /// <param name="source">列表数据</param>
+    /// <param name="childrenSelector">设置子节点列表。例如:item => item.Children</param>
+    /// <param name="parentIdSelector">设置元素的父级 Id。例如:item => item.ParentId</param>
+    /// <param name="rootParentId">根节点的父级 Id,默认为 0 </param>
+    /// <returns></returns>
+    /// <exception cref="Exception"></exception>
+    public static IEnumerable<T> ToTree<T>(
+       this IEnumerable<T> source,
+       Func<T, List<T>> childrenSelector,
+       Func<T, long> parentIdSelector,
+       int rootParentId)
+       where T : class
+    {
+        var lookup = source.ToLookup(parentIdSelector);
+        List<T> BuildTree(long parentId)
+        {
+            return lookup[parentId].Select(item =>
+            {
+                var children = BuildTree(GetId(item));
+                childrenSelector(item).Clear();
+                childrenSelector(item).AddRange(children);
+                return item;
+            }).ToList();
+        }
+
+        // 需要提供获取Id的方法,可以用反射或者自己传参数
+        long GetId(T item)
+        {
+            var prop = typeof(T).GetProperty("Id");
+            if (prop == null) throw new Exception("没有找到Id属性");
+            return (long)prop.GetValue(item);
+        }
+
+        return BuildTree(rootParentId);
+    }
 }

+ 25 - 0
Admin.NET/Admin.NET.Core/Option/DeepSeekOptions.cs

@@ -0,0 +1,25 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+public sealed class DeepSeekOptions : IConfigurableOptions
+{
+    /// <summary>
+    /// 源语言
+    /// </summary>
+    public string SourceLang { get; set; }
+
+    /// <summary>
+    /// Api地址
+    /// </summary>
+    public string ApiUrl { get; set; }
+
+    /// <summary>
+    /// API KEY
+    /// </summary>
+    public string ApiKey { get; set; }
+}

+ 112 - 0
Admin.NET/Admin.NET.Core/SeedData/SysLangSeedData.cs

@@ -0,0 +1,112 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 系统字典类型表种子数据
+/// </summary>
+public class SysLangSeedData : ISqlSugarEntitySeedData<SysLang>
+{
+    /// <summary>
+    /// 种子数据
+    /// </summary>
+    /// <returns></returns>
+    public IEnumerable<SysLang> HasData()
+    {
+        return new[]
+        {
+            new SysLang{ Id=1300000000001,Name="Chinese (Simplified) / 简体中文", Code="zh_CN", IsoCode="zh_CN", UrlCode="zh-cn", Direction=DirectionEnum.Ltr, DateFormat="%Y年%m月%d日", TimeFormat="%H时%M分%S秒",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=true},
+            new SysLang{ Id=1300000000002,Name="Chinese (HK) / 繁體中文", Code="zh_HK", IsoCode="zh_HK", UrlCode="zh-hk", Direction=DirectionEnum.Ltr, DateFormat="%Y年%m月%d日 %A", TimeFormat="%I時%M分%S秒",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=true},
+            new SysLang{ Id=1300000000003,Name="Chinese (Traditional) / 繁體中文", Code="zh_TW", IsoCode="zh_TW", UrlCode="zh-tw", Direction=DirectionEnum.Ltr, DateFormat="%Y年%m月%d日", TimeFormat="%H時%M分%S秒",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=true},
+            new SysLang{ Id=1300000000004,Name="Italian / Italiano", Code="it_IT", IsoCode="it", UrlCode="it", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=true},
+            new SysLang{ Id=1300000000005,Name="English (US)", Code="en_US", IsoCode="en", UrlCode="en", Direction=DirectionEnum.Ltr, DateFormat="%m/%d/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=true},
+            new SysLang{ Id=1300000000006,Name="Amharic / አምሃርኛ", Code="am_ET", IsoCode="am_ET", UrlCode="am-et", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%I:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000007,Name="Arabic / الْعَرَبيّة", Code="ar_001", IsoCode="ar", UrlCode="ar", Direction=DirectionEnum.Rtl, DateFormat="%d %b, %Y", TimeFormat="%I:%M:%S",WeekStart=WeekEnum.Saturday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000008,Name="Arabic (Syria) / الْعَرَبيّة", Code="ar_SY", IsoCode="ar_SY", UrlCode="ar-sy", Direction=DirectionEnum.Rtl, DateFormat="%d %b, %Y", TimeFormat="%I:%M:%S",WeekStart=WeekEnum.Saturday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000009,Name="Azerbaijani / Azərbaycanca", Code="az_AZ", IsoCode="az", UrlCode="az", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000010,Name="Basque / Euskara", Code="eu_ES", IsoCode="eu_ES", UrlCode="eu-es", Direction=DirectionEnum.Ltr, DateFormat="%a, %Y.eko %bren %da", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[]",DecimalPoint=",",ThousandsSep="null",Active=false},
+            new SysLang{ Id=1300000000011,Name="Bengali / বাংলা", Code="bn_IN", IsoCode="bn_IN", UrlCode="bn-in", Direction=DirectionEnum.Ltr, DateFormat="%A %d %b %Y", TimeFormat="%I:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[]",DecimalPoint=",",ThousandsSep="null",Active=false},
+            new SysLang{ Id=1300000000012,Name="Bosnian / bosanski jezik", Code="bs_BA", IsoCode="bs", UrlCode="bs", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000013,Name="Bulgarian / български език", Code="bg_BG", IsoCode="bg", UrlCode="bg", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H,%M,%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep="null",Active=false},
+            new SysLang{ Id=1300000000014,Name="Catalan / Català", Code="ca_ES", IsoCode="ca_ES", UrlCode="ca-es", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000015,Name="Croatian / hrvatski jezik", Code="hr_HR", IsoCode="hr", UrlCode="hr", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000016,Name="Czech / Čeština", Code="cs_CZ", IsoCode="cs_CZ", UrlCode="cs-cz", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000017,Name="Danish / Dansk", Code="da_DK", IsoCode="da_DK", UrlCode="da-dk", Direction=DirectionEnum.Ltr, DateFormat="%d-%m-%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000018,Name="Dutch (BE) / Nederlands (BE)", Code="nl_BE", IsoCode="nl_BE", UrlCode="nl-be", Direction=DirectionEnum.Ltr, DateFormat="%d-%m-%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000019,Name="Dutch / Nederlands", Code="nl_NL", IsoCode="nl", UrlCode="nl", Direction=DirectionEnum.Ltr, DateFormat="%d-%m-%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000020,Name="English (AU)", Code="en_AU", IsoCode="en_AU", UrlCode="en-au", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000021,Name="English (CA)", Code="en_CA", IsoCode="en_CA", UrlCode="en-ca", Direction=DirectionEnum.Ltr, DateFormat="%Y-%m-%d", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000022,Name="English (UK)", Code="en_GB", IsoCode="en_GB", UrlCode="en-gb", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000023,Name="English (IN)", Code="en_IN", IsoCode="en_IN", UrlCode="en-in", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,2,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000024,Name="Estonian / Eesti keel", Code="et_EE", IsoCode="et", UrlCode="et", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000025,Name="Finnish / Suomi", Code="fi_FI", IsoCode="fi", UrlCode="fi", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H.%M.%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000026,Name="French (BE) / Français (BE)", Code="fr_BE", IsoCode="fr_BE", UrlCode="fr-be", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000027,Name="French (CA) / Français (CA)", Code="fr_CA", IsoCode="fr_CA", UrlCode="fr-ca", Direction=DirectionEnum.Ltr, DateFormat="%Y-%m-%d", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000028,Name="French (CH) / Français (CH)", Code="fr_CH", IsoCode="fr_CH", UrlCode="fr-ch", Direction=DirectionEnum.Ltr, DateFormat="%d. %m. %Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep="'",Active=false},
+            new SysLang{ Id=1300000000029,Name="French / Français", Code="fr_FR", IsoCode="fr", UrlCode="fr", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000030,Name="Galician / Galego", Code="gl_ES", IsoCode="gl", UrlCode="gl", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[]",DecimalPoint=",",ThousandsSep="null",Active=false},
+            new SysLang{ Id=1300000000031,Name="Georgian / ქართული ენა", Code="ka_GE", IsoCode="ka", UrlCode="ka", Direction=DirectionEnum.Ltr, DateFormat="%m/%d/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000032,Name="German / Deutsch", Code="de_DE", IsoCode="de", UrlCode="de", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000033,Name="German (CH) / Deutsch (CH)", Code="de_CH", IsoCode="de_CH", UrlCode="de-ch", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,3]",DecimalPoint=".",ThousandsSep="'",Active=false},
+            new SysLang{ Id=1300000000034,Name="Greek / Ελληνικά", Code="el_GR", IsoCode="el_GR", UrlCode="el-gr", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%I:%M:%S %p",WeekStart=WeekEnum.Monday,Grouping="[]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000035,Name="Gujarati / ગુજરાતી", Code="gu_IN", IsoCode="gu", UrlCode="gu", Direction=DirectionEnum.Ltr, DateFormat="%A %d %b %Y", TimeFormat="%I:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000036,Name="Hebrew / עִבְרִי", Code="he_IL", IsoCode="he", UrlCode="he", Direction=DirectionEnum.Rtl, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000037,Name="Hindi / हिंदी", Code="hi_IN", IsoCode="hi", UrlCode="hi", Direction=DirectionEnum.Ltr, DateFormat="%A %d %b %Y", TimeFormat="%I:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000038,Name="Hungarian / Magyar", Code="hu_HU", IsoCode="hu", UrlCode="hu", Direction=DirectionEnum.Ltr, DateFormat="%Y-%m-%d", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000039,Name="Indonesian / Bahasa Indonesia", Code="id_ID", IsoCode="id", UrlCode="id", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000040,Name="Japanese / 日本語", Code="ja_JP", IsoCode="ja", UrlCode="ja", Direction=DirectionEnum.Ltr, DateFormat="%Y年%m月%d日", TimeFormat="%H時%M分%S秒",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000041,Name="Kabyle / Taqbaylit", Code="kab_DZ", IsoCode="kab", UrlCode="kab", Direction=DirectionEnum.Ltr, DateFormat="%m/%d/%Y", TimeFormat="%I:%M:%S %p",WeekStart=WeekEnum.Saturday,Grouping="[]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000042,Name="Khmer / ភាសាខ្មែរ", Code="km_KH", IsoCode="km", UrlCode="km", Direction=DirectionEnum.Ltr, DateFormat="%d %B %Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000043,Name="Korean (KP) / 한국어 (KP)", Code="ko_KP", IsoCode="ko_KP", UrlCode="ko-kp", Direction=DirectionEnum.Ltr, DateFormat="%m/%d/%Y", TimeFormat="%I:%M:%S %p",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000044,Name="Korean (KR) / 한국어 (KR)", Code="ko_KR", IsoCode="ko_KR", UrlCode="ko-kr", Direction=DirectionEnum.Ltr, DateFormat="%Y년 %m월 %d일", TimeFormat="%H시 %M분 %S초",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000045,Name="Lao / ພາສາລາວ", Code="lo_LA", IsoCode="lo", UrlCode="lo", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000046,Name="Latvian / latviešu valoda", Code="lv_LV", IsoCode="lv", UrlCode="lv", Direction=DirectionEnum.Ltr, DateFormat="%Y.%m.%d.", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000047,Name="Lithuanian / Lietuvių kalba", Code="lt_LT", IsoCode="lt", UrlCode="lt", Direction=DirectionEnum.Ltr, DateFormat="%Y-%m-%d", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000048,Name="Luxembourgish", Code="lb_LU", IsoCode="lb", UrlCode="lb", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000049,Name="Macedonian / македонски јазик", Code="mk_MK", IsoCode="mk", UrlCode="mk", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000050,Name="Malayalam / മലയാളം", Code="ml_IN", IsoCode="ml", UrlCode="ml", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000051,Name="Mongolian / монгол", Code="mn_MN", IsoCode="mn", UrlCode="mn", Direction=DirectionEnum.Ltr, DateFormat="%Y-%m-%d", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep="'",Active=false},
+            new SysLang{ Id=1300000000052,Name="Malay / Bahasa Melayu", Code="ms_MY", IsoCode="ms", UrlCode="ms", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000053,Name="Norwegian Bokmål / Norsk bokmål", Code="nb_NO", IsoCode="nb_NO", UrlCode="nb-no", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000054,Name="Persian / فارسی", Code="fa_IR", IsoCode="fa", UrlCode="fa", Direction=DirectionEnum.Rtl, DateFormat="%Y/%m/%d", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Saturday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000055,Name="Polish / Język polski", Code="pl_PL", IsoCode="pl", UrlCode="pl", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[]",DecimalPoint=",",ThousandsSep="null",Active=false},
+            new SysLang{ Id=1300000000056,Name="Portuguese (AO) / Português (AO)", Code="pt_AO", IsoCode="pt_AO", UrlCode="pt-ao", Direction=DirectionEnum.Ltr, DateFormat="%d-%m-%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[]",DecimalPoint=",",ThousandsSep="null",Active=false},
+            new SysLang{ Id=1300000000057,Name="Portuguese (BR) / Português (BR)", Code="pt_BR", IsoCode="pt_BR", UrlCode="pt-br", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000058,Name="Portuguese / Português", Code="pt_PT", IsoCode="pt", UrlCode="pt", Direction=DirectionEnum.Ltr, DateFormat="%d-%m-%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[]",DecimalPoint=",",ThousandsSep="null",Active=false},
+            new SysLang{ Id=1300000000059,Name="Romanian / română", Code="ro_RO", IsoCode="ro", UrlCode="ro", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000060,Name="Russian / русский язык", Code="ru_RU", IsoCode="ru", UrlCode="ru", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000061,Name="Serbian (Cyrillic) / српски", Code="sr_RS", IsoCode="sr_RS", UrlCode="sr-rs", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y.", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[]",DecimalPoint=",",ThousandsSep="null",Active=false},
+            new SysLang{ Id=1300000000062,Name="Serbian (Latin) / srpski", Code="sr@latin", IsoCode="sr@latin", UrlCode="sr@latin", Direction=DirectionEnum.Ltr, DateFormat="%m/%d/%Y", TimeFormat="%I:%M:%S %p",WeekStart=WeekEnum.Sunday,Grouping="[]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000063,Name="Slovak / Slovenský jazyk", Code="sk_SK", IsoCode="sk", UrlCode="sk", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000064,Name="Slovenian / slovenščina", Code="sl_SI", IsoCode="sl", UrlCode="sl", Direction=DirectionEnum.Ltr, DateFormat="%d. %m. %Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000065,Name="Spanish (AR) / Español (AR)", Code="es_AR", IsoCode="es_AR", UrlCode="es-ar", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000066,Name="Spanish (BO) / Español (BO)", Code="es_BO", IsoCode="es_BO", UrlCode="es-bo", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000067,Name="Spanish (CL) / Español (CL)", Code="es_CL", IsoCode="es_CL", UrlCode="es-cl", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000068,Name="Spanish (CO) / Español (CO)", Code="es_CO", IsoCode="es_CO", UrlCode="es-co", Direction=DirectionEnum.Ltr, DateFormat="%d-%m-%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000069,Name="Spanish (CR) / Español (CR)", Code="es_CR", IsoCode="es_CR", UrlCode="es-cr", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000070,Name="Spanish (DO) / Español (DO)", Code="es_DO", IsoCode="es_DO", UrlCode="es-do", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%I:%M:%S %p",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000071,Name="Spanish (EC) / Español (EC)", Code="es_EC", IsoCode="es_EC", UrlCode="es-ec", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000072,Name="Spanish (GT) / Español (GT)", Code="es_GT", IsoCode="es_GT", UrlCode="es-gt", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000073,Name="Spanish (MX) / Español (MX)", Code="es_MX", IsoCode="es_MX", UrlCode="es-mx", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000074,Name="Spanish (PA) / Español (PA)", Code="es_PA", IsoCode="es_PA", UrlCode="es-pa", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000075,Name="Spanish (PE) / Español (PE)", Code="es_PE", IsoCode="es_PE", UrlCode="es-pe", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000076,Name="Spanish (PY) / Español (PY)", Code="es_PY", IsoCode="es_PY", UrlCode="es-py", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000077,Name="Spanish (UY) / Español (UY)", Code="es_UY", IsoCode="es_UY", UrlCode="es-uy", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000078,Name="Spanish (VE) / Español (VE)", Code="es_VE", IsoCode="es_VE", UrlCode="es-ve", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000079,Name="Spanish / Español", Code="es_ES", IsoCode="es", UrlCode="es", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000080,Name="Swedish / Svenska", Code="sv_SE", IsoCode="sv", UrlCode="sv", Direction=DirectionEnum.Ltr, DateFormat="%Y-%m-%d", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000081,Name="Thai / ภาษาไทย", Code="th_TH", IsoCode="th", UrlCode="th", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Sunday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000082,Name="Tagalog / Filipino", Code="tl_PH", IsoCode="tl", UrlCode="tl", Direction=DirectionEnum.Ltr, DateFormat="%m/%d/%y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000083,Name="Turkish / Türkçe", Code="tr_TR", IsoCode="tr", UrlCode="tr", Direction=DirectionEnum.Ltr, DateFormat="%d-%m-%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000084,Name="Ukrainian / українська", Code="uk_UA", IsoCode="uk", UrlCode="uk", Direction=DirectionEnum.Ltr, DateFormat="%d.%m.%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=" ",Active=false},
+            new SysLang{ Id=1300000000085,Name="Vietnamese / Tiếng Việt", Code="vi_VN", IsoCode="vi", UrlCode="vi", Direction=DirectionEnum.Ltr, DateFormat="%d/%m/%Y", TimeFormat="%H:%M:%S",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000086,Name="Albanian / Shqip", Code="sq_AL", IsoCode="sq", UrlCode="sq", Direction=DirectionEnum.Ltr, DateFormat="%Y-%b-%d", TimeFormat="%I.%M.%S.",WeekStart=WeekEnum.Monday,Grouping="[3,0]",DecimalPoint=",",ThousandsSep=".",Active=false},
+            new SysLang{ Id=1300000000087,Name="Telugu / తెలుగు", Code="te_IN", IsoCode="te", UrlCode="te", Direction=DirectionEnum.Ltr, DateFormat="%B %d %A %Y", TimeFormat="%p%I.%M.%S",WeekStart=WeekEnum.Sunday,Grouping="[]",DecimalPoint=".",ThousandsSep=",",Active=false},
+            new SysLang{ Id=1300000000088,Name="Burmese / ဗမာစာ", Code="my_MM", IsoCode="my", UrlCode="mya", Direction=DirectionEnum.Ltr, DateFormat="%Y %b %d %A", TimeFormat="%I:%M:%S %p",WeekStart=WeekEnum.Sunday,Grouping="[3,3]",DecimalPoint=".",ThousandsSep=",",Active=false},
+        };
+    }
+}

+ 20 - 6
Admin.NET/Admin.NET.Core/SeedData/SysMenuSeedData.cs

@@ -99,7 +99,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1300200080701, Pid=1300200080101, Title="同步域账户", Permission="sysLdap:syncUser", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=150 },
             new SysMenu{ Id=1300200080801, Pid=1300200080101, Title="同步域组织", Permission="sysLdap:syncOrg", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=160 },
 
-            #endregion
+            #endregion 系统管理
 
             #region 平台管理
 
@@ -173,6 +173,20 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1300200090401, Pid=1300200090101, Title="增加", Permission="sysTenantConfig:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1300200090501, Pid=1300200090101, Title="删除", Permission="sysTenantConfig:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
 
+            // 语言管理
+            new SysMenu{ Id=1300200100101, Pid=1300200000101, Title="语言管理", Path="/system/lang", Name="sysLang", Component="/system/lang/index", Icon="iconfont icon-diqiu", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2025-06-28 00:00:00"), OrderNo=190 },
+            new SysMenu{ Id=1300200100201, Pid=1300200100101, Title="查询", Permission="sysLang:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1300200100301, Pid=1300200100101, Title="编辑", Permission="sysLang:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1300200100401, Pid=1300200100101, Title="增加", Permission="sysLang:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1300200100501, Pid=1300200100101, Title="删除", Permission="sysLang:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+
+            // 翻译管理
+            new SysMenu{ Id=1300200200101, Pid=1300200000101, Title="翻译管理", Path="/system/langText", Name="sysLangText", Component="/system/langText/index", Icon="iconfont icon-zhongyingwen", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2025-06-28 00:00:00"), OrderNo=200 },
+            new SysMenu{ Id=1300200200201, Pid=1300200200101, Title="查询", Permission="sysLangText:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1300200200301, Pid=1300200200101, Title="编辑", Permission="sysLangText:update", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1300200200401, Pid=1300200200101, Title="增加", Permission="sysLangText:add", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+            new SysMenu{ Id=1300200200501, Pid=1300200200101, Title="删除", Permission="sysLangText:delete", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
+
             // 系统监控
             new SysMenu{ Id=1300300070101, Pid=1300300000101, Title="系统监控", Path="/platform/server", Name="sysServer", Component="/system/server/index", Icon="ele-Monitor", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=150 },
 
@@ -239,8 +253,8 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1300300160601, Pid=1300300160101, Title="清除日志", Permission="sysUpdate:clear", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1300300160701, Pid=1300300160101, Title="获取密钥", Permission="sysUpdate:webHookKey", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
 
-	        #endregion
-            
+	        #endregion 平台管理
+
             #region 日志管理
 
             new SysMenu{ Id=1300400000101, Pid=0, Title="日志管理", Path="/log", Name="log", Component="Layout", Icon="ele-DocumentCopy", Type=MenuTypeEnum.Dir, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=12000 },
@@ -267,7 +281,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1300400040201, Pid=1300400040101, Title="查询", Permission="sysDifflog:page", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
             new SysMenu{ Id=1300400040301, Pid=1300400040101, Title="清空", Permission="sysDifflog:clear", Type=MenuTypeEnum.Btn, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=100 },
 
-            #endregion
+            #endregion 日志管理
 
             #region 开发工具
 
@@ -279,7 +293,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1300500040101, Pid=1300500000101, Title="接口压测", Path="/develop/stressTest", Name="SysStressTest", Component="/system/stressTest/index", Icon="ele-DataLine", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 },
             new SysMenu{ Id=1300500050101, Pid=1300500000101, Title="系统接口", Path="/develop/api", Name="sysApi", Component="layout/routerView/iframe", IsIframe=true, OutLink="http://localhost:5005", Icon="ele-Help", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=140 },
 
-            #endregion
+            #endregion 开发工具
 
             #region 帮助文档
 
@@ -290,7 +304,7 @@ public class SysMenuSeedData : ISqlSugarEntitySeedData<SysMenu>
             new SysMenu{ Id=1300600030101, Pid=1300600000101, Title="前端教程", Path="/doc/element", Name="sysElement", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://element-plus.gitee.io/zh-CN/", Icon="ele-Position", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=120 },
             new SysMenu{ Id=1300600040101, Pid=1300600000101, Title="SqlSugar", Path="/doc/SqlSugar", Name="sysSqlSugar", Component="layout/routerView/link", IsIframe=false, IsKeepAlive=false, OutLink="https://www.donet5.com/Home/Doc", Icon="ele-Coin", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2022-02-10 00:00:00"), OrderNo=130 },
 
-	        #endregion
+	        #endregion 帮助文档
 
             // 关于项目
             new SysMenu{ Id=1300700000101, Pid=0, Title="关于项目", Path="/about", Name="about", Component="/about/index", Icon="ele-InfoFilled", Type=MenuTypeEnum.Menu, CreateTime=DateTime.Parse("2023-03-12 00:00:00"), OrderNo=15000 },

+ 5 - 0
Admin.NET/Admin.NET.Core/Service/Auth/Dto/LoginUserOutput.cs

@@ -110,4 +110,9 @@ public class LoginUserOutput
     /// 当前切换到的租户Id
     /// </summary>
     public long? CurrentTenantId { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public string LangCode { get; internal set; }
 }

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

@@ -244,6 +244,7 @@ public class SysAuthService : IDynamicApiController, ITransient
             { ClaimConst.OrgId, user.OrgId },
             { ClaimConst.OrgName, user.SysOrg?.Name },
             { ClaimConst.OrgType, user.SysOrg?.Type },
+            { ClaimConst.LangCode, user.LangCode }
         }, tokenExpire);
 
         // 生成刷新Token令牌
@@ -323,7 +324,8 @@ public class SysAuthService : IDynamicApiController, ITransient
             Buttons = buttons,
             RoleIds = roleIds,
             TenantId = user.TenantId,
-            WatermarkText = watermarkText
+            WatermarkText = watermarkText,
+            LangCode = user.LangCode,
         };
 
         //将登录信息中的当前租户id,更新为当前所切换到的租户

+ 20 - 6
Admin.NET/Admin.NET.Core/Service/Cache/SysCacheService.cs

@@ -108,8 +108,18 @@ public class SysCacheService : IDynamicApiController, ISingleton
         using (_cacheProvider.Cache.AcquireLock($@"lock:AdGetAsync:{cacheName}", 1000))
         {
             var value = Get<T>(key);
-            value ??= await ((dynamic)del).DynamicInvokeAsync(obs);
-            Set(key, value);
+            if (value == null)
+            {
+                value = await ((dynamic)del).DynamicInvoke(obs);
+                if (expiry == null)
+                {
+                    Set(key, value);
+                }
+                else
+                {
+                    Set(key, value, (TimeSpan)expiry);
+                }
+            }
             return value;
         }
     }
@@ -131,15 +141,19 @@ public class SysCacheService : IDynamicApiController, ISingleton
 
     private T Get<T>(String cacheName, Object[] obs)
     {
-        var key = cacheName + ":" + obs.Aggregate(string.Empty, (current, o) => current + $"<{o}>");
+        var key = Key(cacheName, obs);
         return Get<T>(key);
     }
 
     private static string Key(string cacheName, object[] obs)
     {
-        if (obs.OfType<TimeSpan>().Any()) throw new Exception("缓存参数类型不能能是:TimeSpan类型");
-        StringBuilder sb = new(cacheName + ":");
-        foreach (var a in obs) sb.Append($"<{KeySingle(a)}>");
+        if (obs.OfType<TimeSpan>().Any()) throw new Exception("缓存参数类型不能是:TimeSpan类型");
+        StringBuilder sb = new(cacheName);
+        if (obs is { Length: > 0 })
+        {
+            sb.Append(':');
+            foreach (var a in obs) sb.Append($"<{KeySingle(a)}>");
+        }
         return sb.ToString();
     }
 

+ 5 - 4
Admin.NET/Admin.NET.Core/Service/DataBase/SysDatabaseService.cs

@@ -144,7 +144,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient
             DataType = input.DataType
         };
         var db = _db.AsTenant().GetConnectionScope(input.ConfigId);
-        db.DbMaintenance.AddColumn(input.TableName, column);        
+        db.DbMaintenance.AddColumn(input.TableName, column);
         // 默认值直接添加报错
         if (!string.IsNullOrWhiteSpace(input.DefaultValue))
         {
@@ -181,7 +181,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient
             db.DbMaintenance.AddDefaultValue(input.TableName, input.ColumnName, input.DefaultValue);
         }
         if (db.DbMaintenance.IsAnyColumnRemark(input.ColumnName, input.TableName))
-        { 
+        {
             db.DbMaintenance.DeleteColumnRemark(input.ColumnName, input.TableName);
         }
 
@@ -213,6 +213,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient
             case SqlSugar.DbType.MySql:
                 MoveColumnInMySQL(db, input.TableName, input.ColumnName, input.AfterColumnName);
                 break;
+
             default:
                 throw new NotSupportedException($"暂不支持 {dbType} 数据库的列移动操作");
         }
@@ -227,7 +228,7 @@ public class SysDatabaseService : IDynamicApiController, ITransient
     /// <param name="noDefault"></param>
     /// <returns></returns>
     /// <exception cref="Exception"></exception>
-    private string GetColumnDefinitionInMySQL(ISqlSugarClient db, string tableName, string columnName,bool noDefault = false)
+    private string GetColumnDefinitionInMySQL(ISqlSugarClient db, string tableName, string columnName, bool noDefault = false)
     {
         var columnDef = db.Ado.SqlQuery<dynamic>(
             $"SHOW FULL COLUMNS FROM `{tableName}` WHERE Field = '{columnName}'"
@@ -250,8 +251,8 @@ public class SysDatabaseService : IDynamicApiController, ITransient
             definition.Append($"COMMENT '{columnDef.Comment.Replace("'", "''")}'");
 
         return definition.ToString();
-
     }
+
     /// <summary>
     /// MySQL 列移动实现
     /// </summary>

+ 23 - 0
Admin.NET/Admin.NET.Core/Service/Dict/Dto/DictDataOutput.cs

@@ -0,0 +1,23 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.Service;
+
+public class DictDataOutput
+{
+    public long DictDataId { get; set; }
+    public string TypeCode { get; set; }
+    public string Label { get; set; }
+    public string Value { get; set; }
+    public string Code { get; set; }
+    public string TagType { get; set; }
+    public string StyleSetting { get; set; }
+    public string ClassSetting { get; set; }
+    public string ExtData { get; set; }
+    public string Remark { get; set; }
+    public int OrderNo { get; set; }
+    public StatusEnum Status { get; set; }
+}

+ 39 - 4
Admin.NET/Admin.NET.Core/Service/Dict/SysDictDataService.cs

@@ -16,14 +16,17 @@ public class SysDictDataService : IDynamicApiController, ITransient
     public readonly ISugarQueryable<SysDictData> VSysDictData;
     private readonly SysCacheService _sysCacheService;
     private readonly UserManager _userManager;
+    private readonly SysLangTextCacheService _sysLangTextCacheService;
 
     public SysDictDataService(SqlSugarRepository<SysDictData> sysDictDataRep,
         SysCacheService sysCacheService,
-        UserManager userManager)
+        UserManager userManager,
+        SysLangTextCacheService sysLangTextCacheService)
     {
         _userManager = userManager;
         _sysDictDataRep = sysDictDataRep;
         _sysCacheService = sysCacheService;
+        _sysLangTextCacheService = sysLangTextCacheService;
         VSysDictData = _sysDictDataRep.Context.UnionAll(
             _sysDictDataRep.AsQueryable(),
             _sysDictDataRep.Change<SysDictDataTenant>().AsQueryable().WhereIF(_userManager.SuperAdmin, d => d.TenantId == _userManager.TenantId).Select<SysDictData>());
@@ -37,12 +40,29 @@ public class SysDictDataService : IDynamicApiController, ITransient
     [DisplayName("获取字典值分页列表")]
     public async Task<SqlSugarPagedList<SysDictData>> Page(PageDictDataInput input)
     {
-        return await VSysDictData
+        var langCode = _userManager.LangCode;
+        var baseQuery = VSysDictData
             .Where(u => u.DictTypeId == input.DictTypeId)
             .WhereIF(!string.IsNullOrEmpty(input.Code?.Trim()), u => u.Code.Contains(input.Code))
             .WhereIF(!string.IsNullOrEmpty(input.Label?.Trim()), u => u.Label.Contains(input.Label))
-            .OrderBy(u => new { u.OrderNo, u.Code })
-            .ToPagedListAsync(input.Page, input.PageSize);
+            .OrderBy(u => new { u.OrderNo, u.Code });
+        var pageList = await baseQuery.ToPagedListAsync(input.Page, input.PageSize);
+        var list = pageList.Items;
+        var ids = list.Select(d => d.Id).Distinct().ToList();
+        var translations = await _sysLangTextCacheService.GetTranslations(
+                               "SysDictData",
+                               "Label",
+                               ids,
+                               langCode);
+        foreach (var item in list)
+        {
+            if (translations.TryGetValue(item.Id, out var translatedLabel) && !string.IsNullOrEmpty(translatedLabel))
+            {
+                item.Label = translatedLabel;
+            }
+        }
+        pageList.Items = list;
+        return pageList;
     }
 
     /// <summary>
@@ -52,6 +72,21 @@ public class SysDictDataService : IDynamicApiController, ITransient
     [DisplayName("获取字典值列表")]
     public async Task<List<SysDictData>> GetList([FromQuery] GetDataDictDataInput input)
     {
+        var langCode = _userManager.LangCode;
+        var list = await GetDictDataListByDictTypeId(input.DictTypeId);
+        var ids = list.Select(d => d.Id).Distinct().ToList();
+        var translations = await _sysLangTextCacheService.GetTranslations(
+                               "SysDictData",
+                               "Label",
+                               ids,
+                               langCode);
+        foreach (var item in list)
+        {
+            if (translations.TryGetValue(item.Id, out var translatedLabel) && !string.IsNullOrEmpty(translatedLabel))
+            {
+                item.Label = translatedLabel;
+            }
+        }
         return await GetDictDataListByDictTypeId(input.DictTypeId);
     }
 

+ 93 - 16
Admin.NET/Admin.NET.Core/Service/Dict/SysDictTypeService.cs

@@ -16,16 +16,19 @@ public class SysDictTypeService : IDynamicApiController, ITransient
     private readonly SysDictDataService _sysDictDataService;
     private readonly SysCacheService _sysCacheService;
     private readonly UserManager _userManager;
+    private readonly SysLangTextCacheService _sysLangTextCacheService;
 
     public SysDictTypeService(SqlSugarRepository<SysDictType> sysDictTypeRep,
         SysDictDataService sysDictDataService,
         SysCacheService sysCacheService,
-        UserManager userManager)
+        UserManager userManager,
+        SysLangTextCacheService sysLangTextCacheService)
     {
         _sysDictTypeRep = sysDictTypeRep;
         _sysDictDataService = sysDictDataService;
         _sysCacheService = sysCacheService;
         _userManager = userManager;
+        _sysLangTextCacheService = sysLangTextCacheService;
     }
 
     /// <summary>
@@ -35,12 +38,29 @@ public class SysDictTypeService : IDynamicApiController, ITransient
     [DisplayName("获取字典类型分页列表")]
     public async Task<SqlSugarPagedList<SysDictType>> Page(PageDictTypeInput input)
     {
-        var query = _sysDictTypeRep.AsQueryable()
+        var langCode = _userManager.LangCode;
+        var baseQuery = _sysDictTypeRep.AsQueryable()
             .WhereIF(!_userManager.SuperAdmin, u => u.IsTenant == YesNoEnum.Y)
             .WhereIF(!string.IsNullOrEmpty(input.Code?.Trim()), u => u.Code.Contains(input.Code))
             .WhereIF(!string.IsNullOrEmpty(input.Name?.Trim()), u => u.Name.Contains(input.Name));
         //.OrderBy(u => new { u.OrderNo, u.Code })
-        return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
+        var pageList = await baseQuery.ToPagedListAsync(input.Page, input.PageSize);
+        var list = pageList.Items;
+        var ids = list.Select(d => d.Id).Distinct().ToList();
+        var translations = await _sysLangTextCacheService.GetTranslations(
+                               "SysDictType",
+                               "Name",
+                               ids,
+                               langCode);
+        foreach (var item in list)
+        {
+            if (translations.TryGetValue(item.Id, out var translatedName) && !string.IsNullOrEmpty(translatedName))
+            {
+                item.Name = translatedName;
+            }
+        }
+        pageList.Items = list;
+        return pageList;
     }
 
     /// <summary>
@@ -50,7 +70,22 @@ public class SysDictTypeService : IDynamicApiController, ITransient
     [DisplayName("获取字典类型列表")]
     public async Task<List<SysDictType>> GetList()
     {
-        return await _sysDictTypeRep.AsQueryable().OrderBy(u => new { u.OrderNo, u.Code }).ToListAsync();
+        var langCode = _userManager.LangCode;
+        var list = await _sysDictTypeRep.AsQueryable().OrderBy(u => new { u.OrderNo, u.Code }).ToListAsync();
+        var ids = list.Select(d => d.Id).Distinct().ToList();
+        var translations = await _sysLangTextCacheService.GetTranslations(
+                               "SysDictType",
+                               "Name",
+                               ids,
+                               langCode);
+        foreach (var item in list)
+        {
+            if (translations.TryGetValue(item.Id, out var translatedName) && !string.IsNullOrEmpty(translatedName))
+            {
+                item.Name = translatedName;
+            }
+        }
+        return list;
     }
 
     /// <summary>
@@ -62,7 +97,22 @@ public class SysDictTypeService : IDynamicApiController, ITransient
     public async Task<List<SysDictData>> GetDataList([FromQuery] GetDataDictTypeInput input)
     {
         var dictType = await _sysDictTypeRep.GetFirstAsync(u => u.Code == input.Code) ?? throw Oops.Oh(ErrorCodeEnum.D3000);
-        return await _sysDictDataService.GetDictDataListByDictTypeId(dictType.Id);
+        var langCode = _userManager.LangCode;
+        var list = await _sysDictDataService.GetDictDataListByDictTypeId(dictType.Id);
+        var ids = list.Select(d => d.Id).Distinct().ToList();
+        var translations = await _sysLangTextCacheService.GetTranslations(
+                               "SysDictType",
+                               "Name",
+                               ids,
+                               langCode);
+        foreach (var item in list)
+        {
+            if (translations.TryGetValue(item.Id, out var translatedName) && !string.IsNullOrEmpty(translatedName))
+            {
+                item.Name = translatedName;
+            }
+        }
+        return list;
     }
 
     /// <summary>
@@ -161,23 +211,50 @@ public class SysDictTypeService : IDynamicApiController, ITransient
     [DisplayName("获取所有字典集合")]
     public async Task<dynamic> GetAllDictList()
     {
+        var langCode = _userManager.LangCode;
         var ds = await _sysDictTypeRep.AsQueryable()
             .InnerJoin(_sysDictDataService.VSysDictData, (u, w) => u.Id == w.DictTypeId)
-            .Select((u, w) => new
+            .Select((u, w) => new DictDataOutput
             {
+                DictDataId = w.Id, // 给翻译用
                 TypeCode = u.Code,
-                w.Label,
-                w.Value,
-                w.Code,
-                w.TagType,
-                w.StyleSetting,
-                w.ClassSetting,
-                w.ExtData,
-                w.Remark,
-                w.OrderNo,
+                Label = w.Label,
+                Value = w.Value,
+                Code = w.Code,
+                TagType = w.TagType,
+                StyleSetting = w.StyleSetting,
+                ClassSetting = w.ClassSetting,
+                ExtData = w.ExtData,
+                Remark = w.Remark,
+                OrderNo = w.OrderNo,
                 Status = w.Status == StatusEnum.Enable && u.Status == StatusEnum.Enable ? StatusEnum.Enable : StatusEnum.Disable
             })
             .ToListAsync();
-        return ds.OrderBy(u => u.OrderNo).GroupBy(u => u.TypeCode).ToDictionary(u => u.Key, u => u);
+        var ids = ds.Select(x => x.DictDataId).Distinct().ToList();
+
+        Dictionary<long, string> translations = new();
+        if (ids.Any())
+        {
+            translations = await _sysLangTextCacheService.GetTranslations(
+                "SysDictData",
+                "Label",
+                ids,
+                langCode
+            );
+        }
+        foreach (var item in ds)
+        {
+            if (translations.TryGetValue(item.DictDataId, out var translated) && !string.IsNullOrEmpty(translated))
+            {
+                item.Label = translated;
+            }
+        }
+
+        var result = ds
+            .OrderBy(u => u.OrderNo)
+            .GroupBy(u => u.TypeCode)
+            .ToDictionary(u => u.Key, u => u.ToList());
+
+        return result;
     }
 }

+ 108 - 0
Admin.NET/Admin.NET.Core/Service/Lang/Dto/SysLangDto.cs

@@ -0,0 +1,108 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 语言输出参数
+/// </summary>
+public class SysLangDto
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 语言名称
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public string Code { get; set; }
+
+    /// <summary>
+    /// ISO 语言代码
+    /// </summary>
+    public string IsoCode { get; set; }
+
+    /// <summary>
+    /// URL 语言代码
+    /// </summary>
+    public string UrlCode { get; set; }
+
+    /// <summary>
+    /// 书写方向
+    /// </summary>
+    public DirectionEnum Direction { get; set; }
+
+    /// <summary>
+    /// 日期格式
+    /// </summary>
+    public string DateFormat { get; set; }
+
+    /// <summary>
+    /// 时间格式
+    /// </summary>
+    public string TimeFormat { get; set; }
+
+    /// <summary>
+    /// 每周起始日
+    /// </summary>
+    public WeekEnum WeekStart { get; set; }
+
+    /// <summary>
+    /// 分组符号
+    /// </summary>
+    public string Grouping { get; set; }
+
+    /// <summary>
+    /// 小数点符号
+    /// </summary>
+    public string DecimalPoint { get; set; }
+
+    /// <summary>
+    /// 千分位分隔符
+    /// </summary>
+    public string? ThousandsSep { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    public bool Active { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    public DateTime? CreateTime { get; set; }
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    public DateTime? UpdateTime { get; set; }
+
+    /// <summary>
+    /// 创建者Id
+    /// </summary>
+    public long? CreateUserId { get; set; }
+
+    /// <summary>
+    /// 创建者姓名
+    /// </summary>
+    public string? CreateUserName { get; set; }
+
+    /// <summary>
+    /// 修改者Id
+    /// </summary>
+    public long? UpdateUserId { get; set; }
+
+    /// <summary>
+    /// 修改者姓名
+    /// </summary>
+    public string? UpdateUserName { get; set; }
+}

+ 413 - 0
Admin.NET/Admin.NET.Core/Service/Lang/Dto/SysLangInput.cs

@@ -0,0 +1,413 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 语言基础输入参数
+/// </summary>
+public class SysLangBaseInput
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    public virtual long? Id { get; set; }
+
+    /// <summary>
+    /// 语言名称
+    /// </summary>
+    [Required(ErrorMessage = "语言名称不能为空")]
+    public virtual string Name { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "语言代码不能为空")]
+    public virtual string Code { get; set; }
+
+    /// <summary>
+    /// ISO 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "ISO 语言代码不能为空")]
+    public virtual string IsoCode { get; set; }
+
+    /// <summary>
+    /// URL 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "URL 语言代码不能为空")]
+    public virtual string UrlCode { get; set; }
+
+    /// <summary>
+    /// 书写方向
+    /// </summary>
+    [Required(ErrorMessage = "书写方向不能为空")]
+    public virtual DirectionEnum Direction { get; set; }
+
+    /// <summary>
+    /// 日期格式
+    /// </summary>
+    [Required(ErrorMessage = "日期格式不能为空")]
+    public virtual string DateFormat { get; set; }
+
+    /// <summary>
+    /// 时间格式
+    /// </summary>
+    [Required(ErrorMessage = "时间格式不能为空")]
+    public virtual string TimeFormat { get; set; }
+
+    /// <summary>
+    /// 每周起始日
+    /// </summary>
+    [Required(ErrorMessage = "每周起始日不能为空")]
+    public virtual WeekEnum? WeekStart { get; set; }
+
+    /// <summary>
+    /// 分组符号
+    /// </summary>
+    [Required(ErrorMessage = "分组符号不能为空")]
+    public virtual string Grouping { get; set; }
+
+    /// <summary>
+    /// 小数点符号
+    /// </summary>
+    [Required(ErrorMessage = "小数点符号不能为空")]
+    public virtual string DecimalPoint { get; set; }
+
+    /// <summary>
+    /// 千分位分隔符
+    /// </summary>
+    public virtual string? ThousandsSep { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    [Required(ErrorMessage = "是否启用不能为空")]
+    public virtual bool? Active { get; set; }
+}
+
+/// <summary>
+/// 多语言分页查询输入参数
+/// </summary>
+public class PageSysLangInput : BasePageInput
+{
+    /// <summary>
+    /// 语言名称
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public string Code { get; set; }
+
+    /// <summary>
+    /// ISO 语言代码
+    /// </summary>
+    public string IsoCode { get; set; }
+
+    /// <summary>
+    /// URL 语言代码
+    /// </summary>
+    public string UrlCode { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    public bool? Active { get; set; }
+
+    /// <summary>
+    /// 选中主键列表
+    /// </summary>
+    public List<long> SelectKeyList { get; set; }
+}
+
+/// <summary>
+/// 多语言增加输入参数
+/// </summary>
+public class AddSysLangInput
+{
+    /// <summary>
+    /// 语言名称
+    /// </summary>
+    [Required(ErrorMessage = "语言名称不能为空")]
+    [MaxLength(255, ErrorMessage = "语言名称字符长度不能超过255")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "语言代码不能为空")]
+    [MaxLength(255, ErrorMessage = "语言代码字符长度不能超过255")]
+    public string Code { get; set; }
+
+    /// <summary>
+    /// ISO 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "ISO 语言代码不能为空")]
+    [MaxLength(255, ErrorMessage = "ISO 语言代码字符长度不能超过255")]
+    public string IsoCode { get; set; }
+
+    /// <summary>
+    /// URL 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "URL 语言代码不能为空")]
+    [MaxLength(255, ErrorMessage = "URL 语言代码字符长度不能超过255")]
+    public string UrlCode { get; set; }
+
+    /// <summary>
+    /// 书写方向
+    /// </summary>
+    [Required(ErrorMessage = "书写方向不能为空")]
+    public DirectionEnum Direction { get; set; }
+
+    /// <summary>
+    /// 日期格式
+    /// </summary>
+    [Required(ErrorMessage = "日期格式不能为空")]
+    [MaxLength(255, ErrorMessage = "日期格式字符长度不能超过255")]
+    public string DateFormat { get; set; }
+
+    /// <summary>
+    /// 时间格式
+    /// </summary>
+    [Required(ErrorMessage = "时间格式不能为空")]
+    [MaxLength(255, ErrorMessage = "时间格式字符长度不能超过255")]
+    public string TimeFormat { get; set; }
+
+    /// <summary>
+    /// 每周起始日
+    /// </summary>
+    [Required(ErrorMessage = "每周起始日不能为空")]
+    public WeekEnum? WeekStart { get; set; }
+
+    /// <summary>
+    /// 分组符号
+    /// </summary>
+    [Required(ErrorMessage = "分组符号不能为空")]
+    [MaxLength(255, ErrorMessage = "分组符号字符长度不能超过255")]
+    public string Grouping { get; set; }
+
+    /// <summary>
+    /// 小数点符号
+    /// </summary>
+    [Required(ErrorMessage = "小数点符号不能为空")]
+    [MaxLength(255, ErrorMessage = "小数点符号字符长度不能超过255")]
+    public string DecimalPoint { get; set; }
+
+    /// <summary>
+    /// 千分位分隔符
+    /// </summary>
+    [MaxLength(255, ErrorMessage = "千分位分隔符字符长度不能超过255")]
+    public string? ThousandsSep { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    [Required(ErrorMessage = "是否启用不能为空")]
+    public bool? Active { get; set; }
+}
+
+/// <summary>
+/// 多语言删除输入参数
+/// </summary>
+public class DeleteSysLangInput
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    [Required(ErrorMessage = "主键Id不能为空")]
+    public long? Id { get; set; }
+}
+
+/// <summary>
+/// 多语言更新输入参数
+/// </summary>
+public class UpdateSysLangInput
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    [Required(ErrorMessage = "主键Id不能为空")]
+    public long? Id { get; set; }
+
+    /// <summary>
+    /// 语言名称
+    /// </summary>
+    [Required(ErrorMessage = "语言名称不能为空")]
+    [MaxLength(255, ErrorMessage = "语言名称字符长度不能超过255")]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "语言代码不能为空")]
+    [MaxLength(255, ErrorMessage = "语言代码字符长度不能超过255")]
+    public string Code { get; set; }
+
+    /// <summary>
+    /// ISO 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "ISO 语言代码不能为空")]
+    [MaxLength(255, ErrorMessage = "ISO 语言代码字符长度不能超过255")]
+    public string IsoCode { get; set; }
+
+    /// <summary>
+    /// URL 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "URL 语言代码不能为空")]
+    [MaxLength(255, ErrorMessage = "URL 语言代码字符长度不能超过255")]
+    public string UrlCode { get; set; }
+
+    /// <summary>
+    /// 书写方向
+    /// </summary>
+    [Required(ErrorMessage = "书写方向不能为空")]
+    public DirectionEnum Direction { get; set; }
+
+    /// <summary>
+    /// 日期格式
+    /// </summary>
+    [Required(ErrorMessage = "日期格式不能为空")]
+    [MaxLength(255, ErrorMessage = "日期格式字符长度不能超过255")]
+    public string DateFormat { get; set; }
+
+    /// <summary>
+    /// 时间格式
+    /// </summary>
+    [Required(ErrorMessage = "时间格式不能为空")]
+    [MaxLength(255, ErrorMessage = "时间格式字符长度不能超过255")]
+    public string TimeFormat { get; set; }
+
+    /// <summary>
+    /// 每周起始日
+    /// </summary>
+    [Required(ErrorMessage = "每周起始日不能为空")]
+    public WeekEnum? WeekStart { get; set; }
+
+    /// <summary>
+    /// 分组符号
+    /// </summary>
+    [Required(ErrorMessage = "分组符号不能为空")]
+    [MaxLength(255, ErrorMessage = "分组符号字符长度不能超过255")]
+    public string Grouping { get; set; }
+
+    /// <summary>
+    /// 小数点符号
+    /// </summary>
+    [Required(ErrorMessage = "小数点符号不能为空")]
+    [MaxLength(255, ErrorMessage = "小数点符号字符长度不能超过255")]
+    public string DecimalPoint { get; set; }
+
+    /// <summary>
+    /// 千分位分隔符
+    /// </summary>
+    [MaxLength(255, ErrorMessage = "千分位分隔符字符长度不能超过255")]
+    public string? ThousandsSep { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    [Required(ErrorMessage = "是否启用不能为空")]
+    public bool? Active { get; set; }
+}
+
+/// <summary>
+/// 多语言主键查询输入参数
+/// </summary>
+public class QueryByIdSysLangInput : DeleteSysLangInput
+{
+}
+
+/// <summary>
+/// 多语言数据导入实体
+/// </summary>
+[ExcelImporter(SheetIndex = 1, IsOnlyErrorRows = true)]
+public class ImportSysLangInput : BaseImportInput
+{
+    /// <summary>
+    /// 语言名称
+    /// </summary>
+    [ImporterHeader(Name = "*语言名称")]
+    [ExporterHeader("*语言名称", Format = "", Width = 25, IsBold = true)]
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [ImporterHeader(Name = "*语言代码")]
+    [ExporterHeader("*语言代码", Format = "", Width = 25, IsBold = true)]
+    public string Code { get; set; }
+
+    /// <summary>
+    /// ISO 语言代码
+    /// </summary>
+    [ImporterHeader(Name = "*ISO 语言代码")]
+    [ExporterHeader("*ISO 语言代码", Format = "", Width = 25, IsBold = true)]
+    public string IsoCode { get; set; }
+
+    /// <summary>
+    /// URL 语言代码
+    /// </summary>
+    [ImporterHeader(Name = "*URL 语言代码")]
+    [ExporterHeader("*URL 语言代码", Format = "", Width = 25, IsBold = true)]
+    public string UrlCode { get; set; }
+
+    /// <summary>
+    /// 书写方向
+    /// </summary>
+    [ImporterHeader(Name = "*书写方向")]
+    [ExporterHeader("*书写方向", Format = "", Width = 25, IsBold = true)]
+    public DirectionEnum Direction { get; set; }
+
+    /// <summary>
+    /// 日期格式
+    /// </summary>
+    [ImporterHeader(Name = "*日期格式")]
+    [ExporterHeader("*日期格式", Format = "", Width = 25, IsBold = true)]
+    public string DateFormat { get; set; }
+
+    /// <summary>
+    /// 时间格式
+    /// </summary>
+    [ImporterHeader(Name = "*时间格式")]
+    [ExporterHeader("*时间格式", Format = "", Width = 25, IsBold = true)]
+    public string TimeFormat { get; set; }
+
+    /// <summary>
+    /// 每周起始日
+    /// </summary>
+    [ImporterHeader(Name = "*每周起始日")]
+    [ExporterHeader("*每周起始日", Format = "", Width = 25, IsBold = true)]
+    public WeekEnum? WeekStart { get; set; }
+
+    /// <summary>
+    /// 分组符号
+    /// </summary>
+    [ImporterHeader(Name = "*分组符号")]
+    [ExporterHeader("*分组符号", Format = "", Width = 25, IsBold = true)]
+    public string Grouping { get; set; }
+
+    /// <summary>
+    /// 小数点符号
+    /// </summary>
+    [ImporterHeader(Name = "*小数点符号")]
+    [ExporterHeader("*小数点符号", Format = "", Width = 25, IsBold = true)]
+    public string DecimalPoint { get; set; }
+
+    /// <summary>
+    /// 千分位分隔符
+    /// </summary>
+    [ImporterHeader(Name = "千分位分隔符")]
+    [ExporterHeader("千分位分隔符", Format = "", Width = 25, IsBold = true)]
+    public string? ThousandsSep { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    [ImporterHeader(Name = "*是否启用")]
+    [ExporterHeader("*是否启用", Format = "", Width = 25, IsBold = true)]
+    public bool? Active { get; set; }
+}

+ 117 - 0
Admin.NET/Admin.NET.Core/Service/Lang/Dto/SysLangOutput.cs

@@ -0,0 +1,117 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 语言输出参数
+/// </summary>
+public class SysLangOutput
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 语言名称
+    /// </summary>
+    public string Name { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public string Code { get; set; }
+
+    /// <summary>
+    /// ISO 语言代码
+    /// </summary>
+    public string IsoCode { get; set; }
+
+    /// <summary>
+    /// URL 语言代码
+    /// </summary>
+    public string UrlCode { get; set; }
+
+    /// <summary>
+    /// 书写方向
+    /// </summary>
+    public DirectionEnum Direction { get; set; }
+
+    /// <summary>
+    /// 日期格式
+    /// </summary>
+    public string DateFormat { get; set; }
+
+    /// <summary>
+    /// 时间格式
+    /// </summary>
+    public string TimeFormat { get; set; }
+
+    /// <summary>
+    /// 每周起始日
+    /// </summary>
+    public WeekEnum WeekStart { get; set; }
+
+    /// <summary>
+    /// 分组符号
+    /// </summary>
+    public string Grouping { get; set; }
+
+    /// <summary>
+    /// 小数点符号
+    /// </summary>
+    public string DecimalPoint { get; set; }
+
+    /// <summary>
+    /// 千分位分隔符
+    /// </summary>
+    public string? ThousandsSep { get; set; }
+
+    /// <summary>
+    /// 是否启用
+    /// </summary>
+    public bool Active { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    public DateTime? CreateTime { get; set; }
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    public DateTime? UpdateTime { get; set; }
+
+    /// <summary>
+    /// 创建者Id
+    /// </summary>
+    public long? CreateUserId { get; set; }
+
+    /// <summary>
+    /// 创建者姓名
+    /// </summary>
+    public string? CreateUserName { get; set; }
+
+    /// <summary>
+    /// 修改者Id
+    /// </summary>
+    public long? UpdateUserId { get; set; }
+
+    /// <summary>
+    /// 修改者姓名
+    /// </summary>
+    public string? UpdateUserName { get; set; }
+}
+
+/// <summary>
+/// 多语言数据导入模板实体
+/// </summary>
+public class ExportSysLangOutput : ImportSysLangInput
+{
+    [ImporterHeader(IsIgnore = true)]
+    [ExporterHeader(IsIgnore = true)]
+    public override string Error { get; set; }
+}

+ 113 - 0
Admin.NET/Admin.NET.Core/Service/Lang/SysLangService.cs

@@ -0,0 +1,113 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 语言服务 🧩
+/// </summary>
+[ApiDescriptionSettings(Order = 100, Description = "语言服务")]
+public partial class SysLangService : IDynamicApiController, ITransient
+{
+    private readonly SqlSugarRepository<SysLang> _sysLangRep;
+
+    public SysLangService(SqlSugarRepository<SysLang> sysLangRep)
+    {
+        _sysLangRep = sysLangRep;
+    }
+
+    /// <summary>
+    /// 分页查询语言 🔖
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("分页查询语言")]
+    [ApiDescriptionSettings(Name = "Page"), HttpPost]
+    public async Task<SqlSugarPagedList<SysLangOutput>> Page(PageSysLangInput input)
+    {
+        input.Keyword = input.Keyword?.Trim();
+        var query = _sysLangRep.AsQueryable()
+            .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Name.Contains(input.Keyword) || u.Code.Contains(input.Keyword) || u.IsoCode.Contains(input.Keyword) || u.UrlCode.Contains(input.Keyword))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.Name), u => u.Name.Contains(input.Name.Trim()))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.Code), u => u.Code.Contains(input.Code.Trim()))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.IsoCode), u => u.IsoCode.Contains(input.IsoCode.Trim()))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.UrlCode), u => u.UrlCode.Contains(input.UrlCode.Trim()))
+            .Select<SysLangOutput>();
+        return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
+    }
+
+    /// <summary>
+    /// 获取语言详情 ℹ️
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("获取语言详情")]
+    [ApiDescriptionSettings(Name = "Detail"), HttpGet]
+    public async Task<SysLang> Detail([FromQuery] QueryByIdSysLangInput input)
+    {
+        return await _sysLangRep.GetFirstAsync(u => u.Id == input.Id);
+    }
+
+    /// <summary>
+    /// 增加语言 ➕
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("增加语言")]
+    [ApiDescriptionSettings(Name = "Add"), HttpPost]
+    public async Task<long> Add(AddSysLangInput input)
+    {
+        var entity = input.Adapt<SysLang>();
+        return await _sysLangRep.InsertAsync(entity) ? entity.Id : 0;
+    }
+
+    /// <summary>
+    /// 更新语言 ✏️
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("更新语言")]
+    [ApiDescriptionSettings(Name = "Update"), HttpPost]
+    public async Task Update(UpdateSysLangInput input)
+    {
+        var entity = input.Adapt<SysLang>();
+        await _sysLangRep.AsUpdateable(entity)
+        .ExecuteCommandAsync();
+    }
+
+    /// <summary>
+    /// 删除语言 ❌
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("删除语言")]
+    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
+    public async Task Delete(DeleteSysLangInput input)
+    {
+        var entity = await _sysLangRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
+        await _sysLangRep.DeleteAsync(entity);   //真删除
+    }
+
+    /// <summary>
+    /// 获取下拉列表数据 🔖
+    /// </summary>
+    /// <returns></returns>
+    [AllowAnonymous]
+    [DisplayName("获取下拉列表数据")]
+    [ApiDescriptionSettings(Name = "DropdownData"), HttpPost]
+    public async Task<dynamic> DropdownData()
+    {
+        var data = await _sysLangRep.Context.Queryable<SysLang>()
+            .Where(m => m.Active == true)
+            .Select(u => new
+            {
+                Code = u.Code,
+                Value = u.UrlCode,
+                Label = $"{u.Name}"
+            }).ToListAsync();
+        return data;
+    }
+}

+ 73 - 0
Admin.NET/Admin.NET.Core/Service/LangText/Dto/SysLangTextDto.cs

@@ -0,0 +1,73 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 翻译表输出参数
+/// </summary>
+public class SysLangTextDto
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    public string EntityName { get; set; }
+
+    /// <summary>
+    /// 所属实体ID
+    /// </summary>
+    public long EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    public string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public string LangCode { get; set; }
+
+    /// <summary>
+    /// 翻译内容
+    /// </summary>
+    public string Content { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    public DateTime? CreateTime { get; set; }
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    public DateTime? UpdateTime { get; set; }
+
+    /// <summary>
+    /// 创建者Id
+    /// </summary>
+    public long? CreateUserId { get; set; }
+
+    /// <summary>
+    /// 创建者姓名
+    /// </summary>
+    public string? CreateUserName { get; set; }
+
+    /// <summary>
+    /// 修改者Id
+    /// </summary>
+    public long? UpdateUserId { get; set; }
+
+    /// <summary>
+    /// 修改者姓名
+    /// </summary>
+    public string? UpdateUserName { get; set; }
+}

+ 274 - 0
Admin.NET/Admin.NET.Core/Service/LangText/Dto/SysLangTextInput.cs

@@ -0,0 +1,274 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 翻译表基础输入参数
+/// </summary>
+public class SysLangTextBaseInput
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    public virtual long? Id { get; set; }
+
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    [Required(ErrorMessage = "所属实体名不能为空")]
+    public virtual string EntityName { get; set; }
+
+    /// <summary>
+    /// 所属实体ID
+    /// </summary>
+    [Required(ErrorMessage = "所属实体ID不能为空")]
+    public virtual long? EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    [Required(ErrorMessage = "字段名不能为空")]
+    public virtual string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "语言代码不能为空")]
+    public virtual string LangCode { get; set; }
+
+    /// <summary>
+    /// 翻译内容
+    /// </summary>
+    [Required(ErrorMessage = "翻译内容不能为空")]
+    public virtual string Content { get; set; }
+}
+
+/// <summary>
+/// 翻译表分页查询输入参数
+/// </summary>
+public class PageSysLangTextInput : BasePageInput
+{
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    public string EntityName { get; set; }
+
+    /// <summary>
+    /// 所属实体ID
+    /// </summary>
+    public long? EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    public string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public string LangCode { get; set; }
+
+    /// <summary>
+    /// 翻译内容
+    /// </summary>
+    public string Content { get; set; }
+
+    /// <summary>
+    /// 选中主键列表
+    /// </summary>
+    public List<long> SelectKeyList { get; set; }
+}
+
+/// <summary>
+/// 翻译表增加输入参数
+/// </summary>
+public class AddSysLangTextInput
+{
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    [Required(ErrorMessage = "所属实体名不能为空")]
+    [MaxLength(255, ErrorMessage = "所属实体名字符长度不能超过255")]
+    public string EntityName { get; set; }
+
+    /// <summary>
+    /// 所属实体ID
+    /// </summary>
+    [Required(ErrorMessage = "所属实体ID不能为空")]
+    public long? EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    [Required(ErrorMessage = "字段名不能为空")]
+    [MaxLength(255, ErrorMessage = "字段名字符长度不能超过255")]
+    public string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "语言代码不能为空")]
+    [MaxLength(255, ErrorMessage = "语言代码字符长度不能超过255")]
+    public string LangCode { get; set; }
+
+    /// <summary>
+    /// 翻译内容
+    /// </summary>
+    [Required(ErrorMessage = "翻译内容不能为空")]
+    public string Content { get; set; }
+}
+
+/// <summary>
+/// 翻译表输入参数
+/// </summary>
+public class ListSysLangTextInput
+{
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    [Required(ErrorMessage = "所属实体名不能为空")]
+    public string EntityName { get; set; }
+
+    /// <summary>
+    /// 所属实体ID
+    /// </summary>
+    [Required(ErrorMessage = "所属实体ID不能为空")]
+    public long? EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    [Required(ErrorMessage = "字段名不能为空")]
+    public string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public string LangCode { get; set; }
+}
+
+/// <summary>
+/// 翻译表删除输入参数
+/// </summary>
+public class DeleteSysLangTextInput
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    [Required(ErrorMessage = "主键Id不能为空")]
+    public long? Id { get; set; }
+}
+
+/// <summary>
+/// 翻译表更新输入参数
+/// </summary>
+public class UpdateSysLangTextInput
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    [Required(ErrorMessage = "主键Id不能为空")]
+    public long? Id { get; set; }
+
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    [Required(ErrorMessage = "所属实体名不能为空")]
+    [MaxLength(255, ErrorMessage = "所属实体名字符长度不能超过255")]
+    public string EntityName { get; set; }
+
+    /// <summary>
+    /// 所属实体ID
+    /// </summary>
+    [Required(ErrorMessage = "所属实体ID不能为空")]
+    public long? EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    [Required(ErrorMessage = "字段名不能为空")]
+    [MaxLength(255, ErrorMessage = "字段名字符长度不能超过255")]
+    public string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [Required(ErrorMessage = "语言代码不能为空")]
+    [MaxLength(255, ErrorMessage = "语言代码字符长度不能超过255")]
+    public string LangCode { get; set; }
+
+    /// <summary>
+    /// 翻译内容
+    /// </summary>
+    [Required(ErrorMessage = "翻译内容不能为空")]
+    public string Content { get; set; }
+}
+
+/// <summary>
+/// 翻译表主键查询输入参数
+/// </summary>
+public class QueryByIdSysLangTextInput : DeleteSysLangTextInput
+{
+}
+
+/// <summary>
+/// 翻译表数据导入实体
+/// </summary>
+[ExcelImporter(SheetIndex = 1, IsOnlyErrorRows = true)]
+public class ImportSysLangTextInput : BaseImportInput
+{
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    [ImporterHeader(Name = "*所属实体名")]
+    [ExporterHeader("*所属实体名", Format = "", Width = 25, IsBold = true)]
+    public string EntityName { get; set; }
+
+    /// <summary>
+    /// 所属实体ID
+    /// </summary>
+    [ImporterHeader(Name = "*所属实体ID")]
+    [ExporterHeader("*所属实体ID", Format = "", Width = 25, IsBold = true)]
+    public long? EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    [ImporterHeader(Name = "*字段名")]
+    [ExporterHeader("*字段名", Format = "", Width = 25, IsBold = true)]
+    public string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    [ImporterHeader(Name = "*语言代码")]
+    [ExporterHeader("*语言代码", Format = "", Width = 25, IsBold = true)]
+    public string LangCode { get; set; }
+
+    /// <summary>
+    /// 翻译内容
+    /// </summary>
+    [ImporterHeader(Name = "*翻译内容")]
+    [ExporterHeader("*翻译内容", Format = "", Width = 25, IsBold = true)]
+    public string Content { get; set; }
+}
+
+/// <summary>
+///
+/// </summary>
+public class AiTranslateTextInput
+{
+    /// <summary>
+    /// 原文
+    /// </summary>
+    public string OriginalText { get; set; }
+
+    /// <summary>
+    /// 目标语言
+    /// </summary>
+    public string TargetLang { get; set; }
+}

+ 82 - 0
Admin.NET/Admin.NET.Core/Service/LangText/Dto/SysLangTextOutput.cs

@@ -0,0 +1,82 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+namespace Admin.NET.Core;
+
+/// <summary>
+/// 翻译表输出参数
+/// </summary>
+public class SysLangTextOutput
+{
+    /// <summary>
+    /// 主键Id
+    /// </summary>
+    public long Id { get; set; }
+
+    /// <summary>
+    /// 所属实体名
+    /// </summary>
+    public string EntityName { get; set; }
+
+    /// <summary>
+    /// 所属实体ID
+    /// </summary>
+    public long EntityId { get; set; }
+
+    /// <summary>
+    /// 字段名
+    /// </summary>
+    public string FieldName { get; set; }
+
+    /// <summary>
+    /// 语言代码
+    /// </summary>
+    public string LangCode { get; set; }
+
+    /// <summary>
+    /// 翻译内容
+    /// </summary>
+    public string Content { get; set; }
+
+    /// <summary>
+    /// 创建时间
+    /// </summary>
+    public DateTime? CreateTime { get; set; }
+
+    /// <summary>
+    /// 更新时间
+    /// </summary>
+    public DateTime? UpdateTime { get; set; }
+
+    /// <summary>
+    /// 创建者Id
+    /// </summary>
+    public long? CreateUserId { get; set; }
+
+    /// <summary>
+    /// 创建者姓名
+    /// </summary>
+    public string? CreateUserName { get; set; }
+
+    /// <summary>
+    /// 修改者Id
+    /// </summary>
+    public long? UpdateUserId { get; set; }
+
+    /// <summary>
+    /// 修改者姓名
+    /// </summary>
+    public string? UpdateUserName { get; set; }
+}
+
+/// <summary>
+/// 翻译表数据导入模板实体
+/// </summary>
+public class ExportSysLangTextOutput : ImportSysLangTextInput
+{
+    [ImporterHeader(IsIgnore = true)]
+    [ExporterHeader(IsIgnore = true)]
+    public override string Error { get; set; }
+}

+ 316 - 0
Admin.NET/Admin.NET.Core/Service/LangText/SysLangTextCacheService.cs

@@ -0,0 +1,316 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.Service;
+
+public class LangFieldMap<TEntity>
+{
+    /// <summary>实体名,如 Product</summary>
+    public string EntityName { get; set; }
+
+    /// <summary>字段名,如 Name/Description</summary>
+    public string FieldName { get; set; }
+
+    /// <summary>如何取主键ID</summary>
+    public Func<TEntity, long> IdSelector { get; set; }
+
+    /// <summary>如何写回翻译值</summary>
+    public Action<TEntity, string> SetTranslatedValue { get; set; }
+}
+
+/// <summary>
+/// 翻译缓存服务 🧩
+/// </summary>
+[ApiDescriptionSettings(Order = 100, Description = "翻译缓存服务")]
+public class SysLangTextCacheService : IDynamicApiController, ITransient
+{
+    private readonly SysCacheService _sysCacheService;
+    private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;
+    private TimeSpan expireSeconds = TimeSpan.FromHours(1);
+
+    public SysLangTextCacheService(
+        SysCacheService sysCacheService,
+        SqlSugarRepository<SysLangText> sysLangTextRep)
+    {
+        _sysCacheService = sysCacheService;
+        _sysLangTextRep = sysLangTextRep;
+    }
+
+    private string BuildKey(string entityName, string fieldName, long entityId, string langCode)
+    {
+        return $"LangCache_{entityName}_{fieldName}_{entityId}_{langCode}";
+    }
+
+    /// <summary>
+    /// 【单条翻译获取】
+    /// 根据实体类型、字段、主键ID 和语言编码获取翻译内容。<br/>
+    /// 适用于:小表(如菜单、字典),可设置较长缓存时间。<br/>
+    /// <br/>
+    /// 【示例】<br/>
+    /// var content = await _sysLangTextCacheService.GetTranslation("Product", "Name", 123, "en_US");
+    /// </summary>
+    /// <param name="entityName">实体名称,如 "Product"</param>
+    /// <param name="fieldName">字段名称,如 "Name"</param>
+    /// <param name="entityId">实体主键ID</param>
+    /// <param name="langCode">语言编码,如 "zh_CN"</param>
+    /// <returns>翻译后的内容(若无则返回 null 或空)</returns>
+    [NonAction]
+    public async Task<string> GetTranslation(string entityName, string fieldName, long entityId, string langCode)
+    {
+        var key = BuildKey(entityName, fieldName, entityId, langCode);
+        var value = _sysCacheService.Get<string>(key);
+        if (!string.IsNullOrEmpty(value)) return value;
+
+        value = await _sysLangTextRep.AsQueryable()
+            .Where(u => u.EntityName == entityName && u.FieldName == fieldName && u.EntityId == entityId && u.LangCode == langCode)
+            .Select(u => u.Content)
+            .FirstAsync();
+
+        if (!string.IsNullOrEmpty(value))
+        {
+            _sysCacheService.Set(key, value, expireSeconds); // 设置过期
+        }
+
+        return value;
+    }
+
+    /// <summary>
+    /// 【批量翻译获取】<br/>
+    /// 根据实体、字段和一批主键ID获取对应翻译内容,自动从缓存或数据库获取。<br/>
+    /// 适用于:SKU、多商品、批量字典等需要高效批量获取的场景。<br/>
+    ///
+    /// 【示例】<br/>
+    /// var dict = await _sysLangTextCacheService.GetTranslations("SKU", "Name", skuIds, "en_US");
+    /// </summary>
+    /// <param name="entityName">实体名称</param>
+    /// <param name="fieldName">字段名称</param>
+    /// <param name="entityIds">主键ID集合</param>
+    /// <param name="langCode">语言编码</param>
+    /// <returns>主键ID到翻译内容的字典</returns>
+    [NonAction]
+    public async Task<Dictionary<long, string>> GetTranslations(string entityName, string fieldName, List<long> entityIds, string langCode)
+    {
+        var result = new Dictionary<long, string>();
+        var missingIds = new HashSet<long>(); // 用 HashSet 提高后面 Contains 的性能
+
+        foreach (var id in entityIds.Distinct()) // 先去重,防止重复缓存 Key
+        {
+            var key = BuildKey(entityName, fieldName, id, langCode);
+            var value = _sysCacheService.Get<string>(key);
+            if (!string.IsNullOrWhiteSpace(value))
+            {
+                result[id] = value;
+            }
+            else
+            {
+                missingIds.Add(id);
+            }
+        }
+
+        if (missingIds.Any())
+        {
+            var list = await _sysLangTextRep.AsQueryable()
+                .Where(u => u.EntityName == entityName &&
+                            u.FieldName == fieldName &&
+                            missingIds.Contains(u.EntityId) &&
+                            u.LangCode == langCode)
+                .ToListAsync();
+
+            foreach (var item in list)
+            {
+                if (string.IsNullOrWhiteSpace(item.Content)) continue; // 跳过脏数据
+
+                var key = BuildKey(item.EntityName, item.FieldName, item.EntityId, item.LangCode);
+                _sysCacheService.Set(key, item.Content, expireSeconds);
+
+                // 用 TryAdd 防止异常
+                result[item.EntityId] = item.Content;
+            }
+        }
+
+        return result;
+    }
+
+    /// <summary>
+    /// 【列表翻译】<br/>
+    /// 按配置把同一字段的翻译写回到实体列表中。内部会调用批量翻译接口。<br/>
+    /// <br/>
+    /// 【示例】<br/>
+    /// await _sysLangTextCacheService.TranslateList(products, "Product", "Name", p =&gt; p.Id, (p, val) =&gt; p.Name = val, "zh_CN");
+    /// </summary>
+    /// <typeparam name="TEntity">实体类型</typeparam>
+    /// <param name="list">待翻译的实体列表</param>
+    /// <param name="entityName">实体名称</param>
+    /// <param name="fieldName">字段名称</param>
+    /// <param name="idSelector">用于取出主键ID的表达式</param>
+    /// <param name="setTranslatedValue">写回翻译值的委托</param>
+    /// <param name="langCode">语言编码</param>
+    /// <returns>翻译后的实体列表(引用传递)</returns>
+    [NonAction]
+    public async Task<List<TEntity>> TranslateList<TEntity>(List<TEntity> list, string entityName, string fieldName, Func<TEntity, long> idSelector, Action<TEntity, string> setTranslatedValue, string langCode)
+    {
+        var ids = list.Select(idSelector).Distinct().ToList();
+        var dict = await GetTranslations(entityName, fieldName, ids, langCode);
+
+        foreach (var item in list)
+        {
+            var id = idSelector(item);
+            if (dict.TryGetValue(id, out var value))
+            {
+                setTranslatedValue(item, value);
+            }
+        }
+
+        return list;
+    }
+
+    /// <summary>
+    /// 【多字段批量翻译】
+    /// 对列表中的实体对象,按配置的字段映射进行多字段翻译处理。<br/>
+    /// 常用于:菜单多语言、商品多语言、SKU多语言等需要多字段翻译的场景。<br/><br/>
+    /// ✅ 特点:<br/>
+    /// 1️⃣ 可同时翻译同一实体的多个字段(如 Name、Description、Title 等)<br/>
+    /// 2️⃣ 内部先尝试从缓存读取,如缓存未命中则批量查询数据库,并自动写回缓存<br/>
+    /// 3️⃣ 引用传递,直接对原实体对象赋值,无需额外返回<br/><br/>
+    /// 【使用示例】:<br/>
+    /// <code>
+    /// var fields = new List&lt;LangFieldMap&lt;Product&gt;&gt;
+    /// {
+    ///     new LangFieldMap&lt;Product&gt; {
+    ///         EntityName = "Product",
+    ///         FieldName = "Name",
+    ///         IdSelector = p =&gt; p.Id,
+    ///         SetTranslatedValue = (p, val) =&gt; p.Name = val
+    ///     },
+    ///     new LangFieldMap&lt;Product&gt; {
+    ///         EntityName = "Product",
+    ///         FieldName = "Description",
+    ///         IdSelector = p =&gt; p.Id,
+    ///         SetTranslatedValue = (p, val) =&gt; p.Description = val
+    ///     }
+    /// };
+    /// await _sysLangTextCacheService.TranslateMultiFields(products, fields, "zh_CN");
+    /// </code>
+    /// </summary>
+    /// <typeparam name="TEntity">要翻译的实体类型,如 Product/Menu/SKU 等</typeparam>
+    /// <param name="list">需要翻译的实体对象列表</param>
+    /// <param name="fields">需要翻译的字段映射集合,支持多个字段</param>
+    /// <param name="langCode">语言编码,如 "zh_CN"、"en_US"、"it_IT" 等</param>
+    /// <returns>翻译后的实体列表(引用传递,原对象已直接赋值)</returns>
+    [NonAction]
+    public async Task<List<TEntity>> TranslateMultiFields<TEntity>(
+    List<TEntity> list,
+    List<LangFieldMap<TEntity>> fields,
+    string langCode)
+    {
+        var keyToField = new Dictionary<string, (TEntity Entity, LangFieldMap<TEntity> FieldMap)>();
+        var missingKeys = new List<string>();
+
+        // 先尝试从缓存读取
+        foreach (var item in list)
+        {
+            foreach (var field in fields)
+            {
+                var id = field.IdSelector(item);
+                var key = BuildKey(field.EntityName, field.FieldName, id, langCode);
+                var cached = _sysCacheService.Get<string>(key);
+                if (!string.IsNullOrEmpty(cached))
+                {
+                    // 命中缓存,直接赋值
+                    field.SetTranslatedValue(item, cached);
+                }
+                else
+                {
+                    // 缓存未命中,加入待查表
+                    keyToField[key] = (item, field);
+                    missingKeys.Add(key);
+                }
+            }
+        }
+
+        if (missingKeys.Any())
+        {
+            // 把缺失的 keys 拆解成组合实体
+            var missingTuples = missingKeys
+                .Select(key =>
+                {
+                    var parts = key.Split('_');
+                    return new
+                    {
+                        EntityName = parts[1],
+                        FieldName = parts[2],
+                        EntityId = long.Parse(parts[3])
+                    };
+                })
+                .ToList();
+
+            // 按 EntityName + FieldName 分组
+            var grouped = missingTuples
+                .GroupBy(x => new { x.EntityName, x.FieldName })
+                .ToList();
+
+            var result = new List<SysLangText>();
+
+            // 分批查询,每组单独查询
+            const int chunkSize = 500;
+            foreach (var g in grouped)
+            {
+                var allIds = g.Select(x => x.EntityId).Distinct().ToList();
+                for (int i = 0; i < allIds.Count; i += chunkSize)
+                {
+                    var chunk = allIds.Skip(i).Take(chunkSize).ToList();
+                    var temp = await _sysLangTextRep.AsQueryable()
+                        .Where(u => u.LangCode == langCode
+                                    && u.EntityName == g.Key.EntityName
+                                    && u.FieldName == g.Key.FieldName
+                                    && chunk.Contains(u.EntityId))
+                        .ToListAsync();
+                    result.AddRange(temp);
+                }
+            }
+
+            // 遍历查询结果,写回实体和缓存
+            foreach (var item in result)
+            {
+                var key = BuildKey(item.EntityName, item.FieldName, item.EntityId, item.LangCode);
+                if (keyToField.TryGetValue(key, out var tuple))
+                {
+                    tuple.FieldMap.SetTranslatedValue(tuple.Entity, item.Content);
+                    _sysCacheService.Set(key, item.Content, expireSeconds);
+                }
+            }
+        }
+
+        return list;
+    }
+
+    /// <summary>
+    /// 删除缓存
+    /// </summary>
+    /// <param name="entityName"></param>
+    /// <param name="fieldName"></param>
+    /// <param name="entityId"></param>
+    /// <param name="langCode"></param>
+    public void DeleteCache(string entityName, string fieldName, long entityId, string langCode)
+    {
+        var key = BuildKey(entityName, fieldName, entityId, langCode);
+        _sysCacheService.Remove(key);
+    }
+
+    /// <summary>
+    /// 更新缓存
+    /// </summary>
+    /// <param name="entityName"></param>
+    /// <param name="fieldName"></param>
+    /// <param name="entityId"></param>
+    /// <param name="langCode"></param>
+    /// <param name="newValue"></param>
+    public void UpdateCache(string entityName, string fieldName, long entityId, string langCode, string newValue)
+    {
+        var key = BuildKey(entityName, fieldName, entityId, langCode);
+        _sysCacheService.Set(key, newValue, expireSeconds);
+    }
+}

+ 435 - 0
Admin.NET/Admin.NET.Core/Service/LangText/SysLangTextService.cs

@@ -0,0 +1,435 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+//
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+//
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+using Newtonsoft.Json;
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 翻译服务 🧩
+/// </summary>
+[ApiDescriptionSettings(Order = 100, Description = "翻译服务")]
+public partial class SysLangTextService : IDynamicApiController, ITransient
+{
+    private readonly SqlSugarRepository<SysLangText> _sysLangTextRep;
+    private readonly ISqlSugarClient _sqlSugarClient;
+    private readonly SysLangTextCacheService _sysLangTextCacheService;
+
+    public SysLangTextService(
+        SqlSugarRepository<SysLangText> sysLangTextRep,
+        SysLangTextCacheService sysLangTextCacheService,
+        ISqlSugarClient sqlSugarClient)
+    {
+        _sysLangTextRep = sysLangTextRep;
+        _sqlSugarClient = sqlSugarClient;
+        _sysLangTextCacheService = sysLangTextCacheService;
+    }
+
+    /// <summary>
+    /// 分页查询翻译表 🔖
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("分页查询翻译表")]
+    [ApiDescriptionSettings(Name = "Page"), HttpPost]
+    public async Task<SqlSugarPagedList<SysLangTextOutput>> Page(PageSysLangTextInput input)
+    {
+        input.Keyword = input.Keyword?.Trim();
+        var query = _sysLangTextRep.AsQueryable()
+            .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.EntityName.Contains(input.Keyword) || u.FieldName.Contains(input.Keyword) || u.LangCode.Contains(input.Keyword) || u.Content.Contains(input.Keyword))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.EntityName), u => u.EntityName.Contains(input.EntityName.Trim()))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.FieldName), u => u.FieldName.Contains(input.FieldName.Trim()))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode.Contains(input.LangCode.Trim()))
+            .WhereIF(!string.IsNullOrWhiteSpace(input.Content), u => u.Content.Contains(input.Content.Trim()))
+            .WhereIF(input.EntityId != null, u => u.EntityId == input.EntityId)
+            .Select<SysLangTextOutput>();
+        return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
+    }
+
+    [DisplayName("获取翻译表")]
+    [ApiDescriptionSettings(Name = "List"), HttpPost]
+    public async Task<List<SysLangTextOutput>> List(ListSysLangTextInput input)
+    {
+        var query = _sysLangTextRep.AsQueryable()
+            .Where(u => u.EntityName == input.EntityName.Trim() && u.FieldName == input.FieldName.Trim() && u.EntityId == input.EntityId)
+            .WhereIF(!string.IsNullOrWhiteSpace(input.LangCode), u => u.LangCode == input.LangCode.Trim())
+            .Select<SysLangTextOutput>();
+        return await query.ToListAsync();
+    }
+
+    /// <summary>
+    /// 获取翻译表详情 ℹ️
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("获取翻译表详情")]
+    [ApiDescriptionSettings(Name = "Detail"), HttpGet]
+    public async Task<SysLangText> Detail([FromQuery] QueryByIdSysLangTextInput input)
+    {
+        return await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id);
+    }
+
+    /// <summary>
+    /// 增加翻译表 ➕
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("增加翻译表")]
+    [ApiDescriptionSettings(Name = "Add"), HttpPost]
+    public async Task<long> Add(AddSysLangTextInput input)
+    {
+        var entity = input.Adapt<SysLangText>();
+        return await _sysLangTextRep.InsertAsync(entity) ? entity.Id : 0;
+    }
+
+    /// <summary>
+    /// 更新翻译表 ✏️
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("更新翻译表")]
+    [ApiDescriptionSettings(Name = "Update"), HttpPost]
+    public async Task Update(UpdateSysLangTextInput input)
+    {
+        var entity = input.Adapt<SysLangText>();
+        await _sysLangTextRep.AsUpdateable(entity)
+        .ExecuteCommandAsync();
+        _sysLangTextCacheService.UpdateCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode, entity.Content);
+    }
+
+    /// <summary>
+    /// 删除翻译表 ❌
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("删除翻译表")]
+    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
+    public async Task Delete(DeleteSysLangTextInput input)
+    {
+        var entity = await _sysLangTextRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
+
+        await _sysLangTextRep.DeleteAsync(entity);   //真删除
+        _sysLangTextCacheService.DeleteCache(entity.EntityName, entity.FieldName, entity.EntityId, entity.LangCode);
+    }
+
+    /// <summary>
+    /// 批量删除翻译表 ❌
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("批量删除翻译表")]
+    [ApiDescriptionSettings(Name = "BatchDelete"), HttpPost]
+    public async Task BatchDelete([Required(ErrorMessage = "主键列表不能为空")] List<DeleteSysLangTextInput> input)
+    {
+        var exp = Expressionable.Create<SysLangText>();
+        foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);
+        var list = await _sysLangTextRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();
+
+        await _sysLangTextRep.DeleteAsync(list);   //真删除
+        foreach (var item in list)
+        {
+            _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);
+        }
+    }
+
+    private static readonly object _sysLangTextBatchSaveLock = new object();
+
+    /// <summary>
+    /// 批量保存翻译表 ✏️
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("批量保存翻译表")]
+    [ApiDescriptionSettings(Name = "BatchSave"), HttpPost]
+    public void BatchSave([Required(ErrorMessage = "列表不能为空")] List<ImportSysLangTextInput> input)
+    {
+        lock (_sysLangTextBatchSaveLock)
+        {
+            // 校验并过滤必填基本类型为null的字段
+            var rows = input.Where(x =>
+            {
+                if (!string.IsNullOrWhiteSpace(x.Error)) return false;
+                if (x.EntityId == null)
+                {
+                    x.Error = "所属实体ID不能为空";
+                    return false;
+                }
+                return true;
+            }).Adapt<List<SysLangText>>();
+
+            var storageable = _sysLangTextRep.Context.Storageable(rows)
+                .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), "所属实体名不能为空")
+                .SplitError(it => it.Item.EntityName?.Length > 255, "所属实体名长度不能超过255个字符")
+                .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), "字段名不能为空")
+                .SplitError(it => it.Item.FieldName?.Length > 255, "字段名长度不能超过255个字符")
+                .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), "语言代码不能为空")
+                .SplitError(it => it.Item.LangCode?.Length > 255, "语言代码长度不能超过255个字符")
+                .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), "翻译内容不能为空")
+                .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })
+                .SplitInsert(it => it.NotAny())
+                .SplitUpdate(it => it.Any())
+                .ToStorage();
+
+            storageable.AsInsertable.ExecuteCommand();// 不存在插入
+            storageable.AsUpdateable.UpdateColumns(it => new
+            {
+                it.EntityName,
+                it.EntityId,
+                it.FieldName,
+                it.LangCode,
+                it.Content,
+            }).ExecuteCommand();// 存在更新
+            foreach (var item in rows)
+            {
+                _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);
+            }
+            if (storageable.ErrorList.Any())
+            {
+                throw Oops.Oh($"处理过程中出现以下错误:{string.Join(";", storageable.ErrorList.Distinct())}");
+            }
+        }
+    }
+
+    /// <summary>
+    /// 导出翻译表记录 🔖
+    /// </summary>
+    /// <param name="input"></param>
+    /// <returns></returns>
+    [DisplayName("导出翻译表记录")]
+    [ApiDescriptionSettings(Name = "Export"), HttpPost, NonUnify]
+    public async Task<IActionResult> Export(PageSysLangTextInput input)
+    {
+        var list = (await Page(input)).Items?.Adapt<List<ExportSysLangTextOutput>>() ?? new();
+        if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();
+        return ExcelHelper.ExportTemplate(list, "翻译表导出记录");
+    }
+
+    /// <summary>
+    /// 下载翻译表数据导入模板 ⬇️
+    /// </summary>
+    /// <returns></returns>
+    [DisplayName("下载翻译表数据导入模板")]
+    [ApiDescriptionSettings(Name = "Import"), HttpGet, NonUnify]
+    public IActionResult DownloadTemplate()
+    {
+        return ExcelHelper.ExportTemplate(new List<ExportSysLangTextOutput>(), "翻译表导入模板");
+    }
+
+    private static readonly object _sysLangTextImportLock = new object();
+
+    /// <summary>
+    /// 导入翻译表记录 💾
+    /// </summary>
+    /// <returns></returns>
+    [DisplayName("导入翻译表记录")]
+    [ApiDescriptionSettings(Name = "Import"), HttpPost, NonUnify, UnitOfWork]
+    public IActionResult ImportData([Required] IFormFile file)
+    {
+        lock (_sysLangTextImportLock)
+        {
+            var stream = ExcelHelper.ImportData<ImportSysLangTextInput, SysLangText>(file, (list, markerErrorAction) =>
+            {
+                _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>
+                {
+                    // 校验并过滤必填基本类型为null的字段
+                    var rows = pageItems.Where(x =>
+                    {
+                        if (!string.IsNullOrWhiteSpace(x.Error)) return false;
+                        if (x.EntityId == null)
+                        {
+                            x.Error = "所属实体ID不能为空";
+                            return false;
+                        }
+                        return true;
+                    }).Adapt<List<SysLangText>>();
+
+                    var storageable = _sysLangTextRep.Context.Storageable(rows)
+                        .SplitError(it => string.IsNullOrWhiteSpace(it.Item.EntityName), "所属实体名不能为空")
+                        .SplitError(it => it.Item.EntityName?.Length > 255, "所属实体名长度不能超过255个字符")
+                        .SplitError(it => string.IsNullOrWhiteSpace(it.Item.FieldName), "字段名不能为空")
+                        .SplitError(it => it.Item.FieldName?.Length > 255, "字段名长度不能超过255个字符")
+                        .SplitError(it => string.IsNullOrWhiteSpace(it.Item.LangCode), "语言代码不能为空")
+                        .SplitError(it => it.Item.LangCode?.Length > 255, "语言代码长度不能超过255个字符")
+                        .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Content), "翻译内容不能为空")
+                        .SplitError(it => it.Item.Content?.Length > 255, "翻译内容长度不能超过255个字符")
+                        .WhereColumns(it => new { it.EntityId, it.EntityName, it.FieldName, it.LangCode })
+                        .SplitInsert(it => it.NotAny())
+                        .SplitUpdate(it => it.Any())
+                        .ToStorage();
+
+                    storageable.AsInsertable.ExecuteCommand();// 不存在插入
+                    storageable.AsUpdateable.UpdateColumns(it => new
+                    {
+                        it.EntityName,
+                        it.EntityId,
+                        it.FieldName,
+                        it.LangCode,
+                        it.Content,
+                    }).ExecuteCommand();// 存在更新
+
+                    foreach (var item in rows)
+                    {
+                        _sysLangTextCacheService.DeleteCache(item.EntityName, item.FieldName, item.EntityId, item.LangCode);
+                    }
+                    // 标记错误信息
+                    markerErrorAction.Invoke(storageable, pageItems, rows);
+                });
+            });
+
+            return stream;
+        }
+    }
+
+    /// <summary>
+    /// DEEPSEEK 翻译接口
+    /// </summary>
+    /// <returns></returns>
+    [DisplayName("DEEPSEEK 翻译接口")]
+    [ApiDescriptionSettings(Name = "AiTranslateText"), HttpPost]
+    public async Task<string> AiTranslateText(AiTranslateTextInput input)
+    {
+        // 需要先把DeepSeek.example复制改名为DeepSeek.json文件,添加你的 API KEY
+        var deepSeekOptions = App.GetConfig<DeepSeekOptions>("DeepSeekSettings", true);
+        if (deepSeekOptions == null)
+        {
+            throw new InvalidOperationException("DeepSeek.json文件 未定义");
+        }
+        if (string.IsNullOrEmpty(deepSeekOptions.ApiKey))
+        {
+            throw new InvalidOperationException("环境变量 DEEPSEEK_API_KEY 未定义");
+        }
+
+        using (HttpClient client = new HttpClient())
+        {
+            // 构建请求头
+            client.DefaultRequestHeaders.Add("Authorization", $"Bearer {deepSeekOptions.ApiKey}");
+
+            // 构建系统提示词
+            string systemPrompt = BuildSystemPrompt(deepSeekOptions.SourceLang, input.TargetLang);
+
+            // 构建请求体
+            var requestBody = new
+            {
+                model = "deepseek-chat",
+                messages = new[]
+                {
+                    new { role = "system", content = systemPrompt },
+                    new { role = "user", content = input.OriginalText }
+                },
+                temperature = 0.3,
+                max_tokens = 2000
+            };
+
+            // 使用 Newtonsoft.Json 序列化
+            var json = JsonConvert.SerializeObject(requestBody);
+            var content = new StringContent(json, Encoding.UTF8, "application/json");
+
+            // 发送请求
+            HttpResponseMessage response = await client.PostAsync(deepSeekOptions.ApiUrl, content);
+
+            // 处理响应
+            string responseBody = await response.Content.ReadAsStringAsync();
+
+            if (!response.IsSuccessStatusCode)
+            {
+                // 使用 Newtonsoft.Json 反序列化错误响应
+                var errorResponse = JsonConvert.DeserializeObject<ErrorResponse>(responseBody);
+                string errorMsg = errorResponse?.error?.message ?? $"HTTP {response.StatusCode}: {response.ReasonPhrase}";
+                throw new HttpRequestException($"翻译API返回错误:{errorMsg}");
+            }
+
+            // 解析有效响应
+            var result = JsonConvert.DeserializeObject<TranslationResponse>(responseBody);
+
+            if (result?.choices == null || result.choices.Length == 0 ||
+                result.choices[0]?.message?.content == null)
+            {
+                throw new InvalidOperationException("API返回无效的翻译结果");
+            }
+
+            return result.choices[0].message.content.Trim();
+        }
+    }
+
+    // JSON 响应模型
+    private class TranslationResponse
+    {
+        public Choice[] choices { get; set; }
+    }
+
+    private class Choice
+    {
+        public Message message { get; set; }
+    }
+
+    private class Message
+    {
+        public string content { get; set; }
+    }
+
+    private class ErrorResponse
+    {
+        public ErrorInfo error { get; set; }
+    }
+
+    private class ErrorInfo
+    {
+        public string message { get; set; }
+    }
+
+    /// <summary>
+    /// 生成提示词
+    /// </summary>
+    /// <param name="targetLang"></param>
+    /// <returns></returns>
+    private static string BuildSystemPrompt(string sourceLang, string targetLang)
+    {
+        return $@"作为企业软件系统专业翻译,严格遵守以下铁律:
+
+■ 核心原则
+1. 严格逐符号翻译({sourceLang}→{targetLang})
+2. 禁止添加/删除/改写任何内容
+3. 保持批量翻译的编号格式
+
+■ 符号保留规则
+! 所有符号必须原样保留:
+• 编程符号:\${{ }} <% %> @ # & |
+• UI占位符:{{0}} %s [ ]
+• 货币单位:¥100.00 kg cm²
+• 中文符号:【 】 《 》 :
+
+■ 中文符号位置规范
+# 三级处理机制:
+1. 成对符号必须保持完整结构:
+   ✓ 正确:【Warning】Text
+   ✗ 禁止:Warning【 】Text
+
+2. 独立符号位置:
+   • 优先句尾 → Text】?
+   • 次选句首 → 】Text?
+   • 禁止句中 → Text】Text?
+
+3. 跨字符串符号处理:
+   • 前段含【时 → 保留在段尾(""Synchronize【"")
+   • 后段含】时 → 保留在段首(""】authorization data?"")
+   • 符号后接字母时添加空格:】 Authorization
+
+■ 语法规范
+• 外文 → 被动语态(""Item was created"")
+• 中文 → 主动语态(""已创建项目"")
+• 禁止推测上下文(只翻译当前字符串内容)
+
+■ 错误预防(绝对禁止)
+✗ 将中文符号改为西式符号(】→])
+✗ 移动非中文符号位置
+✗ 添加原文不存在的内容
+✗ 合并/拆分原始字符串
+
+■ 批量处理
+▸ 严格保持原始JSON结构
+▸ 语言键名精确匹配(zh-cn/en/it等)";
+    }
+}

+ 84 - 16
Admin.NET/Admin.NET.Core/Service/Menu/SysMenuService.cs

@@ -19,6 +19,7 @@ public class SysMenuService : IDynamicApiController, ITransient
     private readonly SysUserMenuService _sysUserMenuService;
     private readonly SysCacheService _sysCacheService;
     private readonly UserManager _userManager;
+    private readonly SysLangTextCacheService _sysLangTextCacheService;
 
     public SysMenuService(
         SqlSugarRepository<SysTenantMenu> sysTenantMenuRep,
@@ -27,7 +28,8 @@ public class SysMenuService : IDynamicApiController, ITransient
         SysUserRoleService sysUserRoleService,
         SysUserMenuService sysUserMenuService,
         SysCacheService sysCacheService,
-        UserManager userManager)
+        UserManager userManager,
+        SysLangTextCacheService sysLangTextCacheService)
     {
         _userManager = userManager;
         _sysMenuRep = sysMenuRep;
@@ -36,6 +38,7 @@ public class SysMenuService : IDynamicApiController, ITransient
         _sysUserMenuService = sysUserMenuService;
         _sysTenantMenuRep = sysTenantMenuRep;
         _sysCacheService = sysCacheService;
+        _sysLangTextCacheService = sysLangTextCacheService;
     }
 
     /// <summary>
@@ -45,18 +48,42 @@ public class SysMenuService : IDynamicApiController, ITransient
     [DisplayName("获取登录菜单树")]
     public async Task<List<MenuOutput>> GetLoginMenuTree()
     {
+        var langCode = _userManager.LangCode;
         var (query, _) = GetSugarQueryableAndTenantId(_userManager.TenantId);
-        if (_userManager.SuperAdmin || _userManager.SysAdmin)
+
+        // 查询菜单主表(过滤非按钮和禁用)
+        var menuQuery = query.Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable);
+
+        if (!(_userManager.SuperAdmin || _userManager.SysAdmin))
         {
-            var menuList = await query.Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable)
-                .OrderBy(u => new { u.OrderNo, u.Id })
-                .ToTreeAsync(u => u.Children, u => u.Pid, 0);
-            return menuList.Adapt<List<MenuOutput>>();
+            var menuIdList = await GetMenuIdList();
+            menuQuery = menuQuery.Where(u => menuIdList.Contains(u.Id));
         }
 
-        var menuIdList = await GetMenuIdList();
-        var menuTree = await query.Where(u => u.Type != MenuTypeEnum.Btn && u.Status == StatusEnum.Enable)
-            .OrderBy(u => new { u.OrderNo, u.Id }).ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray());
+        // 查询主表(不再 LEFT JOIN)
+        var menuList = await menuQuery
+            .OrderBy(u => new { u.OrderNo, u.Id })
+            .ToListAsync();
+
+        // 调用缓存翻译:翻译 Title 字段
+        var fields = new List<LangFieldMap<SysMenu>>
+        {
+            new LangFieldMap<SysMenu>
+            {
+                EntityName = "SysMenu",
+                FieldName = "Title",
+                IdSelector = m => m.Id,
+                SetTranslatedValue = (m, val) => m.Title = val
+            }
+        };
+        await _sysLangTextCacheService.TranslateMultiFields(menuList, fields, langCode);
+
+        // 构造树
+        var menuTree = menuList.ToTree(
+            it => it.Children, it => it.Pid, 0
+        );
+
+        // 转换为输出 DTO
         return menuTree.Adapt<List<MenuOutput>>();
     }
 
@@ -67,21 +94,62 @@ public class SysMenuService : IDynamicApiController, ITransient
     [DisplayName("获取菜单列表")]
     public async Task<List<SysMenu>> GetList([FromQuery] MenuInput input)
     {
+        var langCode = _userManager.LangCode;
         var menuIdList = _userManager.SuperAdmin || _userManager.SysAdmin ? new List<long>() : await GetMenuIdList();
         var (query, _) = GetSugarQueryableAndTenantId(input.TenantId);
 
-        // 有筛选条件时返回list列表(防止构造不出树
+        // 有条件直接查询菜单列表(带 Title、Type 过滤
         if (!string.IsNullOrWhiteSpace(input.Title) || input.Type is > 0)
         {
-            return await query.WhereIF(!string.IsNullOrWhiteSpace(input.Title), u => u.Title.Contains(input.Title))
+            var menuList = await query
+                .WhereIF(!string.IsNullOrWhiteSpace(input.Title), u => u.Title.Contains(input.Title))
                 .WhereIF(input.Type is > 0, u => u.Type == input.Type)
-                .WhereIF(menuIdList.Count > 1, u => menuIdList.Contains(u.Id))
-                .OrderBy(u => new { u.OrderNo, u.Id }).Distinct().ToListAsync();
+                .WhereIF(menuIdList.Count > 0, u => menuIdList.Contains(u.Id))
+                .OrderBy(u => new { u.OrderNo, u.Id })
+                .ToListAsync();
+
+            // 走缓存批量翻译
+            var fields = new List<LangFieldMap<SysMenu>>
+            {
+                new LangFieldMap<SysMenu>
+                {
+                    EntityName = "SysMenu",
+                    FieldName = "Title",
+                    IdSelector = m => m.Id,
+                    SetTranslatedValue = (m, val) => m.Title = val
+                }
+            };
+            await _sysLangTextCacheService.TranslateMultiFields(menuList, fields, langCode);
+
+            return menuList.Distinct().ToList();
+        }
+
+        // 无筛选条件则走全量树形结构(带权限)
+        if (!(_userManager.SuperAdmin || _userManager.SysAdmin))
+        {
+            query = query.Where(u => menuIdList.Contains(u.Id));
         }
 
-        return _userManager.SuperAdmin || _userManager.SysAdmin ?
-            await query.OrderBy(u => new { u.OrderNo, u.Id }).Distinct().ToTreeAsync(u => u.Children, u => u.Pid, 0) :
-            await query.OrderBy(u => new { u.OrderNo, u.Id }).Distinct().ToTreeAsync(u => u.Children, u => u.Pid, 0, menuIdList.Select(d => (object)d).ToArray()); // 角色菜单授权时
+        var menuFullList = await query
+            .OrderBy(u => new { u.OrderNo, u.Id })
+            .ToListAsync();
+
+        // 走缓存批量翻译
+        var treeFields = new List<LangFieldMap<SysMenu>>
+        {
+            new LangFieldMap<SysMenu>
+            {
+                    EntityName = "SysMenu",
+                    FieldName = "Title",
+                    IdSelector = m => m.Id,
+                    SetTranslatedValue = (m, val) => m.Title = val
+            }
+        };
+        await _sysLangTextCacheService.TranslateMultiFields(menuFullList, treeFields, langCode);
+
+        // 组装树
+        var menuTree = menuFullList.ToTree(it => it.Children, it => it.Pid, 0);
+        return menuTree.ToList();
     }
 
     /// <summary>

+ 3 - 1
Admin.NET/Admin.NET.Core/Service/Org/Dto/OrgTreeOutput.cs

@@ -16,10 +16,12 @@ public class OrgTreeOutput
     /// </summary>
     [SugarColumn(IsTreeKey = true)]
     public long Id { get; set; }
+
     /// <summary>
     /// 租户Id
     /// </summary>
     public long TenantId { get; set; }
+
     /// <summary>
     /// 父Id
     /// </summary>
@@ -39,4 +41,4 @@ public class OrgTreeOutput
     /// 是否禁止选中
     /// </summary>
     public bool Disabled { get; set; }
-}
+}

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

@@ -148,7 +148,6 @@ public class SysServerService : IDynamicApiController, ITransient
         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(Novell.Directory.Ldap.LdapConnection).Assembly.GetName();
         var ipToolAssembly = typeof(IPTools.Core.IpTool).Assembly.GetName();
@@ -180,7 +179,6 @@ public class SysServerService : IDynamicApiController, ITransient
             new { qRCodeGeneratorAssembly.Name, qRCodeGeneratorAssembly.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 },

+ 15 - 0
Admin.NET/Admin.NET.Core/Service/User/SysUserService.cs

@@ -211,6 +211,21 @@ public class SysUserService : IDynamicApiController, ITransient
     }
 
     /// <summary>
+    /// 更新当前用户语言 🔖
+    /// </summary>
+    /// <param name="langCode"></param>
+    /// <returns></returns>
+    [UnitOfWork]
+    [ApiDescriptionSettings(Name = "SetLangCode"), HttpPost]
+    [DisplayName("更新当前用户语言")]
+    public virtual async Task SetLangCode(string langCode)
+    {
+        var user = await _sysUserRep.AsQueryable().ClearFilter().FirstAsync(u => u.Id == _userManager.UserId) ?? throw Oops.Oh(ErrorCodeEnum.D1011).StatusCode(401);
+        user.LangCode = langCode;
+        await _sysUserRep.AsUpdateable(user).UpdateColumns(it => it.LangCode).ExecuteCommandAsync();
+    }
+
+    /// <summary>
     /// 更新角色和扩展机构
     /// </summary>
     /// <param name="input"></param>

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

@@ -58,6 +58,8 @@ public class UserManager : IScoped
     /// </summary>
     public string OpenId => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.OpenId)?.Value;
 
+    public string LangCode => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimConst.LangCode)?.Value ?? "zh_CN";
+
     public UserManager(IHttpContextAccessor httpContextAccessor)
     {
         _httpContextAccessor = httpContextAccessor;

+ 4 - 4
Admin.NET/Admin.NET.Test/Admin.NET.Test.csproj

@@ -12,11 +12,11 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="Furion.Xunit" Version="4.9.7.108" />
+    <PackageReference Include="Furion.Xunit" Version="4.9.7.114" />
     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
-    <PackageReference Include="Selenium.Support" Version="4.34.0" />
-    <PackageReference Include="Selenium.WebDriver" Version="4.34.0" />
-    <PackageReference Include="Selenium.WebDriver.MSEdgeDriver" Version="138.0.3351.121" />
+    <PackageReference Include="Selenium.Support" Version="4.35.0" />
+    <PackageReference Include="Selenium.WebDriver" Version="4.35.0" />
+    <PackageReference Include="Selenium.WebDriver.MSEdgeDriver" Version="139.0.3405.102" />
     <PackageReference Include="xunit.assert" Version="2.9.3" />
   </ItemGroup>
 

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

@@ -39,6 +39,7 @@ public static class ProjectOptions
         services.AddConfigurableOptions<EventBusOptions>();
         services.AddConfigurableOptions<AlipayOptions>();
         services.AddConfigurableOptions<CDConfigOptions>();
+        services.AddConfigurableOptions<DeepSeekOptions>();
         services.Configure<IpRateLimitOptions>(App.Configuration.GetSection("IpRateLimiting"));
         services.Configure<IpRateLimitPolicies>(App.Configuration.GetSection("IpRateLimitPolicies"));
         services.Configure<ClientRateLimitOptions>(App.Configuration.GetSection("ClientRateLimiting"));

+ 2 - 2
Web/src/components/table/index.vue

@@ -138,10 +138,10 @@ import { ElMessage } from 'element-plus';
 import Sortable from 'sortablejs';
 import { storeToRefs } from 'pinia';
 import printJs from 'print-js';
-import { EmptyObjectType } from "/@/types/global";
+//import { EmptyObjectType } from "/@/types/global";
 import formatter from '/@/components/table/formatter.vue';
 import { useThemeConfig } from '/@/stores/themeConfig';
-import { exportExcel } from '/@/utils/exportExcel';
+import { exportExcel } from '/@/utils/exportExcel';  //TODO: 此包会引起浏览器控制台报 Module "stream" has been externalized for browser compatibility. Cannot access "stream.Readable" in client code. 警告,建议替换
 
 // 定义父组件传过来的值
 const props = defineProps({

+ 3 - 0
Web/src/theme/app.scss

@@ -230,6 +230,9 @@ body,
 	width: 100%;
 	overflow: hidden;
 }
+.flex-items-center {
+    align-items: center;
+}
 .flex-margin {
 	margin: auto;
 }

+ 112 - 114
Web/src/views/home/notice/index.vue

@@ -1,80 +1,78 @@
 <template>
-	<div class="notice-container">
-		<el-card shadow="hover" :body-style="{ padding: 5 }">
-			<el-form :model="state.queryParams" ref="queryForm" :inline="true">
-				<el-form-item label="标题">
-					<el-input v-model="state.queryParams.title" placeholder="标题" clearable />
-				</el-form-item>
-				<el-form-item label="类型">
-					<el-select v-model="state.queryParams.type" placeholder="类型" clearable>
-						<el-option label="通知" :value="1" />
-						<el-option label="公告" :value="2" />
-					</el-select>
-				</el-form-item>
-				<el-form-item>
-					<el-button-group>
-						<el-button type="primary" icon="ele-Search" @click="handleQuery"> 查询 </el-button>
-						<el-button icon="ele-Refresh" @click="resetQuery"> 重置 </el-button>
-					</el-button-group>
-				</el-form-item>
-			</el-form>
-		</el-card>
+    <div class="notice-container">
+        <el-card shadow="hover" :body-style="{ padding: 5 }">
+            <el-form :model="state.queryParams" ref="queryForm" :inline="true">
+                <el-form-item label="标题">
+                    <el-input v-model="state.queryParams.title" placeholder="标题" clearable />
+                </el-form-item>
+                <el-form-item label="类型">
+                    <el-select v-model="state.queryParams.type" placeholder="类型" clearable>
+                        <el-option label="通知" :value="1" />
+                        <el-option label="公告" :value="2" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-button-group>
+                        <el-button type="primary" icon="ele-Search" @click="handleQuery"> 查询 </el-button>
+                        <el-button icon="ele-Refresh" @click="resetQuery"> 重置 </el-button>
+                    </el-button-group>
+                </el-form-item>
+            </el-form>
+        </el-card>
 
-		<el-card class="full-table" shadow="hover" style="margin-top: 5px">
-			<el-table :data="state.noticeData" style="width: 100%" v-loading="state.loading" border :row-class-name="tableRowClassName">
-				<el-table-column type="index" label="序号" width="55" align="center" />
-				<el-table-column prop="sysNotice.title" label="标题" header-align="center" show-overflow-tooltip />
-				<el-table-column prop="sysNotice.content" label="内容" header-align="center" show-overflow-tooltip>
-					<template #default="scope"> {{ removeHtml(scope.row.sysNotice.content) }} </template>
-				</el-table-column>
-				<el-table-column prop="sysNotice.type" label="类型" width="100" align="center" show-overflow-tooltip>
-					<template #default="scope">
-            <g-sys-dict v-model="scope.row.sysNotice.type" code="NoticeTypeEnum"/>
-					</template>
-				</el-table-column>
-				<el-table-column prop="sysNotice.createTime" label="创建时间" align="center" show-overflow-tooltip />
-				<el-table-column prop="readStatus" label="阅读状态" width="100" align="center" show-overflow-tooltip>
-					<template #default="scope">
-            <g-sys-dict v-model="scope.row.readStatus" code="NoticeUserStatusEnum" />
-					</template>
-				</el-table-column>
-				<el-table-column prop="sysNotice.publicUserName" label="发布者" align="center" show-overflow-tooltip />
-				<el-table-column prop="sysNotice.publicTime" label="发布时间" align="center" show-overflow-tooltip />
-				<el-table-column label="操作" width="80" fixed="right" align="center" show-overflow-tooltip>
-					<template #default="scope">
-						<el-button icon="ele-InfoFilled" size="small" text type="primary" @click="viewDetail(scope.row)"> 详情 </el-button>
-					</template>
-				</el-table-column>
-			</el-table>
-			<el-pagination
-				v-model:currentPage="state.tableParams.page"
-				v-model:page-size="state.tableParams.pageSize"
-				:total="state.tableParams.total"
-				:page-sizes="[10, 20, 50, 100]"
-				size="small"
-				background
-				@size-change="handleSizeChange"
-				@current-change="handleCurrentChange"
-				layout="total, sizes, prev, pager, next, jumper"
-			/>
-		</el-card>
-		<el-dialog v-model="state.dialogVisible" draggable width="769px">
-			<template #header>
-				<div style="color: #fff">
-					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Bell /> </el-icon>
-					<span> 消息详情 </span>
-				</div>
-			</template>
-			<div class="w-e-text-container">
-				<div v-html="state.content" data-slate-editor></div>
-			</div>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button type="primary" @click="state.dialogVisible = false">确认</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
+        <el-card class="full-table" shadow="hover" style="margin-top: 5px">
+            <el-table :data="state.noticeData" style="width: 100%" v-loading="state.loading" border
+                :row-class-name="tableRowClassName">
+                <el-table-column type="index" label="序号" width="55" align="center" />
+                <el-table-column prop="sysNotice.title" label="标题" width="250" header-align="center" show-overflow-tooltip />
+                <el-table-column prop="sysNotice.content" label="内容" header-align="center" show-overflow-tooltip>
+                    <template #default="scope"> {{ removeHtml(scope.row.sysNotice.content) }} </template>
+                </el-table-column>
+                <el-table-column prop="sysNotice.type" label="类型" width="100" align="center">
+                    <template #default="scope">
+                        <g-sys-dict v-model="scope.row.sysNotice.type" code="NoticeTypeEnum" />
+                    </template>
+                </el-table-column>
+                <el-table-column prop="sysNotice.createTime" label="创建时间" width="180" align="center" />
+                <el-table-column prop="readStatus" label="阅读状态" width="100" align="center">
+                    <template #default="scope">
+                        <g-sys-dict v-model="scope.row.readStatus" code="NoticeUserStatusEnum" />
+                    </template>
+                </el-table-column>
+                <el-table-column prop="sysNotice.publicUserName" label="发布者" width="130" align="center" />
+                <el-table-column prop="sysNotice.publicTime" label="发布时间" width="180" align="center" />
+                <el-table-column label="操作" width="100" align="center" fixed="right">
+                    <template #default="scope">
+                        <el-button icon="ele-InfoFilled" size="small" text type="primary" @click="viewDetail(scope.row)"> 详情 </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <el-pagination size="small" background layout="total, sizes, prev, pager, next, jumper" 
+                v-model:currentPage="state.tableParams.page" 
+                v-model:page-size="state.tableParams.pageSize"
+                :total="state.tableParams.total" 
+                :page-sizes="[10, 20, 50, 100]"
+                @size-change="handleSizeChange" 
+                @current-change="handleCurrentChange"
+            />
+        </el-card>
+        <el-dialog v-model="state.dialogVisible" draggable width="769px">
+            <template #header>
+                <div style="color: #fff">
+                    <el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-Bell /></el-icon>
+                    <span> 消息详情 </span>
+                </div>
+            </template>
+            <div class="w-e-text-container">
+                <div v-html="state.content" data-slate-editor></div>
+            </div>
+            <template #footer>
+                <span class="dialog-footer">
+                    <el-button type="primary" @click="state.dialogVisible = false">确认</el-button>
+                </span>
+            </template>
+        </el-dialog>
+    </div>
 </template>
 
 <script setup lang="ts" name="notice">
@@ -87,68 +85,68 @@ import commonFunction from '/@/utils/commonFunction';
 
 const { removeHtml } = commonFunction();
 const state = reactive({
-	loading: false,
-	noticeData: [] as Array<SysNoticeUser>,
-	queryParams: {
-		title: undefined,
-		type: undefined,
-	},
-	tableParams: {
-		page: 1,
-		pageSize: 50,
-		total: 0 as any,
-	},
-	editNoticeTitle: '',
-	dialogVisible: false,
-	content: '',
+    loading: false,
+    noticeData: [] as Array<SysNoticeUser>,
+    queryParams: {
+        title: undefined,
+        type: undefined,
+    },
+    tableParams: {
+        page: 1,
+        pageSize: 50,
+        total: 0 as any,
+    },
+    editNoticeTitle: '',
+    dialogVisible: false,
+    content: '',
 });
 onMounted(async () => {
-	handleQuery();
+    handleQuery();
 });
 
 // 查询操作
 const handleQuery = async () => {
-	state.loading = true;
-	const pageNoticeInput = {
-		title: state.queryParams.title,
-		type: state.queryParams.type,
-		page: state.tableParams.page,
-		pageSize: state.tableParams.pageSize
-	};
-	var res = await getAPI(SysNoticeApi).apiSysNoticePageReceivedPost(pageNoticeInput);
-	state.noticeData = res.data.result?.items ?? [];
-	state.tableParams.total = res.data.result?.total;
-	state.loading = false;
+    state.loading = true;
+    const pageNoticeInput = {
+        title: state.queryParams.title,
+        type: state.queryParams.type,
+        page: state.tableParams.page,
+        pageSize: state.tableParams.pageSize
+    };
+    var res = await getAPI(SysNoticeApi).apiSysNoticePageReceivedPost(pageNoticeInput);
+    state.noticeData = res.data.result?.items ?? [];
+    state.tableParams.total = res.data.result?.total;
+    state.loading = false;
 };
 // 重置操作
 const resetQuery = () => {
-	state.queryParams.title = undefined;
-	state.queryParams.type = undefined;
-	handleQuery();
+    state.queryParams.title = undefined;
+    state.queryParams.type = undefined;
+    handleQuery();
 };
 // 改变页面容量
 const handleSizeChange = (val: number) => {
-	state.tableParams.pageSize = val;
-	handleQuery();
+    state.tableParams.pageSize = val;
+    handleQuery();
 };
 // 改变页码序号
 const handleCurrentChange = (val: number) => {
-	state.tableParams.page = val;
-	handleQuery();
+    state.tableParams.page = val;
+    handleQuery();
 };
 // 查看详情
 const viewDetail = async (row: any) => {
-	state.content = row.sysNotice.content;
-	state.dialogVisible = true;
+    state.content = row.sysNotice.content;
+    state.dialogVisible = true;
 
-	row.readStatus = 1;
-	// mittBus.emit('noticeRead', row.sysNotice.id);
-	await getAPI(SysNoticeApi).apiSysNoticeSetReadPost({ id: row.sysNotice.id });
+    row.readStatus = 1;
+    // mittBus.emit('noticeRead', row.sysNotice.id);
+    await getAPI(SysNoticeApi).apiSysNoticeSetReadPost({ id: row.sysNotice.id });
 };
 
 // eslint-disable-next-line @typescript-eslint/no-unused-vars
 const tableRowClassName = ({ row, rowIndex }: { row: SysNoticeUser; rowIndex: number }) => {
-	return row.readStatus === 1 ? 'info-row' : '';
+    return row.readStatus === 1 ? 'info-row' : '';
 };
 </script>
 

+ 23 - 1
Web/src/views/system/database/index.vue

@@ -9,7 +9,18 @@
 				</el-form-item>
 				<el-form-item label="表名">
 					<el-select v-model="state.tableName" placeholder="表名" filterable clearable @change="handleQueryColumn">
-						<el-option v-for="item in state.tableData" :key="item.name" :label="item.name + '[' + item.description + ']'" :value="item.name" />
+                        <template #label="{ label, value }">
+                            <div class="flex flex-items-center">
+                                <span>{{ value }}</span>
+                                <span class="desc">{{ label }}</span>
+                            </div>
+                        </template>
+						<el-option v-for="item in state.tableData" :key="item.name" :data="item" :label="item.description" :value="item.name">
+                            <div class="flex flex-items-center">
+                                <span style="flex: 1">{{ item.name }}</span>
+                                <el-tag type="info" size="small">{{ item.description }}</el-tag>
+                            </div>
+                        </el-option>
 					</el-select>
 				</el-form-item>
 				<el-form-item>
@@ -377,3 +388,14 @@ const visualTable = () => {
 	router.push(`/develop/database/visual?configId=${state.configId}`);
 };
 </script>
+
+<style lang="scss" scoped>
+.el-select__placeholder {
+    .desc {
+        color: var(--el-color-info); 
+        font-size: var(--el-font-size-extra-small);
+        //font-style: italic;
+        margin-left: 5px;
+    }
+}
+</style>

+ 114 - 114
Web/src/views/system/notice/index.vue

@@ -1,68 +1,68 @@
 <template>
-	<div class="sys-notice-container">
-		<el-card shadow="hover" :body-style="{ padding: 5 }">
-			<el-form :model="state.queryParams" ref="queryForm" :inline="true">
-				<el-form-item label="标题">
-					<el-input v-model="state.queryParams.title" placeholder="标题" clearable />
-				</el-form-item>
-				<el-form-item label="类型">
-          <g-sys-dict v-model="state.queryParams.type" code="NoticeTypeEnum" render-as="select" clearable />
-				</el-form-item>
-				<el-form-item>
-					<el-button-group>
-						<el-button type="primary" icon="ele-Search" @click="handleQuery" v-auth="'sysNotice:page'"> 查询 </el-button>
-						<el-button icon="ele-Refresh" @click="resetQuery"> 重置 </el-button>
-					</el-button-group>
-				</el-form-item>
-				<el-form-item>
-					<el-button type="primary" icon="ele-Plus" @click="openAddNotice" v-auth="'sysNotice:add'"> 新增 </el-button>
-				</el-form-item>
-			</el-form>
-		</el-card>
+    <div class="sys-notice-container">
+        <el-card shadow="hover" :body-style="{ padding: 5 }">
+            <el-form :model="state.queryParams" ref="queryForm" :inline="true">
+                <el-form-item label="标题">
+                    <el-input v-model="state.queryParams.title" placeholder="标题" clearable />
+                </el-form-item>
+                <el-form-item label="类型">
+                    <g-sys-dict v-model="state.queryParams.type" code="NoticeTypeEnum" render-as="select" clearable />
+                </el-form-item>
+                <el-form-item>
+                    <el-button-group>
+                        <el-button type="primary" icon="ele-Search" @click="handleQuery" v-auth="'sysNotice:page'"> 查询 </el-button>
+                        <el-button icon="ele-Refresh" @click="resetQuery"> 重置 </el-button>
+                    </el-button-group>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" icon="ele-Plus" @click="openAddNotice" v-auth="'sysNotice:add'"> 新增 </el-button>
+                </el-form-item>
+            </el-form>
+        </el-card>
 
-		<el-card class="full-table" shadow="hover" style="margin-top: 5px">
-			<el-table :data="state.noticeData" v-loading="state.loading" border>
-				<el-table-column type="index" label="序号" width="55" align="center" />
-				<el-table-column prop="title" label="标题" header-align="center" show-overflow-tooltip />
-				<el-table-column prop="content" label="内容" header-align="center" show-overflow-tooltip>
-					<template #default="scope"> {{ removeHtml(scope.row.content) }} </template>
-				</el-table-column>
-				<el-table-column prop="type" label="类型" width="100" align="center" show-overflow-tooltip>
-					<template #default="scope">
-            <g-sys-dict v-model="scope.row.type" code="NoticeTypeEnum" />
-					</template>
-				</el-table-column>
-				<el-table-column prop="createTime" label="创建时间" align="center" show-overflow-tooltip />
-				<el-table-column prop="status" label="状态" width="100" align="center" show-overflow-tooltip>
-					<template #default="scope">
-            <g-sys-dict v-model="scope.row.status" code="NoticeStatusEnum" />
-					</template>
-				</el-table-column>
-				<el-table-column prop="publicUserName" label="发布者" align="center" show-overflow-tooltip />
-				<el-table-column prop="publicTime" label="发布时间" align="center" show-overflow-tooltip />
-				<el-table-column label="操作" width="200" fixed="right" align="center" show-overflow-tooltip>
-					<template #default="scope">
-						<el-button icon="ele-Position" size="small" text type="primary" @click="publicNotice(scope.row)" v-auth="'sysNotice:public'" :disabled="scope.row.status === 1"> 发布 </el-button>
-						<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditNotice(scope.row)" v-auth="'sysNotice:update'" :disabled="scope.row.status === 1"> 编辑 </el-button>
-						<el-button icon="ele-Delete" size="small" text type="danger" @click="delNotice(scope.row)" v-auth="'sysNotice:delete'" :disabled="scope.row.status === 1"> 删除 </el-button>
-					</template>
-				</el-table-column>
-			</el-table>
-			<el-pagination
-				v-model:currentPage="state.tableParams.page"
-				v-model:page-size="state.tableParams.pageSize"
-				:total="state.tableParams.total"
-				:page-sizes="[10, 20, 50, 100]"
-				size="small"
-				background
-				@size-change="handleSizeChange"
-				@current-change="handleCurrentChange"
-				layout="total, sizes, prev, pager, next, jumper"
-			/>
-		</el-card>
+        <el-card class="full-table" shadow="hover" style="margin-top: 5px">
+            <el-table :data="state.noticeData" v-loading="state.loading" border>
+                <el-table-column type="index" label="序号" width="55" align="center" />
+                <el-table-column prop="title" label="标题" width="250" header-align="center" show-overflow-tooltip />
+                <el-table-column prop="content" label="内容" header-align="center" show-overflow-tooltip>
+                    <template #default="scope"> {{ removeHtml(scope.row.content) }} </template>
+                </el-table-column>
+                <el-table-column prop="type" label="类型" width="100" align="center">
+                    <template #default="scope">
+                        <g-sys-dict v-model="scope.row.type" code="NoticeTypeEnum" />
+                    </template>
+                </el-table-column>
+                <el-table-column prop="createTime" label="创建时间" width="180" align="center" />
+                <el-table-column prop="status" label="状态" width="100" align="center">
+                    <template #default="scope">
+                        <g-sys-dict v-model="scope.row.status" code="NoticeStatusEnum" />
+                    </template>
+                </el-table-column>
+                <el-table-column prop="publicUserName" label="发布者" width="130" align="center" />
+                <el-table-column prop="publicTime" label="发布时间" width="180" align="center" />
+                <el-table-column label="操作" width="200" fixed="right" align="center">
+                    <template #default="scope">
+                        <el-button icon="ele-Position" size="small" text type="primary" @click="publicNotice(scope.row)" v-auth="'sysNotice:public'" :disabled="scope.row.status === 1"> 发布 </el-button>
+                        <el-button icon="ele-Edit" size="small" text type="primary" @click="openEditNotice(scope.row)" v-auth="'sysNotice:update'" :disabled="scope.row.status === 1"> 编辑 </el-button>
+                        <el-button icon="ele-Delete" size="small" text type="danger" @click="delNotice(scope.row)" v-auth="'sysNotice:delete'" :disabled="scope.row.status === 1"> 删除 </el-button>
+                    </template>
+                </el-table-column>
+            </el-table>
+            <el-pagination 
+                v-model:currentPage="state.tableParams.page" 
+                v-model:page-size="state.tableParams.pageSize"
+                :total="state.tableParams.total" 
+                :page-sizes="[10, 20, 50, 100]" 
+                size="small" 
+                background
+                @size-change="handleSizeChange" 
+                @current-change="handleCurrentChange"
+                layout="total, sizes, prev, pager, next, jumper" 
+            />
+        </el-card>
 
-		<EditNotice ref="editNoticeRef" :title="state.editNoticeTitle" @handleQuery="handleQuery" />
-	</div>
+        <EditNotice ref="editNoticeRef" :title="state.editNoticeTitle" @handleQuery="handleQuery" />
+    </div>
 </template>
 
 <script lang="ts" setup name="sysNotice">
@@ -77,92 +77,92 @@ import EditNotice from '/@/views/system/notice/component/editNotice.vue';
 const editNoticeRef = ref<InstanceType<typeof EditNotice>>();
 const { removeHtml } = commonFunction();
 const state = reactive({
-	loading: false,
-	noticeData: [] as Array<SysNotice>,
-	queryParams: {
-		title: undefined,
-		type: undefined,
-	},
-	tableParams: {
-		page: 1,
-		pageSize: 50,
-		total: 0 as any,
-	},
-	editNoticeTitle: '',
+    loading: false,
+    noticeData: [] as Array<SysNotice>,
+    queryParams: {
+        title: undefined,
+        type: undefined,
+    },
+    tableParams: {
+        page: 1,
+        pageSize: 50,
+        total: 0 as any,
+    },
+    editNoticeTitle: '',
 });
 
 onMounted(async () => {
-	handleQuery();
+    handleQuery();
 });
 
 // 查询操作
 const handleQuery = async () => {
-	state.loading = true;
-	let params = Object.assign(state.queryParams, state.tableParams);
-	var res = await getAPI(SysNoticeApi).apiSysNoticePagePost(params);
-	state.noticeData = res.data.result?.items ?? [];
-	state.tableParams.total = res.data.result?.total;
-	state.loading = false;
+    state.loading = true;
+    let params = Object.assign(state.queryParams, state.tableParams);
+    var res = await getAPI(SysNoticeApi).apiSysNoticePagePost(params);
+    state.noticeData = res.data.result?.items ?? [];
+    state.tableParams.total = res.data.result?.total;
+    state.loading = false;
 };
 
 // 重置操作
 const resetQuery = () => {
-	state.queryParams.title = undefined;
-	state.queryParams.type = undefined;
-	handleQuery();
+    state.queryParams.title = undefined;
+    state.queryParams.type = undefined;
+    handleQuery();
 };
 
 // 打开新增页面
 const openAddNotice = () => {
-	state.editNoticeTitle = '添加通知公告';
-	editNoticeRef.value?.openDialog({ type: 1 });
+    state.editNoticeTitle = '添加通知公告';
+    editNoticeRef.value?.openDialog({ type: 1 });
 };
 
 // 打开编辑页面
 const openEditNotice = (row: any) => {
-	state.editNoticeTitle = '编辑通知公告';
-	editNoticeRef.value?.openDialog(row);
+    state.editNoticeTitle = '编辑通知公告';
+    editNoticeRef.value?.openDialog(row);
 };
 
 // 删除
 const delNotice = (row: any) => {
-	ElMessageBox.confirm(`确定删除通知公告:【${row.title}】?`, '提示', {
-		confirmButtonText: '确定',
-		cancelButtonText: '取消',
-		type: 'warning',
-	})
-		.then(async () => {
-			await getAPI(SysNoticeApi).apiSysNoticeDeletePost({ id: row.id });
-			handleQuery();
-			ElMessage.success('删除成功');
-		})
-		.catch(() => {});
+    ElMessageBox.confirm(`确定删除通知公告:【${row.title}】?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+    })
+        .then(async () => {
+            await getAPI(SysNoticeApi).apiSysNoticeDeletePost({ id: row.id });
+            handleQuery();
+            ElMessage.success('删除成功');
+        })
+        .catch(() => { });
 };
 
 // 发布
 const publicNotice = (row: any) => {
-	ElMessageBox.confirm(`确定发布通知公告:【${row.title}】,不可撤销?`, '提示', {
-		confirmButtonText: '确定',
-		cancelButtonText: '取消',
-		type: 'warning',
-	})
-		.then(async () => {
-			await getAPI(SysNoticeApi).apiSysNoticePublicPost({ id: row.id });
-			handleQuery();
-			ElMessage.success('发布成功');
-		})
-		.catch(() => {});
+    ElMessageBox.confirm(`确定发布通知公告:【${row.title}】,不可撤销?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+    })
+        .then(async () => {
+            await getAPI(SysNoticeApi).apiSysNoticePublicPost({ id: row.id });
+            handleQuery();
+            ElMessage.success('发布成功');
+        })
+        .catch(() => { });
 };
 
 // 改变页面容量
 const handleSizeChange = (val: number) => {
-	state.tableParams.pageSize = val;
-	handleQuery();
+    state.tableParams.pageSize = val;
+    handleQuery();
 };
 
 // 改变页码序号
 const handleCurrentChange = (val: number) => {
-	state.tableParams.page = val;
-	handleQuery();
+    state.tableParams.page = val;
+    handleQuery();
 };
 </script>

+ 2 - 2
Web/src/views/system/tenantConfig/index.vue

@@ -34,7 +34,7 @@ import { getAPI } from '/@/utils/axios-utils';
 import { SysTenantConfigApi } from '/@/api-services/api';
 import ModifyRecord from '/@/components/table/modifyRecord.vue';
 import EditConfig from '/@/views/system/tenantConfig/component/editConfig.vue';
-import { EmptyObjectType, RefType } from '/@/types/global';
+//import { EmptyObjectType, RefType } from '/@/types/global';
 
 // 引入组件
 const Table = defineAsyncComponent(() => import('/@/components/table/index.vue'));
@@ -122,7 +122,7 @@ const getGroupList = async () => {
 	} as TableSearchType;
 	state.groupList = res.data.result ?? [];
 	res.data.result?.forEach((item) => {
-		groupSearch.options?.push({ label: item, value: item });
+		if(item) groupSearch.options?.push({ label: item, value: item });
 	});
 	let group = tb.tableData.search.filter((item) => {
 		return item.prop == 'groupCode';