Commit 0cb951f3344107f01a3c7168a24dd49493bc0914

Authored by YunaiV
1 parent ef9f43e7

fix(codegen): 修复 UniApp Wot 生成代码兼容问题

- 完善日期、浮点数、布尔字典和上传字段生成逻辑
- 修复主子表必填校验和详情按钮类型
- 修复树表查询及生成快照路径问题
- 增加主子表类名、属性名和输出路径碰撞校验
- 补充 normal、inner、ERP 等场景单元测试
Showing 18 changed files with 453 additions and 156 deletions
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/enums/ErrorCodeConstants.java
... ... @@ -48,6 +48,10 @@ public interface ErrorCodeConstants {
48 48 ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, "主表(id={})定义不存在,请检查");
49 49 ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, "子表的字段(id={})不存在,请检查");
50 50 ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_001_004_012, "主表生成代码失败,原因:它没有子表");
  51 + ErrorCode CODEGEN_MASTER_TABLE_NAME_DUPLICATE = new ErrorCode(1_001_004_013,
  52 + "主子表规范化类名({})重复,请调整主表或子表类名");
  53 + ErrorCode CODEGEN_MASTER_TABLE_FIELD_DUPLICATE = new ErrorCode(1_001_004_014,
  54 + "主子表属性名({})重复,请调整主表字段、子表类名或关联关系");
51 55  
52 56 // ========== 文件配置 1-001-006-000 ==========
53 57 ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, "文件配置不存在");
... ...
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
... ... @@ -48,6 +48,9 @@ import java.util.*;
48 48  
49 49 import static cn.hutool.core.map.MapUtil.getStr;
50 50 import static cn.hutool.core.text.CharSequenceUtil.*;
  51 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  52 +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.CODEGEN_MASTER_TABLE_FIELD_DUPLICATE;
  53 +import static cn.iocoder.yudao.module.infra.enums.ErrorCodeConstants.CODEGEN_MASTER_TABLE_NAME_DUPLICATE;
51 54  
52 55 /**
53 56 * 代码生成的引擎,用于具体生成代码
... ... @@ -426,16 +429,23 @@ public class CodegenEngine {
426 429 private void generateCode(Map<String, String> result, Map<String, String> generatedSources, String vmPath,
427 430 String filePath, Map<String, Object> bindingMap) {
428 431 filePath = formatFilePath(filePath, bindingMap);
429   - String source = vmPath + (bindingMap.containsKey("subIndex") ? "[subIndex=" + bindingMap.get("subIndex") + "]" : "");
  432 + registerGeneratedSource(generatedSources, filePath, vmPath, bindingMap);
  433 + String content = templateEngine.getTemplate(vmPath).render(bindingMap);
  434 + // 格式化代码
  435 + content = prettyCode(content, vmPath);
  436 + result.put(filePath, content);
  437 + }
  438 +
  439 + @VisibleForTesting
  440 + static void registerGeneratedSource(Map<String, String> generatedSources, String filePath, String vmPath,
  441 + Map<String, Object> bindingMap) {
  442 + String source = vmPath
  443 + + (bindingMap.containsKey("subIndex") ? "[subIndex=" + bindingMap.get("subIndex") + "]" : "");
430 444 String previousSource = generatedSources.putIfAbsent(filePath, source);
431 445 if (previousSource != null) {
432 446 throw new IllegalStateException(String.format("生成文件路径重复:%s,来源模板:%s、%s",
433 447 filePath, previousSource, source));
434 448 }
435   - String content = templateEngine.getTemplate(vmPath).render(bindingMap);
436   - // 格式化代码
437   - content = prettyCode(content, vmPath);
438   - result.put(filePath, content);
439 449 }
440 450  
441 451 private void generateSubCode(CodegenTableDO table, List<CodegenTableDO> subTables,
... ... @@ -561,6 +571,14 @@ public class CodegenEngine {
561 571 List<String> subClassNameVars = new ArrayList<>();
562 572 List<String> simpleClassNameUnderlineCases = new ArrayList<>();
563 573 List<String> subSimpleClassNameStrikeCases = new ArrayList<>();
  574 + Set<String> masterSymbols = new HashSet<>();
  575 + masterSymbols.add(simpleClassName);
  576 + boolean mergeSubTableFields = ObjectUtils.equalsAny(table.getTemplateType(),
  577 + CodegenTemplateTypeEnum.MASTER_NORMAL.getType(), CodegenTemplateTypeEnum.MASTER_INNER.getType());
  578 + Set<String> masterFieldSymbols = new HashSet<>();
  579 + if (mergeSubTableFields) {
  580 + columns.forEach(column -> masterFieldSymbols.add(column.getJavaField()));
  581 + }
564 582 for (int i = 0; i < subTables.size(); i++) {
565 583 CodegenTableDO subTable = subTables.get(i);
566 584 List<CodegenColumnDO> subColumns = subColumnsList.get(i);
... ... @@ -571,9 +589,18 @@ public class CodegenEngine {
571 589 subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type
572 590 // className 相关
573 591 String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));
  592 + if (!masterSymbols.add(subSimpleClassName)) {
  593 + throw exception(CODEGEN_MASTER_TABLE_NAME_DUPLICATE, subSimpleClassName);
  594 + }
  595 + String subClassNameVar = lowerFirst(subSimpleClassName);
  596 + String subFieldName = subClassNameVar
  597 + + (Boolean.TRUE.equals(subTable.getSubJoinMany()) ? "s" : "");
  598 + if (mergeSubTableFields && !masterFieldSymbols.add(subFieldName)) {
  599 + throw exception(CODEGEN_MASTER_TABLE_FIELD_DUPLICATE, subFieldName);
  600 + }
574 601 subSimpleClassNames.add(subSimpleClassName);
575 602 simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type
576   - subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType,用于变量
  603 + subClassNameVars.add(subClassNameVar); // 将 DictType 转换成 dictType,用于变量
577 604 subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type
578 605 }
579 606 bindingMap.put("subPrimaryColumns", subPrimaryColumns);
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/api/api.ts.vm
... ... @@ -4,7 +4,7 @@ import type { PageParam, PageResult } from &#39;@/http/types&#39;
4 4 import { http } from '@/http/http'
5 5  
6 6 #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
7   -#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
  7 +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
8 8 #set ($primaryTsType = "number")
9 9 #else
10 10 #set ($primaryTsType = "string")
... ... @@ -20,7 +20,7 @@ export interface ${subSimpleClassName} {
20 20 #if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult)
21 21 #set ($javaType = $column.javaType.toLowerCase())
22 22 #set ($optional = $column.nullable || $column.primaryKey || ($table.templateType != 11 && $column.id == $subJoinColumn.id))
23   -#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
  23 +#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
24 24 ${column.javaField}#if($optional)?#end: number
25 25 #elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
26 26 ${column.javaField}#if($optional)?#end: number
... ... @@ -40,7 +40,7 @@ export interface ${simpleClassName} {
40 40 #if ($column.primaryKey || $column.createOperation || $column.updateOperation || $column.listOperationResult)
41 41 #set ($javaType = $column.javaType.toLowerCase())
42 42 #set ($optional = $column.nullable || $column.primaryKey)
43   -#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
  43 +#if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
44 44 ${column.javaField}#if($optional)?#end: number
45 45 #elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
46 46 ${column.javaField}#if($optional)?#end: number
... ... @@ -101,13 +101,13 @@ export function delete${simpleClassName}(id: ${primaryTsType}) {
101 101 #set ($subPrimaryColumn = $subPrimaryColumns.get($index))
102 102 #set ($subJoinColumn = $subJoinColumns.get($index))
103 103 #set ($subPrimaryJavaType = $subPrimaryColumn.javaType.toLowerCase())
104   -#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte")
  104 +#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "float" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte")
105 105 #set ($subPrimaryTsType = "number")
106 106 #else
107 107 #set ($subPrimaryTsType = "string")
108 108 #end
109 109 #set ($subJoinJavaType = $subJoinColumn.javaType.toLowerCase())
110   -#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte")
  110 +#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "float" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte")
111 111 #set ($subJoinTsType = "number")
112 112 #else
113 113 #set ($subJoinTsType = "string")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/components/search-form.vue.vm
... ... @@ -145,7 +145,7 @@ const formData = reactive({
145 145 #else
146 146 ${column.javaField}: undefined as number | undefined,
147 147 #end
148   - #elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
  148 + #elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
149 149 ${column.javaField}: undefined as number | undefined,
150 150 #elseif(${javaType} == "boolean")
151 151 ${column.javaField}: undefined as boolean | undefined,
... ... @@ -174,7 +174,7 @@ const placeholder = computed(() =&gt; {
174 174 conditions.push(`${comment}:#[[${]]#getDictLabel(DICT_TYPE.${dictType.toUpperCase()}, formData.${javaField})#[[}]]#`)
175 175 }
176 176 #else
177   - #if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean")
  177 + #if(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte" || ${javaType} == "boolean")
178 178 if (formData.${javaField} !== undefined) {
179 179 conditions.push(`${comment}:#[[${]]#formData.${javaField}#[[}]]#`)
180 180 }
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail/index.vue.vm
... ... @@ -50,7 +50,7 @@
50 50  
51 51 <script lang="ts" setup>
52 52 #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
53   -#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
  53 +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
54 54 #set ($primaryTsType = "number")
55 55 #else
56 56 #set ($primaryTsType = "string")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail_erp.vue.vm
... ... @@ -101,7 +101,7 @@
101 101  
102 102 <script lang="ts" setup>
103 103 #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
104   -#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
  104 +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
105 105 #set ($primaryTsType = "number")
106 106 #else
107 107 #set ($primaryTsType = "string")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/detail_master.vue.vm
... ... @@ -94,7 +94,7 @@
94 94  
95 95 <script lang="ts" setup>
96 96 #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
97   -#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
  97 +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
98 98 #set ($primaryTsType = "number")
99 99 #else
100 100 #set ($primaryTsType = "string")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/form/index.vue.vm
... ... @@ -42,10 +42,14 @@
42 42 label="${comment}"
43 43 label-width="180rpx"
44 44 prop="${javaField}"
  45 +#if ($javaType == "Boolean")
  46 + :columns="getBoolDictOptions(DICT_TYPE.${dictType.toUpperCase()})"
  47 +#else
45 48 :dict-type="DICT_TYPE.${dictType.toUpperCase()}"
46 49 #if ($javaType == "String")
47 50 dict-kind="str"
48 51 #end
  52 +#end
49 53 placeholder="请选择${comment}"
50 54 />
51 55 ## 单选(字典)→ radio-group,适合选项较少
... ... @@ -62,14 +66,14 @@
62 66 </wd-radio-group>
63 67 </wd-form-item>
64 68 ## 数字类型(无字典)
65   -#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte")
  69 +#elseif (${javaType.toLowerCase()} == "long" || ${javaType.toLowerCase()} == "integer" || ${javaType.toLowerCase()} == "short" || ${javaType.toLowerCase()} == "float" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal" || ${javaType.toLowerCase()} == "byte")
66 70 <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
67 71 <wd-input-number
68 72 v-model="formData.${javaField}"
69 73 #if ($column.nullable)
70 74 allow-null
71 75 #end
72   -#if (${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal")
  76 +#if (${javaType.toLowerCase()} == "float" || ${javaType.toLowerCase()} == "double" || ${javaType.toLowerCase()} == "bigdecimal")
73 77 :precision="2"
74 78 input-type="number"
75 79 #end
... ... @@ -104,20 +108,20 @@
104 108 v-model:visible="${javaField}Visible"
105 109 :model-value="formData.${javaField} || Date.now()"
106 110 type="${pickerType}"
107   -#if ($pickerType == "datetime")
108 111 @update:model-value="value => formData.${javaField} = value"
109   -#else
110   - @update:model-value="value => formData.${javaField} = value"
111   -#end
112 112 />
113 113 ## 文本域
114   -#elseif ($column.htmlType == "textarea")
  114 +#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
115 115 <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
116 116 <wd-textarea
117 117 v-model="formData.${javaField}"
118 118 clearable
119 119 placeholder="请输入${comment}"
  120 +#if ($column.htmlType == "editor")
  121 + :maxlength="2000"
  122 +#else
120 123 :maxlength="200"
  124 +#end
121 125 show-word-limit
122 126 />
123 127 </wd-form-item>
... ... @@ -129,7 +133,7 @@
129 133 ## 文件上传 → yd-upload-file
130 134 #elseif ($column.htmlType == "fileUpload")
131 135 <wd-form-item title="${comment}" title-width="180rpx" prop="${javaField}">
132   - <yd-upload-file v-model="formData.${javaField}" directory="${table.moduleName}/${table.businessName}" />
  136 + <yd-upload-file v-model="formData.${javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
133 137 </wd-form-item>
134 138 ## 默认:文本输入
135 139 #else
... ... @@ -202,10 +206,14 @@
202 206 v-model="item.${column.javaField}"
203 207 label="${column.columnComment}"
204 208 label-width="180rpx"
  209 +#if ($column.javaType == "Boolean")
  210 + :columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
  211 +#else
205 212 :dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
206 213 #if ($column.javaType == "String")
207 214 dict-kind="str"
208 215 #end
  216 +#end
209 217 placeholder="请选择${column.columnComment}"
210 218 />
211 219 #elseif ($column.htmlType == "radio" && $column.dictType && "" != $column.dictType)
... ... @@ -224,14 +232,14 @@
224 232 <wd-form-item title="${column.columnComment}" title-width="180rpx" center>
225 233 <wd-switch v-model="item.${column.javaField}" />
226 234 </wd-form-item>
227   -#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
  235 +#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
228 236 <wd-form-item title="${column.columnComment}" title-width="180rpx">
229 237 <wd-input-number
230 238 v-model="item.${column.javaField}"
231 239 #if($column.nullable)
232 240 allow-null
233 241 #end
234   -#if ($javaType == "double" || $javaType == "bigdecimal")
  242 +#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
235 243 :precision="2"
236 244 input-type="number"
237 245 #end
... ... @@ -259,15 +267,20 @@
259 267 v-model:visible="subDateVisible[`${subClassNameVar}s.#[[${]]#index#[[}]]#.${column.javaField}`]"
260 268 :model-value="item.${column.javaField} || Date.now()"
261 269 type="${subPickerType}"
262   -#if ($javaType == "localdatetime")
263 270 @update:model-value="value => item.${column.javaField} = value"
264   -#else
265   - @update:model-value="value => item.${column.javaField} = value"
266   -#end
267 271 />
268   -#elseif ($column.htmlType == "textarea")
  272 +#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
269 273 <wd-form-item title="${column.columnComment}" title-width="180rpx">
270   - <wd-textarea v-model="item.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
  274 + <wd-textarea
  275 + v-model="item.${column.javaField}"
  276 + clearable
  277 + placeholder="请输入${column.columnComment}"
  278 +#if ($column.htmlType == "editor")
  279 + :maxlength="2000"
  280 +#else
  281 + :maxlength="200"
  282 +#end
  283 + />
271 284 </wd-form-item>
272 285 #elseif ($column.htmlType == "imageUpload")
273 286 <wd-form-item title="${column.columnComment}" title-width="180rpx">
... ... @@ -275,7 +288,7 @@
275 288 </wd-form-item>
276 289 #elseif ($column.htmlType == "fileUpload")
277 290 <wd-form-item title="${column.columnComment}" title-width="180rpx">
278   - <yd-upload-file v-model="item.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
  291 + <yd-upload-file v-model="item.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
279 292 </wd-form-item>
280 293 #else
281 294 <wd-form-item title="${column.columnComment}" title-width="180rpx">
... ... @@ -306,10 +319,14 @@
306 319 v-model="${subClassNameVar}.${column.javaField}"
307 320 label="${column.columnComment}"
308 321 label-width="180rpx"
  322 +#if ($column.javaType == "Boolean")
  323 + :columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
  324 +#else
309 325 :dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
310 326 #if ($column.javaType == "String")
311 327 dict-kind="str"
312 328 #end
  329 +#end
313 330 placeholder="请选择${column.columnComment}"
314 331 />
315 332 #elseif ($column.htmlType == "radio" && $column.dictType && "" != $column.dictType)
... ... @@ -328,14 +345,14 @@
328 345 <wd-form-item title="${column.columnComment}" title-width="180rpx" center>
329 346 <wd-switch v-model="${subClassNameVar}.${column.javaField}" />
330 347 </wd-form-item>
331   -#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
  348 +#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
332 349 <wd-form-item title="${column.columnComment}" title-width="180rpx">
333 350 <wd-input-number
334 351 v-model="${subClassNameVar}.${column.javaField}"
335 352 #if($column.nullable)
336 353 allow-null
337 354 #end
338   -#if ($javaType == "double" || $javaType == "bigdecimal")
  355 +#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
339 356 :precision="2"
340 357 input-type="number"
341 358 #end
... ... @@ -363,15 +380,20 @@
363 380 v-model:visible="subDateVisible['${subClassNameVar}.${column.javaField}']"
364 381 :model-value="${subClassNameVar}.${column.javaField} || Date.now()"
365 382 type="${subPickerType}"
366   -#if ($javaType == "localdatetime")
367   - @update:model-value="value => ${subClassNameVar}.${column.javaField} = value"
368   -#else
369 383 @update:model-value="value => ${subClassNameVar}.${column.javaField} = value"
370   -#end
371 384 />
372   -#elseif ($column.htmlType == "textarea")
  385 +#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
373 386 <wd-form-item title="${column.columnComment}" title-width="180rpx">
374   - <wd-textarea v-model="${subClassNameVar}.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
  387 + <wd-textarea
  388 + v-model="${subClassNameVar}.${column.javaField}"
  389 + clearable
  390 + placeholder="请输入${column.columnComment}"
  391 +#if ($column.htmlType == "editor")
  392 + :maxlength="2000"
  393 +#else
  394 + :maxlength="200"
  395 +#end
  396 + />
375 397 </wd-form-item>
376 398 #elseif ($column.htmlType == "imageUpload")
377 399 <wd-form-item title="${column.columnComment}" title-width="180rpx">
... ... @@ -379,7 +401,7 @@
379 401 </wd-form-item>
380 402 #elseif ($column.htmlType == "fileUpload")
381 403 <wd-form-item title="${column.columnComment}" title-width="180rpx">
382   - <yd-upload-file v-model="${subClassNameVar}.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
  404 + <yd-upload-file v-model="${subClassNameVar}.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
383 405 </wd-form-item>
384 406 #else
385 407 <wd-form-item title="${column.columnComment}" title-width="180rpx">
... ... @@ -412,7 +434,7 @@
412 434 <script lang="ts" setup>
413 435 import type { FormInstance } from '@wot-ui/ui/components/wd-form/types'
414 436 #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
415   -#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
  437 +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
416 438 #set ($primaryTsType = "number")
417 439 #else
418 440 #set ($primaryTsType = "string")
... ... @@ -434,7 +456,7 @@ import type { FormInstance } from &#39;@wot-ui/ui/components/wd-form/types&#39;
434 456 #set ($hasGetIntDictOptions = 1)
435 457 #elseif ($column.htmlType == "radio" && $column.javaType == "String")
436 458 #set ($hasGetStrDictOptions = 1)
437   -#elseif ($column.htmlType == "radio" && $column.javaType == "Boolean")
  459 +#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $column.javaType == "Boolean")
438 460 #set ($hasGetBoolDictOptions = 1)
439 461 #elseif ($column.htmlType == "radio")
440 462 #set ($hasGetDictOptions = 1)
... ... @@ -460,7 +482,7 @@ import type { FormInstance } from &#39;@wot-ui/ui/components/wd-form/types&#39;
460 482 #set ($hasGetIntDictOptions = 1)
461 483 #elseif ($column.htmlType == "radio" && $column.javaType == "String")
462 484 #set ($hasGetStrDictOptions = 1)
463   -#elseif ($column.htmlType == "radio" && $column.javaType == "Boolean")
  485 +#elseif (($column.htmlType == "select" || $column.htmlType == "radio") && $column.javaType == "Boolean")
464 486 #set ($hasGetBoolDictOptions = 1)
465 487 #elseif ($column.htmlType == "radio")
466 488 #set ($hasGetDictOptions = 1)
... ... @@ -567,7 +589,7 @@ definePage({
567 589 const toast = useToast()
568 590 const getTitle = computed(() => props.id ? '编辑${table.classComment}' : '新增${table.classComment}')
569 591 const formLoading = ref(false) // 表单提交状态
570   -const formData = ref<${simpleClassName}>({
  592 +const formData = ref<Partial<${simpleClassName}>>({
571 593 #foreach($column in $columns)
572 594 #if (($column.createOperation || $column.updateOperation) || $column.primaryKey)
573 595 #set ($javaType = $column.javaType.toLowerCase())
... ... @@ -577,7 +599,9 @@ const formData = ref&lt;${simpleClassName}&gt;({
577 599 ${column.javaField}: undefined,
578 600 #elseif($table.templateType == 2 && $column.id == $treeParentColumn.id)
579 601 ${column.javaField}: props.${column.javaField} ?? 0,
580   -#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
  602 +#elseif(($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType)
  603 + ${column.javaField}: undefined,
  604 +#elseif(${javaType} == "long" || ${javaType} == "integer" || ${javaType} == "short" || ${javaType} == "float" || ${javaType} == "double" || ${javaType} == "bigdecimal" || ${javaType} == "byte")
581 605 #if($column.nullable)
582 606 ${column.javaField}: undefined,
583 607 #else
... ... @@ -586,7 +610,7 @@ const formData = ref&lt;${simpleClassName}&gt;({
586 610 #elseif(${javaType} == "boolean")
587 611 ${column.javaField}: false,
588 612 #elseif(${javaType} == "date" || ${javaType} == "localdate" || ${javaType} == "localdatetime")
589   - ${column.javaField}:#if($column.nullable || $column.primaryKey || $javaFieldLower == "createtime" || $javaFieldLower == "updatetime") undefined#else 0#end,
  613 + ${column.javaField}: undefined,
590 614 #else
591 615 ${column.javaField}: '',
592 616 #end
... ... @@ -601,9 +625,9 @@ const formData = ref&lt;${simpleClassName}&gt;({
601 625 #set ($subColumns = $subColumnsList.get($index))
602 626 #set ($subJoinColumn = $subJoinColumns.get($index))
603 627 #if ($subTable.subJoinMany)
604   -const ${subClassNameVar}s = ref<${subSimpleClassName}[]>([]) // ${subTable.classComment}数据
  628 +const ${subClassNameVar}s = ref<Partial<${subSimpleClassName}>[]>([]) // ${subTable.classComment}数据
605 629 #else
606   -const ${subClassNameVar} = ref<${subSimpleClassName} | null>(null) // ${subTable.classComment}数据
  630 +const ${subClassNameVar} = ref<Partial<${subSimpleClassName}> | null>(null) // ${subTable.classComment}数据
607 631 #end
608 632 #end
609 633 #end
... ... @@ -646,7 +670,11 @@ function add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.sub
646 670 #foreach ($column in $subColumns)
647 671 #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
648 672 #set ($javaType = $column.javaType.toLowerCase())
649   - ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte" || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") 0#elseif($javaType == "boolean") false#else ''#end,
  673 +#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
  674 + ${column.javaField}: undefined,
  675 +#else
  676 + ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end,
  677 +#end
650 678 #end
651 679 #end
652 680 })
... ... @@ -664,7 +692,11 @@ function add${subClassNameVar.substring(0,1).toUpperCase()}${subClassNameVar.sub
664 692 #foreach ($column in $subColumns)
665 693 #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
666 694 #set ($javaType = $column.javaType.toLowerCase())
667   - ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte" || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") 0#elseif($javaType == "boolean") false#else ''#end,
  695 +#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
  696 + ${column.javaField}: undefined,
  697 +#else
  698 + ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end,
  699 +#end
668 700 #end
669 701 #end
670 702 }
... ... @@ -771,16 +803,16 @@ async function handleSubmit() {
771 803 formLoading.value = true
772 804 try {
773 805 #if ($table.templateType == 10 || $table.templateType == 12)
774   - const data: ${simpleClassName} = {
  806 + const data = {
775 807 ...formData.value,
776 808 #foreach ($subTable in $subTables)
777 809 #set ($index = $foreach.count - 1)
778 810 #set ($subClassNameVar = $subClassNameVars.get($index))
779 811 ${subClassNameVar}#if($subTable.subJoinMany)s#end: ${subClassNameVar}#if($subTable.subJoinMany)s#end.value,
780 812 #end
781   - }
  813 + } as ${simpleClassName}
782 814 #else
783   - const data = formData.value
  815 + const data = formData.value as ${simpleClassName}
784 816 #end
785 817 if (props.id) {
786 818 await update${simpleClassName}(data)
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/form_sub_erp.vue.vm
... ... @@ -4,24 +4,28 @@
4 4 #set ($subPrimaryColumn = $subPrimaryColumns.get($subIndex))
5 5 #set ($subJoinColumn = $subJoinColumns.get($subIndex))
6 6 #set ($subPrimaryJavaType = $subPrimaryColumn.javaType.toLowerCase())
7   -#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte")
  7 +#if(${subPrimaryJavaType} == "long" || ${subPrimaryJavaType} == "integer" || ${subPrimaryJavaType} == "short" || ${subPrimaryJavaType} == "float" || ${subPrimaryJavaType} == "double" || ${subPrimaryJavaType} == "bigdecimal" || ${subPrimaryJavaType} == "byte")
8 8 #set ($subPrimaryTsType = "number")
9 9 #else
10 10 #set ($subPrimaryTsType = "string")
11 11 #end
12 12 #set ($subJoinJavaType = $subJoinColumn.javaType.toLowerCase())
13   -#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte")
  13 +#if(${subJoinJavaType} == "long" || ${subJoinJavaType} == "integer" || ${subJoinJavaType} == "short" || ${subJoinJavaType} == "float" || ${subJoinJavaType} == "double" || ${subJoinJavaType} == "bigdecimal" || ${subJoinJavaType} == "byte")
14 14 #set ($subJoinTsType = "number")
15 15 #else
16 16 #set ($subJoinTsType = "string")
17 17 #end
18 18 #set ($hasDict = 0)
  19 +#set ($hasBoolDict = 0)
19 20 #set ($hasDate = 0)
20 21 #set ($hasDateTime = 0)
21 22 #foreach ($column in $subColumns)
22 23 #if (($column.createOperation || $column.updateOperation) && !$column.primaryKey && $column.id != $subJoinColumn.id)
23 24 #if (($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType)
24 25 #set ($hasDict = 1)
  26 +#if ($column.javaType == "Boolean")
  27 +#set ($hasBoolDict = 1)
  28 +#end
25 29 #end
26 30 #if (${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate")
27 31 #set ($hasDate = 1)
... ... @@ -48,10 +52,14 @@
48 52 label="${column.columnComment}"
49 53 label-width="180rpx"
50 54 prop="${column.javaField}"
  55 +#if ($column.javaType == "Boolean")
  56 + :columns="getBoolDictOptions(DICT_TYPE.${column.dictType.toUpperCase()})"
  57 +#else
51 58 :dict-type="DICT_TYPE.${column.dictType.toUpperCase()}"
52 59 #if ($column.javaType == "String")
53 60 dict-kind="str"
54 61 #end
  62 +#end
55 63 placeholder="请选择${column.columnComment}"
56 64 />
57 65 #elseif ($javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
... ... @@ -76,34 +84,39 @@
76 84 v-model:visible="${column.javaField}Visible"
77 85 :model-value="formData.${column.javaField} || Date.now()"
78 86 type="${pickerType}"
79   -#if ($javaType == "localdatetime")
80 87 @update:model-value="value => formData.${column.javaField} = value"
81   -#else
82   - @update:model-value="value => formData.${column.javaField} = value"
83   -#end
84 88 />
85 89 #else
86 90 <wd-form-item title="${column.columnComment}" title-width="180rpx" prop="${column.javaField}">
87 91 #if ($javaType == "boolean")
88 92 <wd-switch v-model="formData.${column.javaField}" />
89   -#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
  93 +#elseif ($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte")
90 94 <wd-input-number
91 95 v-model="formData.${column.javaField}"
92 96 #if($column.nullable)
93 97 allow-null
94 98 #end
95   -#if ($javaType == "double" || $javaType == "bigdecimal")
  99 +#if ($javaType == "float" || $javaType == "double" || $javaType == "bigdecimal")
96 100 :precision="2"
97 101 input-type="number"
98 102 #end
99 103 :min="0"
100 104 />
101   -#elseif ($column.htmlType == "textarea")
102   - <wd-textarea v-model="formData.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
  105 +#elseif ($column.htmlType == "textarea" || $column.htmlType == "editor")
  106 + <wd-textarea
  107 + v-model="formData.${column.javaField}"
  108 + clearable
  109 + placeholder="请输入${column.columnComment}"
  110 +#if ($column.htmlType == "editor")
  111 + :maxlength="2000"
  112 +#else
  113 + :maxlength="200"
  114 +#end
  115 + />
103 116 #elseif ($column.htmlType == "imageUpload")
104 117 <yd-upload-img v-model="formData.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
105 118 #elseif ($column.htmlType == "fileUpload")
106   - <yd-upload-file v-model="formData.${column.javaField}" directory="${table.moduleName}/${table.businessName}" />
  119 + <yd-upload-file v-model="formData.${column.javaField}" :limit="1" directory="${table.moduleName}/${table.businessName}" />
107 120 #else
108 121 <wd-input v-model="formData.${column.javaField}" clearable placeholder="请输入${column.columnComment}" />
109 122 #end
... ... @@ -130,6 +143,9 @@ import { computed, onMounted, ref } from &#39;vue&#39;
130 143 import { useToast } from '@wot-ui/ui/components/wd-toast'
131 144 import { create${subSimpleClassName}, get${subSimpleClassName}, update${subSimpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'
132 145 import { delay, navigateBackPlus } from '@/utils'
  146 +#if ($hasBoolDict == 1)
  147 +import { getBoolDictOptions } from '@/hooks/useDict'
  148 +#end
133 149 #if ($hasDict == 1)
134 150 import { DICT_TYPE } from '@/utils/constants'
135 151 #end
... ... @@ -159,7 +175,7 @@ definePage({
159 175 const toast = useToast()
160 176 const getTitle = computed(() => props.id ? '编辑${subTable.classComment}' : '新增${subTable.classComment}')
161 177 const formLoading = ref(false) // 表单提交状态
162   -const formData = ref<${subSimpleClassName}>({
  178 +const formData = ref<Partial<${subSimpleClassName}>>({
163 179 #foreach ($column in $subColumns)
164 180 #if ($column.primaryKey)
165 181 ${column.javaField}: undefined,
... ... @@ -167,7 +183,11 @@ const formData = ref&lt;${subSimpleClassName}&gt;({
167 183 ${column.javaField}: props.${column.javaField},
168 184 #elseif ($column.createOperation || $column.updateOperation)
169 185 #set ($javaType = $column.javaType.toLowerCase())
170   - ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte" || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime") 0#elseif($javaType == "boolean") false#else ''#end,
  186 +#if((($column.htmlType == "select" || $column.htmlType == "radio") && $column.dictType && "" != $column.dictType) || $javaType == "date" || $javaType == "localdate" || $javaType == "localdatetime")
  187 + ${column.javaField}: undefined,
  188 +#else
  189 + ${column.javaField}:#if($column.nullable) undefined#elseif($javaType == "long" || $javaType == "integer" || $javaType == "short" || $javaType == "float" || $javaType == "double" || $javaType == "bigdecimal" || $javaType == "byte") 0#elseif($javaType == "boolean") false#else ''#end,
  190 +#end
171 191 #end
172 192 #end
173 193 }) // 表单数据
... ... @@ -206,7 +226,10 @@ async function handleSubmit() {
206 226 }
207 227 formLoading.value = true
208 228 try {
209   - const data = { ...formData.value, ${subJoinColumn.javaField}: props.${subJoinColumn.javaField} ?? formData.value.${subJoinColumn.javaField} }
  229 + const data = {
  230 + ...formData.value,
  231 + ${subJoinColumn.javaField}: props.${subJoinColumn.javaField} ?? formData.value.${subJoinColumn.javaField},
  232 + } as ${subSimpleClassName}
210 233 if (props.id) {
211 234 await update${subSimpleClassName}(data)
212 235 toast.success('修改成功')
... ...
yudao-module-infra/src/main/resources/codegen/vue3_admin_uniapp/views/index_tree.vue.vm
... ... @@ -61,7 +61,7 @@
61 61  
62 62 <script lang="ts" setup>
63 63 #set ($primaryJavaType = $primaryColumn.javaType.toLowerCase())
64   -#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
  64 +#if(${primaryJavaType} == "long" || ${primaryJavaType} == "integer" || ${primaryJavaType} == "short" || ${primaryJavaType} == "float" || ${primaryJavaType} == "double" || ${primaryJavaType} == "bigdecimal" || ${primaryJavaType} == "byte")
65 65 #set ($primaryTsType = "number")
66 66 #else
67 67 #set ($primaryTsType = "string")
... ...
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineUniappTest.java
1 1 package cn.iocoder.yudao.module.infra.service.codegen.inner;
2 2  
  3 +import cn.iocoder.yudao.framework.common.exception.ServiceException;
3 4 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
4 5 import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
5 6 import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
... ... @@ -8,6 +9,7 @@ import com.baomidou.mybatisplus.annotation.DbType;
8 9 import org.junit.jupiter.api.Test;
9 10  
10 11 import java.util.Arrays;
  12 +import java.util.HashMap;
11 13 import java.util.List;
12 14 import java.util.Map;
13 15 import java.util.Objects;
... ... @@ -83,6 +85,8 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
83 85 .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
84 86 .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
85 87 List<CodegenColumnDO> columns = getColumnList("student");
  88 + columns.stream().filter(column -> "memo".equals(column.getJavaField())).findFirst().orElseThrow()
  89 + .setJavaType("Float").setHtmlType("input");
86 90  
87 91 // 调用
88 92 Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns, null, null);
... ... @@ -90,7 +94,11 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
90 94 String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts");
91 95 assertTrue(api.contains("birthday: number"));
92 96 assertTrue(api.contains("createTime?: number"));
  97 + assertTrue(api.contains("memo: number"));
93 98 assertFalse(api.contains("createTime?: Date"));
  99 + String form = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue");
  100 + assertTrue(form.contains("v-model=\"formData.memo\""));
  101 + assertTrue(form.contains(":precision=\"2\""));
94 102 String detail = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/detail/index.vue");
95 103 assertTrue(detail.contains("type=\"danger\""));
96 104 assertFalse(detail.contains("type=\"error\""));
... ... @@ -150,7 +158,7 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
150 158 }
151 159  
152 160 @Test
153   - public void testExecute_duplicateSubPath() {
  161 + public void testExecute_duplicateErpSymbol() {
154 162 // 准备主表
155 163 CodegenTableDO table = getTable("student")
156 164 .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
... ... @@ -169,13 +177,93 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
169 177 List<CodegenColumnDO> erpItemColumns = getColumnList("contact");
170 178  
171 179 // 调用
172   - IllegalStateException exception = assertThrows(IllegalStateException.class,
  180 + ServiceException exception = assertThrows(ServiceException.class,
173 181 () -> codegenEngine.execute(DbType.MYSQL, table, columns,
174 182 Arrays.asList(crmItemTable, erpItemTable), Arrays.asList(crmItemColumns, erpItemColumns)));
175 183 // 断言
176   - assertTrue(exception.getMessage().contains("生成文件路径重复"));
177   - assertTrue(exception.getMessage().contains("subIndex=0"));
178   - assertTrue(exception.getMessage().contains("subIndex=1"));
  184 + assertTrue(exception.getMessage().contains("主子表规范化类名(OrderItem)重复"));
  185 + }
  186 +
  187 + @Test
  188 + public void testExecute_duplicateMasterSymbol() {
  189 + // 准备主表
  190 + CodegenTableDO table = getTable("student")
  191 + .setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
  192 + .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType());
  193 + List<CodegenColumnDO> columns = getColumnList("student");
  194 + // 准备两个规范化名称相同的子表
  195 + CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmOrderItem")
  196 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
  197 + CodegenTableDO erpItemTable = getTable("contact").setModuleName("erp").setClassName("ErpOrderItem")
  198 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
  199 + List<CodegenColumnDO> crmItemColumns = getColumnList("contact");
  200 + List<CodegenColumnDO> erpItemColumns = getColumnList("contact");
  201 +
  202 + // 调用并断言
  203 + ServiceException exception = assertThrows(ServiceException.class,
  204 + () -> codegenEngine.execute(DbType.MYSQL, table, columns,
  205 + Arrays.asList(crmItemTable, erpItemTable), Arrays.asList(crmItemColumns, erpItemColumns)));
  206 + assertTrue(exception.getMessage().contains("主子表规范化类名(OrderItem)重复"));
  207 + }
  208 +
  209 + @Test
  210 + public void testExecute_duplicateMasterFieldNormal() {
  211 + testExecute_duplicateMasterField(CodegenTemplateTypeEnum.MASTER_NORMAL);
  212 + }
  213 +
  214 + @Test
  215 + public void testExecute_duplicateMasterFieldInner() {
  216 + testExecute_duplicateMasterField(CodegenTemplateTypeEnum.MASTER_INNER);
  217 + }
  218 +
  219 + @Test
  220 + public void testExecute_duplicateMasterFieldWithMainNormal() {
  221 + testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum.MASTER_NORMAL);
  222 + }
  223 +
  224 + @Test
  225 + public void testExecute_duplicateMasterFieldWithMainInner() {
  226 + testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum.MASTER_INNER);
  227 + }
  228 +
  229 + @Test
  230 + public void testExecute_duplicateMasterFieldErp() {
  231 + // 准备主表
  232 + CodegenTableDO table = getTable("student")
  233 + .setFrontType(CodegenFrontTypeEnum.VUE3_ADMIN_UNIAPP_WOT.getType())
  234 + .setTemplateType(CodegenTemplateTypeEnum.MASTER_ERP.getType());
  235 + List<CodegenColumnDO> columns = getColumnList("student");
  236 + // 准备属性名同为 items 的子表:Item 一对多、Items 一对一
  237 + CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
  238 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
  239 + CodegenTableDO erpItemsTable = getTable("contact").setModuleName("erp").setClassName("ErpItems")
  240 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(false);
  241 +
  242 + // 调用
  243 + Map<String, String> result = codegenEngine.execute(DbType.MYSQL, table, columns,
  244 + Arrays.asList(crmItemTable, erpItemsTable),
  245 + Arrays.asList(getColumnList("contact"), getColumnList("contact")));
  246 + // 断言
  247 + assertTrue(result.containsKey("yudao-ui-admin-uniapp/src/pages-infra/demo/item/form/index.vue"));
  248 + assertTrue(result.containsKey("yudao-ui-admin-uniapp/src/pages-infra/demo/items/form/index.vue"));
  249 + }
  250 +
  251 + @Test
  252 + public void testRegisterGeneratedSource_duplicatePath() {
  253 + // 准备参数
  254 + Map<String, String> generatedSources = new HashMap<>();
  255 + Map<String, Object> bindingMap = new HashMap<>();
  256 + bindingMap.put("subIndex", 0);
  257 + CodegenEngine.registerGeneratedSource(generatedSources, "same.vue", "form_sub.vm", bindingMap);
  258 + bindingMap.put("subIndex", 1);
  259 +
  260 + // 调用
  261 + IllegalStateException exception = assertThrows(IllegalStateException.class,
  262 + () -> CodegenEngine.registerGeneratedSource(generatedSources, "same.vue", "form_sub.vm", bindingMap));
  263 + // 断言
  264 + assertTrue(exception.getMessage().contains("生成文件路径重复:same.vue"));
  265 + assertTrue(exception.getMessage().contains("form_sub.vm[subIndex=0]"));
  266 + assertTrue(exception.getMessage().contains("form_sub.vm[subIndex=1]"));
179 267 }
180 268  
181 269 @Test
... ... @@ -193,6 +281,46 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
193 281 testExecute_master(CodegenTemplateTypeEnum.MASTER_ERP, "/vue3_admin_uniapp_master_erp");
194 282 }
195 283  
  284 + private void testExecute_duplicateMasterField(CodegenTemplateTypeEnum templateType) {
  285 + // 准备主表
  286 + CodegenTableDO table = getTable("student")
  287 + .setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
  288 + .setTemplateType(templateType.getType());
  289 + List<CodegenColumnDO> columns = getColumnList("student");
  290 + // 准备属性名同为 items 的子表:Item 一对多、Items 一对一
  291 + CodegenTableDO crmItemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
  292 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
  293 + CodegenTableDO erpItemsTable = getTable("contact").setModuleName("erp").setClassName("ErpItems")
  294 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(false);
  295 + List<CodegenColumnDO> crmItemColumns = getColumnList("contact");
  296 + List<CodegenColumnDO> erpItemsColumns = getColumnList("contact");
  297 +
  298 + // 调用并断言
  299 + ServiceException exception = assertThrows(ServiceException.class,
  300 + () -> codegenEngine.execute(DbType.MYSQL, table, columns,
  301 + Arrays.asList(crmItemTable, erpItemsTable), Arrays.asList(crmItemColumns, erpItemsColumns)));
  302 + assertTrue(exception.getMessage().contains("主子表属性名(items)重复"));
  303 + }
  304 +
  305 + private void testExecute_duplicateMasterFieldWithMain(CodegenTemplateTypeEnum templateType) {
  306 + // 准备主表
  307 + CodegenTableDO table = getTable("student")
  308 + .setFrontType(CodegenFrontTypeEnum.VUE3_ELEMENT_PLUS.getType())
  309 + .setTemplateType(templateType.getType());
  310 + List<CodegenColumnDO> columns = getColumnList("student");
  311 + columns.stream().filter(column -> "memo".equals(column.getJavaField())).findFirst().orElseThrow()
  312 + .setJavaField("items");
  313 + // 准备一对多子表 Item,生成属性名 items
  314 + CodegenTableDO itemTable = getTable("contact").setModuleName("crm").setClassName("CrmItem")
  315 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType()).setSubJoinColumnId(100L).setSubJoinMany(true);
  316 +
  317 + // 调用并断言
  318 + ServiceException exception = assertThrows(ServiceException.class,
  319 + () -> codegenEngine.execute(DbType.MYSQL, table, columns,
  320 + List.of(itemTable), List.of(getColumnList("contact"))));
  321 + assertTrue(exception.getMessage().contains("主子表属性名(items)重复"));
  322 + }
  323 +
196 324 private void testExecute_master(CodegenTemplateTypeEnum templateType, String path) {
197 325 // 准备主表
198 326 CodegenTableDO table = getTable("student")
... ... @@ -221,13 +349,16 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
221 349 String api = result.get("yudao-ui-admin-uniapp/src/api/infra/demo/index.ts");
222 350 assertTrue(api.contains("studentTeacher?: StudentTeacher | null"));
223 351 String form = result.get("yudao-ui-admin-uniapp/src/pages-infra/demo/form/index.vue");
224   - assertTrue(form.contains("ref<StudentTeacher | null>(null)"));
  352 + assertTrue(form.contains("ref<Partial<StudentTeacher> | null>(null)"));
225 353 assertTrue(form.contains("studentTeacher.value = studentTeacherData || null"));
226 354 assertTrue(form.contains(":model-value=\"item.birthday || Date.now()\""));
227 355 assertTrue(form.contains("v-model=\"item.sex\""));
228 356 assertTrue(form.contains("<yd-upload-img v-model=\"item.avatar\""));
229 357 assertTrue(form.contains("<yd-upload-file v-model=\"item.video\""));
230   - assertTrue(form.contains("<wd-textarea v-model=\"item.description\""));
  358 + assertTrue(form.contains("<yd-upload-file v-model=\"item.video\" :limit=\"1\""));
  359 + assertTrue(form.contains("v-model=\"item.description\""));
  360 + assertTrue(form.contains("v-model=\"item.memo\""));
  361 + assertTrue(form.contains("birthday: undefined"));
231 362 assertTrue(form.contains("function validateSubTables()"));
232 363 } else {
233 364 String contactForm = result.get(
... ... @@ -236,7 +367,11 @@ public class CodegenEngineUniappTest extends CodegenEngineAbstractTest {
236 367 assertTrue(contactForm.contains("v-model=\"formData.sex\""));
237 368 assertTrue(contactForm.contains("<yd-upload-img v-model=\"formData.avatar\""));
238 369 assertTrue(contactForm.contains("<yd-upload-file v-model=\"formData.video\""));
239   - assertTrue(contactForm.contains("<wd-textarea v-model=\"formData.description\""));
  370 + assertTrue(contactForm.contains("<yd-upload-file v-model=\"formData.video\" :limit=\"1\""));
  371 + assertTrue(contactForm.contains("v-model=\"formData.description\""));
  372 + assertTrue(contactForm.contains("v-model=\"formData.memo\""));
  373 + assertTrue(contactForm.contains("birthday: undefined"));
  374 + assertTrue(contactForm.contains(":columns=\"getBoolDictOptions"));
240 375 }
241 376 }
242 377  
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/form/index
... ... @@ -65,13 +65,15 @@
65 65 <yd-upload-img v-model="formData.avatar" directory="infra/demo" />
66 66 </wd-form-item>
67 67 <wd-form-item title="附件" title-width="180rpx" prop="video">
68   - <yd-upload-file v-model="formData.video" directory="infra/demo" />
  68 + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
69 69 </wd-form-item>
70 70 <wd-form-item title="备注" title-width="180rpx" prop="memo">
71   - <wd-input
  71 + <wd-textarea
72 72 v-model="formData.memo"
73 73 clearable
74 74 placeholder="请输入备注"
  75 + :maxlength="2000"
  76 + show-word-limit
75 77 />
76 78 </wd-form-item>
77 79 </wd-cell-group>
... ... @@ -120,13 +122,13 @@ definePage({
120 122 const toast = useToast()
121 123 const getTitle = computed(() => props.id ? '编辑学生' : '新增学生')
122 124 const formLoading = ref(false) // 表单提交状态
123   -const formData = ref<Student>({
  125 +const formData = ref<Partial<Student>>({
124 126 id: undefined,
125 127 name: '',
126 128 description: '',
127   - birthday: 0,
128   - sex: 0,
129   - enabled: false,
  129 + birthday: undefined,
  130 + sex: undefined,
  131 + enabled: undefined,
130 132 avatar: '',
131 133 video: '',
132 134 memo: '',
... ... @@ -166,7 +168,7 @@ async function handleSubmit() {
166 168  
167 169 formLoading.value = true
168 170 try {
169   - const data = formData.value
  171 + const data = formData.value as Student
170 172 if (props.id) {
171 173 await updateStudent(data)
172 174 toast.success('修改成功')
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/student-contact/form/index
... ... @@ -10,7 +10,12 @@
10 10 <wd-input v-model="formData.name" clearable placeholder="请输入名字" />
11 11 </wd-form-item>
12 12 <wd-form-item title="简介" title-width="180rpx" prop="description">
13   - <wd-textarea v-model="formData.description" clearable placeholder="请输入简介" />
  13 + <wd-textarea
  14 + v-model="formData.description"
  15 + clearable
  16 + placeholder="请输入简介"
  17 + :maxlength="200"
  18 + />
14 19 </wd-form-item>
15 20 <wd-form-item
16 21 title="出生日期"
... ... @@ -40,17 +45,22 @@
40 45 label="是否有效"
41 46 label-width="180rpx"
42 47 prop="enabled"
43   - :dict-type="DICT_TYPE.INFRA_BOOLEAN_STRING"
  48 + :columns="getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
44 49 placeholder="请选择是否有效"
45 50 />
46 51 <wd-form-item title="头像" title-width="180rpx" prop="avatar">
47 52 <yd-upload-img v-model="formData.avatar" directory="infra/demo" />
48 53 </wd-form-item>
49 54 <wd-form-item title="附件" title-width="180rpx" prop="video">
50   - <yd-upload-file v-model="formData.video" directory="infra/demo" />
  55 + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
51 56 </wd-form-item>
52 57 <wd-form-item title="备注" title-width="180rpx" prop="memo">
53   - <wd-input v-model="formData.memo" clearable placeholder="请输入备注" />
  58 + <wd-textarea
  59 + v-model="formData.memo"
  60 + clearable
  61 + placeholder="请输入备注"
  62 + :maxlength="2000"
  63 + />
54 64 </wd-form-item>
55 65 </wd-cell-group>
56 66 </wd-form>
... ... @@ -71,6 +81,7 @@ import { computed, onMounted, ref } from &#39;vue&#39;
71 81 import { useToast } from '@wot-ui/ui/components/wd-toast'
72 82 import { createStudentContact, getStudentContact, updateStudentContact } from '@/api/infra/demo'
73 83 import { delay, navigateBackPlus } from '@/utils'
  84 +import { getBoolDictOptions } from '@/hooks/useDict'
74 85 import { DICT_TYPE } from '@/utils/constants'
75 86 import { formatDateTime } from '@/utils/date'
76 87 import { createFormSchema } from '@/utils/wot'
... ... @@ -87,14 +98,14 @@ definePage({
87 98 const toast = useToast()
88 99 const getTitle = computed(() => props.id ? '编辑学生联系人' : '新增学生联系人')
89 100 const formLoading = ref(false) // 表单提交状态
90   -const formData = ref<StudentContact>({
  101 +const formData = ref<Partial<StudentContact>>({
91 102 id: undefined,
92 103 studentId: props.studentId,
93 104 name: '',
94 105 description: '',
95   - birthday: 0,
96   - sex: 0,
97   - enabled: false,
  106 + birthday: undefined,
  107 + sex: undefined,
  108 + enabled: undefined,
98 109 avatar: '',
99 110 video: undefined,
100 111 memo: '',
... ... @@ -132,7 +143,10 @@ async function handleSubmit() {
132 143 }
133 144 formLoading.value = true
134 145 try {
135   - const data = { ...formData.value, studentId: props.studentId ?? formData.value.studentId }
  146 + const data = {
  147 + ...formData.value,
  148 + studentId: props.studentId ?? formData.value.studentId,
  149 + } as StudentContact
136 150 if (props.id) {
137 151 await updateStudentContact(data)
138 152 toast.success('修改成功')
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_erp/vue/student-teacher/form/index
... ... @@ -10,7 +10,12 @@
10 10 <wd-input v-model="formData.name" clearable placeholder="请输入名字" />
11 11 </wd-form-item>
12 12 <wd-form-item title="简介" title-width="180rpx" prop="description">
13   - <wd-textarea v-model="formData.description" clearable placeholder="请输入简介" />
  13 + <wd-textarea
  14 + v-model="formData.description"
  15 + clearable
  16 + placeholder="请输入简介"
  17 + :maxlength="200"
  18 + />
14 19 </wd-form-item>
15 20 <wd-form-item
16 21 title="出生日期"
... ... @@ -40,17 +45,22 @@
40 45 label="是否有效"
41 46 label-width="180rpx"
42 47 prop="enabled"
43   - :dict-type="DICT_TYPE.INFRA_BOOLEAN_STRING"
  48 + :columns="getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)"
44 49 placeholder="请选择是否有效"
45 50 />
46 51 <wd-form-item title="头像" title-width="180rpx" prop="avatar">
47 52 <yd-upload-img v-model="formData.avatar" directory="infra/demo" />
48 53 </wd-form-item>
49 54 <wd-form-item title="附件" title-width="180rpx" prop="video">
50   - <yd-upload-file v-model="formData.video" directory="infra/demo" />
  55 + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
51 56 </wd-form-item>
52 57 <wd-form-item title="备注" title-width="180rpx" prop="memo">
53   - <wd-input v-model="formData.memo" clearable placeholder="请输入备注" />
  58 + <wd-textarea
  59 + v-model="formData.memo"
  60 + clearable
  61 + placeholder="请输入备注"
  62 + :maxlength="2000"
  63 + />
54 64 </wd-form-item>
55 65 </wd-cell-group>
56 66 </wd-form>
... ... @@ -71,6 +81,7 @@ import { computed, onMounted, ref } from &#39;vue&#39;
71 81 import { useToast } from '@wot-ui/ui/components/wd-toast'
72 82 import { createStudentTeacher, getStudentTeacher, updateStudentTeacher } from '@/api/infra/demo'
73 83 import { delay, navigateBackPlus } from '@/utils'
  84 +import { getBoolDictOptions } from '@/hooks/useDict'
74 85 import { DICT_TYPE } from '@/utils/constants'
75 86 import { formatDateTime } from '@/utils/date'
76 87 import { createFormSchema } from '@/utils/wot'
... ... @@ -87,14 +98,14 @@ definePage({
87 98 const toast = useToast()
88 99 const getTitle = computed(() => props.id ? '编辑学生班主任' : '新增学生班主任')
89 100 const formLoading = ref(false) // 表单提交状态
90   -const formData = ref<StudentTeacher>({
  101 +const formData = ref<Partial<StudentTeacher>>({
91 102 id: undefined,
92 103 studentId: props.studentId,
93 104 name: '',
94 105 description: '',
95   - birthday: 0,
96   - sex: 0,
97   - enabled: false,
  106 + birthday: undefined,
  107 + sex: undefined,
  108 + enabled: undefined,
98 109 avatar: '',
99 110 video: undefined,
100 111 memo: '',
... ... @@ -132,7 +143,10 @@ async function handleSubmit() {
132 143 }
133 144 formLoading.value = true
134 145 try {
135   - const data = { ...formData.value, studentId: props.studentId ?? formData.value.studentId }
  146 + const data = {
  147 + ...formData.value,
  148 + studentId: props.studentId ?? formData.value.studentId,
  149 + } as StudentTeacher
136 150 if (props.id) {
137 151 await updateStudentTeacher(data)
138 152 toast.success('修改成功')
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_inner/vue/form/index
... ... @@ -65,13 +65,15 @@
65 65 <yd-upload-img v-model="formData.avatar" directory="infra/demo" />
66 66 </wd-form-item>
67 67 <wd-form-item title="附件" title-width="180rpx" prop="video">
68   - <yd-upload-file v-model="formData.video" directory="infra/demo" />
  68 + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
69 69 </wd-form-item>
70 70 <wd-form-item title="备注" title-width="180rpx" prop="memo">
71   - <wd-input
  71 + <wd-textarea
72 72 v-model="formData.memo"
73 73 clearable
74 74 placeholder="请输入备注"
  75 + :maxlength="2000"
  76 + show-word-limit
75 77 />
76 78 </wd-form-item>
77 79 </wd-cell-group>
... ... @@ -102,7 +104,12 @@
102 104 <wd-input v-model="item.name" clearable placeholder="请输入名字" />
103 105 </wd-form-item>
104 106 <wd-form-item title="简介" title-width="180rpx">
105   - <wd-textarea v-model="item.description" clearable placeholder="请输入简介" />
  107 + <wd-textarea
  108 + v-model="item.description"
  109 + clearable
  110 + placeholder="请输入简介"
  111 + :maxlength="200"
  112 + />
106 113 </wd-form-item>
107 114 <wd-form-item
108 115 title="出生日期"
... ... @@ -140,10 +147,15 @@
140 147 <yd-upload-img v-model="item.avatar" directory="infra/demo" />
141 148 </wd-form-item>
142 149 <wd-form-item title="附件" title-width="180rpx">
143   - <yd-upload-file v-model="item.video" directory="infra/demo" />
  150 + <yd-upload-file v-model="item.video" :limit="1" directory="infra/demo" />
144 151 </wd-form-item>
145 152 <wd-form-item title="备注" title-width="180rpx">
146   - <wd-input v-model="item.memo" clearable placeholder="请输入备注" />
  153 + <wd-textarea
  154 + v-model="item.memo"
  155 + clearable
  156 + placeholder="请输入备注"
  157 + :maxlength="2000"
  158 + />
147 159 </wd-form-item>
148 160 </view>
149 161 </view>
... ... @@ -170,7 +182,12 @@
170 182 <wd-input v-model="studentTeacher.name" clearable placeholder="请输入名字" />
171 183 </wd-form-item>
172 184 <wd-form-item title="简介" title-width="180rpx">
173   - <wd-textarea v-model="studentTeacher.description" clearable placeholder="请输入简介" />
  185 + <wd-textarea
  186 + v-model="studentTeacher.description"
  187 + clearable
  188 + placeholder="请输入简介"
  189 + :maxlength="200"
  190 + />
174 191 </wd-form-item>
175 192 <wd-form-item
176 193 title="出生日期"
... ... @@ -208,10 +225,15 @@
208 225 <yd-upload-img v-model="studentTeacher.avatar" directory="infra/demo" />
209 226 </wd-form-item>
210 227 <wd-form-item title="附件" title-width="180rpx">
211   - <yd-upload-file v-model="studentTeacher.video" directory="infra/demo" />
  228 + <yd-upload-file v-model="studentTeacher.video" :limit="1" directory="infra/demo" />
212 229 </wd-form-item>
213 230 <wd-form-item title="备注" title-width="180rpx">
214   - <wd-input v-model="studentTeacher.memo" clearable placeholder="请输入备注" />
  231 + <wd-textarea
  232 + v-model="studentTeacher.memo"
  233 + clearable
  234 + placeholder="请输入备注"
  235 + :maxlength="2000"
  236 + />
215 237 </wd-form-item>
216 238 </view>
217 239 </view>
... ... @@ -267,19 +289,19 @@ definePage({
267 289 const toast = useToast()
268 290 const getTitle = computed(() => props.id ? '编辑学生' : '新增学生')
269 291 const formLoading = ref(false) // 表单提交状态
270   -const formData = ref<Student>({
  292 +const formData = ref<Partial<Student>>({
271 293 id: undefined,
272 294 name: '',
273 295 description: '',
274   - birthday: 0,
275   - sex: 0,
276   - enabled: false,
  296 + birthday: undefined,
  297 + sex: undefined,
  298 + enabled: undefined,
277 299 avatar: '',
278 300 video: '',
279 301 memo: '',
280 302 }) // 表单数据
281   -const studentContacts = ref<StudentContact[]>([]) // 学生联系人数据
282   -const studentTeacher = ref<StudentTeacher | null>(null) // 学生班主任数据
  303 +const studentContacts = ref<Partial<StudentContact>[]>([]) // 学生联系人数据
  304 +const studentTeacher = ref<Partial<StudentTeacher> | null>(null) // 学生班主任数据
283 305 const birthdayVisible = ref(false) // 出生日期选择器状态
284 306 const subDateVisible = ref<Record<string, boolean>>({}) // 子表日期选择器状态
285 307 const formSchema = createFormSchema({
... ... @@ -299,9 +321,9 @@ function addStudentContact() {
299 321 studentContacts.value.push({
300 322 name: '',
301 323 description: '',
302   - birthday: 0,
303   - sex: 0,
304   - enabled: false,
  324 + birthday: undefined,
  325 + sex: undefined,
  326 + enabled: undefined,
305 327 avatar: '',
306 328 video: undefined,
307 329 memo: '',
... ... @@ -318,9 +340,9 @@ function addStudentTeacher() {
318 340 studentTeacher.value = {
319 341 name: '',
320 342 description: '',
321   - birthday: 0,
322   - sex: 0,
323   - enabled: false,
  343 + birthday: undefined,
  344 + sex: undefined,
  345 + enabled: undefined,
324 346 avatar: '',
325 347 video: undefined,
326 348 memo: '',
... ... @@ -429,11 +451,11 @@ async function handleSubmit() {
429 451  
430 452 formLoading.value = true
431 453 try {
432   - const data: Student = {
  454 + const data = {
433 455 ...formData.value,
434 456 studentContacts: studentContacts.value,
435 457 studentTeacher: studentTeacher.value,
436   - }
  458 + } as Student
437 459 if (props.id) {
438 460 await updateStudent(data)
439 461 toast.success('修改成功')
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_master_normal/vue/form/index
... ... @@ -65,13 +65,15 @@
65 65 <yd-upload-img v-model="formData.avatar" directory="infra/demo" />
66 66 </wd-form-item>
67 67 <wd-form-item title="附件" title-width="180rpx" prop="video">
68   - <yd-upload-file v-model="formData.video" directory="infra/demo" />
  68 + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
69 69 </wd-form-item>
70 70 <wd-form-item title="备注" title-width="180rpx" prop="memo">
71   - <wd-input
  71 + <wd-textarea
72 72 v-model="formData.memo"
73 73 clearable
74 74 placeholder="请输入备注"
  75 + :maxlength="2000"
  76 + show-word-limit
75 77 />
76 78 </wd-form-item>
77 79 </wd-cell-group>
... ... @@ -102,7 +104,12 @@
102 104 <wd-input v-model="item.name" clearable placeholder="请输入名字" />
103 105 </wd-form-item>
104 106 <wd-form-item title="简介" title-width="180rpx">
105   - <wd-textarea v-model="item.description" clearable placeholder="请输入简介" />
  107 + <wd-textarea
  108 + v-model="item.description"
  109 + clearable
  110 + placeholder="请输入简介"
  111 + :maxlength="200"
  112 + />
106 113 </wd-form-item>
107 114 <wd-form-item
108 115 title="出生日期"
... ... @@ -140,10 +147,15 @@
140 147 <yd-upload-img v-model="item.avatar" directory="infra/demo" />
141 148 </wd-form-item>
142 149 <wd-form-item title="附件" title-width="180rpx">
143   - <yd-upload-file v-model="item.video" directory="infra/demo" />
  150 + <yd-upload-file v-model="item.video" :limit="1" directory="infra/demo" />
144 151 </wd-form-item>
145 152 <wd-form-item title="备注" title-width="180rpx">
146   - <wd-input v-model="item.memo" clearable placeholder="请输入备注" />
  153 + <wd-textarea
  154 + v-model="item.memo"
  155 + clearable
  156 + placeholder="请输入备注"
  157 + :maxlength="2000"
  158 + />
147 159 </wd-form-item>
148 160 </view>
149 161 </view>
... ... @@ -170,7 +182,12 @@
170 182 <wd-input v-model="studentTeacher.name" clearable placeholder="请输入名字" />
171 183 </wd-form-item>
172 184 <wd-form-item title="简介" title-width="180rpx">
173   - <wd-textarea v-model="studentTeacher.description" clearable placeholder="请输入简介" />
  185 + <wd-textarea
  186 + v-model="studentTeacher.description"
  187 + clearable
  188 + placeholder="请输入简介"
  189 + :maxlength="200"
  190 + />
174 191 </wd-form-item>
175 192 <wd-form-item
176 193 title="出生日期"
... ... @@ -208,10 +225,15 @@
208 225 <yd-upload-img v-model="studentTeacher.avatar" directory="infra/demo" />
209 226 </wd-form-item>
210 227 <wd-form-item title="附件" title-width="180rpx">
211   - <yd-upload-file v-model="studentTeacher.video" directory="infra/demo" />
  228 + <yd-upload-file v-model="studentTeacher.video" :limit="1" directory="infra/demo" />
212 229 </wd-form-item>
213 230 <wd-form-item title="备注" title-width="180rpx">
214   - <wd-input v-model="studentTeacher.memo" clearable placeholder="请输入备注" />
  231 + <wd-textarea
  232 + v-model="studentTeacher.memo"
  233 + clearable
  234 + placeholder="请输入备注"
  235 + :maxlength="2000"
  236 + />
215 237 </wd-form-item>
216 238 </view>
217 239 </view>
... ... @@ -267,19 +289,19 @@ definePage({
267 289 const toast = useToast()
268 290 const getTitle = computed(() => props.id ? '编辑学生' : '新增学生')
269 291 const formLoading = ref(false) // 表单提交状态
270   -const formData = ref<Student>({
  292 +const formData = ref<Partial<Student>>({
271 293 id: undefined,
272 294 name: '',
273 295 description: '',
274   - birthday: 0,
275   - sex: 0,
276   - enabled: false,
  296 + birthday: undefined,
  297 + sex: undefined,
  298 + enabled: undefined,
277 299 avatar: '',
278 300 video: '',
279 301 memo: '',
280 302 }) // 表单数据
281   -const studentContacts = ref<StudentContact[]>([]) // 学生联系人数据
282   -const studentTeacher = ref<StudentTeacher | null>(null) // 学生班主任数据
  303 +const studentContacts = ref<Partial<StudentContact>[]>([]) // 学生联系人数据
  304 +const studentTeacher = ref<Partial<StudentTeacher> | null>(null) // 学生班主任数据
283 305 const birthdayVisible = ref(false) // 出生日期选择器状态
284 306 const subDateVisible = ref<Record<string, boolean>>({}) // 子表日期选择器状态
285 307 const formSchema = createFormSchema({
... ... @@ -299,9 +321,9 @@ function addStudentContact() {
299 321 studentContacts.value.push({
300 322 name: '',
301 323 description: '',
302   - birthday: 0,
303   - sex: 0,
304   - enabled: false,
  324 + birthday: undefined,
  325 + sex: undefined,
  326 + enabled: undefined,
305 327 avatar: '',
306 328 video: undefined,
307 329 memo: '',
... ... @@ -318,9 +340,9 @@ function addStudentTeacher() {
318 340 studentTeacher.value = {
319 341 name: '',
320 342 description: '',
321   - birthday: 0,
322   - sex: 0,
323   - enabled: false,
  343 + birthday: undefined,
  344 + sex: undefined,
  345 + enabled: undefined,
324 346 avatar: '',
325 347 video: undefined,
326 348 memo: '',
... ... @@ -429,11 +451,11 @@ async function handleSubmit() {
429 451  
430 452 formLoading.value = true
431 453 try {
432   - const data: Student = {
  454 + const data = {
433 455 ...formData.value,
434 456 studentContacts: studentContacts.value,
435 457 studentTeacher: studentTeacher.value,
436   - }
  458 + } as Student
437 459 if (props.id) {
438 460 await updateStudent(data)
439 461 toast.success('修改成功')
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_one/vue/form/index
... ... @@ -65,13 +65,15 @@
65 65 <yd-upload-img v-model="formData.avatar" directory="infra/demo" />
66 66 </wd-form-item>
67 67 <wd-form-item title="附件" title-width="180rpx" prop="video">
68   - <yd-upload-file v-model="formData.video" directory="infra/demo" />
  68 + <yd-upload-file v-model="formData.video" :limit="1" directory="infra/demo" />
69 69 </wd-form-item>
70 70 <wd-form-item title="备注" title-width="180rpx" prop="memo">
71   - <wd-input
  71 + <wd-textarea
72 72 v-model="formData.memo"
73 73 clearable
74 74 placeholder="请输入备注"
  75 + :maxlength="2000"
  76 + show-word-limit
75 77 />
76 78 </wd-form-item>
77 79 </wd-cell-group>
... ... @@ -120,13 +122,13 @@ definePage({
120 122 const toast = useToast()
121 123 const getTitle = computed(() => props.id ? '编辑学生' : '新增学生')
122 124 const formLoading = ref(false) // 表单提交状态
123   -const formData = ref<Student>({
  125 +const formData = ref<Partial<Student>>({
124 126 id: undefined,
125 127 name: '',
126 128 description: '',
127   - birthday: 0,
128   - sex: 0,
129   - enabled: false,
  129 + birthday: undefined,
  130 + sex: undefined,
  131 + enabled: undefined,
130 132 avatar: '',
131 133 video: '',
132 134 memo: '',
... ... @@ -166,7 +168,7 @@ async function handleSubmit() {
166 168  
167 169 formLoading.value = true
168 170 try {
169   - const data = formData.value
  171 + const data = formData.value as Student
170 172 if (props.id) {
171 173 await updateStudent(data)
172 174 toast.success('修改成功')
... ...
yudao-module-infra/src/test/resources/codegen/vue3_admin_uniapp_tree/vue/form/index
... ... @@ -71,7 +71,7 @@ definePage({
71 71 const toast = useToast()
72 72 const getTitle = computed(() => props.id ? '编辑分类' : '新增分类')
73 73 const formLoading = ref(false) // 表单提交状态
74   -const formData = ref<Category>({
  74 +const formData = ref<Partial<Category>>({
75 75 id: undefined,
76 76 name: '',
77 77 parentId: props.parentId ?? 0,
... ... @@ -105,7 +105,7 @@ async function handleSubmit() {
105 105  
106 106 formLoading.value = true
107 107 try {
108   - const data = formData.value
  108 + const data = formData.value as Category
109 109 if (props.id) {
110 110 await updateCategory(data)
111 111 toast.success('修改成功')
... ...