From f031499cddda5d7b10cecb075d5507c71461903d Mon Sep 17 00:00:00 2001 From: aq982 Date: Wed, 26 Feb 2025 15:49:48 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=8C=85=E6=8B=AC=E4=B8=8D=E9=99=90=E4=BA=8E?= =?UTF-8?q?=E4=BB=A5=E4=B8=8B=E5=8A=9F=E8=83=BD=EF=BC=9A=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E6=9F=A5=E8=AF=A2=E4=B8=AD=E9=97=B4=E4=BB=B6Mid.cs=EF=BC=8C?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E7=BB=9F=E8=AE=A1=E5=AD=97=E6=AE=B5=E5=92=8C?= =?UTF-8?q?GroupBy=E9=85=8D=E7=BD=AE=EF=BC=8C=E5=A2=9E=E5=8A=A0=E8=A1=A8?= =?UTF-8?q?=E6=A0=BC=E5=BA=95=E9=83=A8=E6=A0=B9=E6=8D=AE=E5=AD=97=E6=AE=B5?= =?UTF-8?q?=E9=85=8D=E7=BD=AE=E7=BB=9F=E8=AE=A1=E5=AD=97=E6=AE=B5=E7=BB=9F?= =?UTF-8?q?=E8=AE=A1=EF=BC=8C=E6=96=B0=E5=A2=9E=E7=BB=9F=E8=AE=A1=E5=B7=A5?= =?UTF-8?q?=E5=85=B7AggregationBuilder.cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin.NET.Core/Entity/SysCodeGenConfig.cs | 6 + .../SeedData/SysCodeGenTemplateSeedData.cs | 17 ++ .../Service/CodeGen/Dto/CodeGenConfig.cs | 4 + .../Utils/AggregationBuilder.cs | 144 ++++++++++++++ .../wwwroot/template/Entity.cs.vm | 2 +- .../template/PartialEntity_Entity.cs.vm | 72 +++++-- .../template/PartialService_Service.cs.vm | 52 +++-- .../wwwroot/template/service_InputDto.cs.vm | 2 +- .../wwwroot/template/service_Mid.cs.vm | 116 ++++++++++++ .../wwwroot/template/service_OutputDto.cs.vm | 2 +- .../wwwroot/template/service_Service.cs.vm | 179 ++++++++++++------ .../wwwroot/template/web_api.ts.vm | 16 ++ .../wwwroot/template/web_views_List.vue.vm | 31 ++- .../wwwroot/template/web_views_Tree.vue.vm | 6 +- .../codeGen/component/genConfigDialog.vue | 12 ++ 15 files changed, 553 insertions(+), 108 deletions(-) create mode 100644 Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs create mode 100644 Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm diff --git a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs index 575cebfe..2e21069f 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs @@ -136,6 +136,12 @@ public partial class SysCodeGenConfig : EntityBase [SugarColumn(ColumnDescription = "是否是统计字段", Length = 8)] [MaxLength(8)] public string? Statistical { get; set; } + /// + /// 是否是GroupBy字段 + /// + [SugarColumn(ColumnDescription = "是否是GroupBy字段", Length = 8)] + [MaxLength(8)] + public string? IsGroupBy { get; set; } /// /// 是否是查询条件 diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs index fa13ecd0..c3bb3b94 100644 --- a/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs +++ b/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs @@ -222,6 +222,23 @@ public class SysCodeGenTemplateSeedData : ISqlSugarEntitySeedData public string Statistical { get; set; } + /// + /// 是否是GroupBy字段 + /// + public string? IsGroupBy { get; set; } /// /// 是否是查询条件 diff --git a/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs new file mode 100644 index 00000000..235346ac --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Admin.NET.Core.Utils; +// 聚合配置增强版(独立类) +public class AggregationBuilder +{ + private readonly List _configs; + private readonly Type _entityType; + private readonly Type _outputType; + + public List SelectParts { get; } = new(); + public List HavingConditions { get; } = new(); + + public AggregationBuilder( + IEnumerable configs, + Type entityType, + Type outputType) + { + _configs = configs.ToList(); + _entityType = entityType; + _outputType = outputType; + Build(); + } + + private void Build() + { + foreach (var config in _configs.Where(IsValidConfig)) + { + // 处理SELECT部分 + var expression = string.IsNullOrEmpty(config.CustomExpression) + ? $"{config.Function.ToString().ToUpper()}({config.Field})" + : config.CustomExpression; + + SelectParts.Add($"{expression} AS {config.Alias}"); + + // 处理HAVING条件 + if (!string.IsNullOrEmpty(config.HavingCondition)) + { + HavingConditions.Add($"{expression} {config.HavingCondition}"); + } + } + } + + private bool IsValidConfig(AggregationConfig config) + { + // 字段基础验证 + if (!string.IsNullOrEmpty(config.Field) && + _entityType.GetProperty(config.Field) == null) + return false; + + // 输出属性验证 + return _outputType.GetProperty(config.Alias) != null; + } + /// + /// 验证聚合配置有效性 + /// + private bool ValidateAggregation(AggregationConfig config, Type entityType, Type outputType) + { + // 验证实体字段存在性 + var entityProp = entityType.GetProperty(config.Field); + if (entityProp == null) return false; + + // 验证输出字段存在性 + var outputProp = outputType.GetProperty(config.Alias); + if (outputProp == null) return false; + + // 验证类型兼容性 + return config.Function switch + { + AggregateFunction.Count => outputProp.PropertyType == typeof(int), + _ => outputProp.PropertyType == entityProp.PropertyType || + IsNumericType(outputProp.PropertyType) + }; + } + + private static bool IsNumericType(Type type) + { + return Type.GetTypeCode(type) switch + { + TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 + or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 + or TypeCode.Int32 or TypeCode.Int64 or TypeCode.Decimal + or TypeCode.Double or TypeCode.Single => true, + _ => false + }; + } + /// + /// 验证字段有效性 + /// + public static List ValidateFields(string[] fields, Type targetType) + { + if (fields == null || fields.Length == 0) return new List(); + + return fields + .Where(f => !string.IsNullOrWhiteSpace(f)) + .Select(f => f.Trim()) + .Where(f => targetType.GetProperty( + targetType == typeof(UsagetokenOutput) ? + $"{f}Sum" : f) != null) + .ToList(); + } +} + +// 增强版聚合配置类 +public class AggregationConfig +{ + /// + /// 数据库字段名(与CustomExpression二选一) + /// + public string Field { get; set; } + + /// + /// 自定义聚合表达式(优先级高于Field+Function) + /// + public string CustomExpression { get; set; } + + /// + /// 聚合函数类型(使用CustomExpression时可不填) + /// + public AggregateFunction Function { get; set; } = AggregateFunction.Sum; + + /// + /// 输出字段别名(必须与DTO属性名一致) + /// + public required string Alias { get; set; } + + /// + /// HAVING条件表达式(如"> 100") + /// + public string HavingCondition { get; set; } +} +public enum AggregateFunction +{ + Sum, + Avg, + Count, + Max, + Min, + // 可扩展其他函数 +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm index e50562a7..52565d29 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm @@ -17,7 +17,7 @@ namespace @Model.NameSpace; @if(@Model.ConfigId!="1300000000001"){ @:[Tenant("@(@Model.ConfigId)")] } -public partial class @(@Model.EntityName) @Model.BaseClassName +public class @(@Model.EntityName) @Model.BaseClassName { @foreach (var column in Model.TableField){ if(@Model.BaseClassName=="" && @column.IsPrimarykey){ diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm index 008afc22..c2f9bf9c 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm @@ -1,28 +1,60 @@ - -namespace @(@Model.NameSpace).Entity; +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} +namespace @(@Model.NameSpace); /// -/// 扩展@(@Model.BusName)实体 +/// 扩展@(@Model.BusName)输出实体 /// -public partial class @(@Model.ClassName) +public partial class @(@Model.ClassName)Output { - - @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ - @:/// - @:/// 合约商品树形Name - @:/// - @:[SugarColumn(IsIgnore = true)] - @:public string Name { get; set; } - } - /// - /// @(@Model.BusName)子项 - /// - [SugarColumn(IsIgnore = true)] - public List<@(@Model.ClassName)> Children { get; set; } + /// - /// 是否禁止选中 + /// count /// - [SugarColumn(IsIgnore = true)] - public bool Disabled { get; set; } + public int count { get; set; } + @foreach (var par in Model.TableInoutpar.Where(m => m.inouttype == "Input").ToList()){ + @:/// + @:/// @(@par.Name) + @:/// + @:public @(@par.DataType) @(@par.parameter) { get; set; } + } + + @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ + @:/// + @:/// Name + @:/// + @:public string Name { get; set; } + } +@if(Model.TabType=="Tree"){ + @:/// + @:/// @(@Model.BusName)子项 + @:/// + @:[SugarColumn(IsIgnore = true)] + @:public List<@(@Model.ClassName)Output> Children { get; set; } + + @:/// + @:/// 是否禁止选中 + @:/// + @:[SugarColumn(IsIgnore = true)] + @:public bool Disabled { get; set; } +} } + +/// +/// 扩展@(@Model.BusName)分页查询输入参数 +/// +public partial class Page@(@Model.ClassName)Input +{ + public string[] GroupBy { get; set; } + public string[] Sum { get; set; } + public IEnumerable Aggregations { get; set; } + @foreach (var par in Model.TableInoutpar.Where(m => m.inouttype == "Output").ToList()){ + @:/// + @:/// @(@par.Name) + @:/// + @:public @(@par.DataType) @(@par.parameter) { get; set; } +} + +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm index ab89091e..04e35e14 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm @@ -1,4 +1,3 @@ -using Admin.NET.Core.Service; using Microsoft.AspNetCore.Http; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; @{ @@ -19,7 +18,9 @@ using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; } } } -using @(@Model.NameSpace).Entity; +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} namespace @Model.NameSpace; /// @@ -35,26 +36,35 @@ public partial class @(@Model.ClassName)Service /// [ApiDescriptionSettings(Name = "GetTreeList", Description = "获取列表", Order = 1100), HttpPost] [DisplayName("获取列表")] - public async Task> GetTreeList([FromQuery] Tree input) + public async Task> GetTreeList(Page@(@Model.ClassName)Input input) { - // 带条件筛选时返回列表数据 - var list = await _@(@Model.LowerClassName)Rep.AsQueryable() - .WhereIF(input.Id > 0, t => t.Id == input.Id) - //名称 - //.WhereIF(!string.IsNullOrWhiteSpace(input.tacticsNme), u => u.tacticsNme.Contains(input.tacticsNme.Trim())) - .OrderBy(t => new { t.Id }) - //.Select((u) => new Tree - //{ - // Id = u.Id.ToString(), - // Name = u.@(@Model.TreeName) - //}) - .ToListAsync(); - @if(!string.IsNullOrEmpty(Model.TreeName)){ - @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ - @:list.ForEach(t => t.Name = t.@(@Model.TreeName) ); - } - } - return list; + @if(Model.TabType=="Tree"){ + @:var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToTreeAsync(u => u.Children, u => u.@(@Model.TreeKey), input.Id); + @://var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync();//非树形结构表 + + @:var md = await _@(@Model.LowerClassName)Rep.AsQueryable().Where(u => u.Id == input.Id).Select<@(@Model.ClassName)Output>().FirstAsync(); + @:if (md == null) return list; + @: + @:md.Children = list; + @:list = [md]; + @:return list; + }else{ + @://return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToTreeAsync(u => u.Children, u => u.@(@Model.TreeKey), input.Id));//树形结构表 + @:return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync(); + } } } +/// +/// 扩展@(@Model.BusName)中间件 +/// +public partial class @(@Model.ClassName)Mid +{ + +} +@{ +string LowerFirstLetter(string text) +{ +return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写 +} +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm index acd91601..a35291df 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm @@ -41,7 +41,7 @@ if (@column.ColumnKey != "True"){ /// /// @(@Model.BusName)分页查询输入参数 /// - public class Page@(@Model.ClassName)Input : BasePageInput + public partial class Page@(@Model.ClassName)Input : BasePageInput { /// /// 关键字查询 diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm new file mode 100644 index 00000000..621a81b9 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm @@ -0,0 +1,116 @@ +@{ + string joinTableName = "u"; + Dictionary definedObjects = new Dictionary(); + bool haveLikeCdt = false; + string RemoteField=""; + string PKName=""; + foreach (var column in Model.TableField){ + if (column.QueryWhether == "Y" && column.QueryType == "like"){ + haveLikeCdt = true; + } + if(column.RemoteVerify){ + RemoteField=@column.PropertyName; + } + if(column.ColumnKey == "True"){ + PKName=column.PropertyName; + } + } +} +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} + +namespace @(@Model.NameSpace); +public partial class @(@Model.ClassName)Mid +{ + /// + /// 获取查询 + /// + /// + /// + /// + public static ISugarQueryable<@(@Model.ClassName)Output> GetQuery(SqlSugarRepository<@(@Model.ClassName)> _@(@Model.LowerClassName)Rep,Page@(@Model.ClassName)Input input) + { + var sysCacheService = App.GetRequiredService(); + var db = App.GetRequiredService(); + @if (haveLikeCdt) { + @:input.SearchKey = input.SearchKey?.Trim(); + } + var query = _@(@Model.LowerClassName)Rep.AsQueryable() + @{string conditionFlag = "";} + @if (haveLikeCdt) { + @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => + @foreach (var column in Model.TableField){ + if (@column.QueryWhether == "Y" && column.QueryType == "like"){ + @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey) + conditionFlag="|| "; + } + } + @:) + } + @foreach (var column in Model.TableField){ + if (@column.QueryWhether == "Y"){ + if (@column.NetType?.TrimEnd('?') == "string"){ + if(@column.QueryType == "like"){ + @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim())) + }else{ + @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + } + }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){ + @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ + @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1]) + }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) { + @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + } + } + } + + .Select<@(@Model.ClassName)Output>() + @if(!string.IsNullOrEmpty(Model.TreeName)){ + @:.Mapper(c => c.Name= c.@(@Model.TreeName).ToString()) + } + @foreach (var column in Model.TableField){ + if(@column.EffectType == "Upload"){ + @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName)) + } + else if(@column.EffectType == "ForeignKey"){ + @:.Mapper(t => + @:{ + @: //使用缓存 + @: var key = $"@(@column.FkEntityName)_{t.@(@column.PropertyName)}"; + @: if (!sysCacheService.ExistKey(key)) + @: { + @: var m = db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName)); + @: if (m != null) sysCacheService.Set(key, m); + @: } + @: t.@(@column.PropertyName)@(@column.FkColumnName) = sysCacheService.Get<@(@column.FkEntityName)>(key)?.@(@column.FkColumnName); + @: //t.@(@column.PropertyName)@(@column.FkColumnName)=db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName))).@(@column.FkColumnName);// + @:}) + } + else if(@column.EffectType == "ApiTreeSelector"){ + @:.Mapper(t => + @:{ + @: //使用缓存 + @: var key = $"@(@column.FkEntityName)_{t.@(@column.PropertyName)}"; + @: if (!sysCacheService.ExistKey(key)) + @: { + @: var m = db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.ValueColumn) == t.@(@column.PropertyName)); + @: if (m != null) sysCacheService.Set(key, m); + @: } + @: t.@(@column.PropertyName)@(@column.DisplayColumn) = sysCacheService.Get<@(@column.FkEntityName)>(key)?.@(@column.DisplayColumn); + @: //t.@(@column.PropertyName)@(@column.FkColumnName)=db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName))).@(@column.FkColumnName);// + @:}) + } + } + ; + + return query; + } +} +@{ +string LowerFirstLetter(string text) +{ +return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写 +} +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm index ec2e5cf1..846b0515 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm @@ -9,7 +9,7 @@ namespace @Model.NameSpace; /// /// @(@Model.BusName)输出参数 /// -public class @(@Model.ClassName)Output +public partial class @(@Model.ClassName)Output { @foreach (var column in Model.TableField){ @:/// diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm index a8183649..36f4a90b 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm @@ -71,65 +71,9 @@ public partial class @(@Model.ClassName)Service : IDynamicApiController, ITransi [DisplayName("分页查询@(@Model.BusName)")] public async Task> Page(Page@(@Model.ClassName)Input input) { -@if (haveLikeCdt) { - @:input.SearchKey = input.SearchKey?.Trim(); -} - var query = _@(@Model.LowerClassName)Rep.AsQueryable() -@{string conditionFlag = "";} -@if (haveLikeCdt) { - @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => - @foreach (var column in Model.TableField){ - if (@column.QueryWhether == "Y" && column.QueryType == "like"){ - @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey) - conditionFlag="|| "; - } - } - @:) -} -@foreach (var column in Model.TableField){ -if (@column.QueryWhether == "Y"){ - if (@column.NetType?.TrimEnd('?') == "string"){ - if(@column.QueryType == "like"){ - @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim())) - }else{ - @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - } - }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){ - @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ - @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1]) - }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) { - @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - } -} -} -@foreach (var column in Model.TableField){ - if(@column.EffectType == "Upload"){ - @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName)) - } -} - .Select<@(@Model.ClassName)Output>(); - - - - @if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey"||x.EffectType == "ApiTreeSelector")){ - @:var list = await query.MergeTable().OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); - } else { - @:var list = await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); - } - @if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey"||x.EffectType == "ApiTreeSelector")){ - @:list.Items.ForEach(t =>{ - - @foreach (var column in Model.TableField){ - if(@column.EffectType == "ForeignKey"){ - @:t.@(@column.PropertyName)@(@column.FkColumnName) =_@(@column.LowerFkEntityName)Rep.GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName)).@(@column.FkColumnName); - } - else if(@column.EffectType == "ApiTreeSelector"){ - @:t.@(@column.PropertyName)@(@column.FkColumnName) =_@(@column.LowerFkEntityName)Rep.GetFirst(f => f.@(@column.DisplayColumn) == t.@(@column.PropertyName)).@(@column.FkColumnName); - } - } - @:}); - } + //var query= @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input); + //var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).MergeTable().ToPagedListAsync(input.Page, input.PageSize); + var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); return list; } @@ -217,14 +161,125 @@ if (@column.ColumnKey == "True"){ /// /// /// - [ApiDescriptionSettings(Name = "list", Description = "获取@(@Model.BusName)列表", Order = 950), HttpGet] + [ApiDescriptionSettings(Name = "list", Description = "获取@(@Model.BusName)列表", Order = 950), HttpPost] [DisplayName("获取@(@Model.BusName)列表")] - public async Task> List([FromQuery] Page@(@Model.ClassName)Input input) + public async Task> List(Page@(@Model.ClassName)Input input) { - return await _@(@Model.LowerClassName)Rep.AsQueryable().Select<@(@Model.ClassName)Output>().ToListAsync(); + return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync(); } +@if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @:/// + @:/// 获取@(@Model.BusName) + @:/// + @:/// + @:/// + @:[ApiDescriptionSettings(Name = "GetTotalSum", Description = "获取@(@Model.BusName)统计", Order = 960), HttpPost] + @:[DisplayName("获取@(@Model.BusName)统计")] + @:public async Task> GetTotalSum(Page@(@Model.ClassName)Input input) + @:{ + @:// 单次查询同时获取统计值 + @:var querystats = @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input) + @foreach (var column in Model.TableField){ + if (@column.IsGroupBy == "Y"){ + @: .GroupByIF(input.GroupBy.Contains("@column.PropertyName"), u => u.@column.PropertyName) + } + } + @: //.Having(it => SqlFunc.AggregateCount(it.Id) > 0)//聚合函数过滤 + @: .Select(it => new @(@Model.ClassName)Output + @: { + @: count = SqlFunc.AggregateCount(it.Id), + @foreach (var column in Model.TableField){ + if (@column.IsGroupBy == "Y"){ + @: @(@column.PropertyName) = it.@(@column.PropertyName), + } + if (@column.Statistical == "Y"){ + @: @(@column.PropertyName) = SqlFunc.AggregateSum(it.@(@column.PropertyName)) ?? 0, + } + } + @: }); + @:return await querystats.ToListAsync(); + @:} +} + + /// + /// 根据输入参数获取@(@Model.BusName)统计 + /// 支持双模式聚合配置: + /// 常规模式:Field + Function + /// 高级模式:CustomExpression(支持任意合法SQL表达式) + /// 智能条件组合: + /// 多个HAVING条件自动用AND连接 + /// 条件表达式自动包裹聚合函数(如SUM(cost) > 10000) + /// 可扩展支持OR条件 + /// + /// + /// + [ApiDescriptionSettings(Name = "GetAggregTotalSum", Order = 970), HttpPost] + [DisplayName("根据输入参数获取@(@Model.BusName)统计")] + public async Task> GetAggregTotalSum(Page@(@Model.ClassName)Input input) + { + + @:var query = @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input) + // 输入参数示例 + //input = new Page@(@Model.ClassName)Input + //{ + // GroupBy = ["department", "project"], + // GroupBy = input.GroupBy, + // Aggregations = + // [ + // new AggregationConfig + //{ + // Field = "cost", + // Function = AggregateFunction.Sum, + // Alias = "totalCost", + // HavingCondition = "> 10000" + //}, + //new AggregationConfig + //{ + // CustomExpression = "AVG(CAST(response_time AS FLOAT))", + // Alias = "avgResponse", + // HavingCondition = "< 500" + //} + // ] + //}; + // 生成SQL示例 + // SELECT + // department, project, + // SUM(cost) AS totalCost, + // AVG(CAST(response_time AS FLOAT)) AS avgResponse + // FROM... + // GROUP BY department, project + // HAVING(SUM(cost) > 10000) AND(AVG(CAST(response_time AS FLOAT)) < 500) + + // 处理分组字段 + var groupFields = AggregationBuilder.ValidateFields(input.GroupBy, typeof(@(@Model.ClassName)Output)); + if (groupFields.Count > 0) + { + query = query.GroupBy(string.Join(",", groupFields)); + } + // 构建聚合配置 + var aggregator = new AggregationBuilder( + configs: input.Aggregations, + entityType: typeof(@(@Model.ClassName)), + outputType: typeof(@(@Model.ClassName)Output) + ); + + // 组合SELECT语句 + var selectParts = groupFields.Select(f => $"{f} AS {f}") + .Concat(aggregator.SelectParts) + .ToList(); + + // 应用HAVING条件 + if (aggregator.HavingConditions.Count > 0) + { + query = query.Having(string.Join(" AND ", aggregator.HavingConditions)); + } + // 执行查询 + return await query.Select<@(@Model.ClassName)Output>(string.Join(", ", selectParts)) + .ToListAsync(); + } + @foreach (var column in Model.TableField){ if(@column.EffectType == "ForeignKey" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){ @:/// diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm index cf7594b8..eea5ff09 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm @@ -18,6 +18,8 @@ enum Api { Update@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/update', Page@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/page', Tree@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/GetTreeList', + Get@(@Model.ClassName)TotalSum = '/api/@(@Model.LowerClassName)/GetTotalSum', + Get@(@Model.ClassName)AggregTotalSum = '/api/@(@Model.LowerClassName)/GetAggregTotalSum', Detail@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/detail', @if(Model.RemoteVerify){ @:Exists@(RemoteField) = '/api/@(@Model.LowerClassName)/exists@(RemoteField)', @@ -72,6 +74,20 @@ export const treelist@(@Model.ClassName) = (params?: any) => method: 'post', data: params, }); +// 获取统计,支持全表和分组 +export const get@(@Model.ClassName)TotalSum = (params?: any) => + request({ + url: Api.Get@(@Model.ClassName)TotalSum, + method: 'post', + data: params, + }); +// 输入参数获取统计 +export const get@(@Model.ClassName)AggregTotalSum = (params?: any) => + request({ + url: Api.Get@(@Model.ClassName)AggregTotalSum, + method: 'post', + data: params, + }); // 详情@(@Model.BusName) export const detail@(@Model.ClassName) = (id: any) => request({ diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm index d8fd1b1f..db8effb6 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm @@ -235,7 +235,7 @@ import ModifyRecord from '/@@/components/table/modifyRecord.vue'; @:import { @(@Model.ClassName), @(@Model.ClassName)Input, @(@Model.ClassName)Output } from '/@@/api-services/models'; } else { -@:import { page@(@Model.ClassName), delete@(@Model.ClassName) } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; +@:import { page@(@Model.ClassName), delete@(@Model.ClassName), get@(@Model.ClassName)TotalSum } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; foreach (var column in Model.QueryWhetherList){ if(@column.EffectType == "ForeignKey"){ @:import { get@(@column.FkEntityName)@(@column.PropertyName)Dropdown } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; @@ -278,6 +278,7 @@ const state = reactive({ pageSize: 50 as number, defaultSort: { field: 'Id', order: 'asc', descStr: 'desc' }, }, + totalSum:[] as any, visible: false, title: '', }); @@ -337,6 +338,26 @@ const checkTableColumnVisible = (tableColumnName: any) => { } { title: '操作', fixed: 'right', width: 180, showOverflow: true, slots: { default: 'row_buttons' } }, ], + @if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @:footerMethod: ({ columns, data }) => { + @: const totalSum=state.totalSum[0]; + @: return [ + @: columns.map((column, colIndex) => { + @: if (colIndex === 0) { + @: return `合计:` + @: } + @foreach (var column in Model.TableField){ + if (@column.Statistical == "Y"){ + @: if (column.field === '@(@column.LowerPropertyName)') { + @: // 计算表格内总和 + @: return `${data.reduce((sum, row) => sum + (row.@(@column.LowerPropertyName) || 0), 0)}/总数:${totalSum.@(@column.LowerPropertyName)}` + @: } + } + } + @: }) + @: ] + @:}, + } }, // vxeGrid配置参数(此处可覆写任何参数),参考vxe-table官方文档 { @@ -377,6 +398,14 @@ const handleQueryApi = async (page: VxeGridPropTypes.ProxyAjaxQueryPageParams, s // 查询操作 const handleQuery = async (reset = false) => { options.loading = true; + @if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @if (@Model.IsApiService) { + @:state.totalSum =getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)GetTotalSumPost(params).data.result; + } + } else { + @:state.totalSum =(await get@(@Model.ClassName)TotalSum(state.queryParams)).data.result; + } + } reset ? await xGrid.value?.commitProxy('reload') : await xGrid.value?.commitProxy('query'); options.loading = false; }; diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm index 52f88582..255cb9ec 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm @@ -109,7 +109,11 @@ watch(filterText, (val) => { // 获取树数据 const fetchTreeData = async (showLoading: boolean = true) => { if (showLoading) state.loading = true; - var res = await treelist@(@Model.ClassName)({Id:0}); + @if (@Model.IsApiService) { + @:var res = await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)GetTreeListPost({Id:0}); + } else { + @:var res = await treelist@(@Model.ClassName)({Id:0}); + } state.@(@Model.LowerClassName)Data = res.data.result ?? []; if (showLoading) state.loading = false; return res.data.result ?? []; diff --git a/Web/src/views/system/codeGen/component/genConfigDialog.vue b/Web/src/views/system/codeGen/component/genConfigDialog.vue index db307ae2..581b30a2 100644 --- a/Web/src/views/system/codeGen/component/genConfigDialog.vue +++ b/Web/src/views/system/codeGen/component/genConfigDialog.vue @@ -38,6 +38,9 @@ + @@ -205,6 +208,15 @@ const options = reactive({ default: 'statistical', }, }, + { + field: 'isGroupBy', + title: 'GroupBy', + minWidth: 70, + slots: { + edit: 'isGroupBy', + default: 'isGroupBy', + }, + }, { field: 'queryWhether', title: '是否是查询', From 867743517638ac59385a101d5bff4fdb4d348a21 Mon Sep 17 00:00:00 2001 From: aq982 Date: Wed, 26 Feb 2025 15:49:48 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E4=BB=A3=E7=A0=81=E7=94=9F=E6=88=90?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Admin.NET.Core/Entity/SysCodeGenConfig.cs | 6 + .../SeedData/SysCodeGenTemplateSeedData.cs | 17 ++ .../Service/CodeGen/Dto/CodeGenConfig.cs | 4 + .../Utils/AggregationBuilder.cs | 144 ++++++++++++++ .../wwwroot/template/Entity.cs.vm | 2 +- .../template/PartialEntity_Entity.cs.vm | 72 +++++-- .../template/PartialService_Service.cs.vm | 52 +++-- .../wwwroot/template/service_InputDto.cs.vm | 2 +- .../wwwroot/template/service_Mid.cs.vm | 116 +++++++++++ .../wwwroot/template/service_OutputDto.cs.vm | 2 +- .../wwwroot/template/service_Service.cs.vm | 181 ++++++++++++------ .../wwwroot/template/web_api.ts.vm | 16 ++ .../wwwroot/template/web_views_List.vue.vm | 31 ++- .../wwwroot/template/web_views_Tree.vue.vm | 6 +- .../codeGen/component/genConfigDialog.vue | 12 ++ 15 files changed, 554 insertions(+), 109 deletions(-) create mode 100644 Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs create mode 100644 Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm diff --git a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs index 575cebfe..2e21069f 100644 --- a/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs +++ b/Admin.NET/Admin.NET.Core/Entity/SysCodeGenConfig.cs @@ -136,6 +136,12 @@ public partial class SysCodeGenConfig : EntityBase [SugarColumn(ColumnDescription = "是否是统计字段", Length = 8)] [MaxLength(8)] public string? Statistical { get; set; } + /// + /// 是否是GroupBy字段 + /// + [SugarColumn(ColumnDescription = "是否是GroupBy字段", Length = 8)] + [MaxLength(8)] + public string? IsGroupBy { get; set; } /// /// 是否是查询条件 diff --git a/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs b/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs index fa13ecd0..c3bb3b94 100644 --- a/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs +++ b/Admin.NET/Admin.NET.Core/SeedData/SysCodeGenTemplateSeedData.cs @@ -222,6 +222,23 @@ public class SysCodeGenTemplateSeedData : ISqlSugarEntitySeedData public string Statistical { get; set; } + /// + /// 是否是GroupBy字段 + /// + public string? IsGroupBy { get; set; } /// /// 是否是查询条件 diff --git a/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs new file mode 100644 index 00000000..235346ac --- /dev/null +++ b/Admin.NET/Admin.NET.Core/Utils/AggregationBuilder.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Admin.NET.Core.Utils; +// 聚合配置增强版(独立类) +public class AggregationBuilder +{ + private readonly List _configs; + private readonly Type _entityType; + private readonly Type _outputType; + + public List SelectParts { get; } = new(); + public List HavingConditions { get; } = new(); + + public AggregationBuilder( + IEnumerable configs, + Type entityType, + Type outputType) + { + _configs = configs.ToList(); + _entityType = entityType; + _outputType = outputType; + Build(); + } + + private void Build() + { + foreach (var config in _configs.Where(IsValidConfig)) + { + // 处理SELECT部分 + var expression = string.IsNullOrEmpty(config.CustomExpression) + ? $"{config.Function.ToString().ToUpper()}({config.Field})" + : config.CustomExpression; + + SelectParts.Add($"{expression} AS {config.Alias}"); + + // 处理HAVING条件 + if (!string.IsNullOrEmpty(config.HavingCondition)) + { + HavingConditions.Add($"{expression} {config.HavingCondition}"); + } + } + } + + private bool IsValidConfig(AggregationConfig config) + { + // 字段基础验证 + if (!string.IsNullOrEmpty(config.Field) && + _entityType.GetProperty(config.Field) == null) + return false; + + // 输出属性验证 + return _outputType.GetProperty(config.Alias) != null; + } + /// + /// 验证聚合配置有效性 + /// + private bool ValidateAggregation(AggregationConfig config, Type entityType, Type outputType) + { + // 验证实体字段存在性 + var entityProp = entityType.GetProperty(config.Field); + if (entityProp == null) return false; + + // 验证输出字段存在性 + var outputProp = outputType.GetProperty(config.Alias); + if (outputProp == null) return false; + + // 验证类型兼容性 + return config.Function switch + { + AggregateFunction.Count => outputProp.PropertyType == typeof(int), + _ => outputProp.PropertyType == entityProp.PropertyType || + IsNumericType(outputProp.PropertyType) + }; + } + + private static bool IsNumericType(Type type) + { + return Type.GetTypeCode(type) switch + { + TypeCode.Byte or TypeCode.SByte or TypeCode.UInt16 + or TypeCode.UInt32 or TypeCode.UInt64 or TypeCode.Int16 + or TypeCode.Int32 or TypeCode.Int64 or TypeCode.Decimal + or TypeCode.Double or TypeCode.Single => true, + _ => false + }; + } + /// + /// 验证字段有效性 + /// + public static List ValidateFields(string[] fields, Type targetType) + { + if (fields == null || fields.Length == 0) return new List(); + + return fields + .Where(f => !string.IsNullOrWhiteSpace(f)) + .Select(f => f.Trim()) + .Where(f => targetType.GetProperty( + targetType == typeof(UsagetokenOutput) ? + $"{f}Sum" : f) != null) + .ToList(); + } +} + +// 增强版聚合配置类 +public class AggregationConfig +{ + /// + /// 数据库字段名(与CustomExpression二选一) + /// + public string Field { get; set; } + + /// + /// 自定义聚合表达式(优先级高于Field+Function) + /// + public string CustomExpression { get; set; } + + /// + /// 聚合函数类型(使用CustomExpression时可不填) + /// + public AggregateFunction Function { get; set; } = AggregateFunction.Sum; + + /// + /// 输出字段别名(必须与DTO属性名一致) + /// + public required string Alias { get; set; } + + /// + /// HAVING条件表达式(如"> 100") + /// + public string HavingCondition { get; set; } +} +public enum AggregateFunction +{ + Sum, + Avg, + Count, + Max, + Min, + // 可扩展其他函数 +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm index e50562a7..52565d29 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Entity.cs.vm @@ -17,7 +17,7 @@ namespace @Model.NameSpace; @if(@Model.ConfigId!="1300000000001"){ @:[Tenant("@(@Model.ConfigId)")] } -public partial class @(@Model.EntityName) @Model.BaseClassName +public class @(@Model.EntityName) @Model.BaseClassName { @foreach (var column in Model.TableField){ if(@Model.BaseClassName=="" && @column.IsPrimarykey){ diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm index 008afc22..c2f9bf9c 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialEntity_Entity.cs.vm @@ -1,28 +1,60 @@ - -namespace @(@Model.NameSpace).Entity; +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} +namespace @(@Model.NameSpace); /// -/// 扩展@(@Model.BusName)实体 +/// 扩展@(@Model.BusName)输出实体 /// -public partial class @(@Model.ClassName) +public partial class @(@Model.ClassName)Output { - - @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ - @:/// - @:/// 合约商品树形Name - @:/// - @:[SugarColumn(IsIgnore = true)] - @:public string Name { get; set; } - } - /// - /// @(@Model.BusName)子项 - /// - [SugarColumn(IsIgnore = true)] - public List<@(@Model.ClassName)> Children { get; set; } + /// - /// 是否禁止选中 + /// count /// - [SugarColumn(IsIgnore = true)] - public bool Disabled { get; set; } + public int count { get; set; } + @foreach (var par in Model.TableInoutpar.Where(m => m.inouttype == "Input").ToList()){ + @:/// + @:/// @(@par.Name) + @:/// + @:public @(@par.DataType) @(@par.parameter) { get; set; } + } + + @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ + @:/// + @:/// Name + @:/// + @:public string Name { get; set; } + } +@if(Model.TabType=="Tree"){ + @:/// + @:/// @(@Model.BusName)子项 + @:/// + @:[SugarColumn(IsIgnore = true)] + @:public List<@(@Model.ClassName)Output> Children { get; set; } + + @:/// + @:/// 是否禁止选中 + @:/// + @:[SugarColumn(IsIgnore = true)] + @:public bool Disabled { get; set; } +} } + +/// +/// 扩展@(@Model.BusName)分页查询输入参数 +/// +public partial class Page@(@Model.ClassName)Input +{ + public string[] GroupBy { get; set; } + public string[] Sum { get; set; } + public IEnumerable Aggregations { get; set; } + @foreach (var par in Model.TableInoutpar.Where(m => m.inouttype == "Output").ToList()){ + @:/// + @:/// @(@par.Name) + @:/// + @:public @(@par.DataType) @(@par.parameter) { get; set; } +} + +} diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm index ab89091e..04e35e14 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/PartialService_Service.cs.vm @@ -1,4 +1,3 @@ -using Admin.NET.Core.Service; using Microsoft.AspNetCore.Http; using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; @{ @@ -19,7 +18,9 @@ using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; } } } -using @(@Model.NameSpace).Entity; +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} namespace @Model.NameSpace; /// @@ -35,26 +36,35 @@ public partial class @(@Model.ClassName)Service /// [ApiDescriptionSettings(Name = "GetTreeList", Description = "获取列表", Order = 1100), HttpPost] [DisplayName("获取列表")] - public async Task> GetTreeList([FromQuery] Tree input) + public async Task> GetTreeList(Page@(@Model.ClassName)Input input) { - // 带条件筛选时返回列表数据 - var list = await _@(@Model.LowerClassName)Rep.AsQueryable() - .WhereIF(input.Id > 0, t => t.Id == input.Id) - //名称 - //.WhereIF(!string.IsNullOrWhiteSpace(input.tacticsNme), u => u.tacticsNme.Contains(input.tacticsNme.Trim())) - .OrderBy(t => new { t.Id }) - //.Select((u) => new Tree - //{ - // Id = u.Id.ToString(), - // Name = u.@(@Model.TreeName) - //}) - .ToListAsync(); - @if(!string.IsNullOrEmpty(Model.TreeName)){ - @if(Model.TableField.FirstOrDefault(u => u.ColumnName.ToLower() == "name") == null){ - @:list.ForEach(t => t.Name = t.@(@Model.TreeName) ); - } - } - return list; + @if(Model.TabType=="Tree"){ + @:var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToTreeAsync(u => u.Children, u => u.@(@Model.TreeKey), input.Id); + @://var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync();//非树形结构表 + + @:var md = await _@(@Model.LowerClassName)Rep.AsQueryable().Where(u => u.Id == input.Id).Select<@(@Model.ClassName)Output>().FirstAsync(); + @:if (md == null) return list; + @: + @:md.Children = list; + @:list = [md]; + @:return list; + }else{ + @://return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToTreeAsync(u => u.Children, u => u.@(@Model.TreeKey), input.Id));//树形结构表 + @:return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync(); + } } } +/// +/// 扩展@(@Model.BusName)中间件 +/// +public partial class @(@Model.ClassName)Mid +{ + +} +@{ +string LowerFirstLetter(string text) +{ +return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写 +} +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm index acd91601..a35291df 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_InputDto.cs.vm @@ -41,7 +41,7 @@ if (@column.ColumnKey != "True"){ /// /// @(@Model.BusName)分页查询输入参数 /// - public class Page@(@Model.ClassName)Input : BasePageInput + public partial class Page@(@Model.ClassName)Input : BasePageInput { /// /// 关键字查询 diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm new file mode 100644 index 00000000..621a81b9 --- /dev/null +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Mid.cs.vm @@ -0,0 +1,116 @@ +@{ + string joinTableName = "u"; + Dictionary definedObjects = new Dictionary(); + bool haveLikeCdt = false; + string RemoteField=""; + string PKName=""; + foreach (var column in Model.TableField){ + if (column.QueryWhether == "Y" && column.QueryType == "like"){ + haveLikeCdt = true; + } + if(column.RemoteVerify){ + RemoteField=@column.PropertyName; + } + if(column.ColumnKey == "True"){ + PKName=column.PropertyName; + } + } +} +@if(@Model.NameSpace != "Admin.NET.Core"){ +@:using Admin.NET.Core; +} + +namespace @(@Model.NameSpace); +public partial class @(@Model.ClassName)Mid +{ + /// + /// 获取查询 + /// + /// + /// + /// + public static ISugarQueryable<@(@Model.ClassName)Output> GetQuery(SqlSugarRepository<@(@Model.ClassName)> _@(@Model.LowerClassName)Rep,Page@(@Model.ClassName)Input input) + { + var sysCacheService = App.GetRequiredService(); + var db = App.GetRequiredService(); + @if (haveLikeCdt) { + @:input.SearchKey = input.SearchKey?.Trim(); + } + var query = _@(@Model.LowerClassName)Rep.AsQueryable() + @{string conditionFlag = "";} + @if (haveLikeCdt) { + @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => + @foreach (var column in Model.TableField){ + if (@column.QueryWhether == "Y" && column.QueryType == "like"){ + @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey) + conditionFlag="|| "; + } + } + @:) + } + @foreach (var column in Model.TableField){ + if (@column.QueryWhether == "Y"){ + if (@column.NetType?.TrimEnd('?') == "string"){ + if(@column.QueryType == "like"){ + @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim())) + }else{ + @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + } + }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){ + @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ + @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1]) + }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) { + @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) + } + } + } + + .Select<@(@Model.ClassName)Output>() + @if(!string.IsNullOrEmpty(Model.TreeName)){ + @:.Mapper(c => c.Name= c.@(@Model.TreeName).ToString()) + } + @foreach (var column in Model.TableField){ + if(@column.EffectType == "Upload"){ + @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName)) + } + else if(@column.EffectType == "ForeignKey"){ + @:.Mapper(t => + @:{ + @: //使用缓存 + @: var key = $"@(@column.FkEntityName)_{t.@(@column.PropertyName)}"; + @: if (!sysCacheService.ExistKey(key)) + @: { + @: var m = db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName)); + @: if (m != null) sysCacheService.Set(key, m); + @: } + @: t.@(@column.PropertyName)@(@column.FkColumnName) = sysCacheService.Get<@(@column.FkEntityName)>(key)?.@(@column.FkColumnName); + @: //t.@(@column.PropertyName)@(@column.FkColumnName)=db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName))).@(@column.FkColumnName);// + @:}) + } + else if(@column.EffectType == "ApiTreeSelector"){ + @:.Mapper(t => + @:{ + @: //使用缓存 + @: var key = $"@(@column.FkEntityName)_{t.@(@column.PropertyName)}"; + @: if (!sysCacheService.ExistKey(key)) + @: { + @: var m = db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.ValueColumn) == t.@(@column.PropertyName)); + @: if (m != null) sysCacheService.Set(key, m); + @: } + @: t.@(@column.PropertyName)@(@column.DisplayColumn) = sysCacheService.Get<@(@column.FkEntityName)>(key)?.@(@column.DisplayColumn); + @: //t.@(@column.PropertyName)@(@column.FkColumnName)=db.CopyNew().GetSimpleClient<@(@column.FkEntityName)>().GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName))).@(@column.FkColumnName);// + @:}) + } + } + ; + + return query; + } +} +@{ +string LowerFirstLetter(string text) +{ +return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写 +} +} \ No newline at end of file diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm index ec2e5cf1..846b0515 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_OutputDto.cs.vm @@ -9,7 +9,7 @@ namespace @Model.NameSpace; /// /// @(@Model.BusName)输出参数 /// -public class @(@Model.ClassName)Output +public partial class @(@Model.ClassName)Output { @foreach (var column in Model.TableField){ @:/// diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm index a8183649..22a284d8 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/service_Service.cs.vm @@ -71,65 +71,9 @@ public partial class @(@Model.ClassName)Service : IDynamicApiController, ITransi [DisplayName("分页查询@(@Model.BusName)")] public async Task> Page(Page@(@Model.ClassName)Input input) { -@if (haveLikeCdt) { - @:input.SearchKey = input.SearchKey?.Trim(); -} - var query = _@(@Model.LowerClassName)Rep.AsQueryable() -@{string conditionFlag = "";} -@if (haveLikeCdt) { - @:.WhereIF(!string.IsNullOrEmpty(input.SearchKey), u => - @foreach (var column in Model.TableField){ - if (@column.QueryWhether == "Y" && column.QueryType == "like"){ - @:@(conditionFlag)u.@(@column.PropertyName).Contains(input.SearchKey) - conditionFlag="|| "; - } - } - @:) -} -@foreach (var column in Model.TableField){ -if (@column.QueryWhether == "Y"){ - if (@column.NetType?.TrimEnd('?') == "string"){ - if(@column.QueryType == "like"){ - @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName).Contains(input.@(@column.PropertyName).Trim())) - }else{ - @:.WhereIF(!string.IsNullOrWhiteSpace(input.@column.PropertyName), u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - } - }else if(@column.NetType?.TrimEnd('?') == "int" || @column.NetType?.TrimEnd('?') == "long"){ - @:.WhereIF(input.@column.PropertyName>0, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - }else if(@column.NetType?.TrimEnd('?') == "DateTime" && @column.QueryType == "~"){ - @:.WhereIF(input.@(@column.PropertyName)Range != null && input.@(@column.PropertyName)Range.Length == 2, u => u.@(@column.PropertyName) >= input.@(@column.PropertyName)Range[0] && u.@(@column.PropertyName) <= input.@(@column.PropertyName)Range[1]) - }else if(@column.NetType?.TrimEnd('?').EndsWith("Enum") == true) { - @:.WhereIF(input.@(@column.PropertyName).HasValue, u => u.@(@column.PropertyName) @column.QueryType input.@(@column.PropertyName)) - } -} -} -@foreach (var column in Model.TableField){ - if(@column.EffectType == "Upload"){ - @://.Mapper(c => c.@(@column.PropertyName)Attachment, c => c.@(@column.PropertyName)) - } -} - .Select<@(@Model.ClassName)Output>(); - - - - @if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey"||x.EffectType == "ApiTreeSelector")){ - @:var list = await query.MergeTable().OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); - } else { - @:var list = await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); - } - @if(@Model.TableField.Any(x=>x.EffectType == "ForeignKey"||x.EffectType == "ApiTreeSelector")){ - @:list.Items.ForEach(t =>{ - - @foreach (var column in Model.TableField){ - if(@column.EffectType == "ForeignKey"){ - @:t.@(@column.PropertyName)@(@column.FkColumnName) =_@(@column.LowerFkEntityName)Rep.GetFirst(f => f.@(@column.FkLinkColumnName) == t.@(@column.PropertyName)).@(@column.FkColumnName); - } - else if(@column.EffectType == "ApiTreeSelector"){ - @:t.@(@column.PropertyName)@(@column.FkColumnName) =_@(@column.LowerFkEntityName)Rep.GetFirst(f => f.@(@column.DisplayColumn) == t.@(@column.PropertyName)).@(@column.FkColumnName); - } - } - @:}); - } + //var query= @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input); + //var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).MergeTable().ToPagedListAsync(input.Page, input.PageSize); + var list = await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize); return list; } @@ -217,14 +161,125 @@ if (@column.ColumnKey == "True"){ /// /// /// - [ApiDescriptionSettings(Name = "list", Description = "获取@(@Model.BusName)列表", Order = 950), HttpGet] + [ApiDescriptionSettings(Name = "list", Description = "获取@(@Model.BusName)列表", Order = 950), HttpPost] [DisplayName("获取@(@Model.BusName)列表")] - public async Task> List([FromQuery] Page@(@Model.ClassName)Input input) + public async Task> List(Page@(@Model.ClassName)Input input) { - return await _@(@Model.LowerClassName)Rep.AsQueryable().Select<@(@Model.ClassName)Output>().ToListAsync(); + return await @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input).OrderBuilder(input).ToListAsync(); } +@if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @:/// + @:/// 获取@(@Model.BusName) + @:/// + @:/// + @:/// + @:[ApiDescriptionSettings(Name = "GetTotalSum", Description = "获取@(@Model.BusName)统计", Order = 960), HttpPost] + @:[DisplayName("获取@(@Model.BusName)统计")] + @:public async Task> GetTotalSum(Page@(@Model.ClassName)Input input) + @:{ + @:// 单次查询同时获取统计值 + @:var querystats = @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input) + @foreach (var column in Model.TableField){ + if (@column.IsGroupBy == "Y"){ + @: .GroupByIF(input.GroupBy.Contains("@column.PropertyName"), u => u.@column.PropertyName) + } + } + @: //.Having(it => SqlFunc.AggregateCount(it.Id) > 0)//聚合函数过滤 + @: .Select(it => new @(@Model.ClassName)Output + @: { + @: count = SqlFunc.AggregateCount(it.Id), + @foreach (var column in Model.TableField){ + if (@column.IsGroupBy == "Y"){ + @: @(@column.PropertyName) = it.@(@column.PropertyName), + } + if (@column.Statistical == "Y"){ + @: @(@column.PropertyName) = SqlFunc.AggregateSum(it.@(@column.PropertyName)) ?? 0, + } + } + @: }); + @:return await querystats.ToListAsync(); + @:} +} + + /// + /// 根据输入参数获取@(@Model.BusName)统计 + /// 支持双模式聚合配置: + /// 常规模式:Field + Function + /// 高级模式:CustomExpression(支持任意合法SQL表达式) + /// 智能条件组合: + /// 多个HAVING条件自动用AND连接 + /// 条件表达式自动包裹聚合函数(如SUM(cost) > 10000) + /// 可扩展支持OR条件 + /// + /// + /// + [ApiDescriptionSettings(Name = "GetAggregTotalSum", Order = 970), HttpPost] + [DisplayName("根据输入参数获取@(@Model.BusName)统计")] + public async Task> GetAggregTotalSum(Page@(@Model.ClassName)Input input) + { + + @:var query = @(@Model.ClassName)Mid.GetQuery(_@(@Model.LowerClassName)Rep, input) + // 输入参数示例 + //input = new Page@(@Model.ClassName)Input + //{ + // GroupBy = ["department", "project"], + // GroupBy = input.GroupBy, + // Aggregations = + // [ + // new AggregationConfig + //{ + // Field = "cost", + // Function = AggregateFunction.Sum, + // Alias = "totalCost", + // HavingCondition = "> 10000" + //}, + //new AggregationConfig + //{ + // CustomExpression = "AVG(CAST(response_time AS FLOAT))", + // Alias = "avgResponse", + // HavingCondition = "< 500" + //} + // ] + //}; + // 生成SQL示例 + // SELECT + // department, project, + // SUM(cost) AS totalCost, + // AVG(CAST(response_time AS FLOAT)) AS avgResponse + // FROM... + // GROUP BY department, project + // HAVING(SUM(cost) > 10000) AND(AVG(CAST(response_time AS FLOAT)) < 500) + + // 处理分组字段 + var groupFields = AggregationBuilder.ValidateFields(input.GroupBy, typeof(@(@Model.ClassName)Output)); + if (groupFields.Count > 0) + { + query = query.GroupBy(string.Join(",", groupFields)); + } + // 构建聚合配置 + var aggregator = new AggregationBuilder( + configs: input.Aggregations, + entityType: typeof(@(@Model.ClassName)), + outputType: typeof(@(@Model.ClassName)Output) + ); + + // 组合SELECT语句 + var selectParts = groupFields.Select(f => $"{f} AS {f}") + .Concat(aggregator.SelectParts) + .ToList(); + + // 应用HAVING条件 + if (aggregator.HavingConditions.Count > 0) + { + query = query.Having(string.Join(" AND ", aggregator.HavingConditions)); + } + // 执行查询 + return await query.Select<@(@Model.ClassName)Output>(string.Join(", ", selectParts)) + .ToListAsync(); + } + @foreach (var column in Model.TableField){ if(@column.EffectType == "ForeignKey" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){ @:/// @@ -270,7 +325,7 @@ if(@column.EffectType == "ApiTreeSelector" && !definedObjects.ContainsKey("@(@co @:[DisplayName("获取@(@column.FkEntityName)Tree")] @:public async Task @(@column.FkEntityName)Tree() @:{ - @:return await _@(@Model.LowerClassName)Rep.Context.Queryable<@(@column.FkEntityName)>().ToTreeAsync(u => u.Children, u => u.@(@column.PidColumn), 0); + @:return await _@(@Model.LowerClassName)Rep.Context.Queryable<@(@column.FkEntityName)>().Select<@(@column.FkEntityName)Output>().ToTreeAsync(u => u.Children, u => u.@(@column.PidColumn), 0); @:} } } diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm index cf7594b8..eea5ff09 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_api.ts.vm @@ -18,6 +18,8 @@ enum Api { Update@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/update', Page@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/page', Tree@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/GetTreeList', + Get@(@Model.ClassName)TotalSum = '/api/@(@Model.LowerClassName)/GetTotalSum', + Get@(@Model.ClassName)AggregTotalSum = '/api/@(@Model.LowerClassName)/GetAggregTotalSum', Detail@(@Model.ClassName) = '/api/@(@Model.LowerClassName)/detail', @if(Model.RemoteVerify){ @:Exists@(RemoteField) = '/api/@(@Model.LowerClassName)/exists@(RemoteField)', @@ -72,6 +74,20 @@ export const treelist@(@Model.ClassName) = (params?: any) => method: 'post', data: params, }); +// 获取统计,支持全表和分组 +export const get@(@Model.ClassName)TotalSum = (params?: any) => + request({ + url: Api.Get@(@Model.ClassName)TotalSum, + method: 'post', + data: params, + }); +// 输入参数获取统计 +export const get@(@Model.ClassName)AggregTotalSum = (params?: any) => + request({ + url: Api.Get@(@Model.ClassName)AggregTotalSum, + method: 'post', + data: params, + }); // 详情@(@Model.BusName) export const detail@(@Model.ClassName) = (id: any) => request({ diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm index d8fd1b1f..db8effb6 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_List.vue.vm @@ -235,7 +235,7 @@ import ModifyRecord from '/@@/components/table/modifyRecord.vue'; @:import { @(@Model.ClassName), @(@Model.ClassName)Input, @(@Model.ClassName)Output } from '/@@/api-services/models'; } else { -@:import { page@(@Model.ClassName), delete@(@Model.ClassName) } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; +@:import { page@(@Model.ClassName), delete@(@Model.ClassName), get@(@Model.ClassName)TotalSum } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; foreach (var column in Model.QueryWhetherList){ if(@column.EffectType == "ForeignKey"){ @:import { get@(@column.FkEntityName)@(@column.PropertyName)Dropdown } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)'; @@ -278,6 +278,7 @@ const state = reactive({ pageSize: 50 as number, defaultSort: { field: 'Id', order: 'asc', descStr: 'desc' }, }, + totalSum:[] as any, visible: false, title: '', }); @@ -337,6 +338,26 @@ const checkTableColumnVisible = (tableColumnName: any) => { } { title: '操作', fixed: 'right', width: 180, showOverflow: true, slots: { default: 'row_buttons' } }, ], + @if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @:footerMethod: ({ columns, data }) => { + @: const totalSum=state.totalSum[0]; + @: return [ + @: columns.map((column, colIndex) => { + @: if (colIndex === 0) { + @: return `合计:` + @: } + @foreach (var column in Model.TableField){ + if (@column.Statistical == "Y"){ + @: if (column.field === '@(@column.LowerPropertyName)') { + @: // 计算表格内总和 + @: return `${data.reduce((sum, row) => sum + (row.@(@column.LowerPropertyName) || 0), 0)}/总数:${totalSum.@(@column.LowerPropertyName)}` + @: } + } + } + @: }) + @: ] + @:}, + } }, // vxeGrid配置参数(此处可覆写任何参数),参考vxe-table官方文档 { @@ -377,6 +398,14 @@ const handleQueryApi = async (page: VxeGridPropTypes.ProxyAjaxQueryPageParams, s // 查询操作 const handleQuery = async (reset = false) => { options.loading = true; + @if(@Model.TableField.Any(x=>x.Statistical == "Y")){ + @if (@Model.IsApiService) { + @:state.totalSum =getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)GetTotalSumPost(params).data.result; + } + } else { + @:state.totalSum =(await get@(@Model.ClassName)TotalSum(state.queryParams)).data.result; + } + } reset ? await xGrid.value?.commitProxy('reload') : await xGrid.value?.commitProxy('query'); options.loading = false; }; diff --git a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm index 52f88582..255cb9ec 100644 --- a/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm +++ b/Admin.NET/Admin.NET.Web.Entry/wwwroot/template/web_views_Tree.vue.vm @@ -109,7 +109,11 @@ watch(filterText, (val) => { // 获取树数据 const fetchTreeData = async (showLoading: boolean = true) => { if (showLoading) state.loading = true; - var res = await treelist@(@Model.ClassName)({Id:0}); + @if (@Model.IsApiService) { + @:var res = await getAPI(@(@Model.ClassName)Api).api@(@Model.ClassName)GetTreeListPost({Id:0}); + } else { + @:var res = await treelist@(@Model.ClassName)({Id:0}); + } state.@(@Model.LowerClassName)Data = res.data.result ?? []; if (showLoading) state.loading = false; return res.data.result ?? []; diff --git a/Web/src/views/system/codeGen/component/genConfigDialog.vue b/Web/src/views/system/codeGen/component/genConfigDialog.vue index db307ae2..581b30a2 100644 --- a/Web/src/views/system/codeGen/component/genConfigDialog.vue +++ b/Web/src/views/system/codeGen/component/genConfigDialog.vue @@ -38,6 +38,9 @@ + @@ -205,6 +208,15 @@ const options = reactive({ default: 'statistical', }, }, + { + field: 'isGroupBy', + title: 'GroupBy', + minWidth: 70, + slots: { + edit: 'isGroupBy', + default: 'isGroupBy', + }, + }, { field: 'queryWhether', title: '是否是查询',