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 { InfraDataSourceConfigApi } from '#/api/infra/data-source-config';
|
||||
import type { SystemMenuApi } from '#/api/system/menu';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import { IconifyIcon } from '@vben/icons';
|
||||
|
||||
import { z } from '#/adapter/form';
|
||||
import { getMenuList } from '#/api/system/menu';
|
||||
import { getRangePickerDefaultProps } from '#/utils/date';
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils/dict';
|
||||
import { handleTree } from '#/utils/tree';
|
||||
import { h } from 'vue';
|
||||
|
||||
import { useAccess } from '@vben/access';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
const { hasAccessByCodes } = useAccess();
|
||||
|
||||
|
|
@ -99,21 +106,14 @@ export function useBasicInfoFormSchema(): VbenFormSchema[] {
|
|||
componentProps: {
|
||||
rows: 3,
|
||||
},
|
||||
// 使用Tailwind的col-span-2让元素跨越两列
|
||||
// 使用 Tailwind 的 col-span-2 让元素跨越两列
|
||||
formItemClass: 'md:col-span-2',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 生成信息表单基础 schema */
|
||||
export function useGenerationInfoBaseFormSchema(menus: SystemMenuApi.SystemMenu[]): VbenFormSchema[] {
|
||||
/** 菜单树选项 */
|
||||
const menuTreeProps = {
|
||||
value: 'id',
|
||||
title: 'name',
|
||||
children: 'children',
|
||||
};
|
||||
|
||||
export function useGenerationInfoBaseFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
component: 'Select',
|
||||
|
|
@ -146,19 +146,49 @@ export function useGenerationInfoBaseFormSchema(menus: SystemMenuApi.SystemMenu[
|
|||
rules: z.number().min(1, { message: '生成场景不能为空' }),
|
||||
},
|
||||
{
|
||||
component: 'TreeSelect',
|
||||
fieldName: 'parentMenuId',
|
||||
label: '上级菜单',
|
||||
help: '分配到指定菜单下,例如 系统管理',
|
||||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
api: async () => {
|
||||
const data = await getMenuList();
|
||||
data.unshift({
|
||||
id: 0,
|
||||
name: '顶级菜单',
|
||||
} as SystemMenuApi.SystemMenu);
|
||||
return handleTree(data);
|
||||
},
|
||||
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,
|
||||
treeData: menus,
|
||||
fieldNames: menuTreeProps,
|
||||
treeNodeFilterProp: 'title',
|
||||
treeDefaultExpandAll: false,
|
||||
treeCheckable: false,
|
||||
placeholder: '请选择系统菜单',
|
||||
treeDefaultExpandedKeys: [0],
|
||||
},
|
||||
rules: 'selectRequired',
|
||||
renderComponentContent() {
|
||||
return {
|
||||
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 { Page } from '@vben/common-ui';
|
||||
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 { $t } from '#/locales';
|
||||
import { ref } from 'vue';
|
||||
import { ref, unref } from 'vue';
|
||||
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const loading = ref(false);
|
||||
const activeKey = ref('colum');
|
||||
const currentStep = ref(0);
|
||||
const formData = ref<InfraCodegenApi.CodegenDetail>({
|
||||
table: {},
|
||||
table: {} as InfraCodegenApi.CodegenTable,
|
||||
columns: [],
|
||||
});
|
||||
|
||||
// 表单引用
|
||||
const basicInfoRef = ref();
|
||||
const columnInfoRef = ref();
|
||||
const generateInfoRef = ref();
|
||||
const basicInfoRef = ref<InstanceType<typeof BasicInfo>>();
|
||||
const columnInfoRef = ref<InstanceType<typeof ColumnInfo>>();
|
||||
const generateInfoRef = ref<InstanceType<typeof GenerationInfo>>();
|
||||
|
||||
// 获取详情数据
|
||||
const getDetail = async () => {
|
||||
const id = Number(route.query.id);
|
||||
const id = route.query.id as any;
|
||||
if (!id) return;
|
||||
|
||||
loading.value = true;
|
||||
|
|
@ -43,26 +43,35 @@ const getDetail = async () => {
|
|||
|
||||
// 提交表单
|
||||
const submitForm = async () => {
|
||||
if (!formData.value) return;
|
||||
|
||||
// 表单验证
|
||||
await basicInfoRef.value?.validate();
|
||||
await generateInfoRef.value?.validate();
|
||||
try {
|
||||
// 表单验证
|
||||
await basicInfoRef.value?.validate();
|
||||
await generateInfoRef.value?.validate();
|
||||
} catch {
|
||||
message.warn('保存失败,原因:存在表单校验失败请检查!!!');
|
||||
return;
|
||||
}
|
||||
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.saving'),
|
||||
content: $t('ui.actionMessage.updating'),
|
||||
duration: 0,
|
||||
key: 'action_process_msg',
|
||||
});
|
||||
|
||||
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({
|
||||
content: $t('ui.actionMessage.saveSuccess'),
|
||||
content: $t('ui.actionMessage.operationSuccess'),
|
||||
key: 'action_process_msg',
|
||||
});
|
||||
close();
|
||||
} catch (error) {
|
||||
message.warn('保存失败!!!');
|
||||
console.error('保存失败', error);
|
||||
} finally {
|
||||
hideLoading();
|
||||
|
|
@ -74,28 +83,61 @@ const close = () => {
|
|||
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();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<div v-loading="loading">
|
||||
<Tabs v-model:active-key="activeKey">
|
||||
<Tabs.TabPane key="basicInfo" tab="基本信息">
|
||||
<BasicInfo ref="basicInfoRef" :table="formData.table" />
|
||||
</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>
|
||||
<Page auto-content-height v-loading="loading">
|
||||
<div class="flex h-[95%] flex-col rounded-md bg-white p-4 dark:bg-[#1f1f1f] dark:text-gray-300">
|
||||
<Steps type="navigation" :current="currentStep" class="mb-8 rounded shadow-sm dark:bg-[#141414]">
|
||||
<Steps.Step v-for="(step, index) in steps" :key="index" :title="step.title" />
|
||||
</Steps>
|
||||
|
||||
<div class="mt-4 flex justify-end">
|
||||
<Button type="primary" :loading="loading" @click="submitForm">保存</Button>
|
||||
<Button class="ml-2" @click="close">
|
||||
<div class="flex-1 overflow-auto py-4">
|
||||
<!-- 根据当前步骤显示对应的组件 -->
|
||||
<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" />
|
||||
返回
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ const [Grid, extendedApi] = useVbenVxeGrid({
|
|||
border: true,
|
||||
showOverflow: true,
|
||||
height: 'auto',
|
||||
autoResize: true,
|
||||
keepSource: true,
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
|
|
@ -54,7 +55,7 @@ watch(
|
|||
|
||||
// 提供获取表格数据的方法供父组件调用
|
||||
defineExpose({
|
||||
getData: extendedApi.grid?.getData,
|
||||
getData: (): InfraCodegenApi.CodegenColumn[] => extendedApi.grid.getData(),
|
||||
});
|
||||
|
||||
// 字典类型选项
|
||||
|
|
@ -71,83 +72,81 @@ loadDictTypeOptions();
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<div class="h-[80vh] w-full">
|
||||
<Grid>
|
||||
<!-- 字段描述 -->
|
||||
<template #columnComment="{ row }">
|
||||
<Input v-model:value="row.columnComment" />
|
||||
</template>
|
||||
<Grid>
|
||||
<!-- 字段描述 -->
|
||||
<template #columnComment="{ row }">
|
||||
<Input v-model:value="row.columnComment" />
|
||||
</template>
|
||||
|
||||
<!-- Java类型 -->
|
||||
<template #javaType="{ row, column }">
|
||||
<Select v-model:value="row.javaType" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
<!-- Java类型 -->
|
||||
<template #javaType="{ row, column }">
|
||||
<Select v-model:value="row.javaType" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<!-- Java属性 -->
|
||||
<template #javaField="{ row }">
|
||||
<Input v-model:value="row.javaField" />
|
||||
</template>
|
||||
<!-- Java属性 -->
|
||||
<template #javaField="{ row }">
|
||||
<Input v-model:value="row.javaField" />
|
||||
</template>
|
||||
|
||||
<!-- 插入 -->
|
||||
<template #createOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.createOperation" />
|
||||
</template>
|
||||
<!-- 插入 -->
|
||||
<template #createOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.createOperation" />
|
||||
</template>
|
||||
|
||||
<!-- 编辑 -->
|
||||
<template #updateOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.updateOperation" />
|
||||
</template>
|
||||
<!-- 编辑 -->
|
||||
<template #updateOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.updateOperation" />
|
||||
</template>
|
||||
|
||||
<!-- 列表 -->
|
||||
<template #listOperationResult="{ row }">
|
||||
<Checkbox v-model:checked="row.listOperationResult" />
|
||||
</template>
|
||||
<!-- 列表 -->
|
||||
<template #listOperationResult="{ row }">
|
||||
<Checkbox v-model:checked="row.listOperationResult" />
|
||||
</template>
|
||||
|
||||
<!-- 查询 -->
|
||||
<template #listOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.listOperation" />
|
||||
</template>
|
||||
<!-- 查询 -->
|
||||
<template #listOperation="{ row }">
|
||||
<Checkbox v-model:checked="row.listOperation" />
|
||||
</template>
|
||||
|
||||
<!-- 查询方式 -->
|
||||
<template #listOperationCondition="{ row, column }">
|
||||
<Select v-model:value="row.listOperationCondition" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
<!-- 查询方式 -->
|
||||
<template #listOperationCondition="{ row, column }">
|
||||
<Select v-model:value="row.listOperationCondition" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<!-- 允许空 -->
|
||||
<template #nullable="{ row }">
|
||||
<Checkbox v-model:checked="row.nullable" />
|
||||
</template>
|
||||
<!-- 允许空 -->
|
||||
<template #nullable="{ row }">
|
||||
<Checkbox v-model:checked="row.nullable" />
|
||||
</template>
|
||||
|
||||
<!-- 显示类型 -->
|
||||
<template #htmlType="{ row, column }">
|
||||
<Select v-model:value="row.htmlType" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
<!-- 显示类型 -->
|
||||
<template #htmlType="{ row, column }">
|
||||
<Select v-model:value="row.htmlType" style="width: 100%">
|
||||
<Select.Option v-for="option in column.params.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<!-- 字典类型 -->
|
||||
<template #dictType="{ row }">
|
||||
<Select v-model:value="row.dictType" style="width: 100%" allow-clear show-search>
|
||||
<Select.Option v-for="option in dictTypeOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
<!-- 字典类型 -->
|
||||
<template #dictType="{ row }">
|
||||
<Select v-model:value="row.dictType" style="width: 100%" allow-clear show-search>
|
||||
<Select.Option v-for="option in dictTypeOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</template>
|
||||
|
||||
<!-- 示例 -->
|
||||
<template #example="{ row }">
|
||||
<Input v-model:value="row.example" />
|
||||
</template>
|
||||
</Grid>
|
||||
</div>
|
||||
<!-- 示例 -->
|
||||
<template #example="{ row }">
|
||||
<Input v-model:value="row.example" />
|
||||
</template>
|
||||
</Grid>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
<script lang="ts" setup>
|
||||
import type { InfraCodegenApi } from '#/api/infra/codegen';
|
||||
import type { SystemMenuApi } from '#/api/system/menu';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { getCodegenTableList } from '#/api/infra/codegen';
|
||||
import { getSimpleMenusList } from '#/api/system/menu';
|
||||
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';
|
||||
|
||||
|
|
@ -18,7 +18,6 @@ const props = defineProps<{
|
|||
}>();
|
||||
|
||||
const tables = ref<InfraCodegenApi.CodegenTable[]>([]);
|
||||
const menus = ref<SystemMenuApi.SystemMenu[]>([]);
|
||||
const currentTemplateType = ref<number>();
|
||||
const wrapperClass = 'grid grid-cols-1 md:grid-cols-2 gap-4 mb-4'; // 一行两列布局
|
||||
// 计算当前模板类型
|
||||
|
|
@ -30,7 +29,7 @@ const [BaseForm, baseFormApi] = useVbenForm({
|
|||
wrapperClass,
|
||||
layout: 'horizontal',
|
||||
showDefaultActions: false,
|
||||
schema: [],
|
||||
schema: useGenerationInfoBaseFormSchema(),
|
||||
handleValuesChange: (values) => {
|
||||
// 监听模板类型变化
|
||||
if (values.templateType !== undefined && values.templateType !== currentTemplateType.value) {
|
||||
|
|
@ -55,12 +54,6 @@ const [SubForm, subFormApi] = useVbenForm({
|
|||
schema: [],
|
||||
});
|
||||
|
||||
/** 更新基础表单 schema */
|
||||
function updateBaseSchema(): void {
|
||||
const schema = useGenerationInfoBaseFormSchema(menus.value);
|
||||
baseFormApi.setState({ schema });
|
||||
}
|
||||
|
||||
/** 更新树表信息表单 schema */
|
||||
function updateTreeSchema(): void {
|
||||
const schema = useTreeTableFormSchema(props.columns);
|
||||
|
|
@ -124,29 +117,24 @@ function setAllFormValues(values: Record<string, any>): void {
|
|||
watch(
|
||||
() => props.table,
|
||||
async (val) => {
|
||||
if (!val) return;
|
||||
if (!val || isEmpty(val)) {
|
||||
return;
|
||||
}
|
||||
// 初始化树表的schema
|
||||
updateTreeSchema();
|
||||
// 设置表单值
|
||||
setAllFormValues(val);
|
||||
// 获取表数据,用于主子表选择
|
||||
if (typeof val.dataSourceConfigId !== undefined) {
|
||||
tables.value = await getCodegenTableList(val.dataSourceConfigId);
|
||||
// 初始化子表 schema
|
||||
updateSubSchema();
|
||||
if (typeof val.dataSourceConfigId === undefined) {
|
||||
return;
|
||||
}
|
||||
tables.value = await getCodegenTableList(val.dataSourceConfigId);
|
||||
// 初始化子表 schema
|
||||
updateSubSchema();
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 获取菜单列表
|
||||
menus.value = await getSimpleMenusList();
|
||||
// 初始基础信息 schema
|
||||
updateBaseSchema();
|
||||
});
|
||||
|
||||
/** 暴露出表单校验方法和表单值获取方法 */
|
||||
defineExpose({
|
||||
validate: validateAllForms,
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ function isBoolean(value: unknown): value is boolean {
|
|||
* @param {T} value 要检查的值。
|
||||
* @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) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -75,9 +75,7 @@ function isHttpUrl(url?: string): boolean {
|
|||
* @returns {boolean} 如果值是window对象,返回true,否则返回false。
|
||||
*/
|
||||
function isWindow(value: any): value is Window {
|
||||
return (
|
||||
typeof window !== 'undefined' && value !== null && value === value.window
|
||||
);
|
||||
return 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.
|
||||
* getFirstNonNullOrUndefined(undefined, null); // undefined
|
||||
*/
|
||||
function getFirstNonNullOrUndefined<T>(
|
||||
...values: (null | T | undefined)[]
|
||||
): T | undefined {
|
||||
function getFirstNonNullOrUndefined<T>(...values: (null | T | undefined)[]): T | undefined {
|
||||
for (const value of values) {
|
||||
if (value !== undefined && value !== null) {
|
||||
return value;
|
||||
|
|
|
|||
Loading…
Reference in New Issue