refactor: 完善代码生成器
parent
8a5f7ede42
commit
0f9ccbb955
|
|
@ -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();
|
||||||
|
|
||||||
|
|
@ -106,14 +113,7 @@ export function useBasicInfoFormSchema(): VbenFormSchema[] {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 生成信息表单基础 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);
|
||||||
|
},
|
||||||
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ function isBoolean(value: unknown): value is boolean {
|
||||||
* @param {T} value 要检查的值。
|
* @param {T} value 要检查的值。
|
||||||
* @returns {boolean} 如果值为空,返回true,否则返回false。
|
* @returns {boolean} 如果值为空,返回true,否则返回false。
|
||||||
*/
|
*/
|
||||||
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} 如果值是window对象,返回true,否则返回false。
|
* @returns {boolean} 如果值是window对象,返回true,否则返回false。
|
||||||
*/
|
*/
|
||||||
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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue