Forráskód Böngészése

chore: 优化代码生成

喵你个旺呀 1 éve
szülő
commit
5aff82efdc

+ 2 - 2
Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Dto.cs.vm

@@ -4,7 +4,7 @@
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
-namespace @(Model.NameSpace).Service;
+namespace @(Model.NameSpace);
 
 /// <summary>
 /// @(Model.BusName)输出参数
@@ -16,7 +16,7 @@ if(column.EffectType == "fk" && column.FkEntityName != "" && column.FkColumnName
     @:/// <summary>
     @:/// @column.ColumnComment
     @:/// </summary>
-    @:public @(column.FkColumnNetType) @(column.PropertyName)@(column.FkColumnName) { get; set; }
+    @:public string @(column.PropertyName)FkColumn { get; set; }
     @:
 }
 }

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

@@ -7,7 +7,7 @@
 @if(Model.BaseClassName!=""){
 @:using Admin.NET.Core;
 }
-namespace @(Model.NameSpace).Entity;
+namespace @(Model.NameSpace);
 
 /// <summary>
 /// @(Model.Description)

+ 54 - 58
Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Input.cs.vm

@@ -4,24 +4,27 @@
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
+@{
+    bool IsNetEnumType(dynamic column) => column.NetType.TrimEnd('?').EndsWith("Enum");
+    bool IsStatusEnumField(dynamic column) => column.NetType.TrimEnd('?') == "StatusEnum" && column.PropertyName == "Status";
+    string GetFinalType(string type) => Regex.IsMatch(type, "(.*?Enum|bool|char|int|long|double|float|decimal)[?]?") ? type.TrimEnd('?') + "?" : type;
+}
 using Admin.NET.Core;
 using System.ComponentModel.DataAnnotations;
 
-namespace @(Model.NameSpace).Service;
+namespace @(Model.NameSpace);
 
 /// <summary>
 /// @(Model.BusName)基础输入参数
 /// </summary>
 public class @(Model.ClassName)BaseInput
 {
-@foreach (var column in Model.TableField){
-if (column.ColumnKey != "True" && column.WhetherAddUpdate == "Y" && !(column.NetType == "StatusEnum" && column.PropertyName == "Status")){
+@foreach (var column in Model.TableField.Where(u => u.ColumnKey != "True" && u.WhetherAddUpdate == "Y")){
     @:/// <summary>
     @:/// @column.ColumnComment
     @:/// </summary>
-    @:public virtual @column.NetType @column.PropertyName { get; set; }
-    @:
-}
+    @:public virtual @GetFinalType(column.NetType) @column.PropertyName { get; set; }
+@:
 }
 }
 
@@ -33,16 +36,16 @@ public class Page@(Model.ClassName)Input : BasePageInput
 @foreach (var column in Model.TableField.Where(u => u.QueryWhether == "Y")){
     if(column.NetType?.TrimEnd('?') == "DateTime" && column.QueryType == "~"){
     @:/// <summary>
-    @: /// @(column.ColumnComment)范围
-    @: /// </summary>
+    @:/// @(column.ColumnComment)范围
+    @:/// </summary>
     @: public DateTime?[] @(column.PropertyName)Range { get; set; }
     } else {
     @:/// <summary>
     @:/// @column.ColumnComment
     @:/// </summary>
-    @:public @column.NetType.TrimEnd('?')? @column.PropertyName { get; set; }
+    @:public @GetFinalType(column.NetType) @column.PropertyName { get; set; }
     }
-    @:
+@:
 }
 }
 
@@ -51,21 +54,18 @@ public class Page@(Model.ClassName)Input : BasePageInput
 /// </summary>
 public class Add@(Model.ClassName)Input
 {
-@foreach (var column in Model.TableField){
-if (column.WhetherAddUpdate == "Y"){
+@foreach (var column in Model.TableField.Where(col => col.WhetherAddUpdate == "Y")){
     @:/// <summary>
     @:/// @column.ColumnComment
     @:/// </summary>
-if (column.WhetherRequired == "Y") {
+    if (column.WhetherRequired == "Y") {
     @:[Required(ErrorMessage = "@(column.ColumnComment)不能为空")]
-}
-if (Regex.IsMatch(column.NetType, "(.*?Enum|int|long|double|float|bool)[?]?")){
-    @:public @column.NetType.TrimEnd('?')? @column.PropertyName { get; set; }
-}else{
-    @:public @column.NetType @column.PropertyName { get; set; }
-}
-    @:
-}
+    }
+    if (column.NetType.TrimEnd('?').EndsWith("string") && column.ColumnLength > 0){
+    @:[MaxLength(@column.ColumnLength, ErrorMessage = "@(column.ColumnComment)字符长度不能超过@(column.ColumnLength)")]
+    }
+    @:public @GetFinalType(column.NetType) @column.PropertyName { get; set; }
+@:
 }
 }
 
@@ -79,12 +79,8 @@ public class Delete@(Model.ClassName)Input
     @:/// @column.ColumnComment
     @:/// </summary>
     @:[Required(ErrorMessage = "@(column.ColumnComment)不能为空")]
-if (Regex.IsMatch(column.NetType, "(.*?Enum|int|long|double|float|bool)[?]?")){
-    @:public @column.NetType.TrimEnd('?')? @column.PropertyName { get; set; }
-}else{
-    @:public @column.NetType @column.PropertyName { get; set; }
-}
-    @:
+    @:public @GetFinalType(column.NetType) @column.PropertyName { get; set; }
+@:
 }
 }
 
@@ -98,12 +94,8 @@ public class BatchDelete@(Model.ClassName)Input
     @:/// @column.ColumnComment
     @:/// </summary>
     @:[Required(ErrorMessage = "@(column.ColumnComment)列表不能为空")]
-if (Regex.IsMatch(column.NetType, "(.*?Enum|int|long|double|float|bool)[?]?")){
-    @:public List<@column.NetType.TrimEnd('?')?> @(column.PropertyName)List { get; set; }
-}else{
-    @:public List<@column.NetType> @(column.PropertyName)List { get; set; }
-}
-    @:
+    @:public List<@GetFinalType(column.NetType)> @(column.PropertyName)List { get; set; }
+@:
 }
 }
 
@@ -112,21 +104,18 @@ if (Regex.IsMatch(column.NetType, "(.*?Enum|int|long|double|float|bool)[?]?")){
 /// </summary>
 public class Update@(Model.ClassName)Input
 {
-@foreach (var column in Model.TableField){
-if (column.ColumnKey == "True" || column.WhetherAddUpdate == "Y" && !(column.NetType == "StatusEnum" && column.PropertyName == "Status")){
+@foreach (var column in Model.TableField.Where(u => u.ColumnKey == "True" || u.WhetherAddUpdate == "Y" && !IsStatusEnumField(u))){
     @:/// <summary>
     @:/// @column.ColumnComment
-    @:/// </summary>
-if (column.WhetherRequired == "Y" || column.ColumnKey == "True") {
+    @:/// </summary>    
+    if (column.WhetherRequired == "Y" || column.ColumnKey == "True") {
     @:[Required(ErrorMessage = "@(column.ColumnComment)不能为空")]
-}
-if (Regex.IsMatch(column.NetType, "(.*?Enum|int|long|double|float|bool)[?]?")){
-    @:public @column.NetType.TrimEnd('?')? @column.PropertyName { get; set; }
-}else{
-    @:public @column.NetType @column.PropertyName { get; set; }
-}
-    @:
-}
+    }
+    if (column.NetType.TrimEnd('?').EndsWith("string") && column.ColumnLength > 0){
+    @:[MaxLength(@column.ColumnLength, ErrorMessage = "@(column.ColumnComment)字符长度不能超过@(column.ColumnLength)")]
+    }
+    @:public @GetFinalType(column.NetType) @column.PropertyName { get; set; }
+@:
 }
 }
 
@@ -145,23 +134,30 @@ public class QueryById@(Model.ClassName)Input : Delete@(Model.ClassName)Input
 @:public class Import@(Model.ClassName)Input : BaseImportInput
 @:{
     foreach (var column in Model.TableField.Where(x => x.WhetherImport == "Y")){
-    if (column.WhetherAddUpdate == "Y") {
+    var prefix = column.WhetherRequired == "Y" || column.NetType.TrimEnd('?').EndsWith("Enum") ? "*" : "";
+    if(column.EffectType == "fk" || column.EffectType == "ApiTreeSelect") {
     @:/// <summary>
-    @:/// @column.ColumnComment
+    @:/// @column.ColumnComment 关联值
     @:/// </summary>
-    if (column.WhetherRequired == "Y" || column.NetType.TrimEnd('?').EndsWith("Enum")){
-    @:[ImporterHeader(Name = "*@(column.ColumnComment)")]
-    @:[ExporterHeader("*@(column.ColumnComment)", Format = "@", Width = 25, IsBold = true)]
+    @:[ImporterHeader(IsIgnore = true)]
+    @:[ExporterHeader(IsIgnore = true)]
+    @:public @GetFinalType(column.NetType) @column.PropertyName { get; set; }
+@:
+    @:/// <summary>
+    @:/// @column.ColumnComment 文本
+    @:/// </summary>
+    @:[ImporterHeader(Name = "@(prefix + column.ColumnComment)")]
+    @:[ExporterHeader("@(prefix + column.ColumnComment)", Format = "@", Width = 25, IsBold = true)]
+    @:public string @(column.PropertyName)Label { get; set; }
     } else {
-    @:[ImporterHeader(Name = "@(column.ColumnComment)")]
-    @:[ExporterHeader("@(column.ColumnComment)", Format = "@", Width = 25, IsBold = true)]
+    @:/// <summary>
+    @:/// @column.ColumnComment
+    @:/// </summary>
+    @:[ImporterHeader(Name = "@(prefix + column.ColumnComment)")]
+    @:[ExporterHeader("@(prefix + column.ColumnComment)", Format = "@", Width = 25, IsBold = true)]
+    @:public @GetFinalType(column.NetType) @column.PropertyName { get; set; }
     }
-    if (Regex.IsMatch(column.NetType, "(.*?Enum|int|long|double|float|bool)[?]?")){
-    @:public @column.NetType.TrimEnd('?')? @column.PropertyName { get; set; }
-    } else {
-    @:public @column.NetType @column.PropertyName { get; set; }
+@:
     }
-    @:
-    }}
 @:}
 }

+ 47 - 127
Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Manage.js.vm

@@ -1,137 +1,57 @@
 @{
-	var definedObjects = new Dictionary<string, int>();
+	var definedObjects = new Dictionary<string, object>();
+	string LowerFirstLetter(string text) => text.ToString()[..1].ToLower() + text[1..];
 	var hasSetStatus = Model.TableField.Any(col => col.NetType == "StatusEnum" && col.PropertyName == "Status");
 }
-import request from '/@@/utils/request';
-@if (Model.TableField.Any(col => col.EffectType == "Upload")) {
-@:import uploadFile from '/@/api/system/uploadFile';
-}
-enum Api {
-  Add@(Model.ClassName) = '/api/@(Model.LowerClassName)/add',
-  Delete@(Model.ClassName) = '/api/@(Model.LowerClassName)/delete',
-  BatchDelete@(Model.ClassName) = '/api/@(Model.LowerClassName)/batchDelete',
-  Update@(Model.ClassName) = '/api/@(Model.LowerClassName)/update',
-  Page@(Model.ClassName) = '/api/@(Model.LowerClassName)/page',
-  Detail@(Model.ClassName) = '/api/@(Model.LowerClassName)/detail',
-@if (Model.TableField.Any(x => x.WhetherImport == "Y")) {
-  @:Import@(Model.ClassName)Data = '/api/@(Model.LowerClassName)/import',
-  @:Download@(Model.ClassName)Template = '/api/@(Model.LowerClassName)/import',
-}
-@if (hasSetStatus) {
-  @:Set@(Model.ClassName)Status = '/api/@(Model.LowerClassName)/setStatus',
-}
-@foreach (var column in Model.TableField){
-if(column.EffectType == "fk" && (column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
-  @:Get@(column.FkEntityName)@(column.PropertyName)Dropdown = '/api/@(Model.LowerClassName)/@(column.FkEntityName)@(column.PropertyName)Dropdown',
-}else if(column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("Get@(column.FkEntityName)Tree")){
-  @{definedObjects.Add("Get@(column.FkEntityName)Tree", 1);}
-  @:Get@(column.FkEntityName)Tree = '/api/@(Model.LowerClassName)/@(column.FkEntityName[..1].ToLower() + column.FkEntityName[1..])Tree',
-}else if(column.EffectType == "Upload"){
-  @:Upload@(column.PropertyName) = '/api/@(Model.LowerClassName)/Upload@(column.PropertyName)',
-}
-}
-}
-
-// 增加@(Model.BusName)
-export const add@(Model.ClassName) = (params?: any) =>
-	request({
-		url: Api.Add@(Model.ClassName),
-		method: 'post',
-		data: params,
-	});
-
-// 删除@(Model.BusName)
-export const delete@(Model.ClassName) = (params?: any) => 
-	request({
-			url: Api.Delete@(Model.ClassName),
-			method: 'post',
-			data: params,
-		});
-
-// 批量删除@(Model.BusName)
-export const batchDelete@(Model.ClassName) = (params?: any) =>
-	request({
-		url: Api.BatchDelete@(Model.ClassName),
-		method: 'post',
-		data: params,
-	});
-
-// 编辑@(Model.BusName)
-export const update@(Model.ClassName) = (params?: any) => 
-	request({
-			url: Api.Update@(Model.ClassName),
-			method: 'post',
-			data: params,
-		});
+import {useBaseApi} from '/@@/api/base';
+
+// @(Model.BusName)接口服务
+export const use@(Model.ClassName)Api = () => {
+	const baseApi = useBaseApi("@(Model.LowerClassName)");
+	return {
+		// 分页查询@(Model.BusName)
+		page: baseApi.page,
+		// 查看@(Model.BusName)详细
+		detail: baseApi.detail,
+		// 新增@(Model.BusName)
+		add: baseApi.add,
+		// 更新@(Model.BusName)
+		update: baseApi.update,
 @if (hasSetStatus) {
-@:
-@:// 设置@(Model.BusName)状态
-@:export const set@(Model.ClassName)Status = (params?: any) =>
-	@:request({
-		@:url: Api.Set@(Model.ClassName)Status,
-		@:method: 'post',
-		@:data: params,
-	@:});
+		@:// 设置@(Model.BusName)状态
+		@:setStatus: baseApi.setStatus,
 }
-
-// 分页查询@(Model.BusName)
-export const page@(Model.ClassName) = (params?: any) => 
-	request({
-			url: Api.Page@(Model.ClassName),
-			method: 'post',
-			data: params,
-		});
-
-// 详情@(Model.BusName)
-export const detail@(Model.ClassName) = (id: any) => 
-	request({
-			url: Api.Detail@(Model.ClassName),
-			method: 'get',
-			data: { id },
-		});
-
+		// 删除@(Model.BusName)
+		delete: baseApi.delete,
+		// 批量删除@(Model.BusName)
+		batchDelete: baseApi.batchDelete,
 @if (Model.TableField.Any(x => x.WhetherImport == "Y")) {
-@:// 下载@(Model.BusName)数据导入模板
-@:export const download@(Model.ClassName)Template = () =>
-	@:request({
-		@:url: Api.Download@(Model.ClassName)Template,
-		@:responseType: 'arraybuffer',
-		@:method: 'get',
-	@:});
-@:
-@:// 导入@(Model.BusName)记录
-@:export const import@(Model.ClassName)Data = (file: any) => {
-	@:const formData = new FormData();
-	@:formData.append('file', file);
-	@:return request({
-		@:headers: { 'Content-Type': 'multipart/form-data' },
-		@:url: Api.Import@(Model.ClassName)Data,
-		@:responseType: 'arraybuffer',
-		@:method: 'post',
-		@:data: formData,
-	@:});
-@:}
+		@:// 下载@(Model.BusName)数据导入模板
+		@:downloadTemplate: baseApi.downloadTemplate,
+		@:// 导入@(Model.BusName)数据
+		@:importData: baseApi.importData,
 }
-
 @foreach (var column in Model.TableField) {
-	if(column.EffectType == "fk" && (column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
-@:// 获取@(column.ColumnComment)选择数据
-@:export const get@(column.FkEntityName)@(column.PropertyName)Dropdown = () => 
-	@:request({
-		@:url: Api.Get@(column.FkEntityName)@(column.PropertyName)Dropdown,
-		@:method: 'get'
-	@:});
-	}else if(column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("get@(column.FkEntityName)Tree")){
+		if (column.EffectType == "Upload") {
+		@:// 上传@(column.ColumnComment)
+		@:upload@(column.PropertyName): (params: any) => baseApi.uploadFile(params, baseApi.baseUrl + 'upload@(column.PropertyName)'),
+		} else if (column.EffectType == "fk" && (column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")) {
+		var dropdownName = $"{column.FkEntityName}{Regex.Replace(column.PropertyName, "[iI]d$", "")}Dropdown";
+		@:// 获取@(column.ColumnComment)选择数据
+		@:get@(dropdownName): (all: Boolean = false) => baseApi.request({
+			@:url: baseApi.baseUrl + '@LowerFirstLetter(dropdownName)',
+			@:params: { all },
+			@:method: 'get',
+		@:}),
+		} else if (column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("get@(column.FkEntityName)Tree")) {
 		definedObjects.Add("get@(column.FkEntityName)Tree", 1);
-@:// 获取@(column.ColumnComment)选择数据
-@:export const get@(column.FkEntityName)Tree = () =>
-	@:request({
-		@:url: Api.Get@(column.FkEntityName)Tree,
-		@:method: 'get'
-	@:});
-    } else if (column.EffectType == "Upload") {
-@:// 上传@(column.ColumnComment)
-@:export const upload@(column.PropertyName) = (params: any) =>
-	@:uploadFile(params, Api.Upload@(column.PropertyName));
-	}
+		@:// 获取@(column.ColumnComment)选择数据
+		@:get@(column.FkEntityName)Tree: (all: Boolean = false) => baseApi.request({
+			@:url: baseApi.baseUrl + '@LowerFirstLetter(column.FkEntityName)Tree',
+			@:params: { all },
+			@:method: 'get',
+		@:}),
+		}
 }
+	}
+}

+ 6 - 4
Admin.NET/Admin.NET.Web.Entry/wwwroot/template/Output.cs.vm

@@ -4,7 +4,10 @@
 //
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
-namespace @(Model.NameSpace).Service;
+@{
+    string GetFinalType(string type) => Regex.IsMatch(type, "(.*?Enum|bool|char|int|long|double|float|decimal)[?]?") ? type.TrimEnd('?') + "?" : type;
+}
+namespace @(Model.NameSpace);
 
 /// <summary>
 /// @(Model.BusName)输出参数
@@ -22,8 +25,7 @@ if(column.EffectType == "fk")
     @:/// <summary>
     @:/// @(column.ColumnComment) 描述
     @:/// </summary>
-    @:public @(column.FkColumnNetType) @(column.PropertyName)@(column.FkColumnName) { get; set; } 
-
+    @:public string @(column.PropertyName)FkColumn { get; set; } 
 }else if(column.EffectType == "Upload"){
     @:public @column.NetType @column.PropertyName { get; set; }
     @:public SysFile @(column.PropertyName)Attachment { get; set; }
@@ -35,7 +37,7 @@ if(column.EffectType == "fk")
     @:/// </summary>
     @:public string? @(column.PropertyName)Display { get; set; } 
 }else{
-    @:public @column.NetType @(column.PropertyName) { get; set; }
+    @:public @GetFinalType(column.NetType) @(column.PropertyName) { get; set; }
 }
     @:
 }

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

@@ -7,7 +7,7 @@
 using Admin.NET.Core;
 using @Model.EntityNameSpace;
 
-namespace @(Model.NameSpace).SeedData;
+namespace @(Model.NameSpace);
 
 /// <summary>
 /// @(Model.Description) 表种子数据

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

@@ -7,22 +7,16 @@
 using Admin.NET.Core.Service;
 using Microsoft.AspNetCore.Http;
 @{
-    string joinTableName = "u";
-    string joinTableAlias = "";
-    Dictionary<string, int> definedObjects = new Dictionary<string, int>();
-    bool haveLikeCdt = false;
-    foreach (var column in Model.TableField){
-        if (column.QueryWhether == "Y" && column.QueryType == "like"){
-            haveLikeCdt = true;
-        }
-    }
-    var hasSetStatus = Model.TableField.Any(col => col.NetType == "StatusEnum" && col.PropertyName == "Status");
-    var dictTableField = Model.TableField.Where(x => x.WhetherImport == "Y" && x.EffectType == "Select") ?? default;
-    var hasdictService = dictTableField.Count() > 0;
-    var importField = Model.TableField.Where(x => x.WhetherImport == "Y");
-    var displayColumnList = new List<string>();
+    string LowerFirstLetter(string text) => text.ToString()[..1].ToLower() + text[1..];
+
+    var primaryKey = Model.TableField.First(u => u.ColumnKey == "True");
+    var importFields = Model.TableField.Where(x => x.WhetherImport == "Y");
+
+    var injectServices = new List<string>();
+    if (Model.TableField.Any(u => u.EffectType == "Upload")) injectServices.Add("SysFileService");
+    if (Model.TableField.Any(x => x.WhetherImport == "Y" && x.EffectType == "Select")) injectServices.Add("SysDictTypeService");
 }
-namespace @(Model.NameSpace).Service;
+namespace @(Model.NameSpace);
 
 /// <summary>
 /// @(Model.BusName)服务 🧩
@@ -31,18 +25,16 @@ namespace @(Model.NameSpace).Service;
 public class @(Model.ClassName)Service : IDynamicApiController, ITransient
 {
     private readonly SqlSugarRepository<@(Model.ClassName)> _@(Model.LowerClassName)Rep;
-@if (hasdictService) {
-    @:private readonly SysDictTypeService _sysDictTypeService;
-}
-    public @(Model.ClassName)Service(SqlSugarRepository<@(Model.ClassName)> @(Model.LowerClassName)Rep
-@if (hasdictService) {
-        @:,SysDictTypeService sysDictTypeService
-}
-    ){
+    @foreach(var name in injectServices) {
+    @:private readonly @name _@(LowerFirstLetter(name));
+    }
+
+    public @(Model.ClassName)Service(SqlSugarRepository<@(Model.ClassName)> @(Model.LowerClassName)Rep@(injectServices.Count > 0 ? ", " + string.Join(", ", injectServices.Select(name => $"{name} {LowerFirstLetter(name)}")) : ""))
+    {
         _@(Model.LowerClassName)Rep = @(Model.LowerClassName)Rep;
-@if (hasdictService) {
-        @:_sysDictTypeService = sysDictTypeService;
-}
+        @foreach(var name in injectServices) {
+        @:_@LowerFirstLetter(name) = @LowerFirstLetter(name);
+        }
     }
 
     /// <summary>
@@ -50,76 +42,60 @@ public class @(Model.ClassName)Service : IDynamicApiController, ITransient
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
-    [ApiDescriptionSettings(Name = "Page"), HttpPost]
     [DisplayName("分页查询@(Model.BusName)")]
+    [ApiDescriptionSettings(Name = "Page"), HttpPost]
     public async Task<SqlSugarPagedList<@(Model.ClassName)Output>> Page(Page@(Model.ClassName)Input input)
     {
-@if (haveLikeCdt) {
-		@:input.Keyword = input.Keyword?.Trim();
-}
+        input.Keyword = input.Keyword?.Trim();
         var query = _@(Model.LowerClassName)Rep.AsQueryable()
-@{string conditionFlag = "";}
-@if (haveLikeCdt) {
-            @:.WhereIF(!string.IsNullOrEmpty(input.Keyword), u =>
-    @foreach (var column in Model.TableField){
-        if (column.QueryWhether == "Y" && column.QueryType == "like"){
-                @:@(conditionFlag)u.@(column.PropertyName).Contains(input.Keyword)
-            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 != null, 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))
-    }
-} 
-}
-@if(Model.IsJoinTable){
-            @:// 处理外键和TreeSelector相关字段的连接
-    @foreach (var column in Model.TableField.Where(u => u.EffectType == "fk" || u.EffectType == "ApiTreeSelect")){
-            joinTableAlias = Regex.Replace(column.LowerPropertyName, "[iI]d$", "");
+@{
+          string joinTableName = "u";
+          var queryFields = Model.TableField.Where(u => u.QueryWhether == "Y");
+          // 关键字模糊查询
+          if (queryFields.Any(u => u.QueryType == "like")) {
+            @:.WhereIF(!string.IsNullOrEmpty(input.Keyword), u => @string.Join(" || ", queryFields.Where(u => u.QueryType == "like").Select(col => $"u.{col.PropertyName}.Contains(input.Keyword)")))
+          }
+          // 字段组合查询
+          foreach(var column in queryFields) {
+            if (column.NetType.TrimEnd('?') == "string") {
+            @:.WhereIF(!string.IsNullOrWhiteSpace(input.@(column.PropertyName)), u => u.@(column.PropertyName)@(column.QueryType == "like" ? $".Contains(input.{column.PropertyName}.Trim())" : $" {column.QueryType} input.{column.PropertyName}.Trim()"))
+            } else if (column.NetType.TrimEnd('?') == "int" || column.NetType.TrimEnd('?') == "long") {
+            @:.WhereIF(input.@(column.PropertyName) != null, u => u.@(column.PropertyName) @(column.QueryType) input.@(column.PropertyName))
+            } else if (column.NetType.TrimEnd('?').EndsWith("Enum")) {
+            @:.WhereIF(input.@(column.PropertyName).HasValue, u => u.@(column.PropertyName) == input.@(column.PropertyName))
+            } else if (column.NetType.TrimEnd('?') == "DateTime" && column.QueryType == "~") {
+            @:.WhereIF(input.@(column.PropertyName)Range?.Length == 2, u => u.@(column.PropertyName) >= input.@(column.PropertyName)Range[0] && u.@(column.PropertyName) <= input.@(column.PropertyName)Range[1])
+            }
+          }
+          // 联表
+          if (Model.IsJoinTable) {
+            @foreach (var column in Model.TableField.Where(u => u.EffectType == "fk" || u.EffectType == "ApiTreeSelect")){
+            var joinTableAlias = Regex.Replace(column.LowerPropertyName, "[iI]d$", "");
             joinTableName += ", " + joinTableAlias;
             @:.LeftJoin<@column.FkEntityName>((@joinTableName) => u.@(column.PropertyName) == @joinTableAlias.@(column.EffectType == "fk" ? column.FkLinkColumnName : column.ValueColumn))
-    }
+          }
+            // 查询列表
             @:.Select((@joinTableName) => new @(Model.ClassName)Output
             @:{
-@foreach (var column in Model.TableField){
-                joinTableAlias = Regex.Replace(column.LowerPropertyName, "[iI]d$", "");
-                if(column.EffectType == "fk"){
+            foreach (var column in Model.TableField) {
+                var joinTableAlias = Regex.Replace(column.LowerPropertyName, "[iI]d$", "");
+                if (column.EffectType == "fk") {
+                var columnList = column.FkColumnName.Split(",").Select(n => $"{{{joinTableAlias}.{n}}}").ToList();
                 @:@(column.PropertyName) = u.@(column.PropertyName), 
-                @:@(column.PropertyName)@(column.FkColumnName) = @(joinTableAlias).@(column.FkColumnName),
-                } else if(column.EffectType == "ApiTreeSelect"){
-                displayColumnList = column.DisplayColumn.Split(",").Select(u => $"{{{joinTableAlias}.{u}}}").ToList();
+                @:@(column.PropertyName)FkColumn = $"@(string.Join("-", columnList))",
+                } else if (column.EffectType == "ApiTreeSelect") {
+                var columnList = column.DisplayColumn.Split(",").Select(n => $"{{{joinTableAlias}.{n}}}").ToList();
                 @:@(column.PropertyName) = u.@(column.PropertyName),  
-                @:@(column.PropertyName)Display = $"@(string.Join("-", displayColumnList))",
-                } else if(column.NetType?.TrimEnd('?').EndsWith("Enum") == true){
-                @:@(column.PropertyName) = u.@(column.PropertyName),
+                @:@(column.PropertyName)Display = $"@(string.Join("-", columnList))",
                 } else {
                 @:@(column.PropertyName) = u.@(column.PropertyName),
                 }
-}
+            }
             @:});
-@foreach (var column in Model.TableField){
-    if(column.EffectType == "fk"){   
- 
-    }else if(column.EffectType == "Upload"){
-            @://.Mapper(c => c.@(column.PropertyName)Attachment, c => c.@(column.PropertyName))
-    }
-}
-} else {
+         } else {
+            // 无联表
             @:.Select<@(Model.ClassName)Output>();
+         }
 }
 		return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
     }
@@ -129,13 +105,12 @@ if (column.QueryWhether == "Y"){
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
-    [ApiDescriptionSettings(Name = "Add"), HttpPost]
     [DisplayName("增加@(Model.BusName)")]
+    [ApiDescriptionSettings(Name = "Add"), HttpPost]
     public async Task<long> Add(Add@(Model.ClassName)Input input)
     {
         var entity = input.Adapt<@(Model.ClassName)>();
-        await _@(Model.LowerClassName)Rep.InsertAsync(entity);
-        return entity.Id;
+        return await _@(Model.LowerClassName)Rep.InsertAsync(entity) ? entity.Id : 0;
     }
 
     /// <summary>
@@ -143,13 +118,13 @@ if (column.QueryWhether == "Y"){
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
-    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
     [DisplayName("删除@(Model.BusName)")]
+    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
     public async Task Delete(Delete@(Model.ClassName)Input input)
     {
-@foreach (var column in Model.TableField.Where(u => u.ColumnKey == "True")){
+        @foreach (var column in Model.TableField.Where(u => u.ColumnKey == "True")) {
         @:var entity = await _@(Model.LowerClassName)Rep.GetFirstAsync(u => u.@(column.PropertyName) == input.@(column.PropertyName)) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
-}
+        }
         await _@(Model.LowerClassName)Rep.FakeDeleteAsync(entity);   //假删除
         //await _@(Model.LowerClassName)Rep.DeleteAsync(entity);   //真删除
     }
@@ -159,13 +134,13 @@ if (column.QueryWhether == "Y"){
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
-    [ApiDescriptionSettings(Name = "BatchDelete"), HttpPost]
     [DisplayName("批量删除@(Model.BusName)")]
+    [ApiDescriptionSettings(Name = "BatchDelete"), HttpPost]
     public async Task<int> BatchDelete(BatchDelete@(Model.ClassName)Input input)
     {
-@foreach (var column in Model.TableField.Where(u => u.ColumnKey == "True")){
+        @foreach (var column in Model.TableField.Where(u => u.ColumnKey == "True")) {
         @:var list = await _@(Model.LowerClassName)Rep.AsQueryable().Where(u => input.@(column.PropertyName)List.Contains(u.@(column.PropertyName))).ToListAsync() ?? throw Oops.Oh(ErrorCodeEnum.D1002);
-}
+        }
         return await _@(Model.LowerClassName)Rep.FakeDeleteAsync(list);   //假删除
         //return await _@(Model.LowerClassName)Rep.DeleteAsync(list);   //真删除
     }
@@ -175,42 +150,38 @@ if (column.QueryWhether == "Y"){
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
-    [ApiDescriptionSettings(Name = "Update"), HttpPost]
     [DisplayName("更新@(Model.BusName)")]
+    [ApiDescriptionSettings(Name = "Update"), HttpPost]
     public async Task Update(Update@(Model.ClassName)Input input)
     {
         var entity = input.Adapt<@(Model.ClassName)>();
         await _@(Model.LowerClassName)Rep.AsUpdateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
     }
-@if (hasSetStatus) {
-@:
+    @if (Model.TableField.Any(col => col.NetType == "StatusEnum" && col.PropertyName == "Status")) {
+    @:
     @:/// <summary>
     @:/// 设置@(Model.BusName)状态 🚫
     @:/// </summary>
     @:/// <param name="input"></param>
     @:/// <returns></returns>
-    @:[ApiDescriptionSettings(Name = "SetStatus"), HttpPost]
     @:[DisplayName("设置@(Model.BusName)状态")]
-    @:public async Task SetStatus(BaseStatusInput input)
+    @:[ApiDescriptionSettings(Name = "SetStatus"), HttpPost]
+    @:public async Task Set@(Model.ClassName)Status(BaseStatusInput input)
     @:{
         @:await _@(Model.LowerClassName)Rep.AsUpdateable().SetColumns(u => u.Status, input.Status).Where(u => u.Id == input.Id).ExecuteCommandAsync();
     @:} 
-}
+    }
 
     /// <summary>
-    /// 获取@(Model.BusName) ℹ️
+    /// 获取@(Model.BusName)详情 ℹ️
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
+    [DisplayName("获取@(Model.BusName)详情")]
     [ApiDescriptionSettings(Name = "Detail"), HttpGet]
-    [DisplayName("获取@(Model.BusName)")]
     public async Task<@(Model.ClassName)> Detail([FromQuery] QueryById@(Model.ClassName)Input input)
     {
-@foreach (var column in Model.TableField){
-if (@column.ColumnKey == "True"){
-        @:return await _@(Model.LowerClassName)Rep.GetFirstAsync(u => u.@(column.PropertyName) == input.@(column.PropertyName));
-}   
-}            
+        return await _@(Model.LowerClassName)Rep.GetFirstAsync(u => u.@(primaryKey.PropertyName) == input.@(primaryKey.PropertyName));
     }
 
     /// <summary>
@@ -218,83 +189,71 @@ if (@column.ColumnKey == "True"){
     /// </summary>
     /// <param name="input"></param>
     /// <returns></returns>
-    [ApiDescriptionSettings(Name = "List"), HttpGet]
     [DisplayName("获取@(Model.BusName)列表")]
+    [ApiDescriptionSettings(Name = "List"), HttpGet]
     public async Task<List<@(Model.ClassName)Output>> List([FromQuery] Page@(Model.ClassName)Input input)
     {
         return await _@(Model.LowerClassName)Rep.AsQueryable().Select<@(Model.ClassName)Output>().ToListAsync();
     }
-@foreach (var column in Model.TableField){
-if(column.EffectType == "fk" && (@column.WhetherAddUpdate == "Y" || column.QueryWhether == "Y")){
+@foreach (var column in Model.TableField.Where(u => u.EffectType == "fk" && (u.WhetherAddUpdate == "Y" || u.QueryWhether == "Y"))){
 @:
+    var dropdownName = $"{column.FkEntityName}{Regex.Replace(column.PropertyName, "[iI]d$", "")}Dropdown";
     @:/// <summary>
     @:/// 获取@(column.ColumnComment)列表 🔖
     @:/// </summary>
     @:/// <returns></returns>
-    @:[ApiDescriptionSettings(Name = "@(column.FkEntityName)@(column.PropertyName)Dropdown"), HttpGet]
     @:[DisplayName("获取@(column.ColumnComment)列表")]
-    @:public async Task<dynamic> @(column.FkEntityName)@(column.PropertyName)Dropdown()
+    @:[ApiDescriptionSettings(Name = "@(dropdownName)"), HttpGet]
+    @:public async Task<dynamic> @(dropdownName)([FromQuery]bool all)
     @:{
-        @:return await _@(Model.LowerClassName)Rep.Context.Queryable<@(column.FkEntityName)>().Select(u => new
+        var columnList = column.FkColumnName.Split(",").Select(name => $"{{u.{name}}}").ToList();
+        @:return await _@(Model.LowerClassName)Rep.Context.Queryable<@(column.FkEntityName)>()
+            @:.InnerJoinIF<@Model.ClassName>(!all, (u, r) => u.@(column.FkLinkColumnName) == r.@(column.PropertyName))
+            @:.Select(u => new
             @:{
-                @:Label = u.@(column.FkColumnName),
+                @:Label = $"@(string.Join("-", columnList))",
                 @:Value = u.@(column.FkLinkColumnName)
             @:}
         @:).ToListAsync();
     @:}
 }
-}
-@foreach (var column in Model.TableField){
-if(column.EffectType == "Upload"){
-@:
-    @:/// <summary>
-    @:/// 上传@(column.ColumnComment) ⬆️
-    @:/// </summary>
-    @:/// <param name="file"></param>
-    @:/// <returns></returns>
-    @:[ApiDescriptionSettings(Name = "Upload@(column.PropertyName)"), HttpPost]
-    @:[DisplayName("上传@(column.ColumnComment)")]
-    @:public async Task<SysFile> Upload@(column.PropertyName)([Required] IFormFile file)
-    @:{
-            @:var service = App.GetRequiredService<SysFileService>();
-            @:return await service.UploadFile(new FileUploadInput { File = file, Path = "upload/@(column.PropertyName)" }); 
-    @:} 
-}
-}
-@foreach (var column in Model.TableField){
-if(column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("@(column.FkEntityName)Tree")){
+@{
+var definedObjects = new Dictionary<string, object>();
+@foreach (var column in Model.TableField.Where(u => u.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("@(u.FkEntityName)Tree"))){
 @:
     definedObjects.Add("@(column.FkEntityName)Tree", 1);
     @:/// <summary>
     @:/// 获取@(column.ColumnComment)选择数据 🔖
     @:/// </summary>
-    @:[ApiDescriptionSettings(Name = "@(column.FkEntityName)Tree"), HttpGet]
     @:[DisplayName("获取@(column.ColumnComment)选择数据")]
-    @:public async Task<List<@(column.FkEntityName)TreeOutput>> @(column.FkEntityName)Tree()
+    @:[ApiDescriptionSettings(Name = "@(column.FkEntityName)Tree"), HttpGet]
+    @:public async Task<List<@(column.FkEntityName)TreeOutput>> @(column.FkEntityName)Tree([FromQuery]bool all)
     @:{
-        displayColumnList = column.DisplayColumn.Split(",").Select(u => $"{{u.{u}}}").ToList();
-        @:return await _@(Model.LowerClassName)Rep.Context.Queryable<@(column.FkEntityName)>().Select(u => new @(column.FkEntityName)TreeOutput {
-            @:Label = $"@(string.Join("-", displayColumnList))",
+        var columnList = column.DisplayColumn.Split(",").Select(name => $"{{u.{name}}}").ToList();
+        @:return await _@(Model.LowerClassName)Rep.Context.Queryable<@column.FkEntityName>()
+          @:.InnerJoinIF<@Model.ClassName>(!all, (u, r) => u.@(column.ValueColumn) == r.@(column.PropertyName))
+          @:.Select(u => new @(column.FkEntityName)TreeOutput {
+            @:Label = $"@(string.Join("-", columnList))",
             @:Value = u.@column.ValueColumn
-        @:}, true).ToTreeAsync(u => u.Children, u => u.@(column.PidColumn), @(column.WhetherRequired == "Y" ? "0" : "null"));
+        @:}, true).ToTreeAsync(u => u.Children, u => u.@column.PidColumn, @(column.WhetherRequired == "Y" ? "0" : "null"));
     @:}
 }
 }
-@if (hasdictService) {
+@foreach (var column in Model.TableField.Where(u => u.EffectType == "Upload")) {
 @:
     @:/// <summary>
-    @:/// 获取字典文本列表 🔖
+    @:/// 上传@(column.ColumnComment) ⬆️
     @:/// </summary>
-    @:/// <param name="dictTypeCode"></param>
+    @:/// <param name="file"></param>
     @:/// <returns></returns>
-    @:[ApiDescriptionSettings, HttpGet]
-    @:[DisplayName("获取字典文本列表")]
-    @:private List<string> GetDictDataTextList(string dictTypeCode)
+    @:[DisplayName("上传@(column.ColumnComment)")]
+    @:[ApiDescriptionSettings(Name = "Upload@(column.PropertyName)"), HttpPost]
+    @:public async Task<SysFile> Upload@(column.PropertyName)([Required] IFormFile file)
     @:{
-    @:    return _sysDictTypeService.GetDataList(new GetDataDictTypeInput { Code = dictTypeCode }).Result?.Select(x => x.Value).ToList();
+        @:return await _sysFileService.UploadFile(new FileUploadInput { File = file, Path = "upload/@(Model.ClassName)/@(column.PropertyName)" }); 
     @:}
 }
-@if (importField?.Count() > 0) {
+@if (importFields.Count() > 0) {
 @:
     @:/// <summary>
     @:/// 下载@(Model.BusName)数据导入模板 ⬇️
@@ -304,7 +263,19 @@ if(column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("@(column
     @:[ApiDescriptionSettings(Name = "Import"), HttpGet, NonUnify]
     @:public IActionResult DownloadTemplate()
     @:{
+        var fieldsList = importFields.Where(u => u.EffectType == "fk" || u.EffectType == "ApiTreeSelect").ToList();
+        if (fieldsList.Any()) {
+        @:return ExcelHelper.ExportTemplate(new List<Export@(Model.ClassName)Output>(), "@(Model.BusName)导入模板", (_, info) =>
+        @:{
+            foreach (var column in fieldsList) {
+            var columnList = (column.EffectType == "fk" ? column.FkColumnName : column.DisplayColumn).Split(",").Select(n => $"{{u.{n}}}").ToList();
+            @:if (nameof(Export@(Model.ClassName)Output.@(column.PropertyName)Label) == info.Name) return _@(Model.LowerClassName)Rep.Context.Queryable<@(column.FkEntityName)>().Select(u => $"@(string.Join("-", columnList))").Distinct().ToList();
+            }
+            @:return null;
+        @:});
+        } else {
         @:return ExcelHelper.ExportTemplate(new List<Export@(Model.ClassName)Output>(), "@(Model.BusName)导入模板");
+        }
     @:}
 @:
     @:/// <summary>
@@ -317,47 +288,69 @@ if(column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("@(column
     @:{
         @:lock (this)
         @:{
+            var dictTableField = Model.TableField.Where(x => x.WhetherImport == "Y" && x.EffectType == "Select") ?? default;
             foreach (var column in dictTableField){
-            @:var @(@column.LowerPropertyName)DictMap = _sysDictTypeService.GetDataList(new GetDataDictTypeInput { Code = "@(@column.DictTypeCode)" }).Result.ToDictionary(x => x.Value, x => x.Code);
+            @:var @(column.LowerPropertyName)DictMap = _sysDictTypeService.GetDataList(new GetDataDictTypeInput { Code = "@(column.DictTypeCode)" }).Result.ToDictionary(x => x.Value, x => x.Code);
             }
-            @:var stream = ExcelHelper.ImportData<Import@(@Model.ClassName)Input, @(@Model.ClassName)>(file, (list, markerErrorAction) =>
+
+            @:var stream = ExcelHelper.ImportData<Import@(Model.ClassName)Input, @(Model.ClassName)>(file, (list, markerErrorAction) =>
             @:{
-                @:_@(@Model.LowerClassName)Rep.Context.Utilities.PageEach(list, 2048, pageItems =>
+                @:_@(Model.LowerClassName)Rep.Context.Utilities.PageEach(list, 2048, pageItems =>
                 @:{
+                    foreach (var column in importFields.Where(u => u.EffectType == "fk" || u.EffectType == "ApiTreeSelect")) {
+                    @:// 链接 @(column.ColumnComment)
+                    @:var @(column.LowerPropertyName)LabelList = pageItems.Where(x => x.@(column.PropertyName)Label != null).Select(x => x.@(column.PropertyName)Label).Distinct().ToList();
+                    @:if (@(column.LowerPropertyName)LabelList.Any()) {
+                        var valueColumn = column.EffectType == "fk" ? column.FkLinkColumnName : column.ValueColumn;
+                        var columnList = (column.EffectType == "fk" ? column.FkColumnName : column.DisplayColumn).Split(",").Select(n => $"{{u.{n}}}").ToList();
+                        @:var @(column.LowerPropertyName)LinkMap = _@(Model.LowerClassName)Rep.Context.Queryable<@(column.FkEntityName)>().Where(u => @(column.LowerPropertyName)LabelList.Contains($"@(string.Join("-", columnList))")).ToList().ToDictionary(u => $"@(string.Join("-", columnList))", u => u.@(valueColumn));
+                        @:pageItems.ForEach(e => e.@(column.PropertyName) = @(column.LowerPropertyName)LinkMap?.GetValueOrDefault(e.@(column.PropertyName)Label, default));
+                    @:}
+                    }
+
+                    @:
                     @:// 校验并过滤必填基本类型为null的字段
                     @:var rows = pageItems.Where(x => {
-                        foreach (var column in importField.Where(x => x.WhetherRequired == "Y" && Regex.IsMatch(x.NetType, "(int|long|double|float|bool|Enum[?]?)"))){
-                        @:if (x.@(@column.PropertyName) == null){
-                            @:x.Error = "@(@column.ColumnComment)不能为空";
+                        foreach (var column in importFields.Where(x => x.WhetherRequired == "Y" && Regex.IsMatch(x.NetType, "(int|long|double|float|bool|Enum[?]?)"))){
+                        @:if (x.@(column.PropertyName) == null){
+                            @:x.Error = "@(column.ColumnComment)不能为空";
                             @:return false;
                         @:}
                         }
                         @:return true;
-                    @:}).Adapt<List<@(@Model.ClassName)>>();
-                    if (hasdictService){
+                    @:}).Adapt<List<@(Model.ClassName)>>();
+
+                    if (dictTableField.Any()) {
+                    @:
                     @:// 映射字典值
-                    @:foreach(var row in rows){
+                    @:foreach(var row in rows) {
                         foreach (var column in dictTableField){
-                        @:row.@(@column.PropertyName) = @(@column.LowerPropertyName)DictMap.GetValueOrDefault(row.@(@column.PropertyName) ?? "");
+                        @:row.@(column.PropertyName) = @(column.LowerPropertyName)DictMap.GetValueOrDefault(row.@(column.PropertyName) ?? "");
                         }
                     @:}
                     }
-                    @:var storageable = _@(@Model.LowerClassName)Rep.Context.Storageable(rows)
-                        foreach (var column in importField){
-                        if (@column.WhetherRequired == "Y"){
+
+                    @:
+                    @:var storageable = _@(Model.LowerClassName)Rep.Context.Storageable(rows)
+                        foreach (var column in importFields){
+                        if (column.WhetherRequired == "Y"){
                         if(column.NetType.TrimEnd('?') == "string"){
-                        @:.SplitError(it => string.IsNullOrWhiteSpace(it.Item.@(@column.PropertyName)), "@(@column.ColumnComment)不能为空")
+                        @:.SplitError(it => string.IsNullOrWhiteSpace(it.Item.@(column.PropertyName)), "@(column.ColumnComment)不能为空")
                         } else if(column.NetType.EndsWith('?') == true){
-                        @:.SplitError(it => it.Item.@(@column.PropertyName) == null, "@(@column.ColumnComment)不能为空")
+                        @:.SplitError(it => it.Item.@(column.PropertyName) == null, "@(column.ColumnComment)不能为空")
                         }}
-                        if (@column.NetType?.TrimEnd('?') == "string"){
-                        @:.SplitError(it => it.Item.@(@column.PropertyName)?.Length > @column.ColumnLength, "@(@column.ColumnComment)长度不能超过@(@column.ColumnLength)个字符")
+                        if (column.NetType?.TrimEnd('?') == "string"){
+                        @:.SplitError(it => it.Item.@(column.PropertyName)?.Length > @(column.ColumnLength), "@(column.ColumnComment)长度不能超过@(column.ColumnLength)个字符")
                         }}
+                        @:.SplitError(it => it.Any(), "记录已存在")
+                        @:.SplitInsert(_ => true)
                         @:.ToStorage();
 
+                    @:
                     @:storageable.BulkCopy();
                     @:storageable.BulkUpdate();
-                    
+                    @:
+                    @:// 标记错误信息
                     @:markerErrorAction.Invoke(storageable, pageItems, rows);
                 @:});
             @:});

+ 61 - 97
Admin.NET/Admin.NET.Web.Entry/wwwroot/template/editDialog.vue.vm

@@ -1,4 +1,6 @@
 @{
+	bool IsStatusEnum(dynamic column) => column.NetType == "StatusEnum" && column.PropertyName == "Status";
+	string LowerFirstLetter(string text) => text.ToString()[..1].ToLower() + text[1..];
 	var definedObjects = new Dictionary<string, int>();
 	var pkField = Model.TableField.Where(c => c.ColumnKey == "True").FirstOrDefault();
 	var pkFieldName = LowerFirstLetter(pkField.PropertyName);
@@ -23,10 +25,11 @@
 					}else{
 					if (column.WhetherAddUpdate == "Y"){
 					if(column.EffectType == "fk"){
+					var dropdownName = $"{column.FkEntityName}{Regex.Replace(column.PropertyName, "[iI]d$", "")}Dropdown";
 					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
 						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
 							@:<el-select clearable filterable v-model="ruleForm.@(column.LowerPropertyName)" placeholder="请选择@(column.ColumnComment)">
-								@:<el-option v-for="(item,index) in @LowerFirstLetter(@column.FkEntityName)@(column.PropertyName)DropdownList" :key="index" :value="item.value" :label="item.label" />
+								@:<el-option v-for="(item,index) in @LowerFirstLetter(dropdownName)List" :key="index" :value="item.value" :label="item.label" />
 							</el-select>
 						</el-form-item>
 					</el-col>
@@ -52,73 +55,73 @@
 					}else if(column.EffectType == "Input"){
 					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
 						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
-							@:<el-input v-model="ruleForm.@(column.LowerPropertyName)" placeholder="请输入@(column.ColumnComment)" maxlength="@(@column.ColumnLength)" show-word-limit clearable />
+							@:<el-input v-model="ruleForm.@(column.LowerPropertyName)" placeholder="请输入@(column.ColumnComment)" maxlength="@(column.ColumnLength)" show-word-limit clearable />
 						</el-form-item>
 					</el-col>
 					}else if(column.EffectType == "InputNumber"){
 					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
-							@:<el-input-number v-model="ruleForm.@(@column.LowerPropertyName)" placeholder="请输入@(@column.ColumnComment)" clearable />
+						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
+							@:<el-input-number v-model="ruleForm.@(column.LowerPropertyName)" placeholder="请输入@(column.ColumnComment)" clearable />
 						</el-form-item>
 					</el-col>
 					}else if(column.EffectType == "InputTextArea"){
 					@:<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
-							@:<el-input v-model="ruleForm.@(@column.LowerPropertyName)" placeholder="请输入@(@column.ColumnComment)" type="textarea" maxlength="@(@column.ColumnLength)" show-word-limit clearable />
+						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
+							@:<el-input v-model="ruleForm.@(column.LowerPropertyName)" placeholder="请输入@(column.ColumnComment)" type="textarea" maxlength="@(column.ColumnLength)" show-word-limit clearable />
 						</el-form-item>
 					</el-col>
 					}else if(column.EffectType == "Select"){
 					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
-							@:<el-select clearable v-model="ruleForm.@(@column.LowerPropertyName)" placeholder="请选择@(@column.ColumnComment)">
-								@:<el-option v-for="(item,index) in dl('@(@column.DictTypeCode)')"  :key="index" :value="@(@column.NetType.StartsWith("string") ? "item.code" : "Number(item.code)")" :label="`[${item.code}] ${item.value}`"></el-option>
+						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
+							@:<el-select clearable v-model="ruleForm.@(column.LowerPropertyName)" placeholder="请选择@(column.ColumnComment)">
+								@:<el-option v-for="(item,index) in dl('@(column.DictTypeCode)')"  :key="index" :value="@(column.NetType.StartsWith("string") ? "item.code" : "Number(item.code)")" :label="`[${item.code}] ${item.value}`"></el-option>
 							</el-select>
 						</el-form-item>
 					</el-col>
 					}else if(column.EffectType == "ConstSelector"){
 					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
-							@:<el-select clearable v-model="ruleForm.@(@column.LowerPropertyName)" placeholder="请选择@(@column.ColumnComment)">
-								@:<el-option v-for="(item,index) in getConstType('@column.DictTypeCode')" :key="index" :label="item.name" :value="@(@column.NetType.StartsWith("string") ? "item.code" : "Number(item.code)")">{{ item.name }}</el-option>
+						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
+							@:<el-select clearable v-model="ruleForm.@(column.LowerPropertyName)" placeholder="请选择@(column.ColumnComment)">
+								@:<el-option v-for="(item,index) in getConstType('@column.DictTypeCode')" :key="index" :label="item.name" :value="@(column.NetType.StartsWith("string") ? "item.code" : "Number(item.code)")">{{ item.name }}</el-option>
 							</el-select>
 						</el-form-item>
 					</el-col>
 					}else if(column.EffectType == "Switch"){
 					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
-							@:<el-switch v-model="ruleForm.@(@column.LowerPropertyName)" active-text="是" inactive-text="否" />
+						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
+							@:<el-switch v-model="ruleForm.@(column.LowerPropertyName)" active-text="是" inactive-text="否" />
 						</el-form-item>
 					</el-col>
 					}else if(column.EffectType == "DatePicker"){
 					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
-							@:<el-date-picker v-model="ruleForm.@(@column.LowerPropertyName)" type="date" placeholder="@(@column.ColumnComment)" />
+						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
+							@:<el-date-picker v-model="ruleForm.@(column.LowerPropertyName)" type="date" placeholder="@(column.ColumnComment)" />
 						</el-form-item>
 					</el-col>
 					}else if(column.EffectType == "Upload"){
 					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
-						@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
+						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
 							@:<el-upload
 							@:list-type="picture-card"
 							@::show-file-list="false"
-							@::http-request="upload@(@column.PropertyName)Handle">
+							@::http-request="upload@(column.PropertyName)Handle">
 								@:<img
-								@:v-if="ruleForm.@(@column.LowerPropertyName)"
-								@::src="ruleForm.@(@column.LowerPropertyName)"
-								@:@@click="ruleForm.@(@column.LowerPropertyName)=''"
+								@:v-if="ruleForm.@(column.LowerPropertyName)"
+								@::src="ruleForm.@(column.LowerPropertyName)"
+								@:@@click="ruleForm.@(column.LowerPropertyName)=''"
 								@:style="width: 100%; height: 100%; object-fit: contain"/>
 								@:<el-icon v-else><Plus /></el-icon>
 							</el-upload>
 						</el-form-item>
 					</el-col>
 					}else if(column.EffectType == "EnumSelector"){
-					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" @(@column.NetType == "StatusEnum" && @column.PropertyName == "Status" ? $"v-if=\"!ruleForm.{pkFieldName}\"" : "") >
-						@:<el-form-item label="@column.ColumnComment" prop="@(@column.LowerPropertyName)">
+					@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" @(IsStatusEnum(column) ? $"v-if='!ruleForm.{pkFieldName}'" : "") >
+						@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
 							if (@column.NetType == "StatusEnum" && @column.PropertyName == "Status") {
 							@:<el-switch v-model="ruleForm.@column.LowerPropertyName" :active-value="1" :inactive-value="2" size="small" />
 							} else {
-							@:<el-select clearable v-model="ruleForm.@(@column.LowerPropertyName)" placeholder="请选择@(@column.ColumnComment)">
-								@:<el-option v-for="(item,index) in dl('@(@column.DictTypeCode)')" :key="index" :value="@(@column.NetType.StartsWith("string") ? "item.value" : "Number(item.value)")" :label="`${item.name} (${item.code}) [${item.value}]`"></el-option>
+							@:<el-select clearable v-model="ruleForm.@(column.LowerPropertyName)" placeholder="请选择@(column.ColumnComment)">
+								@:<el-option v-for="(item, index) in dl('@(column.DictTypeCode)')" :key="index" :value="@(column.NetType.TrimEnd('?').StartsWith("string") ? "item.value" : "Number(item.value)")" :label="`${item.name} (${item.code}-${item.value})`"></el-option>
 							@:</el-select>
 							}
 						</el-form-item>
@@ -144,7 +147,7 @@
 	width: 100%;
 }
 </style>
-<script lang="ts" setup>
+<script lang="ts" setup name="@(Model.LowerClassName)">
 	import { ref,onMounted } from "vue";
 	import { ElMessage } from "element-plus";
 	import type { FormRules } from "element-plus";
@@ -163,43 +166,34 @@
 @if(Model.TableField.Any(x=>x.EffectType == "Upload")){
     @:import { Plus } from "@@element-plus/icons-vue";
     @:import { UploadRequestOptions } from "element-plus";
-    @:import {@string.Join(",",Model.TableField.Where(x=>x.EffectType == "Upload").Select(x=>"upload"+x.PropertyName).ToList())} from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
-}
-	import { add@(@Model.ClassName), update@(@Model.ClassName), detail@(@Model.ClassName) } from "/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)";
-@foreach (var column in Model.TableField){
-	if(column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("import__@(@column.FkEntityName)Tree")){
-	@{definedObjects.Add("import__@(@column.FkEntityName)Tree", 1);}
-	@:import { get@(@column.FkEntityName)Tree } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
-	}
-	if(column.EffectType == "fk" && @column.WhetherAddUpdate == "Y"){
-	@:import { get@(@column.FkEntityName)@(@column.PropertyName)Dropdown } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
-	}
 }
 @if(Model.TableField.Any(x=>x.EffectType == "EnumSelector")){
 	@:import { getAPI } from '/@@/utils/axios-utils';
 	@:import { SysEnumApi } from '/@@/api-services/api';
 }
+	import { use@(Model.ClassName)Api } from '/@@/api/@(Model.PagePath)/@(Model.LowerClassName)';
 
 	//父级传递来的参数
 	var props = defineProps({
 		title: {
-		type: String,
-		default: "",
-	},
+			type: String,
+			default: "",
+		},
 	});
 	//父级传递来的函数,用于回调
 	const emit = defineEmits(["reloadTable"]);
 	const ruleFormRef = ref();
 	const isShowDialog = ref(false);
 	const ruleForm = ref<any>({});
+	const @(Model.LowerClassName)Api = use@(Model.ClassName)Api();
 	//自行添加其他规则
 	const rules = ref<FormRules>({
 @foreach (var column in Model.TableField){
 	if(column.WhetherRequired == "Y"){
 		if(column.EffectType == "Input" || @column.EffectType == "InputNumber" || @column.EffectType == "InputTextArea"){
-		@:@column.LowerPropertyName: [{required: true, message: '请输入@(@column.ColumnComment)!', trigger: 'blur',},],
+		@:@column.LowerPropertyName: [{required: true, message: '请输入@(column.ColumnComment)!', trigger: 'blur',},],
 		}else if(column.EffectType == "DatePicker" || @column.EffectType == "Select" || @column.EffectType == "EnumSelector" || @column.EffectType == "ApiTreeSelect"){
-		@:@column.LowerPropertyName: [{required: true, message: '请选择@(@column.ColumnComment)!', trigger: 'change',},],
+		@:@column.LowerPropertyName: [{required: true, message: '请选择@(column.ColumnComment)!', trigger: 'change',},],
 		}
     }
 }
@@ -216,7 +210,7 @@
 		// 改用detail获取最新数据来编辑
 		let rowData = JSON.parse(JSON.stringify(row));
 		if (rowData.id)
-			ruleForm.value = (await detail@(@Model.ClassName)(rowData.id)).data.result;
+			ruleForm.value = (await @(Model.LowerClassName)Api.detail(rowData.id)).data.result;
 		else
 			ruleForm.value = rowData;
 		isShowDialog.value = true;
@@ -238,10 +232,10 @@
 		ruleFormRef.value.validate(async (isValid: boolean, fields?: any) => {
 			if (isValid) {
 				let values = ruleForm.value;
-				if (ruleForm.value.@(@pkFieldName) == undefined || ruleForm.value.@(@pkFieldName) == null || ruleForm.value.@(@pkFieldName) == "" || ruleForm.value.@(@pkFieldName) == 0) {
-					await add@(@Model.ClassName)(values);
+				if (ruleForm.value.@(pkFieldName) == undefined || ruleForm.value.@(pkFieldName) == null || ruleForm.value.@(pkFieldName) == "" || ruleForm.value.@(pkFieldName) == 0) {
+					await @(Model.LowerClassName)Api.add(values);
 				} else {
-					await update@(@Model.ClassName)(values);
+					await @(Model.LowerClassName)Api.update(values);
 				}
 				closeDialog();
 			} else {
@@ -252,63 +246,33 @@
 			}
 		});
 	};
-
-	@foreach (var column in Model.TableField){
-	if(column.EffectType == "fk" && @column.WhetherAddUpdate == "Y"){
-	@:const @LowerFirstLetter(@column.FkEntityName)@(@column.PropertyName)DropdownList = ref<any>([]); 
-	@:const get@(@column.FkEntityName)@(@column.PropertyName)DropdownList = async () => {
-		@:let list = await get@(@column.FkEntityName)@(@column.PropertyName)Dropdown();
-		@:@LowerFirstLetter(@column.FkEntityName)@(@column.PropertyName)DropdownList.value = list.data.result ?? [];
+	
+	@foreach (var column in Model.TableField) {
+	if (column.EffectType == "fk" && column.WhetherAddUpdate == "Y") {
+		var dropdownName = $"{column.FkEntityName}{Regex.Replace(column.PropertyName, "[iI]d$", "")}Dropdown";
+	@:const @LowerFirstLetter(dropdownName)List = ref<any>([]);
+	@:const get@(dropdownName)List = async () => {
+		@:const list = await @(Model.LowerClassName)Api.get@(dropdownName)(true);
+		@:@LowerFirstLetter(dropdownName)List.value = list.data.result ?? [];
 	@:};
-	@:get@(@column.FkEntityName)@(@column.PropertyName)DropdownList();
+	@:get@(dropdownName)List();
 	@:
-	}
-	}
-
-	@foreach (var column in Model.TableField){
-	if(column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("define_get@(@column.FkEntityName)TreeData")){
-		@{definedObjects.Add("define_get@(@column.FkEntityName)TreeData", 1);}
-	@:const @LowerFirstLetter(@column.FkEntityName)TreeData = ref<any>([]); 
-	@:const get@(@column.FkEntityName)TreeData = async () => {
-		@:let list = await get@(@column.FkEntityName)Tree();
+	} else if (column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("define_get@(column.FkEntityName)TreeData")) {
+		definedObjects.Add("define_get@(column.FkEntityName)TreeData", 1);
+	@:const @LowerFirstLetter(@column.FkEntityName)TreeData = ref<any>([]);
+	@:const get@(column.FkEntityName)TreeData = async () => {
+		@:const list = await @(Model.LowerClassName)Api.get@(column.FkEntityName)Tree(true);
 		@:@LowerFirstLetter(@column.FkEntityName)TreeData.value = list.data.result ?? [];
 	@:};
-	@:get@(@column.FkEntityName)TreeData();
-	@:
-	}
-	else if(column.EffectType == "ConstSelector"){   
-	@:const @LowerFirstLetter(@column.DictTypeCode)DropdownList = ref<any>([]); 
-	@:const get@(@column.DictTypeCode)DropdownList = async () => {
-		@:let list = await getConstSelectorList("@column.DictTypeCode");
-		@:@LowerFirstLetter(@column.DictTypeCode)DropdownList.value = list.data.result ?? [];
-	@:};
-	@:get@(@column.DictTypeCode)DropdownList();
+	@:get@(column.FkEntityName)TreeData();
 	@:
-	}
-	}
-
-
-
-	@foreach (var column in Model.TableField){ 
-	if(column.WhetherAddUpdate=="N") continue;
-	if(column.EffectType == "Upload"){
-	@:const upload@(@column.PropertyName)Handle = async (options: UploadRequestOptions) => {
-		@:const res = await upload@(@column.PropertyName)(options);
+	} else if (column.WhetherAddUpdate == "Y" && column.EffectType == "Upload") {
+	@:const upload@(column.PropertyName)Handle = async (options: UploadRequestOptions) => {
+		@:const res = await @(Model.LowerClassName)Api.upload@(column.PropertyName)(options);
 		@:ruleForm.value.@(column.LowerPropertyName) = res.data.result?.url;
 	@:};
-		}
-	}
-
+	@:
+	}}
 	//将属性或者函数暴露给父组件
 	defineExpose({ openDialog });
-</script>
-
-
-
-
-@{
-string LowerFirstLetter(string text)
-{
-return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写
-}
-}
+</script>

+ 55 - 59
Admin.NET/Admin.NET.Web.Entry/wwwroot/template/index.vue.vm

@@ -1,4 +1,6 @@
 @{
+  string LowerFirstLetter(string text) => text.ToString()[..1].ToLower() + text[1..];
+
   var pkField = Model.TableField.Where(c => c.ColumnKey == "True").FirstOrDefault();
 
   string pkFieldName = pkField != null && !string.IsNullOrEmpty(pkField.PropertyName) ? LowerFirstLetter(pkField.PropertyName) : null;
@@ -56,7 +58,7 @@
             }else if(column.EffectType == "EnumSelector"){
             @:<el-form-item label="@column.ColumnComment">
               @:<el-select clearable="" v-model="queryParams.@(column.LowerPropertyName)" placeholder="请选择@(column.ColumnComment)">
-                @:<el-option v-for="(item,index) in dl('@(column.DictTypeCode)')" :key="index" :value="item.value" :label="`${item.name} (${item.code}) [${item.value}] `" />
+                @:<el-option v-for="(item,index) in dl('@(column.DictTypeCode)')" :key="index" :value="item.value" :label="`${item.name} (${item.code}-${item.value})`" />
               @:</el-select>
             @:</el-form-item>
             }else if(column.EffectType == "ApiTreeSelect"){
@@ -138,7 +140,7 @@
             @:fit="scale-down"
             @:preview-teleported=""/>
             }else if(column.EffectType == "fk"){
-            @:<span>{{scope.row.@LowerFirstLetter(@column.PropertyName)@(column.FkColumnName)}}</span>
+            @:<span>{{scope.row.@LowerFirstLetter(@column.PropertyName)FkColumn}}</span>
             }else if(column.EffectType == "ApiTreeSelect"){
             @:<span>{{scope.row.@LowerFirstLetter(@column.PropertyName)Display}}</span>
             }else if(column.EffectType == "Switch"){
@@ -170,13 +172,18 @@
         @:<el-table-column prop="@column.LowerPropertyName" label="@column.ColumnComment" @(column.WhetherSortable == "Y" ? "sortable='custom'" : "") show-overflow-tooltip="" />
         }
         }
-        <el-table-column label="操作" width="@(Model.PrintType == "custom" ? "200" : "140")" align="center" fixed="right" show-overflow-tooltip="" v-if="auth('@(@Model.LowerClassName):update') || auth('@(@Model.LowerClassName):delete')">
+        <el-table-column label="修改记录" width="100" align="center" show-overflow-tooltip>
+          <template #default="scope">
+            <ModifyRecord :data="scope.row" />
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="@(Model.PrintType == "custom" ? "200" : "140")" align="center" fixed="right" show-overflow-tooltip="" v-if="auth('@(Model.LowerClassName):update') || auth('@(Model.LowerClassName):delete')">
           <template #default="scope">
             @if (Model.PrintType == "custom"){
-            @:<el-button icon="ele-Printer" size="small" text="" type="primary" @@click="openPrint@(@Model.ClassName)(scope.row)" v-auth="'@(@Model.LowerClassName):print'"> 打印 </el-button>
+            @:<el-button icon="ele-Printer" size="small" text="" type="primary" @@click="openPrint@(Model.ClassName)(scope.row)" v-auth="'@(Model.LowerClassName):print'"> 打印 </el-button>
             }
-            <el-button icon="ele-Edit" size="small" text="" type="primary" @@click="openEdit@(@Model.ClassName)(scope.row)" v-auth="'@(@Model.LowerClassName):update'"> 编辑 </el-button>
-            <el-button icon="ele-Delete" size="small" text="" type="primary" @@click="del@(@Model.ClassName)(scope.row)" v-auth="'@(@Model.LowerClassName):delete'"> 删除 </el-button>
+            <el-button icon="ele-Edit" size="small" text="" type="primary" @@click="openEdit@(Model.ClassName)(scope.row)" v-auth="'@(Model.LowerClassName):update'"> 编辑 </el-button>
+            <el-button icon="ele-Delete" size="small" text="" type="primary" @@click="del@(Model.ClassName)(scope.row)" v-auth="'@(Model.LowerClassName):delete'"> 删除 </el-button>
           </template>
         </el-table-column>
       </el-table>
@@ -193,11 +200,11 @@
 	/>
       <printDialog
         ref="printDialogRef"
-        :title="print@(@Model.ClassName)Title"
+        :title="print@(Model.ClassName)Title"
         @@reloadTable="handleQuery" />
       <editDialog
         ref="editDialogRef"
-        :title="edit@(@Model.ClassName)Title"
+        :title="edit@(Model.ClassName)Title"
         @@reloadTable="handleQuery"
       />
     </el-card>
@@ -205,15 +212,15 @@
   @if (hasImport) {
   @:<ImportData
         @:ref="importDataRef"
-        @::import="import@(@Model.ClassName)Data"
-        @::download="download@(@Model.ClassName)Template"
-        @:v-auth="'@(@Model.LowerClassName):import'"
+        @::import="@(Model.LowerClassName)Api.importData"
+        @::download="@(Model.LowerClassName)Api.downloadTemplate"
+        @:v-auth="'@(Model.LowerClassName):import'"
         @:@@refresh="handleQuery"
         @:/>
   }
 </template>
 
-<script lang="ts" setup="" name="@(@Model.LowerClassName)">
+<script lang="ts" setup name="@(Model.LowerClassName)">
   import { ref } from "vue";
   import { auth } from '/@@/utils/authFunction';
   import { getAPI } from '/@@/utils/axios-utils';
@@ -239,17 +246,13 @@
   @if(hasImport) {
   @:import ImportData from "/@@/components/table/importData.vue";
   }
-  import editDialog from '/@@/views/@(@Model.PagePath)/@(@Model.LowerClassName)/component/editDialog.vue'
+  import editDialog from '/@@/views/@(Model.PagePath)/@(Model.LowerClassName)/component/editDialog.vue'
   import printDialog from '/@@/views/system/print/component/hiprint/preview.vue'
-  import { page@(@Model.ClassName), delete@(@Model.ClassName), batchDelete@(@Model.ClassName) @(hasImport ? $", download{@Model.ClassName}Template, import{@Model.ClassName}Data" : "")@(hasSetStatus ? $", set{@Model.ClassName}Status" : "") } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
-  @foreach (var column in Model.QueryWhetherList.Where(u => u.EffectType == "fk")) {
-  @:import { get@(@column.FkEntityName)@(@column.PropertyName)Dropdown } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
-  }
-  @foreach (var column in Model.QueryWhetherList.Where(u => u.EffectType == "ApiTreeSelect")) {
-  @:import { get@(column.FkEntityName)Tree } from '/@@/api/@(@Model.PagePath)/@(@Model.LowerClassName)';
-  }
-
-  const showAdvanceQueryUI = ref(@(@haveLikeCdt ? "false" : "true"));
+  import ModifyRecord from '/@@/components/table/modifyRecord.vue';
+  import { use@(Model.ClassName)Api} from '/@@/api/@(Model.PagePath)/@(Model.LowerClassName)';
+  
+  const showAdvanceQueryUI = ref(@(haveLikeCdt ? "false" : "true"));
+  const @(Model.LowerClassName)Api = use@(Model.ClassName)Api();
   const printDialogRef = ref();
   const editDialogRef = ref();
   @if (hasImport){
@@ -268,8 +271,8 @@
     total: 0 as any,
   });
 
-  const print@(@Model.ClassName)Title = ref("");
-  const edit@(@Model.ClassName)Title = ref("")
+  const print@(Model.ClassName)Title = ref("");
+  const edit@(Model.ClassName)Title = ref("")
 
   // 改变高级查询的控件显示状态
   const changeAdvanceQueryUI = () => {
@@ -279,7 +282,7 @@
   // 查询操作
   const handleQuery = async () => {
     loading.value = true;
-    var res = await page@(@Model.ClassName)(Object.assign(queryParams.value, tableParams.value));
+    var res = await @(Model.LowerClassName)Api.page(Object.assign(queryParams.value, tableParams.value));
     tableData.value = res.data.result?.items ?? [];
     tableParams.value.total = res.data.result?.total;
     loading.value = false;
@@ -293,8 +296,8 @@
   };
 
   // 打开新增页面
-  const openAdd@(@Model.ClassName) = () => {
-    edit@(@Model.ClassName)Title.value = '添加@(@Model.BusName)';
+  const openAdd@(Model.ClassName) = () => {
+    edit@(Model.ClassName)Title.value = '添加@(Model.BusName)';
     const data = {
 @if (hasSetStatus) {
         @:status: 1,
@@ -307,18 +310,18 @@
   };
 
   // 设置状态
-  const change@(@Model.ClassName)Status = async (row: any) => {
-    await set@(@Model.ClassName)Status({ id: row.id, status: row.status })
+  const change@(Model.ClassName)Status = async (row: any) => {
+    await @(Model.LowerClassName)Api.setStatus({ id: row.id, status: row.status })
             .then(() => ElMessage.success('状态设置成功'))
             .catch(() => { row.status = row.status == 1 ? 2 : 1; });
   };
   
   // 打开打印页面
-  const openPrint@(@Model.ClassName) = async (row: any) => {
-    print@(@Model.ClassName)Title.value = '打印@(@Model.BusName)';
+  const openPrint@(Model.ClassName) = async (row: any) => {
+    print@(Model.ClassName)Title.value = '打印@(Model.BusName)';
     @if(Model.PrintType == "custom"){
     @:var res = await getAPI(SysPrintApi).apiSysPrintPrintNameGet('@Model.PrintName');
-	  @:var printTemplate = res.data.result as SysPrint;
+	@:var printTemplate = res.data.result as SysPrint;
     @:var template = JSON.parse(printTemplate.template);
     @:row['printDate'] = formatDate(new Date(), 'YYYY-mm-dd HH:MM:SS')
     @:printDialogRef.value.showDialog(new hiprint.PrintTemplate({template: template}), row, template.panels[0].width);
@@ -326,20 +329,20 @@
   }
   
   // 打开编辑页面
-  const openEdit@(@Model.ClassName) = (row: any) => {
-    edit@(@Model.ClassName)Title.value = '编辑@(@Model.BusName)';
+  const openEdit@(Model.ClassName) = (row: any) => {
+    edit@(Model.ClassName)Title.value = '编辑@(Model.BusName)';
     editDialogRef.value.openDialog(row);
   };
 
   // 删除
-  const del@(@Model.ClassName) = (row: any) => {
+  const del@(Model.ClassName) = (row: any) => {
     ElMessageBox.confirm(`确定要删除吗?`, "提示", {
     confirmButtonText: "确定",
     cancelButtonText: "取消",
     type: "warning",
   })
   .then(async () => {
-    await delete@(@Model.ClassName)(row);
+    await @(Model.LowerClassName)Api.delete(row);
     handleQuery();
     ElMessage.success("删除成功");
   })
@@ -347,14 +350,14 @@
   };
 
   // 批量删除
-  const batchDel@(@Model.ClassName) = () => {
+  const batchDel@(Model.ClassName) = () => {
     ElMessageBox.confirm(`确定要删除${selectData.value.length}条记录吗?`, "提示", {
       confirmButtonText: "确定",
       cancelButtonText: "取消",
       type: "warning",
     }).then(async () => {
       @foreach (var column in @Model.TableField.Where(u => u.ColumnKey == "True")) {
-      @:const count = await batchDelete@(@Model.ClassName)({ @(@column.LowerPropertyName)List: selectData.value.map(u => u.@(@column.LowerPropertyName)) });
+      @:const count = await @(Model.LowerClassName)Api.batchDelete({ @(column.LowerPropertyName)List: selectData.value.map(u => u.@(column.LowerPropertyName)) });
       }
       handleQuery();
       ElMessage.success(`成功批量删除${count}条记录`);
@@ -374,37 +377,30 @@
   };
 
 @foreach (var column in Model.TableField) {
-  if (column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("define_get@(@column.FkEntityName)TreeData")) {
-  definedObjects.Add("define_get@(@column.FkEntityName)TreeData", 1);
+  if (column.EffectType == "fk") {
+    var dropdownName = $"{column.FkEntityName}{Regex.Replace(column.PropertyName, "[iI]d$", "")}Dropdown";
+  @:const @LowerFirstLetter(dropdownName)List = ref<any>([]);
+  @:const get@(dropdownName)List = async () => {
+    @:let list = await @(Model.LowerClassName)Api.get@(dropdownName)();
+    @:@(LowerFirstLetter(dropdownName))List.value = list.data.result ?? [];
+  @:};
+  @:get@(dropdownName)List();
+  @:
+  } else if (column.EffectType == "ApiTreeSelect" && !definedObjects.ContainsKey("define_get@(column.FkEntityName)TreeData")) {
+  definedObjects.Add("define_get@(column.FkEntityName)TreeData", 1);
   @:const @LowerFirstLetter(@column.FkEntityName)TreeData = ref<any>([]);
   @:const get@(column.FkEntityName)TreeData = async () => {
-    @:let list = await get@(column.FkEntityName)Tree();
+    @:let list = await @(Model.LowerClassName)Api.get@(column.FkEntityName)Tree();
     @:@LowerFirstLetter(@column.FkEntityName)TreeData.value = list.data.result ?? [];
   @:};
   @:get@(column.FkEntityName)TreeData();
   @:
-  } else if (column.EffectType == "fk") {
-  @:const @LowerFirstLetter(@column.FkEntityName)@(@column.PropertyName)DropdownList = ref<any>([]);
-  @:const get@(@column.FkEntityName)@(@column.PropertyName)DropdownList = async () => {
-    @:let list = await get@(@column.FkEntityName)@(@column.PropertyName)Dropdown();
-    @:@LowerFirstLetter(@column.FkEntityName)@(@column.PropertyName)DropdownList.value = list.data.result ?? [];
-  @:};
-  @:get@(@column.FkEntityName)@(@column.PropertyName)DropdownList();
-  @:
-  }
+  } 
 }
-
   handleQuery();
 </script>
 <style scoped>
 :deep(.el-input), :deep(.el-select), :deep(.el-input-number) {
   width: 100%;
 }
-</style>
-
-@{
-string LowerFirstLetter(string text)
-{
-return text.ToString()[..1].ToLower() + text[1..]; // 首字母小写
-}
-}
+</style>

+ 69 - 0
Web/src/api/base/index.ts

@@ -0,0 +1,69 @@
+import {uploadFile} from '/@/api/system/uploadFile';
+import request from '/@/utils/request';
+
+// 接口基类
+export const useBaseApi = (module: string) => {
+    const baseUrl = `/api/${module}/`;
+    return {
+        request,
+        uploadFile,
+        baseUrl: baseUrl,
+        list: (params: any) => request({
+            url: baseUrl + "list",
+            method: 'get',
+            params,
+        }),
+        page: (data: any) => request({
+            url: baseUrl + "page",
+            method: 'post',
+            data,
+        }),
+        detail: (id: any) => request({
+            url: baseUrl + "detail",
+            method: 'get',
+            data: { id },
+        }),
+        add: (data: any) => request({
+            url: baseUrl + 'add',
+            method: 'post',
+            data
+        }),
+        update: (data: any) => request({
+            url: baseUrl + 'update',
+            method: 'post',
+            data
+        }),
+        setStatus: (data: any) => request({
+            url: baseUrl + 'setStatus',
+            method: 'post',
+            data
+        }),
+        delete: (data: any) => request({
+            url: baseUrl + 'delete',
+            method: 'post',
+            data
+        }),
+        batchDelete: (data: any) => request({
+            url: baseUrl + 'batchDelete',
+            method: 'post',
+            data
+        }),
+        downloadTemplate: () => request({
+            responseType: 'arraybuffer',
+            url: baseUrl + 'import',
+            method: 'get',
+        }),
+        importData: (file: any) => {
+            const formData = new FormData();
+	        formData.append('file', file);
+            return request({
+                headers: { 'Content-Type': 'multipart/form-data;charset=UTF-8' },
+                responseType: 'arraybuffer',
+                url: baseUrl + 'import',
+                method: 'post',
+                data: formData,
+            });
+        },
+        
+    }
+}

+ 8 - 6
Web/src/components/table/importData.vue

@@ -1,6 +1,6 @@
-<template v-loading="state.loading">
-	<div class="sys-import-data-container" v-loading="state.loading">
-		<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="300px" v-loading="state.loading">
+<template>
+	<div class="sys-import-data-container">
+		<el-dialog v-model="state.isShowDialog" draggable :close-on-click-modal="false" width="300px">
 			<template #header>
 				<div style="color: #fff">
 					<el-icon size="16" style="margin-right: 3px; display: inline; vertical-align: middle"> <ele-UploadFilled /> </el-icon>
@@ -21,7 +21,7 @@
 						ref="uploadRef"
 					>
 						<template #trigger>
-							<el-button type="primary" icon="ele-MostlyCloudy">导入</el-button>
+							<el-button type="primary" icon="ele-MostlyCloudy" :disable="state.loading">导入</el-button>
 						</template>
 					</el-upload>
 				</el-col>
@@ -73,11 +73,13 @@ const handleImportData = (opt: UploadRequestOptions) => {
       handleFileStream(res);
 	  state.isShowDialog = false;
       emit('refresh');
-    } catch (err) {
+    } catch {
       ElMessage.error(res.data.message || '上传失败');
     }
+	state.loading = false;
+  }).catch(() => {
+	state.loading = false;
   }).finally(() => {
-    state.loading = false;
     uploadRef.value?.clearFiles();
   });
 }

+ 9 - 7
Web/src/views/system/codeGen/component/fkDialog.vue

@@ -10,28 +10,28 @@
 			<el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto">
 				<el-row :gutter="35">
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="库定位器" prop="configId">
+						<el-form-item label="库定位器" prop="configId" :rules="[{ required: true, message: '库不能为空', trigger: 'blur' }]">
 							<el-select clearable v-model="state.ruleForm.configId" placeholder="库名" filterable @change="DbChanged()" class="w100">
 								<el-option v-for="item in state.dbData" :key="item.configId" :label="item.configId" :value="item.configId" />
 							</el-select>
 						</el-form-item>
 					</el-col>
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="数据库表" prop="tableName">
+						<el-form-item label="数据库表" prop="tableName" :rules="[{ required: true, message: '数据表不能为空', trigger: 'blur' }]">
 							<el-select v-model="state.ruleForm.tableName" filterable clearable @change="TableChanged()" class="w100">
 								<el-option v-for="item in state.tableData" :key="item.entityName" :label="item.entityName + ' ( ' + item.tableName + ' )[' + item.tableComment + ']'" :value="item.tableName" />
 							</el-select>
 						</el-form-item>
 					</el-col>
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="显示字段" prop="columnName">
-							<el-select v-model="state.ruleForm.columnName" class="w100">
+						<el-form-item label="显示字段" prop="columnNames" :rules="[{ required: true, message: '显示字段不能为空', trigger: 'blur' }]">
+							<el-select v-model="state.ruleForm.columnNames" multiple class="w100">
 								<el-option v-for="item in state.columnData" :key="item.columnName" :label="item.columnName + ' [' + item.columnComment + ']'" :value="item.columnName" />
 							</el-select>
 						</el-form-item>
 					</el-col>
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
-						<el-form-item label="链接字段" prop="linkColumnName">
+						<el-form-item label="链接字段" prop="linkColumnName" :rules="[{ required: true, message: '链接字段不能为空', trigger: 'blur' }]">
 							<el-select v-model="state.ruleForm.linkColumnName" class="w100">
 								<el-option v-for="item in state.columnData" :key="item.columnName" :label="item.columnName + ' [' + item.columnComment + ']'" :value="item.columnName" />
 							</el-select>
@@ -109,7 +109,7 @@ const openDialog = async (row: any) => {
 	if (rowdata.fkConfigId) {
 		await getDbList();
 		state.ruleForm.tableName = rowdata.fkTableName;
-		state.ruleForm.columnName = rowdata.fkColumnName;
+		state.ruleForm.columnNames = rowdata.fkColumnName?.split(",");
 		state.ruleForm.linkColumnName = rowdata.fkLinkColumnName;
 		state.ruleForm.configId = rowdata.fkConfigId;
 		await DbChanged();
@@ -123,7 +123,7 @@ const closeDialog = () => {
 	rowdata.fkTableName = state.ruleForm.tableName;
 	let tableData = state.tableData.filter((x: any) => x.tableName == state.ruleForm.tableName);
 	rowdata.fkEntityName = tableData.length == 0 ? '' : tableData[0].entityName;
-	rowdata.fkColumnName = state.ruleForm.columnName;
+	rowdata.fkColumnName = state.ruleForm.fkColumnName;
 	rowdata.fkLinkColumnName = state.ruleForm.linkColumnName;
 	rowdata.fkConfigId = state.ruleForm.configId;
 	let columnData = state.columnData.filter((x: any) => x.columnName == state.ruleForm.columnName);
@@ -146,6 +146,8 @@ const cancel = () => {
 const submit = () => {
 	ruleFormRef.value.validate(async (valid: boolean) => {
 		if (!valid) return;
+		state.ruleForm.fkColumnName = state.ruleForm.columnNames.join()
+		state.ruleForm.columnNames = undefined;
 		closeDialog();
 	});
 };