Quellcode durchsuchen

【功能新增】INFRA: vben next 单表代码生成

puhui999 vor 4 Monaten
Ursprung
Commit
69486939d5

+ 1 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/enums/codegen/CodegenFrontTypeEnum.java

@@ -15,6 +15,7 @@ public enum CodegenFrontTypeEnum {
     VUE2(10), // Vue2 Element UI 标准模版
     VUE3(20), // Vue3 Element Plus 标准模版
     VUE3_VBEN(30), // Vue3 VBEN 模版
+    VUE3_VBEN_NEXT_SCHEMA(40), // Vue3 vben5 schema 模版
     ;
 
     /**

+ 13 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java

@@ -144,6 +144,15 @@ public class CodegenEngine {
                     vue3FilePath("views/${table.moduleName}/${table.businessName}/${simpleClassName}Modal.vue"))
             .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("api/api.ts"),
                     vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
+            // Vue3 vben5 schema 模版
+            .put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/data.ts"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/index.vue"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/form.vue"),
+                    vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("api/api.ts"),
+                    vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts"))
             .build();
 
     @Resource
@@ -496,6 +505,10 @@ public class CodegenEngine {
         return "codegen/vue3_vben/" + path + ".vm";
     }
 
+    private static String vue3VbenNextSchemaTemplatePath(String path) {
+        return "codegen/vue3_vben_next/schema/" + path + ".vm";
+    }
+
     private static boolean isSubTemplate(String path) {
         return path.contains("_sub");
     }

+ 0 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/ant_design_vue/index.vue.vm


+ 115 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/api/api.ts.vm

@@ -0,0 +1,115 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+#set ($baseURL = "/${table.moduleName}/${simpleClassName_strikeCase}")
+
+export namespace ${simpleClassName}Api {
+  /** ${table.classComment}信息 */
+  export interface ${simpleClassName} {
+#foreach ($column in $columns)
+#if ($column.createOperation || $column.updateOperation)
+#if(${column.javaType.toLowerCase()} == "long" || ${column.javaType.toLowerCase()} == "integer" || ${column.javaType.toLowerCase()} == "short" || ${column.javaType.toLowerCase()} == "double" || ${column.javaType.toLowerCase()} == "bigdecimal")
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: number; // ${column.columnComment}
+#elseif(${column.javaType.toLowerCase()} == "date" || ${column.javaType.toLowerCase()} == "localdate" || ${column.javaType.toLowerCase()} == "localdatetime")
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: Date; // ${column.columnComment}
+#else
+    ${column.javaField}#if($column.updateOperation && !$column.primaryKey && !$column.nullable)?#end: ${column.javaType.toLowerCase()}; // ${column.columnComment}
+#end
+#end
+#end
+  }
+}
+
+#if ( $table.templateType != 2 )
+/** 查询${table.classComment}分页 */
+export function get${simpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${simpleClassName}>>('${baseURL}/page', { params });
+}
+#else
+/** 查询${table.classComment}列表 */
+export function get${simpleClassName}List(params: any) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}[]>('${baseURL}/list', { params });
+}
+#end
+
+/** 查询${table.classComment}详情 */
+export function get${simpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}>(`${baseURL}/get?id=${id}`);
+}
+
+/** 新增${table.classComment} */
+export function create${simpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.post('${baseURL}/create', data);
+}
+
+/** 修改${table.classComment} */
+export function update${simpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.put('${baseURL}/update', data);
+}
+
+/** 删除${table.classComment} */
+export function delete${simpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/delete?id=${id}`);
+}
+
+/** 导出${table.classComment} */
+export function export${simpleClassName}(params: any) {
+  return requestClient.download('${baseURL}/export-excel', params);
+}
+
+## 特殊:主子表专属逻辑
+#foreach ($subTable in $subTables)
+#set ($index = $foreach.count - 1)
+#set ($subSimpleClassName = $subSimpleClassNames.get($index))
+#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段
+#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段
+#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写
+#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))
+#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))
+#set ($subClassNameVar = $subClassNameVars.get($index))
+
+// ==================== 子表($subTable.classComment) ====================
+## 情况一:MASTER_ERP 时,需要分查询页子表
+#if ( $table.templateType == 11 )
+/** 获得${subTable.classComment}分页 */
+export function get${subSimpleClassName}Page(params: PageParam) {
+  return requestClient.get<PageResult<${simpleClassName}Api.${simpleClassName}>>(`${baseURL}/${subSimpleClassName_strikeCase}/page`, { params });
+}
+## 情况二:非 MASTER_ERP 时,需要列表查询子表
+#else
+  #if ( $subTable.subJoinMany )
+/** 获得${subTable.classComment}列表 */
+export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}[]>(`${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+  #else
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}: number) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=${${subJoinColumn.javaField}}`);
+}
+  #end
+#end
+## 特殊:MASTER_ERP 时,支持单个的新增、修改、删除操作
+#if ( $table.templateType == 11 )
+/** 新增${subTable.classComment} */
+export function create${subSimpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.post(`${baseURL}/${subSimpleClassName_strikeCase}/create`, data);
+}
+
+/** 修改${subTable.classComment} */
+export function update${subSimpleClassName}(data: ${simpleClassName}Api.${simpleClassName}) {
+  return requestClient.put(`${baseURL}/${subSimpleClassName_strikeCase}/update`, data);
+}
+
+/** 删除${subTable.classComment} */
+export function delete${subSimpleClassName}(id: number) {
+  return requestClient.delete(`${baseURL}/${subSimpleClassName_strikeCase}/delete?id=${id}`);
+}
+
+/** 获得${subTable.classComment} */
+export function get${subSimpleClassName}(id: number) {
+  return requestClient.get<${simpleClassName}Api.${simpleClassName}>(`${baseURL}/${subSimpleClassName_strikeCase}/get?id=${id}`);
+}
+#end
+#end
+

+ 223 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/data.ts.vm

@@ -0,0 +1,223 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+import { z } from '#/adapter/form';
+import { CommonStatusEnum } from '#/utils/constants';
+import { DICT_TYPE, getDictOptions } from '#/utils/dict';
+import { getRangePickerDefaultProps } from '#/utils/date';
+import { useAccess } from '@vben/access';
+
+const { hasAccessByCodes } = useAccess();
+
+/** 新增/修改的表单 */
+export function useFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'id',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
+      },
+    },
+#foreach($column in $columns)
+#if ($column.createOperation || $column.updateOperation)
+#if (!$column.primaryKey)## 忽略主键,不用在表单里
+  #set ($dictType = $column.dictType)
+  #set ($javaType = $column.javaType)
+  #set ($javaField = $column.javaField)
+  #set ($comment = $column.columnComment)
+  #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+    #set ($dictMethod = "number")
+  #elseif ($javaType == "String")
+    #set ($dictMethod = "string")
+  #elseif ($javaType == "Boolean")
+    #set ($dictMethod = "boolean")
+  #end
+    {
+      fieldName: '${javaField}',
+      label: '${comment}',
+  #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+      rules: 'required',
+  #end
+  #if ($column.htmlType == "input")
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入${comment}',
+      },
+  #elseif($column.htmlType == "imageUpload")## 图片上传
+      component: 'FileUpload',
+      componentProps: {
+        fileType: 'image',
+        maxCount: 1,
+      },
+  #elseif($column.htmlType == "fileUpload")## 文件上传
+      component: 'FileUpload',
+      componentProps: {
+        fileType: 'file',
+        maxCount: 1,
+      },
+  #elseif($column.htmlType == "editor")## 文本编辑器
+      component: 'Editor',
+  #elseif($column.htmlType == "select")## 下拉框
+      component: 'Select',
+      componentProps: {
+        #if ("" != $dictType)## 有数据字典
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else##没数据字典
+        options: [],
+        #end
+        placeholder: '请选择${comment}',
+      },
+  #elseif($column.htmlType == "checkbox")## 多选框
+      component: 'Checkbox',
+      componentProps: {
+        #if ("" != $dictType)## 有数据字典
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else##没数据字典
+        options: [],
+        #end
+      },
+  #elseif($column.htmlType == "radio")## 单选框
+      component: 'RadioGroup',
+      componentProps: {
+        #if ("" != $dictType)## 有数据字典
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else##没数据字典
+        options: [],
+        #end
+        buttonStyle: 'solid',
+        optionType: 'button',
+      },
+  #elseif($column.htmlType == "datetime")## 时间框
+      component: 'DatePicker',
+      componentProps: {
+        showTime: true,
+        format: 'YYYY-MM-DD HH:mm:ss',
+        valueFormat: 'x',
+      },
+  #elseif($column.htmlType == "textarea")## 文本域
+      component: 'Textarea',
+      componentProps: {
+        placeholder: '请输入${comment}',
+      },
+  #end
+    },
+#end
+#end
+#end
+  ];
+}
+
+/** 列表的搜索表单 */
+export function useGridFormSchema(): VbenFormSchema[] {
+  return [
+#foreach($column in $columns)
+#if ($column.listOperation)
+  #set ($dictType = $column.dictType)
+  #set ($javaType = $column.javaType)
+  #set ($javaField = $column.javaField)
+  #set ($comment = $column.columnComment)
+  #if ($javaType == "Integer" || $javaType == "Long" || $javaType == "Byte" || $javaType == "Short")
+    #set ($dictMethod = "number")
+  #elseif ($javaType == "String")
+    #set ($dictMethod = "string")
+  #elseif ($javaType == "Boolean")
+    #set ($dictMethod = "boolean")
+  #end
+    {
+      fieldName: '${javaField}',
+      label: '${comment}',
+  #if ($column.htmlType == "input")
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '请输入${comment}',
+      },
+  #elseif ($column.htmlType == "select")
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else## 未设置 dictType 数据字典的情况
+        options: [],
+        #end
+        placeholder: '请选择${comment}',
+      },
+  #elseif ($column.htmlType == "radio")
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), '$dictMethod'),
+        #else## 未设置 dictType 数据字典的情况
+        options: [],
+        #end
+      },
+  #elseif($column.htmlType == "datetime")
+      component: 'RangePicker',
+      componentProps: {
+        ...getRangePickerDefaultProps(),
+        allowClear: true,
+      },
+  #end
+    },
+#end
+#end
+  ];
+}
+
+/** 列表的字段 */
+export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>(
+  onActionClick: OnActionClickFn<T>,
+): VxeTableGridOptions['columns'] {
+  return [
+#foreach($column in $columns)
+#if ($column.listOperationResult)
+  #set ($dictType = $column.dictType)
+  #set ($javaField = $column.javaField)
+  #set ($comment = $column.columnComment)
+    {
+      field: '${javaField}',
+      title: '${comment}',
+      minWidth: 120,
+  #if ($column.javaType == "LocalDateTime")## 时间类型
+      formatter: 'formatDateTime',
+  #elseif("" != $dictType)## 数据字典
+      cellRender: {
+        name: 'CellDict',
+        props: { type: DICT_TYPE.$dictType.toUpperCase() },
+      },
+  #end
+    },
+#end
+#end
+    {
+      field: 'operation',
+      title: '操作',
+      minWidth: 180,
+      align: 'center',
+      fixed: 'right',
+      cellRender: {
+        attrs: {
+          nameField: '${columns[0].javaField}',
+          nameTitle: '${table.classComment}',
+          onClick: onActionClick,
+        },
+        name: 'CellOperation',
+        options: [
+          {
+            code: 'edit',
+            show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:update']),
+          },
+          {
+            code: 'delete',
+            show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:delete']),
+          }
+        ],
+      },
+    },
+  ];
+}

+ 78 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/form.vue.vm

@@ -0,0 +1,78 @@
+<script lang="ts" setup>
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+import { useVbenModal } from '@vben/common-ui';
+import { message } from 'ant-design-vue';
+
+import { $t } from '#/locales';
+import { computed, ref } from 'vue';
+import { useVbenForm } from '#/adapter/form';
+import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formData = ref<${simpleClassName}Api.${simpleClassName}>();
+const getTitle = computed(() => {
+  return formData.value?.id
+    ? $t('ui.actionTitle.edit', ['${table.classComment}'])
+    : $t('ui.actionTitle.create', ['${table.classComment}']);
+});
+
+const [Form, formApi] = useVbenForm({
+  layout: 'horizontal',
+  schema: useFormSchema(),
+  showDefaultActions: false,
+  commonConfig: {
+    labelWidth: 120
+  }
+});
+
+const [Modal, modalApi] = useVbenModal({
+  async onConfirm() {
+    const { valid } = await formApi.validate();
+    if (!valid) {
+      return;
+    }
+    modalApi.lock();
+    // 提交表单
+    const data = (await formApi.getValues()) as ${simpleClassName}Api.${simpleClassName};
+    try {
+      await (formData.value?.id ? update${simpleClassName}(data) : create${simpleClassName}(data));
+      // 关闭并提示
+      await modalApi.close();
+      emit('success');
+      message.success({
+        content: $t('ui.actionMessage.operationSuccess'),
+        key: 'action_process_msg',
+      });
+    } finally {
+      modalApi.lock(false);
+    }
+  },
+  async onOpenChange(isOpen: boolean) {
+    if (!isOpen) {
+      return;
+    }
+    // 加载数据
+    const data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>();
+    if (!data || !data.id) {
+      return;
+    }
+    modalApi.lock();
+    try {
+      formData.value = await get${simpleClassName}(data.id as number);
+      // 设置到 values
+      await formApi.setValues(formData.value);
+    } finally {
+      modalApi.lock(false);
+    }
+  },
+});
+</script>
+
+<template>
+  <Modal :title="getTitle">
+    <Form class="mx-4" />
+  </Modal>
+</template>

+ 128 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/index.vue.vm

@@ -0,0 +1,128 @@
+<script lang="ts" setup>
+import type { OnActionClickParams, VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { Button, message } from 'ant-design-vue';
+import { Download, Plus } from '@vben/icons';
+import Form from './modules/form.vue';
+import { DocAlert } from '#/components/doc-alert';
+
+import { $t } from '#/locales';
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { get${simpleClassName}Page, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}';
+import { downloadByData } from '#/utils/download';
+
+import { useGridColumns, useGridFormSchema } from './data';
+
+const [FormModal, formModalApi] = useVbenModal({
+  connectedComponent: Form,
+  destroyOnClose: true,
+});
+
+/** 刷新表格 */
+function onRefresh() {
+  gridApi.query();
+}
+
+/** 导出表格 */
+async function onExport() {
+  const data = await export${simpleClassName}(await gridApi.formApi.getValues());
+  downloadByData(data, '${table.classComment}.xls');
+}
+
+/** 创建${table.classComment} */
+function onCreate() {
+  formModalApi.setData(null).open();
+}
+
+/** 编辑${table.classComment} */
+function onEdit(row: ${simpleClassName}Api.${simpleClassName}) {
+  formModalApi.setData(row).open();
+}
+
+/** 删除${table.classComment} */
+async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) {
+  const hideLoading = message.loading({
+    content: $t('ui.actionMessage.deleting', [row.${columns[0].javaField}]),
+    duration: 0,
+    key: 'action_process_msg',
+  });
+  try {
+    await delete${simpleClassName}(row.id as number);
+    message.success({
+      content: $t('ui.actionMessage.deleteSuccess', [row.${columns[0].javaField}]),
+      key: 'action_process_msg',
+    });
+    onRefresh();
+  } catch {
+    hideLoading();
+  }
+}
+
+/** 表格操作按钮的回调函数 */
+function onActionClick({
+  code,
+  row,
+}: OnActionClickParams<${simpleClassName}Api.${simpleClassName}>) {
+  switch (code) {
+    case 'edit': {
+      onEdit(row);
+      break;
+    }
+    case 'delete': {
+      onDelete(row);
+      break;
+    }
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useGridColumns(onActionClick),
+    height: 'auto',
+    keepSource: true,
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await get${simpleClassName}Page({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+    },
+    toolbarConfig: {
+      refresh: { code: 'query' },
+      search: true,
+    },
+  } as VxeTableGridOptions<${simpleClassName}Api.${simpleClassName}>,
+});
+</script>
+
+<template>
+  <Page auto-content-height>
+    <DocAlert title="${table.classComment}" url="https://doc.iocoder.cn/${table.moduleName}/" />
+
+    <FormModal @success="onRefresh" />
+    <Grid table-title="${table.classComment}列表">
+      <template #toolbar-tools>
+        <Button type="primary" @click="onCreate" v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:create']">
+          <Plus class="size-5" />
+          {{ $t('ui.actionTitle.create', ['${table.classComment}']) }}
+        </Button>
+        <Button type="primary" class="ml-2" @click="onExport" v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:export']">
+          <Download class="size-5" />
+          {{ $t('ui.actionTitle.export') }}
+        </Button>
+      </template>
+    </Grid>
+  </Page>
+</template>