Bladeren bron

!1484 功能优化
Merge pull request !1484 from 喵你个汪/next

zuohuaijun 1 jaar geleden
bovenliggende
commit
e385ecd5d0

+ 7 - 0
Admin.NET/Admin.NET.Core/Entity/SysCodeGen.cs

@@ -84,6 +84,13 @@ public partial class SysCodeGen : EntityBase
     [SugarColumn(ColumnDescription = "业务名", Length = 128)]
     [MaxLength(128)]
     public string? BusName { get; set; }
+    
+    /// <summary>
+    /// 表唯一字段配置
+    /// </summary>
+    [SugarColumn(ColumnDescription = "表唯一字段配置", Length = 512)]
+    [MaxLength(128)]
+    public string? TableUniqueConfig { get; set; }
 
     /// <summary>
     /// 是否生成菜单

+ 2 - 0
Admin.NET/Admin.NET.Core/Service/CodeGen/CustomViewEngine.cs

@@ -34,6 +34,8 @@ public class CustomViewEngine : ViewEngineModel
 
     public string ProjectLastName { get; set; }
 
+    public List<TableUniqueConfigItem> TableUniqueList { get; set; }
+
     public string LowerClassName
     {
         get

+ 7 - 2
Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/CodeGenInput.cs

@@ -70,7 +70,12 @@ public class CodeGenInput : BasePageInput
     /// 功能名(数据库表名称)
     /// </summary>
     public virtual string TableComment { get; set; }
-
+    
+    /// <summary>
+    /// 表唯一字段
+    /// </summary>
+    public virtual string TableUniqueConfig { get; set; }
+    
     /// <summary>
     /// 菜单应用分类(应用编码)
     /// </summary>
@@ -132,7 +137,7 @@ public class AddCodeGenInput : CodeGenInput
     /// </summary>
     [Required(ErrorMessage = "作者姓名不能为空")]
     public override string AuthorName { get; set; }
-
+    
     ///// <summary>
     ///// 类名
     ///// </summary>

+ 23 - 0
Admin.NET/Admin.NET.Core/Service/CodeGen/Dto/TableUniqueConfigItem.cs

@@ -0,0 +1,23 @@
+// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
+// 
+// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
+// 
+// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
+
+namespace Admin.NET.Core.Service;
+
+/// <summary>
+/// 表唯一配置项
+/// </summary>
+public class TableUniqueConfigItem
+{
+    /// <summary>
+    /// 字段列表
+    /// </summary>
+    public List<string> Columns { get; set; }
+    
+    /// <summary>
+    /// 描述信息
+    /// </summary>
+    public string Message { get; set; }
+}

+ 2 - 1
Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenConfigService.cs

@@ -132,8 +132,9 @@ public class SysCodeGenConfigService : IDynamicApiController, ITransient
             
             if (!string.IsNullOrWhiteSpace(tableColumn.DictTypeCode))
             {
+                codeGenConfig.QueryType = "==";
                 codeGenConfig.DictTypeCode = tableColumn.DictTypeCode;
-                codeGenConfig.EffectType = tableColumn.DictTypeCode.TrimEnd('?').EndsWith("Enum") ? "EnumSelector" : "Select";
+                codeGenConfig.EffectType = tableColumn.DictTypeCode.EndsWith("Enum") ? "EnumSelector" : "Select";
             }
 
             orderNo += 10; // 每个配置排序间隔10

+ 6 - 2
Admin.NET/Admin.NET.Core/Service/CodeGen/SysCodeGenService.cs

@@ -5,6 +5,7 @@
 // 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
 
 using System.IO.Compression;
+using NewLife.Serialization;
 
 namespace Admin.NET.Core.Service;
 
@@ -233,9 +234,10 @@ public class SysCodeGenService : IDynamicApiController, ITransient
             {
                 columnOutput.PropertyName = propertyInfo.Name;
                 columnOutput.ColumnComment = propertyInfo.GetCustomAttribute<SugarColumn>().ColumnDescription;
-                if (propertyInfo.PropertyType.IsEnum)
+                var propertyType = Nullable.GetUnderlyingType(propertyInfo.PropertyType);
+                if (propertyInfo.PropertyType.IsEnum || propertyType != null)
                 {
-                    columnOutput.DictTypeCode = propertyInfo.PropertyType.Name;
+                    columnOutput.DictTypeCode = (propertyType ?? propertyInfo.PropertyType).Name;
                 }
                 else
                 {
@@ -355,6 +357,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
             NameSpace = input.NameSpace,
             ClassName = input.TableName,
             PagePath = input.PagePath,
+            TableUniqueList = input.TableUniqueConfig?.ToJsonEntity<List<TableUniqueConfigItem>>() ?? new(),
             ProjectLastName = input.NameSpace.Split('.').Last(),
             QueryWhetherList = queryWhetherList,
             TableField = tableFieldList,
@@ -422,6 +425,7 @@ public class SysCodeGenService : IDynamicApiController, ITransient
             NameSpace = input.NameSpace,
             ClassName = input.TableName,
             PagePath = input.PagePath,
+            TableUniqueList = input.TableUniqueConfig?.ToJsonEntity<List<TableUniqueConfigItem>>() ?? new(),
             ProjectLastName = input.NameSpace.Split('.').Last(),
             QueryWhetherList = queryWhetherList,
             TableField = tableFieldList,

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

@@ -110,6 +110,9 @@ public class @(Model.ClassName)Service : IDynamicApiController, ITransient
     public async Task<long> Add(Add@(Model.ClassName)Input input)
     {
         var entity = input.Adapt<@(Model.ClassName)>();
+        @foreach (var config in Model.TableUniqueList.Where(x => x.Columns.All(x1 => Model.TableField.Any(x2 => x2.WhetherAddUpdate == "Y" && x2.PropertyName == x1)))) {
+        @:if (_@(Model.LowerClassName)Rep.AnyAsync(u => @(string.Join(" && ", config.Columns.Select(x => $"u.{x} != null && u.{x} == input.{x}"))))) throw Oops.Oh("@config.Message");
+        }
         return await _@(Model.LowerClassName)Rep.InsertAsync(entity) ? entity.Id : 0;
     }
 
@@ -154,6 +157,9 @@ public class @(Model.ClassName)Service : IDynamicApiController, ITransient
     [ApiDescriptionSettings(Name = "Update"), HttpPost]
     public async Task Update(Update@(Model.ClassName)Input input)
     {
+        @foreach (var config in Model.TableUniqueList.Where(x => x.Columns.All(x1 => Model.TableField.Any(x2 => x2.WhetherAddUpdate == "Y" && x2.PropertyName == x1)))) {
+        @:if (_@(Model.LowerClassName)Rep.AnyAsync(u => u.@(primaryKey.PropertyName) != input.@(primaryKey.PropertyName) && @(string.Join(" && ", config.Columns.Select(x => $"u.{x} != null && u.{x} == input.{x}"))))) throw Oops.Oh("@config.Message");
+        }
         var entity = input.Adapt<@(Model.ClassName)>();
         await _@(Model.LowerClassName)Rep.AsUpdateable(entity).IgnoreColumns(ignoreAllNullColumns: true).ExecuteCommandAsync();
     }
@@ -342,7 +348,9 @@ var definedObjects = new Dictionary<string, object>();
                         if (column.NetType?.TrimEnd('?') == "string"){
                         @:.SplitError(it => it.Item.@(column.PropertyName)?.Length > @(column.ColumnLength), "@(column.ColumnComment)长度不能超过@(column.ColumnLength)个字符")
                         }}
-                        @:.SplitError(it => it.Any(), "记录已存在")
+                        @foreach (var config in Model.TableUniqueList.Where(x => x.Columns.All(x1 => importFields.Any(x2 => x2.PropertyName == x1)))) {
+                        @:.WhereColumns(it => new { @(string.Join(", ", config.Columns.Select(x => $"it.{x}"))) }).SplitError(it => it.Any(), "@(config.Message)")
+                        }
                         @:.SplitInsert(_ => true)
                         @:.ToStorage();
 

+ 40 - 0
Web/src/views/system/codeGen/component/editCodeGenDialog.vue

@@ -136,6 +136,35 @@
 							</el-select>
 						</el-form-item>
 					</el-col>
+					<el-divider border-style="dashed" content-position="center">
+						<div style="color: #b1b3b8">数据唯一性配置</div>
+					</el-divider>
+					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+						<el-button icon="ele-Plus" type="primary" plain @click="() => state.ruleForm.tableUniqueList?.push({})"> 增加配置 </el-button>
+						<span style="font-size: 12px; color: gray; padding-left: 5px"> 保证字段值的唯一性,排除null值 </span>
+					</el-col>
+					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
+						<template v-if="state.ruleForm.tableUniqueList != undefined && state.ruleForm.tableUniqueList.length > 0">
+							<el-row :gutter="35" v-for="(v, k) in state.ruleForm.tableUniqueList" :key="k">
+								<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20">
+									<el-form-item label="字段" :prop="`tableUniqueList[${k}].columns`" :rules="[{ required: true, message: `字段不能为空`, trigger: 'blur' }]">
+										<template #label>
+											<el-button icon="ele-Delete" type="danger" circle plain size="small" @click="() => state.ruleForm.tableUniqueList?.splice(k, 1)" />
+											<span class="ml5">字段</span>
+										</template>
+										<el-select v-model="state.ruleForm.tableUniqueList[k].columns" multiple filterable 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="12" :md="12" :lg="12" :xl="12" class="mb20">
+									<el-form-item label="描述信息" :prop="`tableUniqueList[${k}].message`" :rules="[{ required: true, message: `描述信息不能为空`, trigger: 'blur' }]">
+										<el-input v-model="state.ruleForm.tableUniqueList[k].message" clearable placeholder="请输入" />
+									</el-form-item>
+								</el-col>
+							</el-row>
+						</template>
+					</el-col>
 				</el-row>
 			</el-form>
 			<template #footer>
@@ -168,6 +197,7 @@ const state = reactive({
 	ruleForm: {} as UpdateCodeGenInput,
 	tableData: [] as any,
 	dbData: [] as any,
+	columnData: [] as any,
 	menuData: [] as Array<SysMenu>,
 	codeGenTypeList: [] as any,
 	printTypeList: [] as any,
@@ -212,6 +242,13 @@ const dbChanged = async () => {
 const tableChanged = (item: any) => {
 	state.ruleForm.tableName = item.entityName;
 	state.ruleForm.busName = item.tableComment;
+	getColumnInfoList();
+};
+
+const getColumnInfoList = async () => {
+	if (state.ruleForm.configId == '' || state.ruleForm.tableName == '') return;
+	var res = await getAPI(SysCodeGenApi).apiSysCodeGenColumnListByTableNameTableNameConfigIdGet(state.ruleForm.tableName, state.ruleForm.configId);
+	state.columnData = res.data.result;
 };
 
 // 菜单改变
@@ -233,8 +270,10 @@ const getGlobalComponentSize = computed(() => {
 // 打开弹窗
 const openDialog = (row: any) => {
 	state.ruleForm = JSON.parse(JSON.stringify(row));
+	state.ruleForm.tableUniqueList = JSON.parse(row.tableUniqueConfig ?? "[]");
 	state.isShowDialog = true;
 	ruleFormRef.value?.resetFields();
+	getColumnInfoList();
 };
 
 // 关闭弹窗
@@ -252,6 +291,7 @@ const cancel = () => {
 const submit = () => {
 	ruleFormRef.value.validate(async (valid: boolean) => {
 		if (!valid) return;
+		state.ruleForm.tableUniqueConfig = state.ruleForm.tableUniqueList?.length > 0 ? JSON.stringify(state.ruleForm.tableUniqueList) : undefined;
 		if (state.ruleForm.id != undefined && state.ruleForm.id > 0) {
 			await getAPI(SysCodeGenApi).apiSysCodeGenUpdatePost(state.ruleForm as UpdateCodeGenInput);
 		} else {

+ 3 - 1
Web/src/views/system/codeGen/component/fkDialog.vue

@@ -25,7 +25,7 @@
 					</el-col>
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
 						<el-form-item label="显示字段" prop="columnNames" :rules="[{ required: true, message: '显示字段不能为空', trigger: 'blur' }]">
-							<el-select v-model="state.ruleForm.columnNames" multiple class="w100">
+							<el-select v-model="state.ruleForm.columnNames" multiple filterable 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>
@@ -83,6 +83,8 @@ const DbChanged = async () => {
 const TableChanged = async () => {
 	state.columnData = [];
 	await getColumnInfoList();
+	state.ruleForm.columnNames = undefined;
+	state.ruleForm.linkColumnName = undefined;
 };
 
 const getDbList = async () => {

+ 4 - 1
Web/src/views/system/codeGen/component/treeDialog.vue

@@ -25,7 +25,7 @@
 					</el-col>
 					<el-col :xs="24" :sm="24" :md="24" :lg="24" :xl="24" class="mb20">
 						<el-form-item label="显示字段" prop="displayColumns" :rules="[{ required: true, message: '显示字段不能为空', trigger: 'blur' }]">
-							<el-select v-model="state.ruleForm.displayColumns" multiple class="w100">
+							<el-select v-model="state.ruleForm.displayColumns" multiple filterable 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>
@@ -90,6 +90,9 @@ const DbChanged = async () => {
 const TableChanged = async () => {
 	state.columnData = [];
 	await getColumnInfoList();
+	state.ruleForm.displayColumns = undefined;
+	state.ruleForm.valueColumn = undefined;
+	state.ruleForm.pidColumn = undefined;
 };
 
 const getDbList = async () => {

+ 31 - 14
Web/src/views/system/codeGen/index.vue

@@ -38,14 +38,15 @@
 						<el-tag type="danger" v-else> 生成到本项目 </el-tag>
 					</template>
 				</el-table-column>
-				<el-table-column label="操作" width="400" fixed="right" align="center" show-overflow-tooltip>
+				<el-table-column label="操作" width="280" fixed="right" align="center" show-overflow-tooltip>
 					<template #default="scope">
-						<el-button icon="ele-Position" size="small" text type="primary" @click="handleGenerate(scope.row)">开始生成</el-button>
-						<el-button icon="ele-View" size="small" text type="primary" @click="handlePreview(scope.row)">预览</el-button>
-						<el-button icon="ele-Setting" size="small" text type="primary" @click="openConfigDialog(scope.row)">配置</el-button>
-						<el-button icon="ele-CopyDocument" size="small" text type="primary" @click="openCopyDialog(scope.row)"> 复制 </el-button>
-						<el-button icon="ele-Edit" size="small" text type="primary" @click="openEditDialog(scope.row)">编辑</el-button>
-						<el-button icon="ele-Delete" size="small" text type="danger" @click="deleConfig(scope.row)">删除</el-button>
+						<el-button icon="ele-Delete" size="small" text type="danger" title="删除" @click="deleConfig(scope.row)" />
+						<el-button icon="ele-Edit" size="small" text type="primary" title="编辑" @click="openEditDialog(scope.row)" />
+						<el-button icon="ele-CopyDocument" size="small" text type="primary" title="复制" @click="openCopyDialog(scope.row)" />
+						<el-button icon="ele-View" size="small" text type="primary" title="预览" @click="handlePreview(scope.row)" />
+						<el-button icon="ele-Setting" size="small" text type="primary" title="配置" @click="openConfigDialog(scope.row)" />
+						<el-button icon="ele-Refresh" size="small" text type="primary" title="同步" @click="syncCodeGen(scope.row)" />
+						<el-button icon="ele-Position" size="small" text type="primary" @click="handleGenerate(scope.row)">生成</el-button>
 					</template>
 				</el-table-column>
 			</el-table>
@@ -62,9 +63,9 @@
 			/>
 		</el-card>
 
-		<EditCodeGenDialog :title="state.editMenuTitle" ref="EditCodeGenRef" @handleQuery="handleQuery" :application-namespaces="state.applicationNamespaces" />
+		<EditCodeGenDialog :title="state.editTitle" ref="EditCodeGenRef" @handleQuery="handleQuery" :application-namespaces="state.applicationNamespaces" />
 		<CodeConfigDialog ref="CodeConfigRef" @handleQuery="handleQuery" />
-		<PreviewDialog :title="state.editMenuTitle" ref="PreviewRef" />
+		<PreviewDialog :title="state.editTitle" ref="PreviewRef" />
 	</div>
 </template>
 
@@ -102,7 +103,7 @@ const state = reactive({
 		pageSize: 50,
 		total: 0 as any,
 	},
-	editMenuTitle: '',
+	editTitle: '',
 	applicationNamespaces: [] as Array<string>,
 });
 
@@ -148,7 +149,7 @@ const handleCurrentChange = (val: number) => {
 
 // 打开表增加页面
 const openAddDialog = () => {
-	state.editMenuTitle = '增加';
+	state.editTitle = '增加';
 	EditCodeGenRef.value?.openDialog({
 		authorName: 'Admin.NET',
 		generateType: '200',
@@ -162,13 +163,13 @@ const openAddDialog = () => {
 
 // 打开表编辑页面
 const openEditDialog = (row: any) => {
-	state.editMenuTitle = '编辑';
+	state.editTitle = '编辑';
 	EditCodeGenRef.value?.openDialog(row);
 };
 
 // 打开复制页面
 const openCopyDialog = (row: any) => {
-	state.editUserTitle = '复制';
+	state.editTitle = '复制';
 	var copyRow = JSON.parse(JSON.stringify(row));
 	copyRow.id = 0;
 	copyRow.busName = '';
@@ -191,6 +192,22 @@ const deleConfig = (row: any) => {
 		.catch(() => {});
 };
 
+// 同步生成
+const syncCodeGen = async (row: any) => {
+  ElMessageBox.confirm(`确定要同步吗?`, '提示', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning',
+  }).then(async () => {
+    await getAPI(SysCodeGenApi).apiSysCodeGenDeletePost([{ id: state.ruleForm.id }]);
+    state.ruleForm.id = undefined;
+    await getAPI(SysCodeGenApi).apiSysCodeGenAddPost(state.ruleForm as AddCodeGenInput);
+    handleQuery();
+    ElMessage.success('同步成功');
+  })
+  .catch(() => {});
+}
+
 // 开始生成代码
 const handleGenerate = (row: any) => {
 	ElMessageBox.confirm(`确定要生成吗?`, '提示', {
@@ -209,7 +226,7 @@ const handleGenerate = (row: any) => {
 
 // 预览代码
 const handlePreview = (row: any) => {
-	state.editMenuTitle = '预览代码';
+	state.editTitle = '预览代码';
 	PreviewRef.value?.openDialog(row);
 };
 </script>