update 重构 增强代码生成器各项功能

update 自动类型判断可根据不同数据库精确识别 对应的java类型
update 优化java字段名与数据库名不匹配则增加别名注解
update 增加 是否导出 是否状态切换 是否组合唯一校验 是否排序调整 和树结构相关字段等功能选择
update 优化自定义路径导出 导出全部代码
update 删除无用主子表相关代码
This commit is contained in:
疯狂的狮子Li
2026-04-17 18:24:05 +08:00
parent 87fad7bc16
commit 6c1dae5b6a
4 changed files with 383 additions and 98 deletions
+41 -9
View File
@@ -4,8 +4,6 @@ export interface TableVO extends BaseEntity {
dataName: string;
tableName: string;
tableComment: string;
subTableName?: any;
subTableFkName?: any;
className: string;
tplCategory: string;
packageName: string;
@@ -25,6 +23,16 @@ export interface TableVO extends BaseEntity {
menuIds?: any;
parentMenuId?: any;
parentMenuName?: any;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
tree: boolean;
crud: boolean;
}
@@ -72,8 +80,6 @@ export interface DbTableVO {
tableId?: any;
tableName: string;
tableComment: string;
subTableName?: any;
subTableFkName?: any;
className?: any;
tplCategory?: any;
packageName?: any;
@@ -93,6 +99,16 @@ export interface DbTableVO {
menuIds?: any;
parentMenuId?: any;
parentMenuName?: any;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
tree: boolean;
crud: boolean;
}
@@ -104,15 +120,13 @@ export interface DbTableQuery extends PageQuery {
}
/**
* 代码生成表详情接口 data 结构(与后端 Map 三键一致)
* 代码生成表详情接口 data 结构
* - info:当前表 GenTable
* - rows:字段列表 GenTableColumn[]
* - tables:全部表 GenTable[](主子表等选项)
*/
export interface GenTableDetailPayload {
info: DbTableVO;
rows: DbColumnVO[];
tables: DbTableVO[];
}
export interface DbColumnForm extends BaseEntity {
@@ -152,6 +166,16 @@ export interface DbParamForm {
treeName?: any;
treeParentCode?: any;
parentMenuId: string;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
}
export interface DbTableForm extends BaseEntity {
@@ -159,8 +183,6 @@ export interface DbTableForm extends BaseEntity {
tableId: string | string;
tableName: string;
tableComment: string;
subTableName?: any;
subTableFkName?: any;
className: string;
tplCategory: string;
packageName: string;
@@ -180,6 +202,16 @@ export interface DbTableForm extends BaseEntity {
menuIds?: any;
parentMenuId: string;
parentMenuName?: any;
enableExport?: boolean;
enableStatus?: boolean;
statusField?: string;
enableUnique?: boolean;
uniqueFields?: string[];
enableSort?: boolean;
sortField?: string;
treeRootValue?: string;
treeAncestorsField?: string;
treeOrderField?: string;
tree: boolean;
crud: boolean;
params: DbParamForm;
+56 -9
View File
@@ -22,7 +22,7 @@
<el-option label="Integer" value="Integer" />
<el-option label="Double" value="Double" />
<el-option label="BigDecimal" value="BigDecimal" />
<el-option label="Date" value="Date" />
<el-option label="LocalDateTime" value="LocalDateTime" />
<el-option label="Boolean" value="Boolean" />
</el-select>
</template>
@@ -74,12 +74,14 @@
</el-table-column>
<el-table-column label="显示类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.htmlType">
<el-select v-model="scope.row.htmlType" @change="handleHtmlTypeChange(scope.row)">
<el-option label="文本框" value="input" />
<el-option label="数字输入" value="inputNumber" />
<el-option label="文本域" value="textarea" />
<el-option label="下拉框" value="select" />
<el-option label="单选框" value="radio" />
<el-option label="复选框" value="checkbox" />
<el-option label="开关" value="switch" />
<el-option label="日期控件" value="datetime" />
<el-option label="图片上传" value="imageUpload" />
<el-option label="文件上传" value="fileUpload" />
@@ -89,7 +91,14 @@
</el-table-column>
<el-table-column label="字典类型" min-width="12%">
<template #default="scope">
<el-select v-model="scope.row.dictType" clearable filterable placeholder="请选择" value-on-clear="">
<el-select
v-model="scope.row.dictType"
clearable
filterable
placeholder="请选择"
value-on-clear=""
:disabled="!supportsDictHtmlType(scope.row.htmlType)"
>
<el-option
v-for="dict in dictOptions"
:key="dict.dictType"
@@ -105,7 +114,7 @@
</el-table>
</el-tab-pane>
<el-tab-pane label="生成信息" name="genInfo">
<gen-info-form ref="genInfo" :info="info" :tables="tables" />
<gen-info-form ref="genInfo" :info="info" :columns="columns" />
</el-tab-pane>
</el-tabs>
<el-form label-width="100px">
@@ -131,14 +140,26 @@ import GenInfoForm from './genInfoForm.vue';
const route = useRoute();
const activeName = ref('columnInfo');
const tableHeight = ref(document.documentElement.scrollHeight - 245 + 'px');
const tables = ref<DbTableVO[]>([]);
const columns = ref<DbColumnVO[]>([]);
const dictOptions = ref<DictTypeVO[]>([]);
const info = ref<Partial<DbTableVO>>({});
const DICT_HTML_TYPES = ['select', 'radio', 'checkbox', 'switch'];
const basicInfo = ref<InstanceType<typeof BasicInfoForm>>();
const genInfo = ref<InstanceType<typeof GenInfoForm>>();
const supportsDictHtmlType = (htmlType?: string) => DICT_HTML_TYPES.includes(htmlType ?? '');
const normalizeColumnDictType = (column: Partial<DbColumnVO>) => {
if (!supportsDictHtmlType(String(column.htmlType ?? ''))) {
column.dictType = '';
}
};
const handleHtmlTypeChange = (column: DbColumnVO) => {
normalizeColumnDictType(column);
};
/** 提交按钮 */
const submitForm = async () => {
const basicOk = (await basicInfo.value?.validate()) ?? false;
@@ -147,13 +168,24 @@ const submitForm = async () => {
modal.msgError('表单校验未通过,请重新检查提交内容');
return;
}
columns.value.forEach(normalizeColumnDictType);
const genTable: Record<string, unknown> = { ...info.value };
genTable.columns = columns.value;
genTable.params = {
treeCode: info.value?.treeCode,
treeName: info.value.treeName,
treeParentCode: info.value.treeParentCode,
parentMenuId: info.value.parentMenuId
parentMenuId: info.value.parentMenuId,
enableExport: info.value.enableExport,
enableStatus: info.value.enableStatus,
statusField: info.value.statusField,
enableUnique: info.value.enableUnique,
uniqueFields: info.value.uniqueFields,
enableSort: info.value.enableSort,
sortField: info.value.sortField,
treeRootValue: info.value.treeRootValue,
treeAncestors: info.value.treeAncestorsField,
treeOrderField: info.value.treeOrderField
};
const response = await updateGenTable(genTable as any);
modal.msgSuccess(response.msg ?? '');
@@ -175,9 +207,24 @@ onMounted(async () => {
const res = await getGenTable(tableId);
const detail = res.data;
if (!detail) return;
columns.value = detail.rows;
info.value = detail.info;
tables.value = detail.tables;
columns.value = (detail.rows ?? []).map(column => {
const item = { ...column };
normalizeColumnDictType(item);
return item;
});
info.value = {
enableExport: detail.info.enableExport ?? true,
enableStatus: detail.info.enableStatus ?? false,
statusField: detail.info.statusField ?? '',
enableUnique: detail.info.enableUnique ?? false,
uniqueFields: detail.info.uniqueFields ?? [],
enableSort: detail.info.enableSort ?? false,
sortField: detail.info.sortField ?? '',
treeRootValue: detail.info.treeRootValue ?? '0',
treeAncestorsField: detail.info.treeAncestorsField ?? '',
treeOrderField: detail.info.treeOrderField ?? '',
...detail.info
};
const response = await getDictOptionselect();
dictOptions.value = response.data;
});
+257 -71
View File
@@ -4,7 +4,7 @@
<el-col :span="12">
<el-form-item prop="tplCategory">
<template #label>生成模板</template>
<el-select v-model="infoForm.tplCategory" @change="tplSelectChange">
<el-select v-model="infoForm.tplCategory">
<el-option label="单表(增删改查)" value="crud" />
<el-option label="树表(增删改查)" value="tree" />
</el-select>
@@ -122,6 +122,109 @@
</el-col>
</el-row>
<h4 class="form-header">增强选项</h4>
<el-row class="enhance-options" :gutter="20">
<el-col :span="24">
<el-row :gutter="20" class="enhance-option-row">
<el-col :span="8">
<el-form-item prop="enableExport" class="enhance-toggle-item">
<template #label>
导出能力
<el-tooltip content="关闭后将不生成 export 接口与前端导出按钮" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-switch v-model="infoForm.enableExport" />
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col :span="24">
<el-row :gutter="20" class="enhance-option-row">
<el-col :span="8">
<el-form-item prop="enableStatus" class="enhance-toggle-item">
<template #label>
状态切换
<el-tooltip content="开启后生成 changeStatus 接口与列表状态开关列" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-switch v-model="infoForm.enableStatus" />
</el-form-item>
</el-col>
<el-col v-if="infoForm.enableStatus" :span="16">
<el-form-item prop="statusField">
<el-select v-model="infoForm.statusField" placeholder="请选择状态字段">
<el-option
v-for="column in availableColumns"
:key="column.columnName"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col :span="24">
<el-row :gutter="20" class="enhance-option-row">
<el-col :span="8">
<el-form-item prop="enableUnique" class="enhance-toggle-item">
<template #label>
组合唯一校验
<el-tooltip content="开启后按选中的字段生成组合唯一校验,新增和修改都会校验" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-switch v-model="infoForm.enableUnique" />
</el-form-item>
</el-col>
<el-col v-if="infoForm.enableUnique" :span="16">
<el-form-item prop="uniqueFields">
<el-select v-model="infoForm.uniqueFields" multiple clearable filterable placeholder="请选择唯一字段">
<el-option
v-for="column in availableColumns"
:key="column.columnName"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-col>
<el-col :span="24">
<el-row :gutter="20" class="enhance-option-row">
<el-col :span="8">
<el-form-item prop="enableSort" class="enhance-toggle-item">
<template #label>
排序调整
<el-tooltip content="开启后生成 updateSort 接口,并在列表中以输入框形式快速调整排序字段" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-switch v-model="infoForm.enableSort" />
</el-form-item>
</el-col>
<el-col v-if="infoForm.enableSort" :span="16">
<el-form-item prop="sortField">
<el-select v-model="infoForm.sortField" placeholder="请选择排序字段">
<el-option
v-for="column in sortableColumns"
:key="column.columnName"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-col>
</el-row>
<template v-if="info.tplCategory == 'tree'">
<h4 class="form-header">其他信息</h4>
<el-row v-show="info.tplCategory == 'tree'">
@@ -135,7 +238,7 @@
</template>
<el-select v-model="infoForm.treeCode" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
v-for="(column, index) in availableColumns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
@@ -153,7 +256,7 @@
</template>
<el-select v-model="infoForm.treeParentCode" placeholder="请选择">
<el-option
v-for="(column, index) in infoForm.columns"
v-for="(column, index) in availableColumns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
@@ -171,7 +274,7 @@
</template>
<el-select v-model="infoForm.treeName" placeholder="请选择">
<el-option
v-for="(column, index) in info.columns"
v-for="(column, index) in availableColumns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
@@ -179,50 +282,56 @@
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="treeRootValue">
<template #label>
根节点值
<el-tooltip content="默认是 0,用于根节点 parentId 的默认值" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-input v-model="infoForm.treeRootValue" placeholder="请输入根节点值" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="treeAncestorsField">
<template #label>
祖级字段
<el-tooltip content="选择 ancestors 一类字段后,生成器会自动维护祖级链,避免依赖数据库递归语法" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.treeAncestorsField" clearable placeholder="请选择祖级字段">
<el-option
v-for="column in availableColumns"
:key="column.columnName"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="treeOrderField">
<template #label>
树排序字段
<el-tooltip content="树列表默认按祖级、父节点、树排序字段、主键升序排列" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.treeOrderField" clearable placeholder="请选择树排序字段">
<el-option
v-for="column in sortableColumns"
:key="column.columnName"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
</template>
<template v-if="info.tplCategory == 'sub'">
<h4 class="form-header">关联信息</h4>
<el-row>
<el-col :span="12">
<el-form-item>
<template #label>
关联子表的表名
<el-tooltip content="关联子表的表名, 如:sys_user" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.subTableName" placeholder="请选择" @change="subSelectChange">
<el-option
v-for="(t, index) in table"
:key="index"
:label="t.tableName + '' + t.tableComment"
:value="t.tableName"
></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
子表关联的外键名
<el-tooltip content="子表关联的外键名, 如:user_id" placement="top">
<el-icon><question-filled /></el-icon>
</el-tooltip>
</template>
<el-select v-model="infoForm.subTableFkName" placeholder="请选择">
<el-option
v-for="(column, index) in subColumns"
:key="index"
:label="column.columnName + '' + column.columnComment"
:value="column.columnName"
></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</template>
</el-form>
</template>
@@ -238,18 +347,21 @@ interface MenuOptionsType {
children?: MenuOptionsType[];
}
const subColumns = ref<any>([]);
const menuOptions = ref<Array<MenuOptionsType>>([]);
const formRef = ref<FormInstance>();
const props = defineProps({
info: propTypes.any.isRequired,
tables: propTypes.any.isRequired
columns: propTypes.any.isRequired
});
const infoForm = computed(() => props.info);
const table = computed(() => props.tables);
const availableColumns = computed(() => props.columns ?? []);
const sortableColumns = computed(() =>
availableColumns.value.filter((column: any) =>
['Integer', 'Long', 'Double', 'BigDecimal', 'LocalDateTime'].includes(column.javaType)
)
);
// 表单校验
const rules = ref({
@@ -257,27 +369,56 @@ const rules = ref({
packageName: [{ required: true, message: '请输入生成包路径', trigger: 'blur' }],
moduleName: [{ required: true, message: '请输入生成模块名', trigger: 'blur' }],
businessName: [{ required: true, message: '请输入生成业务名', trigger: 'blur' }],
functionName: [{ required: true, message: '请输入生成功能名', trigger: 'blur' }]
});
const subSelectChange = () => {
infoForm.value.subTableFkName = '';
};
const tplSelectChange = (value: string) => {
if (value !== 'sub') {
infoForm.value.subTableName = '';
infoForm.value.subTableFkName = '';
}
};
const setSubTableColumns = (value: string) => {
table.value.forEach((item: any) => {
const name = item.tableName;
if (value === name) {
subColumns.value = item.columns;
return;
functionName: [{ required: true, message: '请输入生成功能名', trigger: 'blur' }],
statusField: [
{
validator: (_rule: any, value: string, callback: (error?: Error) => void) => {
if (infoForm.value.enableStatus && !value) {
callback(new Error('请选择状态字段'));
return;
}
callback();
},
trigger: 'change'
}
});
};
],
uniqueFields: [
{
validator: (_rule: any, value: string[], callback: (error?: Error) => void) => {
if (infoForm.value.enableUnique && (!value || value.length === 0)) {
callback(new Error('请选择唯一字段'));
return;
}
callback();
},
trigger: 'change'
}
],
sortField: [
{
validator: (_rule: any, value: string, callback: (error?: Error) => void) => {
if (infoForm.value.enableSort && !value) {
callback(new Error('请选择排序字段'));
return;
}
callback();
},
trigger: 'change'
}
],
treeRootValue: [
{
validator: (_rule: any, value: string, callback: (error?: Error) => void) => {
if (infoForm.value.tplCategory === 'tree' && !value) {
callback(new Error('请输入根节点值'));
return;
}
callback();
},
trigger: 'blur'
}
]
});
/** 查询菜单下拉树结构 */
const getMenuTreeselect = async () => {
const res = await listMenu();
@@ -289,9 +430,29 @@ const getMenuTreeselect = async () => {
};
watch(
() => props.info.subTableName,
() => infoForm.value.enableStatus,
val => {
setSubTableColumns(val);
if (!val) {
infoForm.value.statusField = '';
}
}
);
watch(
() => infoForm.value.enableUnique,
val => {
if (!val) {
infoForm.value.uniqueFields = [];
}
}
);
watch(
() => infoForm.value.enableSort,
val => {
if (!val) {
infoForm.value.sortField = '';
}
}
);
@@ -311,3 +472,28 @@ onMounted(() => {
getMenuTreeselect();
});
</script>
<style scoped>
.enhance-options {
margin-top: 4px;
}
.enhance-option-row {
min-height: 52px;
}
.enhance-toggle-item :deep(.el-form-item__content) {
justify-content: flex-start;
}
@media (max-width: 992px) {
.enhance-options :deep(.el-col) {
max-width: 100%;
flex: 0 0 100%;
}
.enhance-option-row {
min-height: auto;
}
}
</style>
+29 -9
View File
@@ -91,7 +91,7 @@
border
class="data-table"
:data="tableList"
@selection-change="handleSelectionChange"
@selection-change="handleTableSelectionChange"
>
<el-table-column type="selection" align="center" width="55"></el-table-column>
<el-table-column label="序号" type="index" width="50" align="center">
@@ -217,6 +217,7 @@ const dataNameList = ref<Array<string>>([]);
const queryFormRef = ref<ElFormInstance>();
const importRef = ref<InstanceType<typeof ImportTable>>();
const selectedRows = ref<TableVO[]>([]);
const queryParams = ref<TableQuery>({
pageNum: 1,
@@ -233,9 +234,14 @@ const preview = ref<{
data: {},
activeName: 'domain.java'
});
const { ids, single, multiple, handleSelectionChange } = useTableSelection<TableVO>(item => item.tableId);
const { ids, single, multiple, handleSelectionChange: updateSelection } = useTableSelection<TableVO>(item => item.tableId);
const { dialog, openDialog: openPreviewDialog } = useDialogState('代码预览');
const handleTableSelectionChange = (selection: TableVO[]) => {
selectedRows.value = selection;
updateSelection(selection);
};
/** 查询多数据源名称 */
const getDataNameList = async () => {
const res = await getDataNames();
@@ -257,16 +263,30 @@ const handleQuery = () => {
};
/** 生成代码操作 */
const handleGenTable = async (row?: TableVO) => {
const tbIds = row?.tableId || ids.value;
if (tbIds == '') {
const currentRows = row ? [row] : selectedRows.value;
if (!currentRows.length) {
modal.msgError('请选择要生成的数据');
return;
}
if (row?.genType === '1') {
await genCode(row.tableId);
modal.msgSuccess('成功生成到自定义路径:' + row.genPath);
} else {
download.zip('/tool/gen/batchGenCode?tableIdStr=' + tbIds, 'ruoyi.zip');
const customRows = currentRows.filter(item => item.genType === '1');
const zipRows = currentRows.filter(item => item.genType !== '1');
for (const item of customRows) {
await genCode(item.tableId);
}
if (customRows.length === 1 && zipRows.length === 0) {
modal.msgSuccess('成功生成到自定义路径:' + customRows[0].genPath);
return;
}
if (customRows.length > 1) {
modal.msgSuccess('已生成到自定义路径,共 ' + customRows.length + ' 张表');
}
if (zipRows.length > 0) {
const zipIds = zipRows.map(item => item.tableId).join(',');
download.zip('/tool/gen/batchGenCode?tableIdStr=' + zipIds, 'ruoyi.zip');
}
};
/** 同步数据库操作 */