Commit b3296b4e9711bc36e3e843f4a3076217ba592d0e

Authored by YunaiV
1 parent e001bbbd

fix(codegen): 修复 Vben5 Antdv Next 模板兼容问题

- 修正组件根导入、子组件用法及内部模板引用
- 按实际生成分支条件导入 Tabs、FormItem、SelectOption 和 RangePicker
- 修复一对一及一对多 join-select 场景的导入判定
- 补充模板类型、RangePicker helper 和稀疏主子表回归测试
Showing 15 changed files with 576 additions and 98 deletions
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/form.vue.vm
  1 +#set ($selectOptionEnable = false)
  2 +#foreach ($column in $columns)
  3 + #if (($column.createOperation || $column.updateOperation) && $column.htmlType == "select")
  4 + #set ($selectOptionEnable = true)
  5 + #end
  6 +#end
1 7 <script lang="ts" setup>
2   -import type { Rule } from 'antdv-next/es/form';
  8 +import type { Rule } from 'antdv-next';
3 9 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
4 10  
5 11 import { computed, ref } from 'vue';
... ... @@ -9,7 +15,7 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;;
9 15 import { getDictOptions } from '@vben/hooks';
10 16 import { Tinymce as RichTextarea } from '#/components/tinymce';
11 17 import { ImageUpload, FileUpload } from "#/components/upload";
12   -import { message, Tabs, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'antdv-next';
  18 +import { Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end#if ( $table.templateType == 10 || $table.templateType == 12 ), TabPane, Tabs#end, TextArea, TreeSelect } from 'antdv-next';
13 19 #if($table.templateType == 2)## 树表需要导入这些
14 20 import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}';
15 21 import { handleTree } from '@vben/utils'
... ... @@ -204,7 +210,7 @@ const [Modal, modalApi] = useVbenModal({
204 210 #set ($dictMethod = "boolean")
205 211 #end
206 212 #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
207   - <Form.Item label="${comment}" name="${javaField}">
  213 + <FormItem label="${comment}" name="${javaField}">
208 214 <TreeSelect
209 215 v-model:value="formData.${javaField}"
210 216 :treeData="${classNameVar}Tree"
... ... @@ -225,25 +231,25 @@ const [Modal, modalApi] = useVbenModal({
225 231 treeDefaultExpandAll
226 232 placeholder="请选择${comment}"
227 233 />
228   - </Form.Item>
  234 + </FormItem>
229 235 #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
230   - <Form.Item label="${comment}" name="${javaField}">
  236 + <FormItem label="${comment}" name="${javaField}">
231 237 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
232   - </Form.Item>
  238 + </FormItem>
233 239 #elseif($column.htmlType == "imageUpload")## 图片上传
234   - <Form.Item label="${comment}" name="${javaField}">
  240 + <FormItem label="${comment}" name="${javaField}">
235 241 <ImageUpload v-model:value="formData.${javaField}" />
236   - </Form.Item>
  242 + </FormItem>
237 243 #elseif($column.htmlType == "fileUpload")## 文件上传
238   - <Form.Item label="${comment}" name="${javaField}">
  244 + <FormItem label="${comment}" name="${javaField}">
239 245 <FileUpload v-model:value="formData.${javaField}" />
240   - </Form.Item>
  246 + </FormItem>
241 247 #elseif($column.htmlType == "editor")## 文本编辑器
242   - <Form.Item label="${comment}" name="${javaField}">
  248 + <FormItem label="${comment}" name="${javaField}">
243 249 <RichTextarea v-model="formData.${javaField}" height="500px" />
244   - </Form.Item>
  250 + </FormItem>
245 251 #elseif($column.htmlType == "select")## 下拉框
246   - <Form.Item label="${comment}" name="${javaField}">
  252 + <FormItem label="${comment}" name="${javaField}">
247 253 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
248 254 #if ("" != $dictType)## 有数据字典
249 255 <SelectOption
... ... @@ -257,9 +263,9 @@ const [Modal, modalApi] = useVbenModal({
257 263 <SelectOption label="请选择字典生成" value="" />
258 264 #end
259 265 </Select>
260   - </Form.Item>
  266 + </FormItem>
261 267 #elseif($column.htmlType == "checkbox")## 多选框
262   - <Form.Item label="${comment}" name="${javaField}">
  268 + <FormItem label="${comment}" name="${javaField}">
263 269 <CheckboxGroup v-model:value="formData.${javaField}">
264 270 #if ("" != $dictType)## 有数据字典
265 271 <Checkbox
... ... @@ -273,9 +279,9 @@ const [Modal, modalApi] = useVbenModal({
273 279 <Checkbox label="请选择字典生成" />
274 280 #end
275 281 </CheckboxGroup>
276   - </Form.Item>
  282 + </FormItem>
277 283 #elseif($column.htmlType == "radio")## 单选框
278   - <Form.Item label="${comment}" name="${javaField}">
  284 + <FormItem label="${comment}" name="${javaField}">
279 285 <RadioGroup v-model:value="formData.${javaField}">
280 286 #if ("" != $dictType)## 有数据字典
281 287 <Radio
... ... @@ -289,19 +295,19 @@ const [Modal, modalApi] = useVbenModal({
289 295 <Radio value="1">请选择字典生成</Radio>
290 296 #end
291 297 </RadioGroup>
292   - </Form.Item>
  298 + </FormItem>
293 299 #elseif($column.htmlType == "datetime")## 时间框
294   - <Form.Item label="${comment}" name="${javaField}">
  300 + <FormItem label="${comment}" name="${javaField}">
295 301 <DatePicker
296 302 v-model:value="formData.${javaField}"
297 303 valueFormat="x"
298 304 placeholder="选择${comment}"
299 305 />
300   - </Form.Item>
  306 + </FormItem>
301 307 #elseif($column.htmlType == "textarea")## 文本框
302   - <Form.Item label="${comment}" name="${javaField}">
  308 + <FormItem label="${comment}" name="${javaField}">
303 309 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
304   - </Form.Item>
  310 + </FormItem>
305 311 #end
306 312 #end
307 313 #end
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/import.vue.vm
1 1 <script lang="ts" setup>
2   -import type { FileType } from 'antdv-next/es/upload/interface';
3   -
4 2 import { ref } from 'vue';
5 3  
6 4 import { useVbenModal } from '@vben/common-ui';
... ... @@ -43,8 +41,8 @@ const [Modal, modalApi] = useVbenModal({
43 41 });
44 42  
45 43 /** 上传前:拦截 antd Upload 的自动上传,文件存到 ref */
46   -function beforeUpload(file: FileType) {
47   - fileRef.value = file as unknown as File;
  44 +function beforeUpload(file: File) {
  45 + fileRef.value = file;
48 46 return false;
49 47 }
50 48  
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/index.vue.vm
  1 +#set ($rangePickerEnable = false)
  2 +#set ($selectOptionEnable = false)
  3 +#foreach ($column in $columns)
  4 + #if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
  5 + #set ($rangePickerEnable = true)
  6 + #end
  7 + #if ($column.listOperation && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox"))
  8 + #set ($selectOptionEnable = true)
  9 + #end
  10 +#end
1 11 <script lang="ts" setup>
2 12 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
3 13  
... ... @@ -8,12 +18,14 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;;
8 18 import { getDictOptions } from '@vben/hooks';
9 19 import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
10 20 import { cloneDeep, downloadFileFromBlobPart, formatDateTime, isEmpty } from '@vben/utils';
11   -import { Button, Card, message, Tabs, Pagination, Form, RangePicker, DatePicker, Select, Input } from 'antdv-next';
  21 +import { Button, Card, DatePicker, Form, FormItem, Input, message, Pagination#if ($rangePickerEnable), DateRangePicker as RangePicker#end, Select#if ($selectOptionEnable), SelectOption#end#if ( $table.templateType == 11 || $table.templateType == 12 ), TabPane, Tabs#end } from 'antdv-next';
12 22 import ${simpleClassName}Form from './modules/form.vue';
13 23 import { Download, Plus, RefreshCw, Search, Trash2 } from '@vben/icons';
14 24 import { DictTag } from '#/components/dict-tag';
15 25 import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
  26 +#if ($rangePickerEnable)
16 27 import { getRangePickerDefaultProps } from '#/utils/rangePickerProps';
  28 +#end
17 29  
18 30 ## 特殊:主子表专属逻辑
19 31 #if ( $table.templateType == 11 || $table.templateType == 12 )
... ... @@ -249,7 +261,7 @@ onMounted(() =&gt; {
249 261 #set ($dictMethod = "boolean")
250 262 #end
251 263 #if ($column.htmlType == "input")
252   - <Form.Item label="${comment}" name="${javaField}">
  264 + <FormItem label="${comment}" name="${javaField}">
253 265 <Input
254 266 v-model:value="queryParams.${javaField}"
255 267 placeholder="请输入${comment}"
... ... @@ -257,9 +269,9 @@ onMounted(() =&gt; {
257 269 @pressEnter="handleQuery"
258 270 class="w-full"
259 271 />
260   - </Form.Item>
  272 + </FormItem>
261 273 #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
262   - <Form.Item label="${comment}" name="${javaField}">
  274 + <FormItem label="${comment}" name="${javaField}">
263 275 <Select
264 276 v-model:value="queryParams.${javaField}"
265 277 placeholder="请选择${comment}"
... ... @@ -278,10 +290,10 @@ onMounted(() =&gt; {
278 290 <SelectOption label="请选择字典生成" value="" />
279 291 #end
280 292 </Select>
281   - </Form.Item>
  293 + </FormItem>
282 294 #elseif($column.htmlType == "datetime")
283 295 #if ($column.listOperationCondition != "BETWEEN")## 非范围
284   - <Form.Item label="${comment}" name="${javaField}">
  296 + <FormItem label="${comment}" name="${javaField}">
285 297 <DatePicker
286 298 v-model:value="queryParams.${javaField}"
287 299 valueFormat="YYYY-MM-DD"
... ... @@ -289,25 +301,25 @@ onMounted(() =&gt; {
289 301 allowClear
290 302 class="w-full"
291 303 />
292   - </Form.Item>
  304 + </FormItem>
293 305 #else## 范围
294   - <Form.Item label="${comment}" name="${javaField}">
  306 + <FormItem label="${comment}" name="${javaField}">
295 307 <RangePicker
296 308 v-model:value="queryParams.${javaField}"
297 309 v-bind="getRangePickerDefaultProps()"
298 310 class="w-full"
299 311 />
300   - </Form.Item>
  312 + </FormItem>
301 313 #end
302 314 #end
303 315 #end
304 316 #end
305   - <Form.Item>
  317 + <FormItem>
306 318 <Button class="ml-2" @click="resetQuery"> 重置 </Button>
307 319 <Button class="ml-2" @click="handleQuery" type="primary">
308 320 搜索
309 321 </Button>
310   - </Form.Item>
  322 + </FormItem>
311 323 </Form>
312 324 </Card>
313 325  
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_erp.vue.vm
... ... @@ -2,8 +2,14 @@
2 2 #set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
3 3 #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
4 4 #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
  5 +#set ($selectOptionEnable = false)
  6 +#foreach ($column in $subColumns)
  7 + #if (($column.createOperation || $column.updateOperation) && $column.htmlType == "select")
  8 + #set ($selectOptionEnable = true)
  9 + #end
  10 +#end
5 11 <script lang="ts" setup>
6   -import type { Rule } from 'antdv-next/es/form';
  12 +import type { Rule } from 'antdv-next';
7 13 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
8 14  
9 15 import { computed, ref } from 'vue';
... ... @@ -13,7 +19,7 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;;
13 19 import { getDictOptions } from '@vben/hooks';
14 20 import { Tinymce as RichTextarea } from '#/components/tinymce';
15 21 import { ImageUpload, FileUpload } from "#/components/upload";
16   -import { message, Tabs, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker, TreeSelect } from 'antdv-next';
  22 +import { Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end, TextArea, TreeSelect } from 'antdv-next';
17 23  
18 24 import { $t } from '#/locales';
19 25  
... ... @@ -130,23 +136,23 @@ function resetForm(){
130 136 #set ($dictMethod = "boolean")
131 137 #end
132 138 #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
133   - <Form.Item label="${comment}" name="${javaField}">
  139 + <FormItem label="${comment}" name="${javaField}">
134 140 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
135   - </Form.Item>
  141 + </FormItem>
136 142 #elseif($column.htmlType == "imageUpload")## 图片上传
137   - <Form.Item label="${comment}" name="${javaField}">
  143 + <FormItem label="${comment}" name="${javaField}">
138 144 <ImageUpload v-model:value="formData.${javaField}" />
139   - </Form.Item>
  145 + </FormItem>
140 146 #elseif($column.htmlType == "fileUpload")## 文件上传
141   - <Form.Item label="${comment}" name="${javaField}">
  147 + <FormItem label="${comment}" name="${javaField}">
142 148 <FileUpload v-model:value="formData.${javaField}" />
143   - </Form.Item>
  149 + </FormItem>
144 150 #elseif($column.htmlType == "editor")## 文本编辑器
145   - <Form.Item label="${comment}" name="${javaField}">
  151 + <FormItem label="${comment}" name="${javaField}">
146 152 <RichTextarea v-model="formData.${javaField}" height="500px" />
147   - </Form.Item>
  153 + </FormItem>
148 154 #elseif($column.htmlType == "select")## 下拉框
149   - <Form.Item label="${comment}" name="${javaField}">
  155 + <FormItem label="${comment}" name="${javaField}">
150 156 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
151 157 #if ("" != $dictType)## 有数据字典
152 158 <SelectOption
... ... @@ -160,9 +166,9 @@ function resetForm(){
160 166 <SelectOption label="请选择字典生成" value="" />
161 167 #end
162 168 </Select>
163   - </Form.Item>
  169 + </FormItem>
164 170 #elseif($column.htmlType == "checkbox")## 多选框
165   - <Form.Item label="${comment}" name="${javaField}">
  171 + <FormItem label="${comment}" name="${javaField}">
166 172 <CheckboxGroup v-model:value="formData.${javaField}">
167 173 #if ("" != $dictType)## 有数据字典
168 174 <Checkbox
... ... @@ -176,9 +182,9 @@ function resetForm(){
176 182 <Checkbox label="请选择字典生成" />
177 183 #end
178 184 </CheckboxGroup>
179   - </Form.Item>
  185 + </FormItem>
180 186 #elseif($column.htmlType == "radio")## 单选框
181   - <Form.Item label="${comment}" name="${javaField}">
  187 + <FormItem label="${comment}" name="${javaField}">
182 188 <RadioGroup v-model:value="formData.${javaField}">
183 189 #if ("" != $dictType)## 有数据字典
184 190 <Radio
... ... @@ -192,19 +198,19 @@ function resetForm(){
192 198 <Radio value="1">请选择字典生成</Radio>
193 199 #end
194 200 </RadioGroup>
195   - </Form.Item>
  201 + </FormItem>
196 202 #elseif($column.htmlType == "datetime")## 时间框
197   - <Form.Item label="${comment}" name="${javaField}">
  203 + <FormItem label="${comment}" name="${javaField}">
198 204 <DatePicker
199 205 v-model:value="formData.${javaField}"
200 206 valueFormat="x"
201 207 placeholder="选择${comment}"
202 208 />
203   - </Form.Item>
  209 + </FormItem>
204 210 #elseif($column.htmlType == "textarea")## 文本框
205   - <Form.Item label="${comment}" name="${javaField}">
  211 + <FormItem label="${comment}" name="${javaField}">
206 212 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
207   - </Form.Item>
  213 + </FormItem>
208 214 #end
209 215 #end
210 216 #end
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_inner.vue.vm
1 1 ## 主表的 normal 和 inner 使用相同的 form 表单
2   -#parse("codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm")
3 2 \ No newline at end of file
  3 +#parse("codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_normal.vue.vm")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_normal.vue.vm
... ... @@ -4,6 +4,12 @@
4 4 #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
5 5 #set ($subClassNameVar = $subClassNameVars.get($subIndex))
6 6 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
  7 +#set ($selectOptionEnable = false)
  8 +#foreach ($column in $subColumns)
  9 + #if (($column.createOperation || $column.updateOperation) && (!$subTable.subJoinMany || $column.id != $subJoinColumn.id) && $column.htmlType == "select")
  10 + #set ($selectOptionEnable = true)
  11 + #end
  12 +#end
7 13 <script lang="ts" setup>
8 14 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
9 15  
... ... @@ -12,7 +18,7 @@ import { computed, ref, h, onMounted, watch, nextTick } from &#39;vue&#39;;
12 18 import { DICT_TYPE } from '@vben/constants';
13 19 import { getDictOptions } from '@vben/hooks';
14 20  
15   -import { message, Tabs, Form, Input, TextArea, Button, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, DatePicker } from 'antdv-next';
  21 +import { Button, Checkbox, CheckboxGroup, DatePicker#if (!$subTable.subJoinMany), Form, FormItem#end, Input, Radio, RadioGroup, Select#if ($selectOptionEnable), SelectOption#end, TextArea } from 'antdv-next';
16 22 import { $t } from '#/locales';
17 23  
18 24 #if ($subTable.subJoinMany) ## 一对多
... ... @@ -22,7 +28,7 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
22 28 import { ImageUpload, FileUpload } from "#/components/upload";
23 29 import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
24 30 #else
25   -import type { Rule } from 'antdv-next/es/form';
  31 +import type { Rule } from 'antdv-next';
26 32 import { Tinymce as RichTextarea } from '#/components/tinymce';
27 33 import { ImageUpload, FileUpload } from "#/components/upload";
28 34 import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
... ... @@ -264,23 +270,23 @@ watch(
264 270 #set ($dictMethod = "boolean")
265 271 #end
266 272 #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
267   - <Form.Item label="${comment}" name="${javaField}">
  273 + <FormItem label="${comment}" name="${javaField}">
268 274 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
269   - </Form.Item>
  275 + </FormItem>
270 276 #elseif($column.htmlType == "imageUpload")## 图片上传
271   - <Form.Item label="${comment}" name="${javaField}">
  277 + <FormItem label="${comment}" name="${javaField}">
272 278 <ImageUpload v-model:value="formData.${javaField}" />
273   - </Form.Item>
  279 + </FormItem>
274 280 #elseif($column.htmlType == "fileUpload")## 文件上传
275   - <Form.Item label="${comment}" name="${javaField}">
  281 + <FormItem label="${comment}" name="${javaField}">
276 282 <FileUpload v-model:value="formData.${javaField}" />
277   - </Form.Item>
  283 + </FormItem>
278 284 #elseif($column.htmlType == "editor")## 文本编辑器
279   - <Form.Item label="${comment}" name="${javaField}">
  285 + <FormItem label="${comment}" name="${javaField}">
280 286 <RichTextarea v-model="formData.${javaField}" height="500px" />
281   - </Form.Item>
  287 + </FormItem>
282 288 #elseif($column.htmlType == "select")## 下拉框
283   - <Form.Item label="${comment}" name="${javaField}">
  289 + <FormItem label="${comment}" name="${javaField}">
284 290 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
285 291 #if ("" != $dictType)## 有数据字典
286 292 <SelectOption
... ... @@ -294,9 +300,9 @@ watch(
294 300 <SelectOption label="请选择字典生成" value="" />
295 301 #end
296 302 </Select>
297   - </Form.Item>
  303 + </FormItem>
298 304 #elseif($column.htmlType == "checkbox")## 多选框
299   - <Form.Item label="${comment}" name="${javaField}">
  305 + <FormItem label="${comment}" name="${javaField}">
300 306 <CheckboxGroup v-model:value="formData.${javaField}">
301 307 #if ("" != $dictType)## 有数据字典
302 308 <Checkbox
... ... @@ -310,9 +316,9 @@ watch(
310 316 <Checkbox label="请选择字典生成" />
311 317 #end
312 318 </CheckboxGroup>
313   - </Form.Item>
  319 + </FormItem>
314 320 #elseif($column.htmlType == "radio")## 单选框
315   - <Form.Item label="${comment}" name="${javaField}">
  321 + <FormItem label="${comment}" name="${javaField}">
316 322 <RadioGroup v-model:value="formData.${javaField}">
317 323 #if ("" != $dictType)## 有数据字典
318 324 <Radio
... ... @@ -326,19 +332,19 @@ watch(
326 332 <Radio value="1">请选择字典生成</Radio>
327 333 #end
328 334 </RadioGroup>
329   - </Form.Item>
  335 + </FormItem>
330 336 #elseif($column.htmlType == "datetime")## 时间框
331   - <Form.Item label="${comment}" name="${javaField}">
  337 + <FormItem label="${comment}" name="${javaField}">
332 338 <DatePicker
333 339 v-model:value="formData.${javaField}"
334 340 valueFormat="x"
335 341 placeholder="选择${comment}"
336 342 />
337   - </Form.Item>
  343 + </FormItem>
338 344 #elseif($column.htmlType == "textarea")## 文本框
339   - <Form.Item label="${comment}" name="${javaField}">
  345 + <FormItem label="${comment}" name="${javaField}">
340 346 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
341   - </Form.Item>
  347 + </FormItem>
342 348 #end
343 349 #end
344 350 #end
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_erp.vue.vm
... ... @@ -5,6 +5,16 @@
5 5 #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
6 6 #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex))
7 7 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
  8 +#set ($rangePickerEnable = false)
  9 +#set ($selectOptionEnable = false)
  10 +#foreach ($column in $subColumns)
  11 + #if ($column.listOperation && $column.htmlType == "datetime" && $column.listOperationCondition == "BETWEEN")
  12 + #set ($rangePickerEnable = true)
  13 + #end
  14 + #if ($column.listOperation && ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox"))
  15 + #set ($selectOptionEnable = true)
  16 + #end
  17 +#end
8 18 <script lang="ts" setup>
9 19 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
10 20 import type { VxeTableInstance } from '#/adapter/vxe-table';
... ... @@ -15,7 +25,9 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;;
15 25 import { getDictOptions } from '@vben/hooks';
16 26  
17 27 import { DictTag } from '#/components/dict-tag';
  28 +#if ($table.templateType == 11 && $rangePickerEnable)
18 29 import { getRangePickerDefaultProps } from '#/utils/rangePickerProps';
  30 +#end
19 31 import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
20 32 import { formatDateTime } from '@vben/utils';
21 33  
... ... @@ -25,7 +37,7 @@ import { useTableToolbar, VbenVxeTableToolbar } from &#39;@vben/plugins/vxe-table&#39;;
25 37 import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
26 38 import { Tinymce as RichTextarea } from '#/components/tinymce';
27 39 import { ImageUpload, FileUpload } from "#/components/upload";
28   -import { message, Button, Card, Tabs, Pagination, Form, Input, TextArea, Select, RadioGroup, Radio, CheckboxGroup, Checkbox, RangePicker, DatePicker, TreeSelect } from 'antdv-next';
  40 +import { Button, Card, Checkbox, CheckboxGroup, DatePicker, Form, FormItem, Input, message, Pagination, Radio, RadioGroup#if ($rangePickerEnable), DateRangePicker as RangePicker#end, Select#if ($selectOptionEnable), SelectOption#end, TextArea, TreeSelect } from 'antdv-next';
29 41 import { Plus, Trash2 } from '@vben/icons';
30 42 import { $t } from '#/locales';
31 43 #end
... ... @@ -224,7 +236,7 @@ onMounted(() =&gt; {
224 236 #set ($dictMethod = "boolean")
225 237 #end
226 238 #if ($column.htmlType == "input")
227   - <Form.Item label="${comment}" name="${javaField}">
  239 + <FormItem label="${comment}" name="${javaField}">
228 240 <Input
229 241 v-model:value="queryParams.${javaField}"
230 242 placeholder="请输入${comment}"
... ... @@ -232,9 +244,9 @@ onMounted(() =&gt; {
232 244 @pressEnter="handleQuery"
233 245 class="w-full"
234 246 />
235   - </Form.Item>
  247 + </FormItem>
236 248 #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox")
237   - <Form.Item label="${comment}" name="${javaField}">
  249 + <FormItem label="${comment}" name="${javaField}">
238 250 <Select
239 251 v-model:value="queryParams.${javaField}"
240 252 placeholder="请选择${comment}"
... ... @@ -253,10 +265,10 @@ onMounted(() =&gt; {
253 265 <SelectOption label="请选择字典生成" value="" />
254 266 #end
255 267 </Select>
256   - </Form.Item>
  268 + </FormItem>
257 269 #elseif($column.htmlType == "datetime")
258 270 #if ($column.listOperationCondition != "BETWEEN")## 非范围
259   - <Form.Item label="${comment}" name="${javaField}">
  271 + <FormItem label="${comment}" name="${javaField}">
260 272 <DatePicker
261 273 v-model:value="queryParams.${javaField}"
262 274 valueFormat="YYYY-MM-DD"
... ... @@ -264,25 +276,25 @@ onMounted(() =&gt; {
264 276 allowClear
265 277 class="w-full"
266 278 />
267   - </Form.Item>
  279 + </FormItem>
268 280 #else## 范围
269   - <Form.Item label="${comment}" name="${javaField}">
  281 + <FormItem label="${comment}" name="${javaField}">
270 282 <RangePicker
271 283 v-model:value="queryParams.${javaField}"
272 284 v-bind="getRangePickerDefaultProps()"
273 285 class="w-full"
274 286 />
275   - </Form.Item>
  287 + </FormItem>
276 288 #end
277 289 #end
278 290 #end
279 291 #end
280   - <Form.Item>
  292 + <FormItem>
281 293 <Button class="ml-2" @click="resetQuery"> 重置 </Button>
282 294 <Button class="ml-2" @click="handleQuery" type="primary">
283 295 搜索
284 296 </Button>
285   - </Form.Item>
  297 + </FormItem>
286 298 </Form>
287 299 </Card>
288 300  
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_inner.vue.vm
1 1 ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
2 2 ## 1)inner 使用 list 不分页,erp 使用 page 分页
3 3 ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
4   -#parse("codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm")
5 4 \ No newline at end of file
  5 +#parse("codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_erp.vue.vm")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/form.vue.vm
... ... @@ -6,7 +6,7 @@ import { computed, ref } from &#39;vue&#39;;
6 6  
7 7 import { useVbenModal } from '@vben/common-ui';
8 8  
9   -import { message#if ($table.templateType == 11), Tabs#end } from 'antdv-next';
  9 +import { message#if ($table.templateType == 10 || $table.templateType == 12), TabPane, Tabs#end } from 'antdv-next';
10 10  
11 11 import { useVbenForm } from '#/adapter/form';
12 12 import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}';
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/import.vue.vm
1 1 <script lang="ts" setup>
2   -import type { FileType } from 'antdv-next/es/upload/interface';
3   -
4 2 import { useVbenModal } from '@vben/common-ui';
5 3 import { downloadFileFromBlobPart } from '@vben/utils';
6 4  
... ... @@ -46,7 +44,7 @@ const [Modal, modalApi] = useVbenModal({
46 44 });
47 45  
48 46 /** 上传前 */
49   -function beforeUpload(file: FileType) {
  47 +function beforeUpload(file: File) {
50 48 formApi.setFieldValue('file', file);
51 49 return false;
52 50 }
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/index.vue.vm
... ... @@ -8,7 +8,7 @@ import { ref } from &#39;vue&#39;;
8 8 import {#if ($table.templateType != 2 && $deleteBatchEnable) confirm,#end Page, useVbenModal } from '@vben/common-ui';
9 9 import { downloadFileFromBlobPart#if ($table.templateType != 2 && $deleteBatchEnable), isEmpty#end } from '@vben/utils';
10 10  
11   -import { message#if ($table.templateType == 11), Tabs#end } from 'antdv-next';
  11 +import { message#if ($table.templateType == 11 || $table.templateType == 12), TabPane, Tabs#end } from 'antdv-next';
12 12  
13 13 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
14 14 #if (${table.templateType} == 2)## 树表接口
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_inner.vue.vm
1 1 ## 主表的 normal 和 inner 使用相同的 form 表单
2   -#parse("codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm")
3 2 \ No newline at end of file
  3 +#parse("codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_normal.vue.vm")
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_normal.vue.vm
... ... @@ -5,6 +5,12 @@
5 5 #set ($subClassNameVar = $subClassNameVars.get($subIndex))
6 6 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
7 7 #set ($apiName = "${table.moduleName.substring(0,1).toUpperCase()}${table.moduleName.substring(1)}${simpleClassName}Api")
  8 +#set ($selectOptionEnable = false)
  9 +#foreach ($column in $subColumns)
  10 + #if (($column.createOperation || $column.updateOperation) && $column.id != $subJoinColumn.id && $column.htmlType == "select")
  11 + #set ($selectOptionEnable = true)
  12 + #end
  13 +#end
8 14 <script lang="ts" setup>
9 15 import type { ${apiName} } from '#/api/${table.moduleName}/${table.businessName}';
10 16  
... ... @@ -14,7 +20,19 @@
14 20  
15 21 #if ($subTable.subJoinMany) ## 一对多
16 22 import { Plus } from "@vben/icons";
17   -import { Button, Tabs, Checkbox, Input, TextArea, Select,RadioGroup,CheckboxGroup, DatePicker } from 'antdv-next';
  23 +import {
  24 + Button,
  25 + Checkbox,
  26 + CheckboxGroup,
  27 + DatePicker,
  28 + Input,
  29 + RadioGroup,
  30 + Select,
  31 +#if ($selectOptionEnable)
  32 + SelectOption,
  33 +#end
  34 + TextArea,
  35 +} from 'antdv-next';
18 36 import { ImageUpload, FileUpload } from "#/components/upload";
19 37 import { useVbenVxeGrid } from '#/adapter/vxe-table';
20 38 import { use${subSimpleClassName}GridEditColumns } from '../data';
... ...
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_inner.vue.vm
1 1 ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
2 2 ## 1)inner 使用 list 不分页,erp 使用 page 分页
3 3 ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
4   -#parse("codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm")
5 4 \ No newline at end of file
  5 +#parse("codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_erp.vue.vm")
... ...
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngineVben5AntdvNextTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.infra.service.codegen.inner;
  2 +
  3 +import cn.hutool.core.io.resource.ResourceUtil;
  4 +import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenColumnDO;
  5 +import cn.iocoder.yudao.module.infra.dal.dataobject.codegen.CodegenTableDO;
  6 +import cn.iocoder.yudao.module.infra.enums.codegen.CodegenFrontTypeEnum;
  7 +import cn.iocoder.yudao.module.infra.enums.codegen.CodegenTemplateTypeEnum;
  8 +import com.baomidou.mybatisplus.annotation.DbType;
  9 +import org.junit.jupiter.api.Test;
  10 +
  11 +import java.util.Arrays;
  12 +import java.util.List;
  13 +import java.util.Map;
  14 +import java.util.regex.Pattern;
  15 +
  16 +import static org.junit.jupiter.api.Assertions.assertEquals;
  17 +import static org.junit.jupiter.api.Assertions.assertFalse;
  18 +import static org.junit.jupiter.api.Assertions.assertNotNull;
  19 +import static org.junit.jupiter.api.Assertions.assertTrue;
  20 +
  21 +/**
  22 + * {@link CodegenEngine} 的 Vue3 + Vben5 + Antdv Next 单元测试
  23 + *
  24 + * @author 芋道源码
  25 + */
  26 +public class CodegenEngineVben5AntdvNextTest extends CodegenEngineAbstractTest {
  27 +
  28 + private static final Pattern ANTDV_NEXT_VALUE_IMPORT_PATTERN = Pattern.compile(
  29 + "import\\s*\\{([^}]*)}\\s*from\\s*'antdv-next';?", Pattern.DOTALL);
  30 + private static final Pattern RANGE_PICKER_COMPONENT_PATTERN = Pattern.compile("<RangePicker\\b");
  31 + private static final Pattern RANGE_PICKER_HELPER_BINDING_PATTERN = Pattern.compile(
  32 + "\\bv-bind\\s*=\\s*([\"'])\\s*getRangePickerDefaultProps\\s*\\(\\s*\\)\\s*\\1");
  33 + private static final Pattern RANGE_PICKER_HELPER_CALL_PATTERN = Pattern.compile(
  34 + "\\bgetRangePickerDefaultProps\\s*\\(\\s*\\)");
  35 + private static final Pattern RANGE_PICKER_HELPER_IMPORT_PATTERN = Pattern.compile(
  36 + "import\\s*\\{[^}]*\\bgetRangePickerDefaultProps\\b[^}]*}"
  37 + + "\\s*from\\s*'#/utils(?:/rangePickerProps)?';?", Pattern.DOTALL);
  38 + private static final Pattern SCHEMA_RANGE_PICKER_COMPONENT_PATTERN = Pattern.compile(
  39 + "\\bcomponent\\s*:\\s*([\"'])RangePicker\\1");
  40 + private static final Pattern SCHEMA_RANGE_PICKER_HELPER_SPREAD_PATTERN = Pattern.compile(
  41 + "\\.\\.\\.\\s*getRangePickerDefaultProps\\s*\\(\\s*\\)");
  42 +
  43 + private static final String IMPORT_FORM_PATH =
  44 + "yudao-ui-admin-vben/src/views/infra/demo/modules/import-form.vue";
  45 + private static final String SCHEMA_DATA_PATH =
  46 + "yudao-ui-admin-vben/src/views/infra/demo/data.ts";
  47 + private static final String STUDENT_CONTACT_FORM_PATH =
  48 + "yudao-ui-admin-vben/src/views/infra/demo/modules/student-contact-form.vue";
  49 + private static final String STUDENT_TEACHER_FORM_PATH =
  50 + "yudao-ui-admin-vben/src/views/infra/demo/modules/student-teacher-form.vue";
  51 +
  52 + private static final List<String> INNER_TEMPLATE_PATHS = Arrays.asList(
  53 + "codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_inner.vue.vm",
  54 + "codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_inner.vue.vm",
  55 + "codegen/vue3_vben5_antdv_next/schema/views/modules/form_sub_inner.vue.vm",
  56 + "codegen/vue3_vben5_antdv_next/schema/views/modules/list_sub_inner.vue.vm");
  57 +
  58 + @Test
  59 + public void testExecute_schemaMasterNormal() {
  60 + // 调用
  61 + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
  62 + CodegenTemplateTypeEnum.MASTER_NORMAL);
  63 +
  64 + // 断言
  65 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  66 + assertSchemaRangePickerGenerated(result);
  67 + assertTabsGenerated(result);
  68 + }
  69 +
  70 + @Test
  71 + public void testExecute_schemaMasterErp() {
  72 + // 调用
  73 + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
  74 + CodegenTemplateTypeEnum.MASTER_ERP);
  75 +
  76 + // 断言
  77 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  78 + assertSchemaRangePickerGenerated(result);
  79 + assertTabsGenerated(result);
  80 + }
  81 +
  82 + @Test
  83 + public void testExecute_schemaMasterInner() {
  84 + // 调用
  85 + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType(),
  86 + CodegenTemplateTypeEnum.MASTER_INNER);
  87 +
  88 + // 断言
  89 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  90 + assertSchemaRangePickerGenerated(result);
  91 + assertTabsGenerated(result);
  92 + }
  93 +
  94 + @Test
  95 + public void testExecute_generalTree() {
  96 + // 调用
  97 + Map<String, String> result = executeTree(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType());
  98 +
  99 + // 断言
  100 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  101 + assertRuleGenerated(result);
  102 + assertComponentGenerated(result, "FormItem");
  103 + assertComponentGenerated(result, "TreeSelect");
  104 + }
  105 +
  106 + @Test
  107 + public void testExecute_generalMasterNormal() {
  108 + // 调用
  109 + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
  110 + CodegenTemplateTypeEnum.MASTER_NORMAL);
  111 +
  112 + // 断言
  113 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  114 + assertRuleGenerated(result);
  115 + assertGeneralCrudComponentsGenerated(result);
  116 + assertTabsGenerated(result);
  117 + }
  118 +
  119 + @Test
  120 + public void testExecute_generalMasterNormal_oneToOneJoinColumnOnlySelect() {
  121 + // 调用
  122 + Map<String, String> result = executeGeneralMasterNormalWithOneToOneJoinSelect();
  123 +
  124 + // 断言
  125 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  126 + String content = result.get(STUDENT_TEACHER_FORM_PATH);
  127 + assertNotNull(content, STUDENT_TEACHER_FORM_PATH + ":未生成一对一子表表单");
  128 + assertTrue(content.contains("<Select v-model:value=\"formData.studentId\""),
  129 + STUDENT_TEACHER_FORM_PATH + ":应渲染 join 列的 Select 组件");
  130 + assertTrue(content.contains("<SelectOption"), STUDENT_TEACHER_FORM_PATH + ":应渲染 SelectOption 组件");
  131 + assertRootValueImport(STUDENT_TEACHER_FORM_PATH, content, "\\bSelectOption\\b");
  132 + }
  133 +
  134 + @Test
  135 + public void testExecute_generalMasterNormal_oneToManyJoinColumnOnlySelect() {
  136 + // 调用
  137 + Map<String, String> result = executeGeneralMasterNormalWithOneToManyJoinSelect();
  138 +
  139 + // 断言
  140 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  141 + String content = result.get(STUDENT_CONTACT_FORM_PATH);
  142 + assertNotNull(content, STUDENT_CONTACT_FORM_PATH + ":未生成一对多子表表单");
  143 + assertTrue(content.contains("<VxeTable"), STUDENT_CONTACT_FORM_PATH + ":应生成一对多子表表格");
  144 + assertFalse(content.contains("<VxeColumn field=\"studentId\""),
  145 + STUDENT_CONTACT_FORM_PATH + ":不应生成 join 列");
  146 + assertFalse(content.contains("<Select v-model:value=\"row.studentId\""),
  147 + STUDENT_CONTACT_FORM_PATH + ":不应渲染 join 列的 Select 组件");
  148 + assertFalse(content.contains("<SelectOption"), STUDENT_CONTACT_FORM_PATH + ":不应渲染 SelectOption 组件");
  149 + assertFalse(createRootImportPattern("import\\s*\\{", "\\bSelectOption\\b").matcher(content).find(),
  150 + STUDENT_CONTACT_FORM_PATH + ":不应导入 SelectOption 组件");
  151 + }
  152 +
  153 + @Test
  154 + public void testExecute_generalMasterInner() {
  155 + // 调用
  156 + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
  157 + CodegenTemplateTypeEnum.MASTER_INNER);
  158 +
  159 + // 断言
  160 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  161 + assertRuleGenerated(result);
  162 + assertGeneralCrudComponentsGenerated(result);
  163 + assertTabsGenerated(result);
  164 + }
  165 +
  166 + @Test
  167 + public void testExecute_generalMasterErp() {
  168 + // 调用
  169 + Map<String, String> result = executeMaster(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType(),
  170 + CodegenTemplateTypeEnum.MASTER_ERP);
  171 +
  172 + // 断言
  173 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  174 + assertRuleGenerated(result);
  175 + assertGeneralCrudComponentsGenerated(result);
  176 + assertTabsGenerated(result);
  177 + }
  178 +
  179 + @Test
  180 + public void testExecute_oneImportEnable() {
  181 + // 开启 import 开关
  182 + codegenProperties.setImportEnable(true);
  183 + codegenEngine.initGlobalBindingMap();
  184 +
  185 + // 调用并断言
  186 + assertImportFormCompatibleWithAntdvNext(
  187 + executeOne(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_SCHEMA.getType()));
  188 + assertImportFormCompatibleWithAntdvNext(
  189 + executeOne(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType()));
  190 + }
  191 +
  192 + @Test
  193 + public void testInnerTemplatesParseAntdvNext() {
  194 + INNER_TEMPLATE_PATHS.forEach(path -> {
  195 + String template = ResourceUtil.readUtf8Str(path);
  196 + assertFalse(template.contains("codegen/vue3_vben5_antd/"), path + ":不应引用 Ant Design Vue 模板");
  197 + assertTrue(template.contains("codegen/vue3_vben5_antdv_next/"), path + ":应引用 Antdv Next 模板");
  198 + });
  199 + }
  200 +
  201 + private Map<String, String> executeOne(Integer frontType) {
  202 + CodegenTableDO table = getTable("student")
  203 + .setFrontType(frontType)
  204 + .setTemplateType(CodegenTemplateTypeEnum.ONE.getType());
  205 + return codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"), null, null);
  206 + }
  207 +
  208 + private Map<String, String> executeTree(Integer frontType) {
  209 + CodegenTableDO table = getTable("category")
  210 + .setFrontType(frontType)
  211 + .setTemplateType(CodegenTemplateTypeEnum.TREE.getType());
  212 + return codegenEngine.execute(DbType.MYSQL, table, getColumnList("category"), null, null);
  213 + }
  214 +
  215 + private Map<String, String> executeGeneralMasterNormalWithOneToOneJoinSelect() {
  216 + return executeGeneralMasterNormalWithJoinSelect("teacher", 200L, false);
  217 + }
  218 +
  219 + private Map<String, String> executeGeneralMasterNormalWithOneToManyJoinSelect() {
  220 + return executeGeneralMasterNormalWithJoinSelect("contact", 100L, true);
  221 + }
  222 +
  223 + private Map<String, String> executeGeneralMasterNormalWithJoinSelect(String subTableName,
  224 + Long subJoinColumnId,
  225 + boolean subJoinMany) {
  226 + CodegenTableDO table = getTable("student")
  227 + .setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType())
  228 + .setTemplateType(CodegenTemplateTypeEnum.MASTER_NORMAL.getType());
  229 + CodegenTableDO subTable = getTable(subTableName)
  230 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
  231 + .setFrontType(CodegenFrontTypeEnum.VUE3_VBEN5_ANTDV_NEXT_GENERAL.getType())
  232 + .setSubJoinColumnId(subJoinColumnId).setSubJoinMany(subJoinMany);
  233 + List<CodegenColumnDO> subColumns = getColumnList(subTableName);
  234 + // 稀疏场景:join 列是唯一的 select,避免其它 select 掩盖 join 列对导入判定的影响。
  235 + subColumns.forEach(column -> {
  236 + if (subJoinColumnId.equals(column.getId())) {
  237 + column.setHtmlType("select");
  238 + column.setDictType("");
  239 + } else if ("select".equals(column.getHtmlType())) {
  240 + column.setHtmlType("input");
  241 + }
  242 + });
  243 + return codegenEngine.execute(DbType.MYSQL, table, getColumnList("student"),
  244 + List.of(subTable), List.of(subColumns));
  245 + }
  246 +
  247 + private Map<String, String> executeMaster(Integer frontType, CodegenTemplateTypeEnum templateType) {
  248 + // 准备参数
  249 + CodegenTableDO table = getTable("student")
  250 + .setFrontType(frontType)
  251 + .setTemplateType(templateType.getType());
  252 + List<CodegenColumnDO> columns = getColumnList("student");
  253 + // 准备参数(子表)
  254 + CodegenTableDO contactTable = getTable("contact")
  255 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
  256 + .setFrontType(frontType)
  257 + .setSubJoinColumnId(100L).setSubJoinMany(true);
  258 + List<CodegenColumnDO> contactColumns = getColumnList("contact");
  259 + // 准备参数(班主任)
  260 + CodegenTableDO teacherTable = getTable("teacher")
  261 + .setTemplateType(CodegenTemplateTypeEnum.SUB.getType())
  262 + .setFrontType(frontType)
  263 + .setSubJoinColumnId(200L).setSubJoinMany(false);
  264 + List<CodegenColumnDO> teacherColumns = getColumnList("teacher");
  265 +
  266 + // 调用
  267 + return codegenEngine.execute(DbType.MYSQL, table, columns,
  268 + Arrays.asList(contactTable, teacherTable), Arrays.asList(contactColumns, teacherColumns));
  269 + }
  270 +
  271 + private static void assertGeneratedCodeCompatibleWithAntdvNext(Map<String, String> result) {
  272 + assertTrue(result.values().stream().anyMatch(content -> content.contains("from 'antdv-next")),
  273 + "生成结果应使用 antdv-next");
  274 + result.forEach((filePath, content) -> {
  275 + assertFalse(content.contains("ant-design-vue"), filePath + ":不应使用 ant-design-vue");
  276 + assertFalse(content.contains("antdv-next/es/"), filePath + ":不应使用未导出的 antdv-next 深层路径");
  277 + assertFalse(content.contains("<Form.Item"), filePath + ":不应使用 Form.Item");
  278 + assertFalse(content.contains("</Form.Item>"), filePath + ":不应使用 Form.Item");
  279 + assertRootValueImportsSorted(filePath, content);
  280 + assertRootValueImportMatchesUsage(filePath, content,
  281 + "\\bDateRangePicker\\s+as\\s+RangePicker\\b", "<RangePicker\\b", "RangePicker");
  282 + assertRootValueImportMatchesUsage(filePath, content,
  283 + "\\bFormItem\\b", "<FormItem\\b", "FormItem");
  284 + assertRootValueImportMatchesUsage(filePath, content,
  285 + "\\bSelectOption\\b", "<SelectOption\\b", "SelectOption");
  286 + assertRootValueImportMatchesUsage(filePath, content,
  287 + "\\bTabPane\\b", "<TabPane\\b", "TabPane");
  288 + assertRootValueImportMatchesUsage(filePath, content,
  289 + "\\bTabs\\b", "<Tabs\\b", "Tabs");
  290 + assertRootTypeImportMatchesUsage(filePath, content,
  291 + "\\bRule\\b", "\\bRule\\b", "Rule");
  292 + assertRangePickerHelperImportMatchesUsage(filePath, content);
  293 + if (filePath.endsWith(".vue")) {
  294 + assertRangePickerHelperBindingMatchesComponent(filePath, content);
  295 + } else if (filePath.endsWith(".ts")) {
  296 + assertSchemaRangePickerHelperMatchesComponent(filePath, content);
  297 + }
  298 + });
  299 + }
  300 +
  301 + private static void assertImportFormCompatibleWithAntdvNext(Map<String, String> result) {
  302 + assertGeneratedCodeCompatibleWithAntdvNext(result);
  303 + String content = result.get(IMPORT_FORM_PATH);
  304 + assertNotNull(content, IMPORT_FORM_PATH + ":未生成导入表单");
  305 + assertTrue(content.contains("<Upload"), IMPORT_FORM_PATH + ":应生成 Upload 组件");
  306 + assertRootValueImport(IMPORT_FORM_PATH, content, "\\bUpload\\b");
  307 + assertTrue(content.contains("beforeUpload(file: File)"), IMPORT_FORM_PATH + ":上传参数应使用原生 File 类型");
  308 + assertFalse(content.contains("UploadFile"), IMPORT_FORM_PATH + ":不应使用上传列表项类型 UploadFile");
  309 + assertFalse(content.contains("as unknown as File"), IMPORT_FORM_PATH + ":不应通过双重断言转换上传文件");
  310 + assertFalse(content.contains("FileType"), IMPORT_FORM_PATH + ":不应继续使用旧 FileType");
  311 + }
  312 +
  313 + private static void assertTabsGenerated(Map<String, String> result) {
  314 + assertComponentGenerated(result, "Tabs");
  315 + assertComponentGenerated(result, "TabPane");
  316 + }
  317 +
  318 + private static void assertGeneralCrudComponentsGenerated(Map<String, String> result) {
  319 + assertComponentGenerated(result, "FormItem");
  320 + assertComponentGenerated(result, "RangePicker");
  321 + assertComponentGenerated(result, "SelectOption");
  322 + }
  323 +
  324 + private static void assertRuleGenerated(Map<String, String> result) {
  325 + assertTrue(result.values().stream().map(CodegenEngineVben5AntdvNextTest::removeAntdvNextRootImports)
  326 + .anyMatch(content -> Pattern.compile("\\bRule\\b").matcher(content).find()),
  327 + "生成结果应包含 Rule 类型校验规则");
  328 + }
  329 +
  330 + private static void assertSchemaRangePickerGenerated(Map<String, String> result) {
  331 + String content = result.get(SCHEMA_DATA_PATH);
  332 + assertNotNull(content, SCHEMA_DATA_PATH + ":未生成 Schema 配置");
  333 + long componentCount = countMatches(SCHEMA_RANGE_PICKER_COMPONENT_PATTERN, content);
  334 + assertTrue(componentCount > 0, SCHEMA_DATA_PATH + ":应生成 RangePicker 组件配置");
  335 + }
  336 +
  337 + private static void assertComponentGenerated(Map<String, String> result, String component) {
  338 + assertTrue(result.values().stream().anyMatch(content -> content.contains("<" + component)),
  339 + "生成结果应包含 " + component + " 组件");
  340 + }
  341 +
  342 + private static void assertRootValueImportMatchesUsage(String filePath, String content,
  343 + String expectedImportRegex, String usageRegex,
  344 + String bindingName) {
  345 + assertRootImportMatchesUsage(filePath, content, "import\\s*\\{", expectedImportRegex,
  346 + usageRegex, bindingName);
  347 + }
  348 +
  349 + private static void assertRootTypeImportMatchesUsage(String filePath, String content,
  350 + String expectedImportRegex, String usageRegex,
  351 + String bindingName) {
  352 + assertRootImportMatchesUsage(filePath, content, "import\\s+type\\s*\\{", expectedImportRegex,
  353 + usageRegex, bindingName);
  354 + }
  355 +
  356 + private static void assertRootImportMatchesUsage(String filePath, String content, String importPrefix,
  357 + String expectedImportRegex, String usageRegex,
  358 + String bindingName) {
  359 + boolean imported = createRootImportPattern(importPrefix, expectedImportRegex).matcher(content).find();
  360 + boolean used = Pattern.compile(usageRegex).matcher(removeAntdvNextRootImports(content)).find();
  361 + assertEquals(used, imported, filePath + ":" + bindingName + (used ? " 缺少合法根导入" : " 存在孤立根导入"));
  362 + }
  363 +
  364 + private static String removeAntdvNextRootImports(String content) {
  365 + Pattern pattern = Pattern.compile("import\\s+(?:type\\s+)?\\{[^}]*}\\s*from\\s*'antdv-next';?",
  366 + Pattern.DOTALL);
  367 + return pattern.matcher(content).replaceAll("");
  368 + }
  369 +
  370 + private static void assertRootValueImport(String filePath, String content, String expectedRegex) {
  371 + assertRootImport(filePath, content, "import\\s*\\{", expectedRegex);
  372 + }
  373 +
  374 + private static void assertRootValueImportsSorted(String filePath, String content) {
  375 + var matcher = ANTDV_NEXT_VALUE_IMPORT_PATTERN.matcher(content);
  376 + while (matcher.find()) {
  377 + List<String> actual = Arrays.stream(matcher.group(1).split(","))
  378 + .map(String::trim)
  379 + .filter(item -> !item.isEmpty())
  380 + .map(item -> item.replaceFirst("^.*\\s+as\\s+", ""))
  381 + .toList();
  382 + List<String> expected = actual.stream().sorted(String.CASE_INSENSITIVE_ORDER).toList();
  383 + assertEquals(expected, actual, filePath + ":antdv-next 命名导入未按 natural 顺序排列");
  384 + }
  385 + }
  386 +
  387 + private static void assertRangePickerHelperImportMatchesUsage(String filePath, String content) {
  388 + boolean imported = RANGE_PICKER_HELPER_IMPORT_PATTERN.matcher(content).find();
  389 + boolean called = RANGE_PICKER_HELPER_CALL_PATTERN.matcher(content).find();
  390 + assertEquals(called, imported,
  391 + filePath + ":getRangePickerDefaultProps() 调用与导入不一致");
  392 + }
  393 +
  394 + private static void assertRangePickerHelperBindingMatchesComponent(String filePath, String content) {
  395 + long componentCount = countMatches(RANGE_PICKER_COMPONENT_PATTERN, content);
  396 + long bindingCount = countMatches(RANGE_PICKER_HELPER_BINDING_PATTERN, content);
  397 + assertEquals(componentCount, bindingCount,
  398 + filePath + ":RangePicker 与 getRangePickerDefaultProps() 绑定数量不一致");
  399 + }
  400 +
  401 + private static void assertSchemaRangePickerHelperMatchesComponent(String filePath, String content) {
  402 + long componentCount = countMatches(SCHEMA_RANGE_PICKER_COMPONENT_PATTERN, content);
  403 + long helperSpreadCount = countMatches(SCHEMA_RANGE_PICKER_HELPER_SPREAD_PATTERN, content);
  404 + assertEquals(componentCount, helperSpreadCount,
  405 + filePath + ":Schema RangePicker 与 getRangePickerDefaultProps() 展开数量不一致");
  406 + }
  407 +
  408 + private static long countMatches(Pattern pattern, String content) {
  409 + return pattern.matcher(content).results().count();
  410 + }
  411 +
  412 + private static void assertRootImport(String filePath, String content, String importPrefix, String expectedRegex) {
  413 + Pattern pattern = createRootImportPattern(importPrefix, expectedRegex);
  414 + assertTrue(pattern.matcher(content).find(), filePath + ":缺少合法的 antdv-next 根导入 " + expectedRegex);
  415 + }
  416 +
  417 + private static Pattern createRootImportPattern(String importPrefix, String expectedRegex) {
  418 + return Pattern.compile(importPrefix + "[^}]*" + expectedRegex
  419 + + "[^}]*}\\s*from\\s*'antdv-next';?", Pattern.DOTALL);
  420 + }
  421 +
  422 +}
... ...