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: '是否是查询',