diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm
index 906d12c..bfe2dc0 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm
@@ -63,7 +63,7 @@ export function export${simpleClassName}Excel(params) {
responseType: 'blob'
})
}
-## 特殊:主子表专属逻辑
+## 特殊:主子表专属逻辑 TODO @puhui999:下面方法的【空格】不太对
#foreach ($subTable in $subTables)
#set ($index = $foreach.count - 1)
#set ($subSimpleClassName = $subSimpleClassNames.get($index))
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm
index c159579..1c15536 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm
@@ -1,137 +1,137 @@
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
index d7794b0..eab90c1 100644
--- a/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
+++ b/yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm
@@ -2,375 +2,372 @@
- #foreach($column in $columns)
- #if ($column.listOperation)
- #set ($dictType = $column.dictType)
- #set ($javaField = $column.javaField)
- #set ($javaType = $column.javaType)
- #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
- #set ($comment = $column.columnComment)
- #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
- #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
- #set ($dictMethod = "getIntDictOptions")
- #elseif ($javaType == "String")
- #set ($dictMethod = "getStrDictOptions")
- #elseif ($javaType == "Boolean")
- #set ($dictMethod = "getBoolDictOptions")
- #end
- #if ($column.htmlType == "input")
-
-
-
- #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
-
-
- #if ("" != $dictType)## 设置了 dictType 数据字典的情况
-
- #else## 未设置 dictType 数据字典的情况
-
- #end
-
-
- #elseif($column.htmlType == "datetime")
- #if ($column.listOperationCondition != "BETWEEN")## 非范围
-
-
-
- #else## 范围
-
-
-
- #end
- #end
+ #foreach($column in $columns)
+ #if ($column.listOperation)
+ #set ($dictType = $column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($javaType = $column.javaType)
+ #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment = $column.columnComment)
+ #set ($dictMethod = "getDictOptions")## 计算使用哪个 dict 字典方法
+ #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+ #set ($dictMethod = "getIntDictOptions")
+ #elseif ($javaType == "String")
+ #set ($dictMethod = "getStrDictOptions")
+ #elseif ($javaType == "Boolean")
+ #set ($dictMethod = "getBoolDictOptions")
#end
- #end
+ #if ($column.htmlType == "input")
+
+
+
+ #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+
+
+ #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+
+ #else## 未设置 dictType 数据字典的情况
+
+ #end
+
+
+ #elseif($column.htmlType == "datetime")
+ #if ($column.listOperationCondition != "BETWEEN")## 非范围
+
+
+
+ #else## 范围
+
+
+
+ #end
+ #end
+ #end
+ #end
搜索
重置
新增
导出
- ## 特殊:树表专属逻辑
- #if ( $table.templateType == 2 )
-
- 展开/折叠
-
- #end
+## 特殊:树表专属逻辑
+#if ( $table.templateType == 2 )
+
+ 展开/折叠
+
+#end
- ## 特殊:主子表专属逻辑
- #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
-
- ## 特殊:树表专属逻辑
- #elseif ( $table.templateType == 2 )
-
- #else
-
- #end
- ## 特殊:主子表专属逻辑
- #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
-
-
-
-
- #foreach ($subTable in $subTables)
- #set ($index = $foreach.count - 1)
- #set ($subClassNameVar = $subClassNameVars.get($index))
- #set ($subSimpleClassName = $subSimpleClassNames.get($index))
- #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
-
- <${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
-
- #end
-
-
-
- #end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+
+## 特殊:树表专属逻辑
+#elseif ( $table.templateType == 2 )
+
+#else
+
+#end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )
+
+
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}List :${subJoinColumn_strikeCase}="scope.row.id" />
+
+ #end
+
+
+
+#end
#foreach($column in $columns)
- #if ($column.listOperationResult)
- #set ($dictType=$column.dictType)
- #set ($javaField = $column.javaField)
- #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
- #set ($comment=$column.columnComment)
- #if ($column.javaType == "LocalDateTime")## 时间类型
-
- #elseif($column.dictType && "" != $column.dictType)## 数据字典
-
-
-
-
-
- #else
-
- #end
- #end
+ #if ($column.listOperationResult)
+ #set ($dictType=$column.dictType)
+ #set ($javaField = $column.javaField)
+ #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+ #set ($comment=$column.columnComment)
+ #if ($column.javaType == "LocalDateTime")## 时间类型
+
+ #elseif($column.dictType && "" != $column.dictType)## 数据字典
+
+
+
+
+
+ #else
+
+ #end
#end
-
-
-
+
+
- 编辑
-
-
+ 编辑
+
+
- 删除
-
-
-
-
-## 特殊:树表专属逻辑(树不需要分页)
-#if ( $table.templateType != 2 )
+ >
+ 删除
+
+
+
+
-#end
<${simpleClassName}Form ref="formRef" @success="getList" />
- ## 特殊:主子表专属逻辑
- #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
-
-
-
- #foreach ($subTable in $subTables)
- #set ($index = $foreach.count - 1)
- #set ($subClassNameVar = $subClassNameVars.get($index))
- #set ($subSimpleClassName = $subSimpleClassNames.get($index))
- #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
-
- <${subSimpleClassName}List :${subJoinColumn_strikeCase}="currentRow.id" />
-
- #end
-
-
- #end
+## 特殊:主子表专属逻辑
+#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )
+
+
+
+ #foreach ($subTable in $subTables)
+ #set ($index = $foreach.count - 1)
+ #set ($subClassNameVar = $subClassNameVars.get($index))
+ #set ($subSimpleClassName = $subSimpleClassNames.get($index))
+ #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+
+ <${subSimpleClassName}List :${subJoinColumn_strikeCase}="currentRow.id" />
+
+ #end
+
+
+#end
\ No newline at end of file
diff --git a/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java b/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java
new file mode 100644
index 0000000..588f2b0
--- /dev/null
+++ b/yudao-module-infra/yudao-module-infra-biz/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineAbstractTest.java
@@ -0,0 +1,127 @@
+package cn.iocoder.yudao.module.infra.service.codegen.inner;
+
+import cn.hutool.core.io.FileUtil;
+import cn.hutool.core.io.IoUtil;
+import cn.hutool.core.io.resource.ResourceUtil;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.core.util.ZipUtil;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
+import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
+import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
+import cn.iocoder.yudao.module.infra.framework.codegen.config.CodegenProperties;
+import org.junit.jupiter.api.BeforeEach;
+import org.mockito.InjectMocks;
+import org.mockito.Spy;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * {@link CodegenEngine} 的单元测试抽象基类
+ *
+ * @author 芋道源码
+ */
+public abstract class CodegenEngineAbstractTest extends BaseMockitoUnitTest {
+
+ @InjectMocks
+ protected CodegenEngine codegenEngine;
+
+ @Spy
+ protected CodegenProperties codegenProperties = new CodegenProperties()
+ .setBasePackage("cn.iocoder.yudao");
+
+ @BeforeEach
+ public void setUp() {
+ codegenEngine.initGlobalBindingMap();
+ }
+
+ protected static CodegenTableDO getTable(String name) {
+ String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
+ return JsonUtils.parseObject(content, "table", CodegenTableDO.class);
+ }
+
+ protected static List getColumnList(String name) {
+ String content = ResourceUtil.readUtf8Str("codegen/table/" + name + ".json");
+ List list = JsonUtils.parseArray(content, "columns", CodegenColumnDO.class);
+ list.forEach(column -> {
+ if (column.getNullable() == null) {
+ column.setNullable(false);
+ }
+ if (column.getCreateOperation() == null) {
+ column.setCreateOperation(false);
+ }
+ if (column.getUpdateOperation() == null) {
+ column.setUpdateOperation(false);
+ }
+ if (column.getListOperation() == null) {
+ column.setListOperation(false);
+ }
+ if (column.getListOperationResult() == null) {
+ column.setListOperationResult(false);
+ }
+ });
+ return list;
+ }
+
+ @SuppressWarnings("rawtypes")
+ protected static void assertResult(Map result, String path) {
+ String assertContent = ResourceUtil.readUtf8Str(path + "/assert.json");
+ List asserts = JsonUtils.parseArray(assertContent, HashMap.class);
+ assertEquals(asserts.size(), result.size());
+ // 校验每个文件
+ asserts.forEach(assertMap -> {
+ String contentPath = (String) assertMap.get("contentPath");
+ String filePath = (String) assertMap.get("filePath");
+ String content = ResourceUtil.readUtf8Str(path + "/" + contentPath);
+ assertEquals(content, result.get(filePath), filePath + ":不匹配");
+ });
+ }
+
+ // ==================== 调试专用 ====================
+
+ /**
+ * 【调试使用】将生成的代码,写入到文件
+ *
+ * @param result 生成的代码
+ * @param path 写入文件的路径
+ */
+ protected void writeFile(Map result, String path) {
+ // 生成压缩包
+ String[] paths = result.keySet().toArray(new String[0]);
+ ByteArrayInputStream[] ins = result.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);
+ ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ ZipUtil.zip(outputStream, paths, ins);
+ // 写入文件
+ FileUtil.writeBytes(outputStream.toByteArray(), path);
+ }
+
+ /**
+ * 【调试使用】将生成的结果,写入到文件
+ *
+ * @param result 生成的代码
+ * @param basePath 写入文件的路径(绝对路径)
+ */
+ protected void writeResult(Map result, String basePath) {
+ // 写入文件内容
+ List