Commit 015565cc9a577f6e2174f08d8a683f2574d41d53
1 parent
69486939
【功能新增】INFRA: vben next 树表代码生成
Showing
5 changed files
with
187 additions
and
23 deletions
yudao-module-infra/yudao-module-infra-biz/src/main/java/cn/iocoder/yudao/module/infra/service/codegen/inner/CodegenEngine.java
| ... | ... | @@ -153,6 +153,15 @@ public class CodegenEngine { |
| 153 | 153 | vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue")) |
| 154 | 154 | .put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("api/api.ts"), |
| 155 | 155 | vue3FilePath("api/${table.moduleName}/${table.businessName}/index.ts")) |
| 156 | + // 主子表模板配置 - Vue3 vben5 schema 模版 | |
| 157 | + //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/master_slave_data.ts"), | |
| 158 | + // vue3FilePath("views/${table.moduleName}/${table.businessName}/data.ts")) | |
| 159 | + //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/master_slave_index.vue"), | |
| 160 | + // vue3FilePath("views/${table.moduleName}/${table.businessName}/index.vue")) | |
| 161 | + //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/modules/master_slave_form.vue"), | |
| 162 | + // vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/form.vue")) | |
| 163 | + //.put(CodegenFrontTypeEnum.VUE3_VBEN_NEXT_SCHEMA.getType(), vue3VbenNextSchemaTemplatePath("views/modules/sub_table.vue"), | |
| 164 | + // vue3FilePath("views/${table.moduleName}/${table.businessName}/modules/sub_table.vue")) | |
| 156 | 165 | .build(); |
| 157 | 166 | |
| 158 | 167 | @Resource | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/api/api.ts.vm
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/data.ts.vm
| 1 | +import type { VxeTableGridOptions } from '@vben/plugins/vxe-table'; | |
| 1 | 2 | import type { VbenFormSchema } from '#/adapter/form'; |
| 2 | -import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; | |
| 3 | +import type { OnActionClickFn } from '#/adapter/vxe-table'; | |
| 3 | 4 | import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}'; |
| 4 | 5 | |
| 5 | 6 | import { z } from '#/adapter/form'; |
| 6 | -import { CommonStatusEnum } from '#/utils/constants'; | |
| 7 | +#if(${table.templateType} == 2)## 树表需要导入这些 | |
| 8 | +import { get${simpleClassName}List } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}'; | |
| 9 | +import { handleTree } from '#/utils/tree'; | |
| 10 | +#end | |
| 7 | 11 | import { DICT_TYPE, getDictOptions } from '#/utils/dict'; |
| 8 | -import { getRangePickerDefaultProps } from '#/utils/date'; | |
| 12 | +import { CommonStatusEnum } from '#/utils/constants'; | |
| 9 | 13 | import { useAccess } from '@vben/access'; |
| 10 | 14 | |
| 11 | 15 | const { hasAccessByCodes } = useAccess(); |
| ... | ... | @@ -21,9 +25,34 @@ export function useFormSchema(): VbenFormSchema[] { |
| 21 | 25 | show: () => false, |
| 22 | 26 | }, |
| 23 | 27 | }, |
| 28 | +#if(${table.templateType} == 2)## 树表特有字段:上级 | |
| 29 | + { | |
| 30 | + fieldName: '${treeParentColumn.javaField}', | |
| 31 | + label: '上级${table.classComment}', | |
| 32 | + component: 'ApiTreeSelect', | |
| 33 | + componentProps: { | |
| 34 | + allowClear: true, | |
| 35 | + api: async () => { | |
| 36 | + const data = await get${simpleClassName}List({}); | |
| 37 | + data.unshift({ | |
| 38 | + id: 0, | |
| 39 | + ${treeNameColumn.javaField}: '顶级${table.classComment}', | |
| 40 | + }); | |
| 41 | + return handleTree(data); | |
| 42 | + }, | |
| 43 | + class: 'w-full', | |
| 44 | + labelField: '${treeNameColumn.javaField}', | |
| 45 | + valueField: 'id', | |
| 46 | + childrenField: 'children', | |
| 47 | + placeholder: '请选择上级${table.classComment}', | |
| 48 | + treeDefaultExpandAll: true, | |
| 49 | + }, | |
| 50 | + rules: 'selectRequired', | |
| 51 | + }, | |
| 52 | +#end | |
| 24 | 53 | #foreach($column in $columns) |
| 25 | 54 | #if ($column.createOperation || $column.updateOperation) |
| 26 | -#if (!$column.primaryKey)## 忽略主键,不用在表单里 | |
| 55 | +#if (!$column.primaryKey && ($table.templateType != 2 || ($table.templateType == 2 && $column.id != $treeParentColumn.id)))## 树表中已经添加了父ID字段,这里排除 | |
| 27 | 56 | #set ($dictType = $column.dictType) |
| 28 | 57 | #set ($javaType = $column.javaType) |
| 29 | 58 | #set ($javaField = $column.javaField) |
| ... | ... | @@ -69,6 +98,7 @@ export function useFormSchema(): VbenFormSchema[] { |
| 69 | 98 | options: [], |
| 70 | 99 | #end |
| 71 | 100 | placeholder: '请选择${comment}', |
| 101 | + class: 'w-full', | |
| 72 | 102 | }, |
| 73 | 103 | #elseif($column.htmlType == "checkbox")## 多选框 |
| 74 | 104 | component: 'Checkbox', |
| ... | ... | @@ -102,6 +132,14 @@ export function useFormSchema(): VbenFormSchema[] { |
| 102 | 132 | componentProps: { |
| 103 | 133 | placeholder: '请输入${comment}', |
| 104 | 134 | }, |
| 135 | + #elseif($column.htmlType == "inputNumber")## 数字输入框 | |
| 136 | + component: 'InputNumber', | |
| 137 | + componentProps: { | |
| 138 | + min: 0, | |
| 139 | + class: 'w-full', | |
| 140 | + controlsPosition: 'right', | |
| 141 | + placeholder: '请输入${comment}', | |
| 142 | + }, | |
| 105 | 143 | #end |
| 106 | 144 | }, |
| 107 | 145 | #end |
| ... | ... | @@ -159,7 +197,6 @@ export function useGridFormSchema(): VbenFormSchema[] { |
| 159 | 197 | #elseif($column.htmlType == "datetime") |
| 160 | 198 | component: 'RangePicker', |
| 161 | 199 | componentProps: { |
| 162 | - ...getRangePickerDefaultProps(), | |
| 163 | 200 | allowClear: true, |
| 164 | 201 | }, |
| 165 | 202 | #end |
| ... | ... | @@ -170,9 +207,9 @@ export function useGridFormSchema(): VbenFormSchema[] { |
| 170 | 207 | } |
| 171 | 208 | |
| 172 | 209 | /** 列表的字段 */ |
| 173 | -export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>( | |
| 174 | - onActionClick: OnActionClickFn<T>, | |
| 175 | -): VxeTableGridOptions['columns'] { | |
| 210 | +export function useGridColumns( | |
| 211 | + onActionClick?: OnActionClickFn<${simpleClassName}Api.${simpleClassName}>, | |
| 212 | +): VxeTableGridOptions<${simpleClassName}Api.${simpleClassName}>['columns'] { | |
| 176 | 213 | return [ |
| 177 | 214 | #foreach($column in $columns) |
| 178 | 215 | #if ($column.listOperationResult) |
| ... | ... | @@ -191,15 +228,20 @@ export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>( |
| 191 | 228 | props: { type: DICT_TYPE.$dictType.toUpperCase() }, |
| 192 | 229 | }, |
| 193 | 230 | #end |
| 231 | + #if (${table.templateType} == 2 && $column.id == $treeNameColumn.id)## 树表特有:标记树节点列 | |
| 232 | + treeNode: true, | |
| 233 | + #end | |
| 194 | 234 | }, |
| 195 | 235 | #end |
| 196 | 236 | #end |
| 197 | 237 | { |
| 198 | 238 | field: 'operation', |
| 199 | 239 | title: '操作', |
| 200 | - minWidth: 180, | |
| 201 | - align: 'center', | |
| 240 | + minWidth: 200, | |
| 241 | + align: 'right', | |
| 202 | 242 | fixed: 'right', |
| 243 | + headerAlign: 'center', | |
| 244 | + showOverflow: false, | |
| 203 | 245 | cellRender: { |
| 204 | 246 | attrs: { |
| 205 | 247 | nameField: '${columns[0].javaField}', |
| ... | ... | @@ -208,6 +250,13 @@ export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>( |
| 208 | 250 | }, |
| 209 | 251 | name: 'CellOperation', |
| 210 | 252 | options: [ |
| 253 | +#if (${table.templateType} == 2)## 树表特有操作 | |
| 254 | + { | |
| 255 | + code: 'add_child', | |
| 256 | + text: '新增下级', | |
| 257 | + show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:create']), | |
| 258 | + }, | |
| 259 | +#end | |
| 211 | 260 | { |
| 212 | 261 | code: 'edit', |
| 213 | 262 | show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:update']), |
| ... | ... | @@ -215,7 +264,12 @@ export function useGridColumns<T = ${simpleClassName}Api.${simpleClassName}>( |
| 215 | 264 | { |
| 216 | 265 | code: 'delete', |
| 217 | 266 | show: hasAccessByCodes(['${table.moduleName}:${simpleClassName_strikeCase}:delete']), |
| 218 | - } | |
| 267 | +#if (${table.templateType} == 2)## 树表禁止删除带有子节点的数据 | |
| 268 | + disabled: (row: ${simpleClassName}Api.${simpleClassName}) => { | |
| 269 | + return !!(row.children && row.children.length > 0); | |
| 270 | + }, | |
| 271 | +#end | |
| 272 | + }, | |
| 219 | 273 | ], |
| 220 | 274 | }, |
| 221 | 275 | }, | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/form.vue.vm
| ... | ... | @@ -4,8 +4,8 @@ import type { ${simpleClassName}Api } from '#/api/${table.moduleName}/${simpleCl |
| 4 | 4 | import { useVbenModal } from '@vben/common-ui'; |
| 5 | 5 | import { message } from 'ant-design-vue'; |
| 6 | 6 | |
| 7 | -import { $t } from '#/locales'; | |
| 8 | 7 | import { computed, ref } from 'vue'; |
| 8 | +import { $t } from '#/locales'; | |
| 9 | 9 | import { useVbenForm } from '#/adapter/form'; |
| 10 | 10 | import { get${simpleClassName}, create${simpleClassName}, update${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}'; |
| 11 | 11 | |
| ... | ... | @@ -13,11 +13,25 @@ import { useFormSchema } from '../data'; |
| 13 | 13 | |
| 14 | 14 | const emit = defineEmits(['success']); |
| 15 | 15 | const formData = ref<${simpleClassName}Api.${simpleClassName}>(); |
| 16 | +#if (${table.templateType} == 2)## 树表特有:父ID处理 | |
| 17 | +// 新增下级时的父级ID | |
| 18 | +const parentId = ref<number>(); | |
| 19 | + | |
| 20 | +const getTitle = computed(() => { | |
| 21 | + if (formData.value?.id) { | |
| 22 | + return $t('ui.actionTitle.edit', ['${table.classComment}']); | |
| 23 | + } | |
| 24 | + return parentId.value | |
| 25 | + ? $t('ui.actionTitle.create', ['下级${table.classComment}']) | |
| 26 | + : $t('ui.actionTitle.create', ['${table.classComment}']); | |
| 27 | +}); | |
| 28 | +#else## 标准表标题 | |
| 16 | 29 | const getTitle = computed(() => { |
| 17 | 30 | return formData.value?.id |
| 18 | 31 | ? $t('ui.actionTitle.edit', ['${table.classComment}']) |
| 19 | 32 | : $t('ui.actionTitle.create', ['${table.classComment}']); |
| 20 | 33 | }); |
| 34 | +#end | |
| 21 | 35 | |
| 22 | 36 | const [Form, formApi] = useVbenForm({ |
| 23 | 37 | layout: 'horizontal', |
| ... | ... | @@ -55,17 +69,47 @@ const [Modal, modalApi] = useVbenModal({ |
| 55 | 69 | return; |
| 56 | 70 | } |
| 57 | 71 | // 加载数据 |
| 72 | +#if (${table.templateType} == 2)## 树表处理传入的父ID | |
| 73 | + let data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>(); | |
| 74 | +#else## 标准表直接获取 | |
| 58 | 75 | const data = modalApi.getData<${simpleClassName}Api.${simpleClassName}>(); |
| 59 | - if (!data || !data.id) { | |
| 76 | +#end | |
| 77 | + if (!data) { | |
| 60 | 78 | return; |
| 61 | 79 | } |
| 62 | - modalApi.lock(); | |
| 63 | - try { | |
| 64 | - formData.value = await get${simpleClassName}(data.id as number); | |
| 65 | - // 设置到 values | |
| 80 | + | |
| 81 | +#if (${table.templateType} == 2)## 树表特有:处理新增下级的情况 | |
| 82 | + // 处理新增下级的情况 | |
| 83 | + if (!data.id && data.${treeParentColumn.javaField}) { | |
| 84 | + parentId.value = data.${treeParentColumn.javaField}; | |
| 85 | + formData.value = { ${treeParentColumn.javaField}: parentId.value } as ${simpleClassName}Api.${simpleClassName}; | |
| 66 | 86 | await formApi.setValues(formData.value); |
| 67 | - } finally { | |
| 68 | - modalApi.lock(false); | |
| 87 | + return; | |
| 88 | + } | |
| 89 | +#end | |
| 90 | + | |
| 91 | + if (data.id) { | |
| 92 | + // 编辑 | |
| 93 | + modalApi.lock(); | |
| 94 | + try { | |
| 95 | +#if (${table.templateType} == 2)## 树表获取数据后重新赋值 | |
| 96 | + data = await get${simpleClassName}(data.id); | |
| 97 | + formData.value = data; | |
| 98 | +#else## 标准表设置表单数据 | |
| 99 | + formData.value = await get${simpleClassName}(data.id as number); | |
| 100 | +#end | |
| 101 | + await formApi.setValues(formData.value); | |
| 102 | + } finally { | |
| 103 | + modalApi.lock(false); | |
| 104 | + } | |
| 105 | + } else { | |
| 106 | + // 新增 | |
| 107 | +#if (${table.templateType} == 2)## 树表特有:设置顶级ID | |
| 108 | + formData.value = { ${treeParentColumn.javaField}: 0 } as ${simpleClassName}Api.${simpleClassName}; | |
| 109 | +#else## 标准表:设置空值 | |
| 110 | + formData.value = data; | |
| 111 | +#end | |
| 112 | + await formApi.setValues(formData.value || {}); | |
| 69 | 113 | } |
| 70 | 114 | }, |
| 71 | 115 | }); | ... | ... |
yudao-module-infra/yudao-module-infra-biz/src/main/resources/codegen/vue3_vben_next/schema/views/index.vue.vm
| ... | ... | @@ -8,9 +8,15 @@ import { Download, Plus } from '@vben/icons'; |
| 8 | 8 | import Form from './modules/form.vue'; |
| 9 | 9 | import { DocAlert } from '#/components/doc-alert'; |
| 10 | 10 | |
| 11 | +import { ref } from 'vue'; | |
| 11 | 12 | import { $t } from '#/locales'; |
| 12 | 13 | import { useVbenVxeGrid } from '#/adapter/vxe-table'; |
| 14 | +#if (${table.templateType} == 2)## 树表接口 | |
| 15 | +import { get${simpleClassName}List, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}'; | |
| 16 | +import { handleTree } from '#/utils/tree'; | |
| 17 | +#else## 标准表接口 | |
| 13 | 18 | import { get${simpleClassName}Page, delete${simpleClassName}, export${simpleClassName} } from '#/api/${table.moduleName}/${simpleClassName_strikeCase}'; |
| 19 | +#end | |
| 14 | 20 | import { downloadByData } from '#/utils/download'; |
| 15 | 21 | |
| 16 | 22 | import { useGridColumns, useGridFormSchema } from './data'; |
| ... | ... | @@ -20,6 +26,15 @@ const [FormModal, formModalApi] = useVbenModal({ |
| 20 | 26 | destroyOnClose: true, |
| 21 | 27 | }); |
| 22 | 28 | |
| 29 | +#if (${table.templateType} == 2)## 树表特有:控制表格展开收缩 | |
| 30 | +/** 切换树形展开/收缩状态 */ | |
| 31 | +const isExpanded = ref(true); | |
| 32 | +function toggleExpand() { | |
| 33 | + isExpanded.value = !isExpanded.value; | |
| 34 | + gridApi.grid.setAllTreeExpand(isExpanded.value); | |
| 35 | +} | |
| 36 | +#end | |
| 37 | + | |
| 23 | 38 | /** 刷新表格 */ |
| 24 | 39 | function onRefresh() { |
| 25 | 40 | gridApi.query(); |
| ... | ... | @@ -41,17 +56,24 @@ function onEdit(row: ${simpleClassName}Api.${simpleClassName}) { |
| 41 | 56 | formModalApi.setData(row).open(); |
| 42 | 57 | } |
| 43 | 58 | |
| 59 | +#if (${table.templateType} == 2)## 树表特有:新增下级 | |
| 60 | +/** 新增下级${table.classComment} */ | |
| 61 | +function onAddChild(row: ${simpleClassName}Api.${simpleClassName}) { | |
| 62 | + formModalApi.setData({ ${treeParentColumn.javaField}: row.id }).open(); | |
| 63 | +} | |
| 64 | +#end | |
| 65 | + | |
| 44 | 66 | /** 删除${table.classComment} */ |
| 45 | 67 | async function onDelete(row: ${simpleClassName}Api.${simpleClassName}) { |
| 46 | 68 | const hideLoading = message.loading({ |
| 47 | - content: $t('ui.actionMessage.deleting', [row.${columns[0].javaField}]), | |
| 69 | + content: $t('ui.actionMessage.deleting', [row.id]), | |
| 48 | 70 | duration: 0, |
| 49 | 71 | key: 'action_process_msg', |
| 50 | 72 | }); |
| 51 | 73 | try { |
| 52 | 74 | await delete${simpleClassName}(row.id as number); |
| 53 | 75 | message.success({ |
| 54 | - content: $t('ui.actionMessage.deleteSuccess', [row.${columns[0].javaField}]), | |
| 76 | + content: $t('ui.actionMessage.deleteSuccess', [row.id]), | |
| 55 | 77 | key: 'action_process_msg', |
| 56 | 78 | }); |
| 57 | 79 | onRefresh(); |
| ... | ... | @@ -74,6 +96,12 @@ function onActionClick({ |
| 74 | 96 | onDelete(row); |
| 75 | 97 | break; |
| 76 | 98 | } |
| 99 | +#if (${table.templateType} == 2)## 树表特有:新增下级 | |
| 100 | + case 'add_child': { | |
| 101 | + onAddChild(row); | |
| 102 | + break; | |
| 103 | + } | |
| 104 | +#end | |
| 77 | 105 | } |
| 78 | 106 | } |
| 79 | 107 | |
| ... | ... | @@ -84,20 +112,40 @@ const [Grid, gridApi] = useVbenVxeGrid({ |
| 84 | 112 | gridOptions: { |
| 85 | 113 | columns: useGridColumns(onActionClick), |
| 86 | 114 | height: 'auto', |
| 87 | - keepSource: true, | |
| 115 | +#if (${table.templateType} == 2)## 树表设置 | |
| 116 | + treeConfig: { | |
| 117 | + parentField: '${treeParentColumn.javaField}', | |
| 118 | + rowField: 'id', | |
| 119 | + transform: true, | |
| 120 | + expandAll: true, | |
| 121 | + reserve: true, | |
| 122 | + }, | |
| 123 | +#else## 标准表设置 | |
| 124 | + pagerConfig: { | |
| 125 | + enabled: true, | |
| 126 | + }, | |
| 127 | +#end | |
| 88 | 128 | proxyConfig: { |
| 89 | 129 | ajax: { |
| 130 | +#if (${table.templateType} == 2)## 树表数据加载 | |
| 131 | + query: async (_, formValues) => { | |
| 132 | + return await get${simpleClassName}List(formValues); | |
| 133 | + }, | |
| 134 | +#else## 标准表数据加载 | |
| 90 | 135 | query: async ({ page }, formValues) => { |
| 91 | - return await get${simpleClassName}Page({ | |
| 136 | + const { items, total } = await get${simpleClassName}Page({ | |
| 92 | 137 | pageNo: page.currentPage, |
| 93 | 138 | pageSize: page.pageSize, |
| 94 | 139 | ...formValues, |
| 95 | 140 | }); |
| 141 | + return { items, total }; | |
| 96 | 142 | }, |
| 143 | +#end | |
| 97 | 144 | }, |
| 98 | 145 | }, |
| 99 | 146 | rowConfig: { |
| 100 | 147 | keyField: 'id', |
| 148 | + isHover: true, | |
| 101 | 149 | }, |
| 102 | 150 | toolbarConfig: { |
| 103 | 151 | refresh: { code: 'query' }, |
| ... | ... | @@ -112,8 +160,14 @@ const [Grid, gridApi] = useVbenVxeGrid({ |
| 112 | 160 | <DocAlert title="${table.classComment}" url="https://doc.iocoder.cn/${table.moduleName}/" /> |
| 113 | 161 | |
| 114 | 162 | <FormModal @success="onRefresh" /> |
| 163 | + | |
| 115 | 164 | <Grid table-title="${table.classComment}列表"> |
| 116 | 165 | <template #toolbar-tools> |
| 166 | +#if (${table.templateType} == 2)## 树表特有:展开/收缩按钮 | |
| 167 | + <Button @click="toggleExpand" class="mr-2"> | |
| 168 | + {{ isExpanded ? '收缩' : '展开' }} | |
| 169 | + </Button> | |
| 170 | +#end | |
| 117 | 171 | <Button type="primary" @click="onCreate" v-access:code="['${table.moduleName}:${simpleClassName_strikeCase}:create']"> |
| 118 | 172 | <Plus class="size-5" /> |
| 119 | 173 | {{ $t('ui.actionTitle.create', ['${table.classComment}']) }} | ... | ... |