소스 검색

feat: 代码生成 vben

xingyuv 2 년 전
부모
커밋
da0f9ca528

+ 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_SCHEMA(21), // Vue3 Element Plus Schema 模版
+    VUE3_VBEN(30), // Vue3 VBEN 模版
     ;
 
     /**

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

@@ -116,6 +116,17 @@ public class CodegenEngine {
                     vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
             .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/form.vue"),
                     vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Form.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("api/api.ts"),
+                    vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
+            // Vue3 vben 模版
+            .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/data.ts"),
+                    vue3FilePath("views/${table.moduleName}/${classNameVar}/${classNameVar}.data.ts"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/index.vue"),
+                    vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/form.vue"),
+                    vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Modal.vue"))
+            .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("api/api.ts"),
+                    vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
             .build();
 
     @Resource
@@ -283,4 +294,8 @@ public class CodegenEngine {
     private static String vue3SchemaTemplatePath(String path) {
         return "codegen/vue3_schema/" + path + ".vm";
     }
+
+    private static String vue3VbenTemplatePath(String path) {
+        return "codegen/vue3_vben/" + path + ".vm";
+    }
 }

+ 32 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm

@@ -0,0 +1,32 @@
+import { defHttp } from '@/utils/http/axios'
+
+
+// 查询${table.classComment}列表
+export function get${simpleClassName}Page(params) {
+    return defHttp.get({ url: '${baseURL}/page', params })
+}
+
+// 查询${table.classComment}详情
+export function get${simpleClassName}(id: number) {
+    return defHttp.get({ url: '${baseURL}/get?id=' + id })
+}
+
+// 新增${table.classComment}
+export function create${simpleClassName}(data) {
+    return defHttp.post({ url: '${baseURL}/create', data })
+}
+
+// 修改${table.classComment}
+export function update${simpleClassName}(data) {
+    return defHttp.put({ url: '${baseURL}/update', data })
+}
+
+// 删除${table.classComment}
+export function delete${simpleClassName}(id: number) {
+    return defHttp.delete({ url: '${baseURL}/delete?id=' + id })
+}
+
+// 导出${table.classComment} Excel
+export function export${simpleClassName}(params) {
+    return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls')
+}

+ 134 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm

@@ -0,0 +1,134 @@
+import { BasicColumn, FormSchema, useRender } from '@/components/Table'
+import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
+
+export const columns: BasicColumn[] = [
+#foreach($column in $columns)
+#if ($column.listOperationResult)
+  #set ($dictType=$column.dictType)
+  #set ($javaField = $column.javaField)
+  #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  #set ($comment=$column.columnComment)
+#if ($column.javaType == "LocalDateTime")## 时间类型
+  {
+    title: '${comment}',
+    dataIndex: '${javaField}',
+    width: 180,
+    customRender: ({ text }) => {
+      return useRender.renderDate(text)
+    }
+  },
+#elseif("" != $column.dictType)## 数据字典
+  {
+    title: '${comment}',
+    dataIndex: '${javaField}',
+    width: 180,
+    customRender: ({ text }) => {
+      return useRender.renderDict(text, DICT_TYPE.$dictType.toUpperCase())
+    }
+  },
+#else
+  {
+    title: '${comment}',
+    dataIndex: '${javaField}',
+    width: 160
+  },
+#end
+#end
+#end
+]
+
+export const searchFormSchema: FormSchema[] = [
+#foreach($column in $columns)
+#if ($column.listOperation)
+  #set ($dictType=$column.dictType)
+  #set ($javaField = $column.javaField)
+  #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  #set ($comment=$column.columnComment)
+  {
+    label: '${comment}',
+    field: '${javaField}',
+  #if ($column.htmlType == "input")
+    component: 'Input',
+  #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
+    component: 'Select',
+    componentProps: {
+      #if ("" != $dictType)## 设置了 dictType 数据字典的情况
+        options: getIntDictOptions(DICT_TYPE.$dictType.toUpperCase())
+      #else## 未设置 dictType 数据字典的情况
+        options: []
+      #end
+    },
+  #elseif($column.htmlType == "datetime")
+    component: 'RangePicker',
+    #end
+    colProps: { span: 8 }
+  },
+#end
+#end
+]
+
+export const formSchema: FormSchema[] = [
+  {
+    label: '编号',
+    field: 'id',
+    show: false,
+    component: 'Input'
+  },
+#foreach($column in $columns)
+#if ($column.createOperation || $column.updateOperation)
+  #set ($dictType = $column.dictType)
+  #set ($javaField = $column.javaField)
+  #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
+  #set ($comment = $column.columnComment)
+#if (!$column.primaryKey)## 忽略主键,不用在表单里
+  {
+    label: '${comment}',
+    field: '${javaField}',
+  #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
+    required: true,
+    #end
+  #if ($column.htmlType == "input")
+    component: 'Input'
+  #elseif($column.htmlType == "imageUpload")## 图片上传
+    component: 'Upload'
+  #elseif($column.htmlType == "fileUpload")## 文件上传
+    component: 'Upload'
+  #elseif($column.htmlType == "editor")## 文本编辑器
+    component: 'InputTextArea'
+  #elseif($column.htmlType == "select")## 下拉框
+    component: 'Select',
+    componentProps: {
+      #if ("" != $dictType)## 有数据字典
+        options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
+      #else##没数据字典
+        options:[]
+      #end
+    }
+  #elseif($column.htmlType == "checkbox")## 多选框
+    component: 'Checkbox',
+    componentProps: {
+      #if ("" != $dictType)## 有数据字典
+        options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
+      #else##没数据字典
+        options:[]
+      #end
+    }
+  #elseif($column.htmlType == "radio")## 单选框
+    component: 'Radio',
+    componentProps: {
+      #if ("" != $dictType)## 有数据字典
+        options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
+      #else##没数据字典
+        options:[]
+      #end
+    }
+  #elseif($column.htmlType == "datetime")## 时间框
+    component: 'DatePicker'
+  #elseif($column.htmlType == "textarea")## 文本域
+    component: 'InputTextArea'
+  #end
+  },
+#end
+#end
+#end
+]

+ 49 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm

@@ -0,0 +1,49 @@
+<template>
+  <BasicModal v-bind="$attrs" @register="registerModal" :title="isUpdate ? '编辑' : '新增'" @ok="handleSubmit">
+    <BasicForm @register="registerForm" />
+  </BasicModal>
+</template>
+<script lang="ts" setup name="${table.className}Modal">
+  import { ref, unref } from 'vue'
+  import { BasicModal, useModalInner } from '@/components/Modal'
+  import { BasicForm, useForm } from '@/components/Form'
+  import { formSchema } from './${classNameVar}.data'
+  import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${classNameVar}'
+
+  const emit = defineEmits(['success', 'register'])
+  const isUpdate = ref(true)
+
+  const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
+    labelWidth: 120,
+    baseColProps: { span: 24 },
+    schemas: formSchema,
+    showActionButtonGroup: false,
+    actionColOptions: { span: 23 }
+  })
+
+  const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
+    resetFields()
+    setModalProps({ confirmLoading: false })
+    isUpdate.value = !!data?.isUpdate
+    if (unref(isUpdate)) {
+      const res = await get${simpleClassName}(data.record.id)
+      setFieldsValue({ ...res })
+    }
+  })
+
+  async function handleSubmit() {
+    try {
+      const values = await validate()
+      setModalProps({ confirmLoading: true })
+      if (unref(isUpdate)) {
+        await update${simpleClassName}(values)
+      } else {
+        await create${simpleClassName}(values)
+      }
+      closeModal()
+      emit('success')
+    } finally {
+      setModalProps({ confirmLoading: false })
+    }
+  }
+</script>

+ 90 - 0
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm

@@ -0,0 +1,90 @@
+<template>
+  <div>
+    <BasicTable @register="registerTable">
+      <template #toolbar>
+        <a-button type="primary" v-auth="['${permissionPrefix}:create']" :preIcon="IconEnum.ADD" @click="handleCreate">
+          {{ t('action.create') }}
+        </a-button>
+        <a-button type="warning" v-auth="['${permissionPrefix}:export']" :preIcon="IconEnum.EXPORT" @click="handleExport">
+          {{ t('action.export') }}
+        </a-button>
+      </template>
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'action'">
+          <TableAction
+             :actions="[
+               { icon: IconEnum.EDIT, label: t('action.edit'), auth: '${permissionPrefix}:update', onClick: handleEdit.bind(null, record) },
+               {
+                  icon: IconEnum.DELETE,
+                  color: 'error',
+                  label: t('action.delete'),
+                  auth: '${permissionPrefix}:delete',
+                  popConfirm: {
+                    title: t('common.delMessage'),
+                    placement: 'left',
+                    confirm: handleDelete.bind(null, record)
+                  }
+              }
+            ]"
+          />
+        </template>
+      </template>
+    </BasicTable>
+    <${simpleClassName}Modal @register="registerModal" @success="reload()" />
+  </div>
+</template>
+<script lang="ts" setup name="${table.className}">
+  import { useI18n } from '@/hooks/web/useI18n'
+  import { useMessage } from '@/hooks/web/useMessage'
+  import { useModal } from '@/components/Modal'
+  import ${simpleClassName}Modal from './${simpleClassName}Modal.vue'
+  import { IconEnum } from '@/enums/appEnum'
+  import { BasicTable, useTable, TableAction } from '@/components/Table'
+  import { delete${simpleClassName}, export${simpleClassName}, get${simpleClassName}Page } from '@/api/${table.moduleName}/${classNameVar}'
+  import { columns, searchFormSchema } from './${classNameVar}.data'
+
+  const { t } = useI18n()
+  const { createConfirm, createMessage } = useMessage()
+  const [registerModal, { openModal }] = useModal()
+
+  const [registerTable, { getForm, reload }] = useTable({
+    title: '${table.classComment}列表',
+    api: get${simpleClassName}Page,
+    columns,
+    formConfig: { labelWidth: 120, schemas: searchFormSchema },
+    useSearchForm: true,
+    showTableSetting: true,
+    actionColumn: {
+      width: 140,
+      title: t('common.action'),
+      dataIndex: 'action',
+      fixed: 'right'
+    }
+  })
+
+  function handleCreate() {
+    openModal(true, { isUpdate: false })
+  }
+
+  function handleEdit(record: Recordable) {
+    openModal(true, { record, isUpdate: true })
+  }
+
+  async function handleExport() {
+    createConfirm({
+      title: t('common.exportTitle'),
+      iconType: 'warning',
+      content: t('common.exportMessage'),
+      async onOk() {
+        await export${simpleClassName}(getForm().getFieldsValue())
+        createMessage.success(t('common.exportSuccessText'))
+      }
+    })
+  }
+
+  async function handleDelete(record: Recordable) {
+    await delete${simpleClassName}(record.id)
+    createMessage.success(t('common.delSuccessText'))
+    reload()
+  }
+</script>