refactor: 完善代码生成器

pull/69/head
puhui999 2025-04-09 17:33:25 +08:00
parent 8a5f7ede42
commit 0f9ccbb955
5 changed files with 203 additions and 148 deletions

View File

@ -3,12 +3,19 @@ import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table';
import type { InfraCodegenApi } from '#/api/infra/codegen'; import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config'; import type { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
import type { SystemMenuApi } from '#/api/system/menu'; import type { SystemMenuApi } from '#/api/system/menu';
import type { Recordable } from '@vben/types';
import { IconifyIcon } from '@vben/icons';
import { z } from '#/adapter/form'; import { z } from '#/adapter/form';
import { getMenuList } from '#/api/system/menu';
import { getRangePickerDefaultProps } from '#/utils/date'; import { getRangePickerDefaultProps } from '#/utils/date';
import { DICT_TYPE, getDictOptions } from '#/utils/dict'; import { DICT_TYPE, getDictOptions } from '#/utils/dict';
import { handleTree } from '#/utils/tree';
import { h } from 'vue';
import { useAccess } from '@vben/access'; import { useAccess } from '@vben/access';
import { $t } from '@vben/locales';
const { hasAccessByCodes } = useAccess(); const { hasAccessByCodes } = useAccess();
@ -99,21 +106,14 @@ export function useBasicInfoFormSchema(): VbenFormSchema[] {
componentProps: { componentProps: {
rows: 3, rows: 3,
}, },
// 使用Tailwind的col-span-2让元素跨越两列 // 使用 Tailwind col-span-2 让元素跨越两列
formItemClass: 'md:col-span-2', formItemClass: 'md:col-span-2',
}, },
]; ];
} }
/** 生成信息表单基础 schema */ /** 生成信息表单基础 schema */
export function useGenerationInfoBaseFormSchema(menus: SystemMenuApi.SystemMenu[]): VbenFormSchema[] { export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] {
/** 菜单树选项 */
const menuTreeProps = {
value: 'id',
title: 'name',
children: 'children',
};
return [ return [
{ {
component: 'Select', component: 'Select',
@ -146,19 +146,49 @@ export function useGenerationInfoBaseFormSchema(menus: SystemMenuApi.SystemMenu[
rules: z.number().min(1, { message: '生成场景不能为空' }), rules: z.number().min(1, { message: '生成场景不能为空' }),
}, },
{ {
component: 'TreeSelect',
fieldName: 'parentMenuId', fieldName: 'parentMenuId',
label: '上级菜单', label: '上级菜单',
help: '分配到指定菜单下,例如 系统管理', help: '分配到指定菜单下,例如 系统管理',
component: 'ApiTreeSelect',
componentProps: { componentProps: {
allowClear: true,
api: async () => {
const data = await getMenuList();
data.unshift({
id: 0,
name: '顶级菜单',
} as SystemMenuApi.SystemMenu);
return handleTree(data);
},
class: 'w-full', class: 'w-full',
labelField: 'name',
valueField: 'id',
childrenField: 'children',
placeholder: '请选择上级菜单',
filterTreeNode(input: string, node: Recordable<any>) {
if (!input || input.length === 0) {
return true;
}
const name: string = node.label ?? '';
if (!name) return false;
return name.includes(input) || $t(name).includes(input);
},
showSearch: true, showSearch: true,
treeData: menus, treeDefaultExpandedKeys: [0],
fieldNames: menuTreeProps, },
treeNodeFilterProp: 'title', rules: 'selectRequired',
treeDefaultExpandAll: false, renderComponentContent() {
treeCheckable: false, return {
placeholder: '请选择系统菜单', title({ label, icon }: { icon: string; label: string }) {
const components = [];
if (!label) return '';
if (icon) {
components.push(h(IconifyIcon, { class: 'size-4', icon }));
}
components.push(h('span', { class: '' }, $t(label || '')));
return h('div', { class: 'flex items-center gap-1' }, components);
},
};
}, },
}, },
{ {

View File

@ -6,31 +6,31 @@ import ColumnInfo from './modules/column-info.vue';
import GenerationInfo from './modules/generation-info.vue'; import GenerationInfo from './modules/generation-info.vue';
import { Page } from '@vben/common-ui'; import { Page } from '@vben/common-ui';
import { ChevronsLeft } from '@vben/icons'; import { ChevronsLeft } from '@vben/icons';
import { Button, message, Tabs } from 'ant-design-vue'; import { Button, message, Steps } from 'ant-design-vue';
import { getCodegenTable, updateCodegenTable } from '#/api/infra/codegen'; import { getCodegenTable, updateCodegenTable } from '#/api/infra/codegen';
import { $t } from '#/locales'; import { $t } from '#/locales';
import { ref } from 'vue'; import { ref, unref } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
const loading = ref(false); const loading = ref(false);
const activeKey = ref('colum'); const currentStep = ref(0);
const formData = ref<InfraCodegenApi.CodegenDetail>({ const formData = ref<InfraCodegenApi.CodegenDetail>({
table: {}, table: {} as InfraCodegenApi.CodegenTable,
columns: [], columns: [],
}); });
// //
const basicInfoRef = ref(); const basicInfoRef = ref<InstanceType<typeof BasicInfo>>();
const columnInfoRef = ref(); const columnInfoRef = ref<InstanceType<typeof ColumnInfo>>();
const generateInfoRef = ref(); const generateInfoRef = ref<InstanceType<typeof GenerationInfo>>();
// //
const getDetail = async () => { const getDetail = async () => {
const id = Number(route.query.id); const id = route.query.id as any;
if (!id) return; if (!id) return;
loading.value = true; loading.value = true;
@ -43,26 +43,35 @@ const getDetail = async () => {
// //
const submitForm = async () => { const submitForm = async () => {
if (!formData.value) return; try {
// //
await basicInfoRef.value?.validate(); await basicInfoRef.value?.validate();
await generateInfoRef.value?.validate(); await generateInfoRef.value?.validate();
} catch {
message.warn('保存失败,原因:存在表单校验失败请检查!!!');
return;
}
const hideLoading = message.loading({ const hideLoading = message.loading({
content: $t('ui.actionMessage.saving'), content: $t('ui.actionMessage.updating'),
duration: 0, duration: 0,
key: 'action_process_msg', key: 'action_process_msg',
}); });
try { try {
await updateCodegenTable(formData.value); debugger;
//
const basicInfo = await basicInfoRef.value?.getValues();
const columns = columnInfoRef.value?.getData() || unref(formData).columns;
const generateInfo = await generateInfoRef.value?.getValues();
await updateCodegenTable({ table: { ...unref(formData).table, ...basicInfo, ...generateInfo }, columns });
message.success({ message.success({
content: $t('ui.actionMessage.saveSuccess'), content: $t('ui.actionMessage.operationSuccess'),
key: 'action_process_msg', key: 'action_process_msg',
}); });
close(); close();
} catch (error) { } catch (error) {
message.warn('保存失败!!!');
console.error('保存失败', error); console.error('保存失败', error);
} finally { } finally {
hideLoading(); hideLoading();
@ -74,28 +83,61 @@ const close = () => {
router.push('/infra/codegen'); router.push('/infra/codegen');
}; };
//
const nextStep = async () => {
currentStep.value += 1;
};
//
const prevStep = () => {
if (currentStep.value > 0) {
currentStep.value -= 1;
}
};
//
const steps = [
{
title: '基本信息',
},
{
title: '字段信息',
},
{
title: '生成信息',
},
];
// //
getDetail(); getDetail();
</script> </script>
<template> <template>
<Page auto-content-height> <Page auto-content-height v-loading="loading">
<div v-loading="loading"> <div class="flex h-[95%] flex-col rounded-md bg-white p-4 dark:bg-[#1f1f1f] dark:text-gray-300">
<Tabs v-model:active-key="activeKey"> <Steps type="navigation" :current="currentStep" class="mb-8 rounded shadow-sm dark:bg-[#141414]">
<Tabs.TabPane key="basicInfo" tab="基本信息"> <Steps.Step v-for="(step, index) in steps" :key="index" :title="step.title" />
<BasicInfo ref="basicInfoRef" :table="formData.table" /> </Steps>
</Tabs.TabPane>
<Tabs.TabPane key="colum" tab="字段信息">
<ColumnInfo ref="columnInfoRef" :columns="formData.columns" />
</Tabs.TabPane>
<Tabs.TabPane key="generateInfo" tab="生成信息">
<GenerationInfo ref="generateInfoRef" :table="formData.table" :columns="formData.columns" />
</Tabs.TabPane>
</Tabs>
<div class="mt-4 flex justify-end"> <div class="flex-1 overflow-auto py-4">
<Button type="primary" :loading="loading" @click="submitForm"></Button> <!-- 根据当前步骤显示对应的组件 -->
<Button class="ml-2" @click="close"> <BasicInfo v-show="currentStep === 0" ref="basicInfoRef" :table="formData.table" />
<ColumnInfo v-show="currentStep === 1" ref="columnInfoRef" :columns="formData.columns" />
<GenerationInfo
v-show="currentStep === 2"
ref="generateInfoRef"
:table="formData.table"
:columns="formData.columns"
/>
</div>
<div class="mt-4 flex justify-end space-x-2">
<Button v-show="currentStep > 0" @click="prevStep"></Button>
<Button v-show="currentStep < steps.length - 1" type="primary" @click="nextStep"></Button>
<Button v-show="currentStep === steps.length - 1" type="primary" :loading="loading" @click="submitForm">
保存
</Button>
<Button @click="close">
<ChevronsLeft class="mr-1" /> <ChevronsLeft class="mr-1" />
返回 返回
</Button> </Button>

View File

@ -23,6 +23,7 @@ const [Grid, extendedApi] = useVbenVxeGrid({
border: true, border: true,
showOverflow: true, showOverflow: true,
height: 'auto', height: 'auto',
autoResize: true,
keepSource: true, keepSource: true,
rowConfig: { rowConfig: {
keyField: 'id', keyField: 'id',
@ -54,7 +55,7 @@ watch(
// //
defineExpose({ defineExpose({
getData: extendedApi.grid?.getData, getData: (): InfraCodegenApi.CodegenColumn[] => extendedApi.grid.getData(),
}); });
// //
@ -71,7 +72,6 @@ loadDictTypeOptions();
</script> </script>
<template> <template>
<div class="h-[80vh] w-full">
<Grid> <Grid>
<!-- 字段描述 --> <!-- 字段描述 -->
<template #columnComment="{ row }"> <template #columnComment="{ row }">
@ -149,5 +149,4 @@ loadDictTypeOptions();
<Input v-model:value="row.example" /> <Input v-model:value="row.example" />
</template> </template>
</Grid> </Grid>
</div>
</template> </template>

View File

@ -1,12 +1,12 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { InfraCodegenApi } from '#/api/infra/codegen'; import type { InfraCodegenApi } from '#/api/infra/codegen';
import type { SystemMenuApi } from '#/api/system/menu';
import { useVbenForm } from '#/adapter/form'; import { useVbenForm } from '#/adapter/form';
import { getCodegenTableList } from '#/api/infra/codegen'; import { getCodegenTableList } from '#/api/infra/codegen';
import { getSimpleMenusList } from '#/api/system/menu';
import { InfraCodegenTemplateTypeEnum } from '#/utils/constants'; import { InfraCodegenTemplateTypeEnum } from '#/utils/constants';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
import { isEmpty } from '@vben/utils';
import { useGenerationInfoBaseFormSchema, useSubTableFormSchema, useTreeTableFormSchema } from '../data'; import { useGenerationInfoBaseFormSchema, useSubTableFormSchema, useTreeTableFormSchema } from '../data';
@ -18,7 +18,6 @@ const props = defineProps<{
}>(); }>();
const tables = ref<InfraCodegenApi.CodegenTable[]>([]); const tables = ref<InfraCodegenApi.CodegenTable[]>([]);
const menus = ref<SystemMenuApi.SystemMenu[]>([]);
const currentTemplateType = ref<number>(); const currentTemplateType = ref<number>();
const wrapperClass = 'grid grid-cols-1 md:grid-cols-2 gap-4 mb-4'; // const wrapperClass = 'grid grid-cols-1 md:grid-cols-2 gap-4 mb-4'; //
// //
@ -30,7 +29,7 @@ const [BaseForm, baseFormApi] = useVbenForm({
wrapperClass, wrapperClass,
layout: 'horizontal', layout: 'horizontal',
showDefaultActions: false, showDefaultActions: false,
schema: [], schema: useGenerationInfoBaseFormSchema(),
handleValuesChange: (values) => { handleValuesChange: (values) => {
// //
if (values.templateType !== undefined && values.templateType !== currentTemplateType.value) { if (values.templateType !== undefined && values.templateType !== currentTemplateType.value) {
@ -55,12 +54,6 @@ const [SubForm, subFormApi] = useVbenForm({
schema: [], schema: [],
}); });
/** 更新基础表单 schema */
function updateBaseSchema(): void {
const schema = useGenerationInfoBaseFormSchema(menus.value);
baseFormApi.setState({ schema });
}
/** 更新树表信息表单 schema */ /** 更新树表信息表单 schema */
function updateTreeSchema(): void { function updateTreeSchema(): void {
const schema = useTreeTableFormSchema(props.columns); const schema = useTreeTableFormSchema(props.columns);
@ -124,29 +117,24 @@ function setAllFormValues(values: Record<string, any>): void {
watch( watch(
() => props.table, () => props.table,
async (val) => { async (val) => {
if (!val) return; if (!val || isEmpty(val)) {
return;
}
// schema // schema
updateTreeSchema(); updateTreeSchema();
// //
setAllFormValues(val); setAllFormValues(val);
// //
if (typeof val.dataSourceConfigId !== undefined) { if (typeof val.dataSourceConfigId === undefined) {
return;
}
tables.value = await getCodegenTableList(val.dataSourceConfigId); tables.value = await getCodegenTableList(val.dataSourceConfigId);
// schema // schema
updateSubSchema(); updateSubSchema();
}
}, },
{ immediate: true }, { immediate: true },
); );
/** 初始化 */
onMounted(async () => {
//
menus.value = await getSimpleMenusList();
// schema
updateBaseSchema();
});
/** 暴露出表单校验方法和表单值获取方法 */ /** 暴露出表单校验方法和表单值获取方法 */
defineExpose({ defineExpose({
validate: validateAllForms, validate: validateAllForms,

View File

@ -33,7 +33,7 @@ function isBoolean(value: unknown): value is boolean {
* @param {T} value * @param {T} value
* @returns {boolean} truefalse * @returns {boolean} truefalse
*/ */
function isEmpty<T = unknown>(value?: T): value is T { function isEmpty<T = unknown>(value?: T): boolean {
if (value === null || value === undefined) { if (value === null || value === undefined) {
return true; return true;
} }
@ -75,9 +75,7 @@ function isHttpUrl(url?: string): boolean {
* @returns {boolean} windowtruefalse * @returns {boolean} windowtruefalse
*/ */
function isWindow(value: any): value is Window { function isWindow(value: any): value is Window {
return ( return typeof window !== 'undefined' && value !== null && value === value.window;
typeof window !== 'undefined' && value !== null && value === value.window
);
} }
/** /**
@ -137,9 +135,7 @@ function isNumber(value: any): value is number {
* // Returns undefined because all values are either null or undefined. * // Returns undefined because all values are either null or undefined.
* getFirstNonNullOrUndefined(undefined, null); // undefined * getFirstNonNullOrUndefined(undefined, null); // undefined
*/ */
function getFirstNonNullOrUndefined<T>( function getFirstNonNullOrUndefined<T>(...values: (null | T | undefined)[]): T | undefined {
...values: (null | T | undefined)[]
): T | undefined {
for (const value of values) { for (const value of values) {
if (value !== undefined && value !== null) { if (value !== undefined && value !== null) {
return value; return value;