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 <script lang="ts" setup> 7 <script lang="ts" setup>
2 -import type { Rule } from 'antdv-next/es/form'; 8 +import type { Rule } from 'antdv-next';
3 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; 9 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
4 10
5 import { computed, ref } from 'vue'; 11 import { computed, ref } from 'vue';
@@ -9,7 +15,7 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;; @@ -9,7 +15,7 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;;
9 import { getDictOptions } from '@vben/hooks'; 15 import { getDictOptions } from '@vben/hooks';
10 import { Tinymce as RichTextarea } from '#/components/tinymce'; 16 import { Tinymce as RichTextarea } from '#/components/tinymce';
11 import { ImageUpload, FileUpload } from "#/components/upload"; 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 #if($table.templateType == 2)## 树表需要导入这些 19 #if($table.templateType == 2)## 树表需要导入这些
14 import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}'; 20 import { get${simpleClassName}List } from '#/api/${table.moduleName}/${table.businessName}';
15 import { handleTree } from '@vben/utils' 21 import { handleTree } from '@vben/utils'
@@ -204,7 +210,7 @@ const [Modal, modalApi] = useVbenModal({ @@ -204,7 +210,7 @@ const [Modal, modalApi] = useVbenModal({
204 #set ($dictMethod = "boolean") 210 #set ($dictMethod = "boolean")
205 #end 211 #end
206 #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id ) 212 #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )
207 - <Form.Item label="${comment}" name="${javaField}"> 213 + <FormItem label="${comment}" name="${javaField}">
208 <TreeSelect 214 <TreeSelect
209 v-model:value="formData.${javaField}" 215 v-model:value="formData.${javaField}"
210 :treeData="${classNameVar}Tree" 216 :treeData="${classNameVar}Tree"
@@ -225,25 +231,25 @@ const [Modal, modalApi] = useVbenModal({ @@ -225,25 +231,25 @@ const [Modal, modalApi] = useVbenModal({
225 treeDefaultExpandAll 231 treeDefaultExpandAll
226 placeholder="请选择${comment}" 232 placeholder="请选择${comment}"
227 /> 233 />
228 - </Form.Item> 234 + </FormItem>
229 #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 235 #elseif ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
230 - <Form.Item label="${comment}" name="${javaField}"> 236 + <FormItem label="${comment}" name="${javaField}">
231 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> 237 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
232 - </Form.Item> 238 + </FormItem>
233 #elseif($column.htmlType == "imageUpload")## 图片上传 239 #elseif($column.htmlType == "imageUpload")## 图片上传
234 - <Form.Item label="${comment}" name="${javaField}"> 240 + <FormItem label="${comment}" name="${javaField}">
235 <ImageUpload v-model:value="formData.${javaField}" /> 241 <ImageUpload v-model:value="formData.${javaField}" />
236 - </Form.Item> 242 + </FormItem>
237 #elseif($column.htmlType == "fileUpload")## 文件上传 243 #elseif($column.htmlType == "fileUpload")## 文件上传
238 - <Form.Item label="${comment}" name="${javaField}"> 244 + <FormItem label="${comment}" name="${javaField}">
239 <FileUpload v-model:value="formData.${javaField}" /> 245 <FileUpload v-model:value="formData.${javaField}" />
240 - </Form.Item> 246 + </FormItem>
241 #elseif($column.htmlType == "editor")## 文本编辑器 247 #elseif($column.htmlType == "editor")## 文本编辑器
242 - <Form.Item label="${comment}" name="${javaField}"> 248 + <FormItem label="${comment}" name="${javaField}">
243 <RichTextarea v-model="formData.${javaField}" height="500px" /> 249 <RichTextarea v-model="formData.${javaField}" height="500px" />
244 - </Form.Item> 250 + </FormItem>
245 #elseif($column.htmlType == "select")## 下拉框 251 #elseif($column.htmlType == "select")## 下拉框
246 - <Form.Item label="${comment}" name="${javaField}"> 252 + <FormItem label="${comment}" name="${javaField}">
247 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> 253 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
248 #if ("" != $dictType)## 有数据字典 254 #if ("" != $dictType)## 有数据字典
249 <SelectOption 255 <SelectOption
@@ -257,9 +263,9 @@ const [Modal, modalApi] = useVbenModal({ @@ -257,9 +263,9 @@ const [Modal, modalApi] = useVbenModal({
257 <SelectOption label="请选择字典生成" value="" /> 263 <SelectOption label="请选择字典生成" value="" />
258 #end 264 #end
259 </Select> 265 </Select>
260 - </Form.Item> 266 + </FormItem>
261 #elseif($column.htmlType == "checkbox")## 多选框 267 #elseif($column.htmlType == "checkbox")## 多选框
262 - <Form.Item label="${comment}" name="${javaField}"> 268 + <FormItem label="${comment}" name="${javaField}">
263 <CheckboxGroup v-model:value="formData.${javaField}"> 269 <CheckboxGroup v-model:value="formData.${javaField}">
264 #if ("" != $dictType)## 有数据字典 270 #if ("" != $dictType)## 有数据字典
265 <Checkbox 271 <Checkbox
@@ -273,9 +279,9 @@ const [Modal, modalApi] = useVbenModal({ @@ -273,9 +279,9 @@ const [Modal, modalApi] = useVbenModal({
273 <Checkbox label="请选择字典生成" /> 279 <Checkbox label="请选择字典生成" />
274 #end 280 #end
275 </CheckboxGroup> 281 </CheckboxGroup>
276 - </Form.Item> 282 + </FormItem>
277 #elseif($column.htmlType == "radio")## 单选框 283 #elseif($column.htmlType == "radio")## 单选框
278 - <Form.Item label="${comment}" name="${javaField}"> 284 + <FormItem label="${comment}" name="${javaField}">
279 <RadioGroup v-model:value="formData.${javaField}"> 285 <RadioGroup v-model:value="formData.${javaField}">
280 #if ("" != $dictType)## 有数据字典 286 #if ("" != $dictType)## 有数据字典
281 <Radio 287 <Radio
@@ -289,19 +295,19 @@ const [Modal, modalApi] = useVbenModal({ @@ -289,19 +295,19 @@ const [Modal, modalApi] = useVbenModal({
289 <Radio value="1">请选择字典生成</Radio> 295 <Radio value="1">请选择字典生成</Radio>
290 #end 296 #end
291 </RadioGroup> 297 </RadioGroup>
292 - </Form.Item> 298 + </FormItem>
293 #elseif($column.htmlType == "datetime")## 时间框 299 #elseif($column.htmlType == "datetime")## 时间框
294 - <Form.Item label="${comment}" name="${javaField}"> 300 + <FormItem label="${comment}" name="${javaField}">
295 <DatePicker 301 <DatePicker
296 v-model:value="formData.${javaField}" 302 v-model:value="formData.${javaField}"
297 valueFormat="x" 303 valueFormat="x"
298 placeholder="选择${comment}" 304 placeholder="选择${comment}"
299 /> 305 />
300 - </Form.Item> 306 + </FormItem>
301 #elseif($column.htmlType == "textarea")## 文本框 307 #elseif($column.htmlType == "textarea")## 文本框
302 - <Form.Item label="${comment}" name="${javaField}"> 308 + <FormItem label="${comment}" name="${javaField}">
303 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> 309 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
304 - </Form.Item> 310 + </FormItem>
305 #end 311 #end
306 #end 312 #end
307 #end 313 #end
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/import.vue.vm
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 -import type { FileType } from 'antdv-next/es/upload/interface';  
3 -  
4 import { ref } from 'vue'; 2 import { ref } from 'vue';
5 3
6 import { useVbenModal } from '@vben/common-ui'; 4 import { useVbenModal } from '@vben/common-ui';
@@ -43,8 +41,8 @@ const [Modal, modalApi] = useVbenModal({ @@ -43,8 +41,8 @@ const [Modal, modalApi] = useVbenModal({
43 }); 41 });
44 42
45 /** 上传前:拦截 antd Upload 的自动上传,文件存到 ref */ 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 return false; 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 <script lang="ts" setup> 11 <script lang="ts" setup>
2 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; 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,12 +18,14 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;;
8 import { getDictOptions } from '@vben/hooks'; 18 import { getDictOptions } from '@vben/hooks';
9 import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table'; 19 import { useTableToolbar, VbenVxeTableToolbar } from '@vben/plugins/vxe-table';
10 import { cloneDeep, downloadFileFromBlobPart, formatDateTime, isEmpty } from '@vben/utils'; 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 import ${simpleClassName}Form from './modules/form.vue'; 22 import ${simpleClassName}Form from './modules/form.vue';
13 import { Download, Plus, RefreshCw, Search, Trash2 } from '@vben/icons'; 23 import { Download, Plus, RefreshCw, Search, Trash2 } from '@vben/icons';
14 import { DictTag } from '#/components/dict-tag'; 24 import { DictTag } from '#/components/dict-tag';
15 import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; 25 import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
  26 +#if ($rangePickerEnable)
16 import { getRangePickerDefaultProps } from '#/utils/rangePickerProps'; 27 import { getRangePickerDefaultProps } from '#/utils/rangePickerProps';
  28 +#end
17 29
18 ## 特殊:主子表专属逻辑 30 ## 特殊:主子表专属逻辑
19 #if ( $table.templateType == 11 || $table.templateType == 12 ) 31 #if ( $table.templateType == 11 || $table.templateType == 12 )
@@ -249,7 +261,7 @@ onMounted(() =&gt; { @@ -249,7 +261,7 @@ onMounted(() =&gt; {
249 #set ($dictMethod = "boolean") 261 #set ($dictMethod = "boolean")
250 #end 262 #end
251 #if ($column.htmlType == "input") 263 #if ($column.htmlType == "input")
252 - <Form.Item label="${comment}" name="${javaField}"> 264 + <FormItem label="${comment}" name="${javaField}">
253 <Input 265 <Input
254 v-model:value="queryParams.${javaField}" 266 v-model:value="queryParams.${javaField}"
255 placeholder="请输入${comment}" 267 placeholder="请输入${comment}"
@@ -257,9 +269,9 @@ onMounted(() =&gt; { @@ -257,9 +269,9 @@ onMounted(() =&gt; {
257 @pressEnter="handleQuery" 269 @pressEnter="handleQuery"
258 class="w-full" 270 class="w-full"
259 /> 271 />
260 - </Form.Item> 272 + </FormItem>
261 #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox") 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 <Select 275 <Select
264 v-model:value="queryParams.${javaField}" 276 v-model:value="queryParams.${javaField}"
265 placeholder="请选择${comment}" 277 placeholder="请选择${comment}"
@@ -278,10 +290,10 @@ onMounted(() =&gt; { @@ -278,10 +290,10 @@ onMounted(() =&gt; {
278 <SelectOption label="请选择字典生成" value="" /> 290 <SelectOption label="请选择字典生成" value="" />
279 #end 291 #end
280 </Select> 292 </Select>
281 - </Form.Item> 293 + </FormItem>
282 #elseif($column.htmlType == "datetime") 294 #elseif($column.htmlType == "datetime")
283 #if ($column.listOperationCondition != "BETWEEN")## 非范围 295 #if ($column.listOperationCondition != "BETWEEN")## 非范围
284 - <Form.Item label="${comment}" name="${javaField}"> 296 + <FormItem label="${comment}" name="${javaField}">
285 <DatePicker 297 <DatePicker
286 v-model:value="queryParams.${javaField}" 298 v-model:value="queryParams.${javaField}"
287 valueFormat="YYYY-MM-DD" 299 valueFormat="YYYY-MM-DD"
@@ -289,25 +301,25 @@ onMounted(() =&gt; { @@ -289,25 +301,25 @@ onMounted(() =&gt; {
289 allowClear 301 allowClear
290 class="w-full" 302 class="w-full"
291 /> 303 />
292 - </Form.Item> 304 + </FormItem>
293 #else## 范围 305 #else## 范围
294 - <Form.Item label="${comment}" name="${javaField}"> 306 + <FormItem label="${comment}" name="${javaField}">
295 <RangePicker 307 <RangePicker
296 v-model:value="queryParams.${javaField}" 308 v-model:value="queryParams.${javaField}"
297 v-bind="getRangePickerDefaultProps()" 309 v-bind="getRangePickerDefaultProps()"
298 class="w-full" 310 class="w-full"
299 /> 311 />
300 - </Form.Item> 312 + </FormItem>
301 #end 313 #end
302 #end 314 #end
303 #end 315 #end
304 #end 316 #end
305 - <Form.Item> 317 + <FormItem>
306 <Button class="ml-2" @click="resetQuery"> 重置 </Button> 318 <Button class="ml-2" @click="resetQuery"> 重置 </Button>
307 <Button class="ml-2" @click="handleQuery" type="primary"> 319 <Button class="ml-2" @click="handleQuery" type="primary">
308 搜索 320 搜索
309 </Button> 321 </Button>
310 - </Form.Item> 322 + </FormItem>
311 </Form> 323 </Form>
312 </Card> 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,8 +2,14 @@
2 #set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组 2 #set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组
3 #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 3 #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
4 #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) 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 <script lang="ts" setup> 11 <script lang="ts" setup>
6 -import type { Rule } from 'antdv-next/es/form'; 12 +import type { Rule } from 'antdv-next';
7 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; 13 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
8 14
9 import { computed, ref } from 'vue'; 15 import { computed, ref } from 'vue';
@@ -13,7 +19,7 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;; @@ -13,7 +19,7 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;;
13 import { getDictOptions } from '@vben/hooks'; 19 import { getDictOptions } from '@vben/hooks';
14 import { Tinymce as RichTextarea } from '#/components/tinymce'; 20 import { Tinymce as RichTextarea } from '#/components/tinymce';
15 import { ImageUpload, FileUpload } from "#/components/upload"; 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 import { $t } from '#/locales'; 24 import { $t } from '#/locales';
19 25
@@ -130,23 +136,23 @@ function resetForm(){ @@ -130,23 +136,23 @@ function resetForm(){
130 #set ($dictMethod = "boolean") 136 #set ($dictMethod = "boolean")
131 #end 137 #end
132 #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 138 #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
133 - <Form.Item label="${comment}" name="${javaField}"> 139 + <FormItem label="${comment}" name="${javaField}">
134 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> 140 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
135 - </Form.Item> 141 + </FormItem>
136 #elseif($column.htmlType == "imageUpload")## 图片上传 142 #elseif($column.htmlType == "imageUpload")## 图片上传
137 - <Form.Item label="${comment}" name="${javaField}"> 143 + <FormItem label="${comment}" name="${javaField}">
138 <ImageUpload v-model:value="formData.${javaField}" /> 144 <ImageUpload v-model:value="formData.${javaField}" />
139 - </Form.Item> 145 + </FormItem>
140 #elseif($column.htmlType == "fileUpload")## 文件上传 146 #elseif($column.htmlType == "fileUpload")## 文件上传
141 - <Form.Item label="${comment}" name="${javaField}"> 147 + <FormItem label="${comment}" name="${javaField}">
142 <FileUpload v-model:value="formData.${javaField}" /> 148 <FileUpload v-model:value="formData.${javaField}" />
143 - </Form.Item> 149 + </FormItem>
144 #elseif($column.htmlType == "editor")## 文本编辑器 150 #elseif($column.htmlType == "editor")## 文本编辑器
145 - <Form.Item label="${comment}" name="${javaField}"> 151 + <FormItem label="${comment}" name="${javaField}">
146 <RichTextarea v-model="formData.${javaField}" height="500px" /> 152 <RichTextarea v-model="formData.${javaField}" height="500px" />
147 - </Form.Item> 153 + </FormItem>
148 #elseif($column.htmlType == "select")## 下拉框 154 #elseif($column.htmlType == "select")## 下拉框
149 - <Form.Item label="${comment}" name="${javaField}"> 155 + <FormItem label="${comment}" name="${javaField}">
150 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> 156 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
151 #if ("" != $dictType)## 有数据字典 157 #if ("" != $dictType)## 有数据字典
152 <SelectOption 158 <SelectOption
@@ -160,9 +166,9 @@ function resetForm(){ @@ -160,9 +166,9 @@ function resetForm(){
160 <SelectOption label="请选择字典生成" value="" /> 166 <SelectOption label="请选择字典生成" value="" />
161 #end 167 #end
162 </Select> 168 </Select>
163 - </Form.Item> 169 + </FormItem>
164 #elseif($column.htmlType == "checkbox")## 多选框 170 #elseif($column.htmlType == "checkbox")## 多选框
165 - <Form.Item label="${comment}" name="${javaField}"> 171 + <FormItem label="${comment}" name="${javaField}">
166 <CheckboxGroup v-model:value="formData.${javaField}"> 172 <CheckboxGroup v-model:value="formData.${javaField}">
167 #if ("" != $dictType)## 有数据字典 173 #if ("" != $dictType)## 有数据字典
168 <Checkbox 174 <Checkbox
@@ -176,9 +182,9 @@ function resetForm(){ @@ -176,9 +182,9 @@ function resetForm(){
176 <Checkbox label="请选择字典生成" /> 182 <Checkbox label="请选择字典生成" />
177 #end 183 #end
178 </CheckboxGroup> 184 </CheckboxGroup>
179 - </Form.Item> 185 + </FormItem>
180 #elseif($column.htmlType == "radio")## 单选框 186 #elseif($column.htmlType == "radio")## 单选框
181 - <Form.Item label="${comment}" name="${javaField}"> 187 + <FormItem label="${comment}" name="${javaField}">
182 <RadioGroup v-model:value="formData.${javaField}"> 188 <RadioGroup v-model:value="formData.${javaField}">
183 #if ("" != $dictType)## 有数据字典 189 #if ("" != $dictType)## 有数据字典
184 <Radio 190 <Radio
@@ -192,19 +198,19 @@ function resetForm(){ @@ -192,19 +198,19 @@ function resetForm(){
192 <Radio value="1">请选择字典生成</Radio> 198 <Radio value="1">请选择字典生成</Radio>
193 #end 199 #end
194 </RadioGroup> 200 </RadioGroup>
195 - </Form.Item> 201 + </FormItem>
196 #elseif($column.htmlType == "datetime")## 时间框 202 #elseif($column.htmlType == "datetime")## 时间框
197 - <Form.Item label="${comment}" name="${javaField}"> 203 + <FormItem label="${comment}" name="${javaField}">
198 <DatePicker 204 <DatePicker
199 v-model:value="formData.${javaField}" 205 v-model:value="formData.${javaField}"
200 valueFormat="x" 206 valueFormat="x"
201 placeholder="选择${comment}" 207 placeholder="选择${comment}"
202 /> 208 />
203 - </Form.Item> 209 + </FormItem>
204 #elseif($column.htmlType == "textarea")## 文本框 210 #elseif($column.htmlType == "textarea")## 文本框
205 - <Form.Item label="${comment}" name="${javaField}"> 211 + <FormItem label="${comment}" name="${javaField}">
206 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> 212 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
207 - </Form.Item> 213 + </FormItem>
208 #end 214 #end
209 #end 215 #end
210 #end 216 #end
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/form_sub_inner.vue.vm
1 ## 主表的 normal 和 inner 使用相同的 form 表单 1 ## 主表的 normal 和 inner 使用相同的 form 表单
2 -#parse("codegen/vue3_vben5_antd/general/views/modules/form_sub_normal.vue.vm")  
3 \ No newline at end of file 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,6 +4,12 @@
4 #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex)) 4 #set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))
5 #set ($subClassNameVar = $subClassNameVars.get($subIndex)) 5 #set ($subClassNameVar = $subClassNameVars.get($subIndex))
6 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 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 <script lang="ts" setup> 13 <script lang="ts" setup>
8 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; 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,7 +18,7 @@ import { computed, ref, h, onMounted, watch, nextTick } from &#39;vue&#39;;
12 import { DICT_TYPE } from '@vben/constants'; 18 import { DICT_TYPE } from '@vben/constants';
13 import { getDictOptions } from '@vben/hooks'; 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 import { $t } from '#/locales'; 22 import { $t } from '#/locales';
17 23
18 #if ($subTable.subJoinMany) ## 一对多 24 #if ($subTable.subJoinMany) ## 一对多
@@ -22,7 +28,7 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;; @@ -22,7 +28,7 @@ import { VxeColumn, VxeTable } from &#39;#/adapter/vxe-table&#39;;
22 import { ImageUpload, FileUpload } from "#/components/upload"; 28 import { ImageUpload, FileUpload } from "#/components/upload";
23 import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}'; 29 import { get${subSimpleClassName}ListBy${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
24 #else 30 #else
25 -import type { Rule } from 'antdv-next/es/form'; 31 +import type { Rule } from 'antdv-next';
26 import { Tinymce as RichTextarea } from '#/components/tinymce'; 32 import { Tinymce as RichTextarea } from '#/components/tinymce';
27 import { ImageUpload, FileUpload } from "#/components/upload"; 33 import { ImageUpload, FileUpload } from "#/components/upload";
28 import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}'; 34 import { get${subSimpleClassName}By${SubJoinColumnName} } from '#/api/${table.moduleName}/${table.businessName}';
@@ -264,23 +270,23 @@ watch( @@ -264,23 +270,23 @@ watch(
264 #set ($dictMethod = "boolean") 270 #set ($dictMethod = "boolean")
265 #end 271 #end
266 #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里 272 #if ($column.htmlType == "input" && !$column.primaryKey)## 忽略主键,不用在表单里
267 - <Form.Item label="${comment}" name="${javaField}"> 273 + <FormItem label="${comment}" name="${javaField}">
268 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> 274 <Input v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
269 - </Form.Item> 275 + </FormItem>
270 #elseif($column.htmlType == "imageUpload")## 图片上传 276 #elseif($column.htmlType == "imageUpload")## 图片上传
271 - <Form.Item label="${comment}" name="${javaField}"> 277 + <FormItem label="${comment}" name="${javaField}">
272 <ImageUpload v-model:value="formData.${javaField}" /> 278 <ImageUpload v-model:value="formData.${javaField}" />
273 - </Form.Item> 279 + </FormItem>
274 #elseif($column.htmlType == "fileUpload")## 文件上传 280 #elseif($column.htmlType == "fileUpload")## 文件上传
275 - <Form.Item label="${comment}" name="${javaField}"> 281 + <FormItem label="${comment}" name="${javaField}">
276 <FileUpload v-model:value="formData.${javaField}" /> 282 <FileUpload v-model:value="formData.${javaField}" />
277 - </Form.Item> 283 + </FormItem>
278 #elseif($column.htmlType == "editor")## 文本编辑器 284 #elseif($column.htmlType == "editor")## 文本编辑器
279 - <Form.Item label="${comment}" name="${javaField}"> 285 + <FormItem label="${comment}" name="${javaField}">
280 <RichTextarea v-model="formData.${javaField}" height="500px" /> 286 <RichTextarea v-model="formData.${javaField}" height="500px" />
281 - </Form.Item> 287 + </FormItem>
282 #elseif($column.htmlType == "select")## 下拉框 288 #elseif($column.htmlType == "select")## 下拉框
283 - <Form.Item label="${comment}" name="${javaField}"> 289 + <FormItem label="${comment}" name="${javaField}">
284 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}"> 290 <Select v-model:value="formData.${javaField}" placeholder="请选择${comment}">
285 #if ("" != $dictType)## 有数据字典 291 #if ("" != $dictType)## 有数据字典
286 <SelectOption 292 <SelectOption
@@ -294,9 +300,9 @@ watch( @@ -294,9 +300,9 @@ watch(
294 <SelectOption label="请选择字典生成" value="" /> 300 <SelectOption label="请选择字典生成" value="" />
295 #end 301 #end
296 </Select> 302 </Select>
297 - </Form.Item> 303 + </FormItem>
298 #elseif($column.htmlType == "checkbox")## 多选框 304 #elseif($column.htmlType == "checkbox")## 多选框
299 - <Form.Item label="${comment}" name="${javaField}"> 305 + <FormItem label="${comment}" name="${javaField}">
300 <CheckboxGroup v-model:value="formData.${javaField}"> 306 <CheckboxGroup v-model:value="formData.${javaField}">
301 #if ("" != $dictType)## 有数据字典 307 #if ("" != $dictType)## 有数据字典
302 <Checkbox 308 <Checkbox
@@ -310,9 +316,9 @@ watch( @@ -310,9 +316,9 @@ watch(
310 <Checkbox label="请选择字典生成" /> 316 <Checkbox label="请选择字典生成" />
311 #end 317 #end
312 </CheckboxGroup> 318 </CheckboxGroup>
313 - </Form.Item> 319 + </FormItem>
314 #elseif($column.htmlType == "radio")## 单选框 320 #elseif($column.htmlType == "radio")## 单选框
315 - <Form.Item label="${comment}" name="${javaField}"> 321 + <FormItem label="${comment}" name="${javaField}">
316 <RadioGroup v-model:value="formData.${javaField}"> 322 <RadioGroup v-model:value="formData.${javaField}">
317 #if ("" != $dictType)## 有数据字典 323 #if ("" != $dictType)## 有数据字典
318 <Radio 324 <Radio
@@ -326,19 +332,19 @@ watch( @@ -326,19 +332,19 @@ watch(
326 <Radio value="1">请选择字典生成</Radio> 332 <Radio value="1">请选择字典生成</Radio>
327 #end 333 #end
328 </RadioGroup> 334 </RadioGroup>
329 - </Form.Item> 335 + </FormItem>
330 #elseif($column.htmlType == "datetime")## 时间框 336 #elseif($column.htmlType == "datetime")## 时间框
331 - <Form.Item label="${comment}" name="${javaField}"> 337 + <FormItem label="${comment}" name="${javaField}">
332 <DatePicker 338 <DatePicker
333 v-model:value="formData.${javaField}" 339 v-model:value="formData.${javaField}"
334 valueFormat="x" 340 valueFormat="x"
335 placeholder="选择${comment}" 341 placeholder="选择${comment}"
336 /> 342 />
337 - </Form.Item> 343 + </FormItem>
338 #elseif($column.htmlType == "textarea")## 文本框 344 #elseif($column.htmlType == "textarea")## 文本框
339 - <Form.Item label="${comment}" name="${javaField}"> 345 + <FormItem label="${comment}" name="${javaField}">
340 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" /> 346 <TextArea v-model:value="formData.${javaField}" placeholder="请输入${comment}" />
341 - </Form.Item> 347 + </FormItem>
342 #end 348 #end
343 #end 349 #end
344 #end 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,6 +5,16 @@
5 #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段 5 #set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段
6 #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex)) 6 #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($subIndex))
7 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 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 <script lang="ts" setup> 18 <script lang="ts" setup>
9 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}'; 19 import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${table.businessName}';
10 import type { VxeTableInstance } from '#/adapter/vxe-table'; 20 import type { VxeTableInstance } from '#/adapter/vxe-table';
@@ -15,7 +25,9 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;; @@ -15,7 +25,9 @@ import { DICT_TYPE } from &#39;@vben/constants&#39;;
15 import { getDictOptions } from '@vben/hooks'; 25 import { getDictOptions } from '@vben/hooks';
16 26
17 import { DictTag } from '#/components/dict-tag'; 27 import { DictTag } from '#/components/dict-tag';
  28 +#if ($table.templateType == 11 && $rangePickerEnable)
18 import { getRangePickerDefaultProps } from '#/utils/rangePickerProps'; 29 import { getRangePickerDefaultProps } from '#/utils/rangePickerProps';
  30 +#end
19 import { VxeColumn, VxeTable } from '#/adapter/vxe-table'; 31 import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
20 import { formatDateTime } from '@vben/utils'; 32 import { formatDateTime } from '@vben/utils';
21 33
@@ -25,7 +37,7 @@ import { useTableToolbar, VbenVxeTableToolbar } from &#39;@vben/plugins/vxe-table&#39;; @@ -25,7 +37,7 @@ import { useTableToolbar, VbenVxeTableToolbar } from &#39;@vben/plugins/vxe-table&#39;;
25 import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue' 37 import ${subSimpleClassName}Form from './${subSimpleClassName_strikeCase}-form.vue'
26 import { Tinymce as RichTextarea } from '#/components/tinymce'; 38 import { Tinymce as RichTextarea } from '#/components/tinymce';
27 import { ImageUpload, FileUpload } from "#/components/upload"; 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 import { Plus, Trash2 } from '@vben/icons'; 41 import { Plus, Trash2 } from '@vben/icons';
30 import { $t } from '#/locales'; 42 import { $t } from '#/locales';
31 #end 43 #end
@@ -224,7 +236,7 @@ onMounted(() =&gt; { @@ -224,7 +236,7 @@ onMounted(() =&gt; {
224 #set ($dictMethod = "boolean") 236 #set ($dictMethod = "boolean")
225 #end 237 #end
226 #if ($column.htmlType == "input") 238 #if ($column.htmlType == "input")
227 - <Form.Item label="${comment}" name="${javaField}"> 239 + <FormItem label="${comment}" name="${javaField}">
228 <Input 240 <Input
229 v-model:value="queryParams.${javaField}" 241 v-model:value="queryParams.${javaField}"
230 placeholder="请输入${comment}" 242 placeholder="请输入${comment}"
@@ -232,9 +244,9 @@ onMounted(() =&gt; { @@ -232,9 +244,9 @@ onMounted(() =&gt; {
232 @pressEnter="handleQuery" 244 @pressEnter="handleQuery"
233 class="w-full" 245 class="w-full"
234 /> 246 />
235 - </Form.Item> 247 + </FormItem>
236 #elseif ($column.htmlType == "select" || $column.htmlType == "radio" || $column.htmlType == "checkbox") 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 <Select 250 <Select
239 v-model:value="queryParams.${javaField}" 251 v-model:value="queryParams.${javaField}"
240 placeholder="请选择${comment}" 252 placeholder="请选择${comment}"
@@ -253,10 +265,10 @@ onMounted(() =&gt; { @@ -253,10 +265,10 @@ onMounted(() =&gt; {
253 <SelectOption label="请选择字典生成" value="" /> 265 <SelectOption label="请选择字典生成" value="" />
254 #end 266 #end
255 </Select> 267 </Select>
256 - </Form.Item> 268 + </FormItem>
257 #elseif($column.htmlType == "datetime") 269 #elseif($column.htmlType == "datetime")
258 #if ($column.listOperationCondition != "BETWEEN")## 非范围 270 #if ($column.listOperationCondition != "BETWEEN")## 非范围
259 - <Form.Item label="${comment}" name="${javaField}"> 271 + <FormItem label="${comment}" name="${javaField}">
260 <DatePicker 272 <DatePicker
261 v-model:value="queryParams.${javaField}" 273 v-model:value="queryParams.${javaField}"
262 valueFormat="YYYY-MM-DD" 274 valueFormat="YYYY-MM-DD"
@@ -264,25 +276,25 @@ onMounted(() =&gt; { @@ -264,25 +276,25 @@ onMounted(() =&gt; {
264 allowClear 276 allowClear
265 class="w-full" 277 class="w-full"
266 /> 278 />
267 - </Form.Item> 279 + </FormItem>
268 #else## 范围 280 #else## 范围
269 - <Form.Item label="${comment}" name="${javaField}"> 281 + <FormItem label="${comment}" name="${javaField}">
270 <RangePicker 282 <RangePicker
271 v-model:value="queryParams.${javaField}" 283 v-model:value="queryParams.${javaField}"
272 v-bind="getRangePickerDefaultProps()" 284 v-bind="getRangePickerDefaultProps()"
273 class="w-full" 285 class="w-full"
274 /> 286 />
275 - </Form.Item> 287 + </FormItem>
276 #end 288 #end
277 #end 289 #end
278 #end 290 #end
279 #end 291 #end
280 - <Form.Item> 292 + <FormItem>
281 <Button class="ml-2" @click="resetQuery"> 重置 </Button> 293 <Button class="ml-2" @click="resetQuery"> 重置 </Button>
282 <Button class="ml-2" @click="handleQuery" type="primary"> 294 <Button class="ml-2" @click="handleQuery" type="primary">
283 搜索 295 搜索
284 </Button> 296 </Button>
285 - </Form.Item> 297 + </FormItem>
286 </Form> 298 </Form>
287 </Card> 299 </Card>
288 300
yudao-module-infra/src/main/resources/codegen/vue3_vben5_antdv_next/general/views/modules/list_sub_inner.vue.vm
1 ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: 1 ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
2 ## 1)inner 使用 list 不分页,erp 使用 page 分页 2 ## 1)inner 使用 list 不分页,erp 使用 page 分页
3 ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 3 ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
4 -#parse("codegen/vue3_vben5_antd/general/views/modules/list_sub_erp.vue.vm")  
5 \ No newline at end of file 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,7 +6,7 @@ import { computed, ref } from &#39;vue&#39;;
6 6
7 import { useVbenModal } from '@vben/common-ui'; 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 import { useVbenForm } from '#/adapter/form'; 11 import { useVbenForm } from '#/adapter/form';
12 import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${table.businessName}'; 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 <script lang="ts" setup> 1 <script lang="ts" setup>
2 -import type { FileType } from 'antdv-next/es/upload/interface';  
3 -  
4 import { useVbenModal } from '@vben/common-ui'; 2 import { useVbenModal } from '@vben/common-ui';
5 import { downloadFileFromBlobPart } from '@vben/utils'; 3 import { downloadFileFromBlobPart } from '@vben/utils';
6 4
@@ -46,7 +44,7 @@ const [Modal, modalApi] = useVbenModal({ @@ -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 formApi.setFieldValue('file', file); 48 formApi.setFieldValue('file', file);
51 return false; 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,7 +8,7 @@ import { ref } from &#39;vue&#39;;
8 import {#if ($table.templateType != 2 && $deleteBatchEnable) confirm,#end Page, useVbenModal } from '@vben/common-ui'; 8 import {#if ($table.templateType != 2 && $deleteBatchEnable) confirm,#end Page, useVbenModal } from '@vben/common-ui';
9 import { downloadFileFromBlobPart#if ($table.templateType != 2 && $deleteBatchEnable), isEmpty#end } from '@vben/utils'; 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 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table'; 13 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
14 #if (${table.templateType} == 2)## 树表接口 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 ## 主表的 normal 和 inner 使用相同的 form 表单 1 ## 主表的 normal 和 inner 使用相同的 form 表单
2 -#parse("codegen/vue3_vben5_antd/schema/views/modules/form_sub_normal.vue.vm")  
3 \ No newline at end of file 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,6 +5,12 @@
5 #set ($subClassNameVar = $subClassNameVars.get($subIndex)) 5 #set ($subClassNameVar = $subClassNameVars.get($subIndex))
6 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写 6 #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
7 #set ($apiName = "${table.moduleName.substring(0,1).toUpperCase()}${table.moduleName.substring(1)}${simpleClassName}Api") 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 <script lang="ts" setup> 14 <script lang="ts" setup>
9 import type { ${apiName} } from '#/api/${table.moduleName}/${table.businessName}'; 15 import type { ${apiName} } from '#/api/${table.moduleName}/${table.businessName}';
10 16
@@ -14,7 +20,19 @@ @@ -14,7 +20,19 @@
14 20
15 #if ($subTable.subJoinMany) ## 一对多 21 #if ($subTable.subJoinMany) ## 一对多
16 import { Plus } from "@vben/icons"; 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 import { ImageUpload, FileUpload } from "#/components/upload"; 36 import { ImageUpload, FileUpload } from "#/components/upload";
19 import { useVbenVxeGrid } from '#/adapter/vxe-table'; 37 import { useVbenVxeGrid } from '#/adapter/vxe-table';
20 import { use${subSimpleClassName}GridEditColumns } from '../data'; 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 ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点: 1 ## 子表的 erp 和 inner 使用相似的 list 列表,差异主要两点:
2 ## 1)inner 使用 list 不分页,erp 使用 page 分页 2 ## 1)inner 使用 list 不分页,erp 使用 page 分页
3 ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持 3 ## 2)erp 支持单个子表的新增、修改、删除,inner 不支持
4 -#parse("codegen/vue3_vben5_antd/schema/views/modules/list_sub_erp.vue.vm")  
5 \ No newline at end of file 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 +}