Commit da0f9ca5284efe3b6dd4e33f370bbc65d70371d1

Authored by xingyuv
1 parent ce80c6f9

feat: 代码生成 vben

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 { @@ -15,6 +15,7 @@ public enum CodegenFrontTypeEnum {
15 VUE2(10), // Vue2 Element UI 标准模版 15 VUE2(10), // Vue2 Element UI 标准模版
16 VUE3(20), // Vue3 Element Plus 标准模版 16 VUE3(20), // Vue3 Element Plus 标准模版
17 VUE3_SCHEMA(21), // Vue3 Element Plus Schema 模版 17 VUE3_SCHEMA(21), // Vue3 Element Plus Schema 模版
  18 + VUE3_VBEN(30), // Vue3 VBEN 模版
18 ; 19 ;
19 20
20 /** 21 /**
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 { @@ -116,6 +116,17 @@ public class CodegenEngine {
116 vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue")) 116 vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
117 .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/form.vue"), 117 .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("views/form.vue"),
118 vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Form.vue")) 118 vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Form.vue"))
  119 + .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath("api/api.ts"),
  120 + vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
  121 + // Vue3 vben 模版
  122 + .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/data.ts"),
  123 + vue3FilePath("views/${table.moduleName}/${classNameVar}/${classNameVar}.data.ts"))
  124 + .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/index.vue"),
  125 + vue3FilePath("views/${table.moduleName}/${classNameVar}/index.vue"))
  126 + .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("views/form.vue"),
  127 + vue3FilePath("views/${table.moduleName}/${classNameVar}/${simpleClassName}Modal.vue"))
  128 + .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath("api/api.ts"),
  129 + vue3FilePath("api/${table.moduleName}/${classNameVar}/index.ts"))
119 .build(); 130 .build();
120 131
121 @Resource 132 @Resource
@@ -283,4 +294,8 @@ public class CodegenEngine { @@ -283,4 +294,8 @@ public class CodegenEngine {
283 private static String vue3SchemaTemplatePath(String path) { 294 private static String vue3SchemaTemplatePath(String path) {
284 return "codegen/vue3_schema/" + path + ".vm"; 295 return "codegen/vue3_schema/" + path + ".vm";
285 } 296 }
  297 +
  298 + private static String vue3VbenTemplatePath(String path) {
  299 + return "codegen/vue3_vben/" + path + ".vm";
  300 + }
286 } 301 }
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm 0 → 100644
  1 +import { defHttp } from '@/utils/http/axios'
  2 +
  3 +
  4 +// 查询${table.classComment}列表
  5 +export function get${simpleClassName}Page(params) {
  6 + return defHttp.get({ url: '${baseURL}/page', params })
  7 +}
  8 +
  9 +// 查询${table.classComment}详情
  10 +export function get${simpleClassName}(id: number) {
  11 + return defHttp.get({ url: '${baseURL}/get?id=' + id })
  12 +}
  13 +
  14 +// 新增${table.classComment}
  15 +export function create${simpleClassName}(data) {
  16 + return defHttp.post({ url: '${baseURL}/create', data })
  17 +}
  18 +
  19 +// 修改${table.classComment}
  20 +export function update${simpleClassName}(data) {
  21 + return defHttp.put({ url: '${baseURL}/update', data })
  22 +}
  23 +
  24 +// 删除${table.classComment}
  25 +export function delete${simpleClassName}(id: number) {
  26 + return defHttp.delete({ url: '${baseURL}/delete?id=' + id })
  27 +}
  28 +
  29 +// 导出${table.classComment} Excel
  30 +export function export${simpleClassName}(params) {
  31 + return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls')
  32 +}
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm 0 → 100644
  1 +import { BasicColumn, FormSchema, useRender } from '@/components/Table'
  2 +import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
  3 +
  4 +export const columns: BasicColumn[] = [
  5 +#foreach($column in $columns)
  6 +#if ($column.listOperationResult)
  7 + #set ($dictType=$column.dictType)
  8 + #set ($javaField = $column.javaField)
  9 + #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
  10 + #set ($comment=$column.columnComment)
  11 +#if ($column.javaType == "LocalDateTime")## 时间类型
  12 + {
  13 + title: '${comment}',
  14 + dataIndex: '${javaField}',
  15 + width: 180,
  16 + customRender: ({ text }) => {
  17 + return useRender.renderDate(text)
  18 + }
  19 + },
  20 +#elseif("" != $column.dictType)## 数据字典
  21 + {
  22 + title: '${comment}',
  23 + dataIndex: '${javaField}',
  24 + width: 180,
  25 + customRender: ({ text }) => {
  26 + return useRender.renderDict(text, DICT_TYPE.$dictType.toUpperCase())
  27 + }
  28 + },
  29 +#else
  30 + {
  31 + title: '${comment}',
  32 + dataIndex: '${javaField}',
  33 + width: 160
  34 + },
  35 +#end
  36 +#end
  37 +#end
  38 +]
  39 +
  40 +export const searchFormSchema: FormSchema[] = [
  41 +#foreach($column in $columns)
  42 +#if ($column.listOperation)
  43 + #set ($dictType=$column.dictType)
  44 + #set ($javaField = $column.javaField)
  45 + #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
  46 + #set ($comment=$column.columnComment)
  47 + {
  48 + label: '${comment}',
  49 + field: '${javaField}',
  50 + #if ($column.htmlType == "input")
  51 + component: 'Input',
  52 + #elseif ($column.htmlType == "select" || $column.htmlType == "radio")
  53 + component: 'Select',
  54 + componentProps: {
  55 + #if ("" != $dictType)## 设置了 dictType 数据字典的情况
  56 + options: getIntDictOptions(DICT_TYPE.$dictType.toUpperCase())
  57 + #else## 未设置 dictType 数据字典的情况
  58 + options: []
  59 + #end
  60 + },
  61 + #elseif($column.htmlType == "datetime")
  62 + component: 'RangePicker',
  63 + #end
  64 + colProps: { span: 8 }
  65 + },
  66 +#end
  67 +#end
  68 +]
  69 +
  70 +export const formSchema: FormSchema[] = [
  71 + {
  72 + label: '编号',
  73 + field: 'id',
  74 + show: false,
  75 + component: 'Input'
  76 + },
  77 +#foreach($column in $columns)
  78 +#if ($column.createOperation || $column.updateOperation)
  79 + #set ($dictType = $column.dictType)
  80 + #set ($javaField = $column.javaField)
  81 + #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})
  82 + #set ($comment = $column.columnComment)
  83 +#if (!$column.primaryKey)## 忽略主键,不用在表单里
  84 + {
  85 + label: '${comment}',
  86 + field: '${javaField}',
  87 + #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键
  88 + required: true,
  89 + #end
  90 + #if ($column.htmlType == "input")
  91 + component: 'Input'
  92 + #elseif($column.htmlType == "imageUpload")## 图片上传
  93 + component: 'Upload'
  94 + #elseif($column.htmlType == "fileUpload")## 文件上传
  95 + component: 'Upload'
  96 + #elseif($column.htmlType == "editor")## 文本编辑器
  97 + component: 'InputTextArea'
  98 + #elseif($column.htmlType == "select")## 下拉框
  99 + component: 'Select',
  100 + componentProps: {
  101 + #if ("" != $dictType)## 有数据字典
  102 + options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
  103 + #else##没数据字典
  104 + options:[]
  105 + #end
  106 + }
  107 + #elseif($column.htmlType == "checkbox")## 多选框
  108 + component: 'Checkbox',
  109 + componentProps: {
  110 + #if ("" != $dictType)## 有数据字典
  111 + options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
  112 + #else##没数据字典
  113 + options:[]
  114 + #end
  115 + }
  116 + #elseif($column.htmlType == "radio")## 单选框
  117 + component: 'Radio',
  118 + componentProps: {
  119 + #if ("" != $dictType)## 有数据字典
  120 + options: getIntDictOptions(DICT_TYPE.COMMON_STATUS)
  121 + #else##没数据字典
  122 + options:[]
  123 + #end
  124 + }
  125 + #elseif($column.htmlType == "datetime")## 时间框
  126 + component: 'DatePicker'
  127 + #elseif($column.htmlType == "textarea")## 文本域
  128 + component: 'InputTextArea'
  129 + #end
  130 + },
  131 +#end
  132 +#end
  133 +#end
  134 +]
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm 0 → 100644
  1 +<template>
  2 + <BasicModal v-bind="$attrs" @register="registerModal" :title="isUpdate ? '编辑' : '新增'" @ok="handleSubmit">
  3 + <BasicForm @register="registerForm" />
  4 + </BasicModal>
  5 +</template>
  6 +<script lang="ts" setup name="${table.className}Modal">
  7 + import { ref, unref } from 'vue'
  8 + import { BasicModal, useModalInner } from '@/components/Modal'
  9 + import { BasicForm, useForm } from '@/components/Form'
  10 + import { formSchema } from './${classNameVar}.data'
  11 + import { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${classNameVar}'
  12 +
  13 + const emit = defineEmits(['success', 'register'])
  14 + const isUpdate = ref(true)
  15 +
  16 + const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
  17 + labelWidth: 120,
  18 + baseColProps: { span: 24 },
  19 + schemas: formSchema,
  20 + showActionButtonGroup: false,
  21 + actionColOptions: { span: 23 }
  22 + })
  23 +
  24 + const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
  25 + resetFields()
  26 + setModalProps({ confirmLoading: false })
  27 + isUpdate.value = !!data?.isUpdate
  28 + if (unref(isUpdate)) {
  29 + const res = await get${simpleClassName}(data.record.id)
  30 + setFieldsValue({ ...res })
  31 + }
  32 + })
  33 +
  34 + async function handleSubmit() {
  35 + try {
  36 + const values = await validate()
  37 + setModalProps({ confirmLoading: true })
  38 + if (unref(isUpdate)) {
  39 + await update${simpleClassName}(values)
  40 + } else {
  41 + await create${simpleClassName}(values)
  42 + }
  43 + closeModal()
  44 + emit('success')
  45 + } finally {
  46 + setModalProps({ confirmLoading: false })
  47 + }
  48 + }
  49 +</script>
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm 0 → 100644
  1 +<template>
  2 + <div>
  3 + <BasicTable @register="registerTable">
  4 + <template #toolbar>
  5 + <a-button type="primary" v-auth="['${permissionPrefix}:create']" :preIcon="IconEnum.ADD" @click="handleCreate">
  6 + {{ t('action.create') }}
  7 + </a-button>
  8 + <a-button type="warning" v-auth="['${permissionPrefix}:export']" :preIcon="IconEnum.EXPORT" @click="handleExport">
  9 + {{ t('action.export') }}
  10 + </a-button>
  11 + </template>
  12 + <template #bodyCell="{ column, record }">
  13 + <template v-if="column.key === 'action'">
  14 + <TableAction
  15 + :actions="[
  16 + { icon: IconEnum.EDIT, label: t('action.edit'), auth: '${permissionPrefix}:update', onClick: handleEdit.bind(null, record) },
  17 + {
  18 + icon: IconEnum.DELETE,
  19 + color: 'error',
  20 + label: t('action.delete'),
  21 + auth: '${permissionPrefix}:delete',
  22 + popConfirm: {
  23 + title: t('common.delMessage'),
  24 + placement: 'left',
  25 + confirm: handleDelete.bind(null, record)
  26 + }
  27 + }
  28 + ]"
  29 + />
  30 + </template>
  31 + </template>
  32 + </BasicTable>
  33 + <${simpleClassName}Modal @register="registerModal" @success="reload()" />
  34 + </div>
  35 +</template>
  36 +<script lang="ts" setup name="${table.className}">
  37 + import { useI18n } from '@/hooks/web/useI18n'
  38 + import { useMessage } from '@/hooks/web/useMessage'
  39 + import { useModal } from '@/components/Modal'
  40 + import ${simpleClassName}Modal from './${simpleClassName}Modal.vue'
  41 + import { IconEnum } from '@/enums/appEnum'
  42 + import { BasicTable, useTable, TableAction } from '@/components/Table'
  43 + import { delete${simpleClassName}, export${simpleClassName}, get${simpleClassName}Page } from '@/api/${table.moduleName}/${classNameVar}'
  44 + import { columns, searchFormSchema } from './${classNameVar}.data'
  45 +
  46 + const { t } = useI18n()
  47 + const { createConfirm, createMessage } = useMessage()
  48 + const [registerModal, { openModal }] = useModal()
  49 +
  50 + const [registerTable, { getForm, reload }] = useTable({
  51 + title: '${table.classComment}列表',
  52 + api: get${simpleClassName}Page,
  53 + columns,
  54 + formConfig: { labelWidth: 120, schemas: searchFormSchema },
  55 + useSearchForm: true,
  56 + showTableSetting: true,
  57 + actionColumn: {
  58 + width: 140,
  59 + title: t('common.action'),
  60 + dataIndex: 'action',
  61 + fixed: 'right'
  62 + }
  63 + })
  64 +
  65 + function handleCreate() {
  66 + openModal(true, { isUpdate: false })
  67 + }
  68 +
  69 + function handleEdit(record: Recordable) {
  70 + openModal(true, { record, isUpdate: true })
  71 + }
  72 +
  73 + async function handleExport() {
  74 + createConfirm({
  75 + title: t('common.exportTitle'),
  76 + iconType: 'warning',
  77 + content: t('common.exportMessage'),
  78 + async onOk() {
  79 + await export${simpleClassName}(getForm().getFieldsValue())
  80 + createMessage.success(t('common.exportSuccessText'))
  81 + }
  82 + })
  83 + }
  84 +
  85 + async function handleDelete(record: Recordable) {
  86 + await delete${simpleClassName}(record.id)
  87 + createMessage.success(t('common.delSuccessText'))
  88 + reload()
  89 + }
  90 +</script>