2025-11-28 10:55:29 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 代码生成数据转换工具
|
|
|
|
|
|
* 确保三种模式输出统一的数据格式
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
export interface FieldType {
|
|
|
|
|
|
name: string;
|
|
|
|
|
|
type: string;
|
|
|
|
|
|
type_length: string;
|
|
|
|
|
|
default: string;
|
|
|
|
|
|
comment: string;
|
|
|
|
|
|
not_null: boolean;
|
|
|
|
|
|
formShow: boolean;
|
|
|
|
|
|
tableShow: boolean;
|
|
|
|
|
|
formType: string;
|
|
|
|
|
|
search: boolean;
|
|
|
|
|
|
searchValue: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface CodeGenData {
|
|
|
|
|
|
class_name: string;
|
|
|
|
|
|
class_comment: string;
|
|
|
|
|
|
icon: string;
|
|
|
|
|
|
sort: number;
|
|
|
|
|
|
pid: number;
|
|
|
|
|
|
field: FieldType[];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 默认字段结构
|
|
|
|
|
|
*/
|
|
|
|
|
|
const createDefaultField = (): FieldType => ({
|
|
|
|
|
|
name: '',
|
|
|
|
|
|
type: 'varchar',
|
|
|
|
|
|
type_length: '',
|
|
|
|
|
|
default: '',
|
|
|
|
|
|
comment: '',
|
|
|
|
|
|
not_null: true,
|
|
|
|
|
|
formShow: true,
|
|
|
|
|
|
tableShow: true,
|
|
|
|
|
|
formType: 'VbenInput',
|
|
|
|
|
|
search: true,
|
|
|
|
|
|
searchValue: '=',
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* JSON模式数据转换
|
2026-01-23 17:13:48 +08:00
|
|
|
|
* 支持单个对象或对象数组(批量生成)
|
2025-11-28 10:55:29 +08:00
|
|
|
|
*/
|
2026-01-23 17:13:48 +08:00
|
|
|
|
export function transformJsonToStandard(json: any): CodeGenData | CodeGenData[] {
|
|
|
|
|
|
// 如果输入是数组(批量生成多个模块)
|
|
|
|
|
|
if (Array.isArray(json) && json.length > 0) {
|
|
|
|
|
|
// 检查第一个元素是否是完整的模块配置
|
|
|
|
|
|
if (json[0] && json[0].class_name && json[0].field) {
|
|
|
|
|
|
// 数组中的每个元素都是完整的模块配置
|
|
|
|
|
|
return json.map((item: any) => ({
|
|
|
|
|
|
class_name: item.class_name || '',
|
|
|
|
|
|
class_comment: item.class_comment || '',
|
|
|
|
|
|
icon: item.icon || '',
|
|
|
|
|
|
sort: item.sort ?? 9999,
|
|
|
|
|
|
pid: item.pid ?? 0,
|
|
|
|
|
|
field: Array.isArray(item.field)
|
|
|
|
|
|
? item.field.map((f: any) => ({
|
|
|
|
|
|
name: f.name || '',
|
|
|
|
|
|
type: f.type || 'varchar',
|
|
|
|
|
|
type_length: f.type_length || '',
|
|
|
|
|
|
default: f.default || '',
|
|
|
|
|
|
comment: f.comment || '',
|
|
|
|
|
|
not_null: f.not_null !== undefined ? f.not_null : true,
|
|
|
|
|
|
formShow: f.formShow !== undefined ? f.formShow : true,
|
|
|
|
|
|
tableShow: f.tableShow !== undefined ? f.tableShow : true,
|
|
|
|
|
|
formType: f.formType || 'VbenInput',
|
|
|
|
|
|
search: f.search !== undefined ? f.search : true,
|
|
|
|
|
|
searchValue: f.searchValue || '=',
|
|
|
|
|
|
}))
|
|
|
|
|
|
: [],
|
|
|
|
|
|
}));
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 数组中的元素只是字段数组,需要用户补充基础信息
|
|
|
|
|
|
return {
|
|
|
|
|
|
class_name: '',
|
|
|
|
|
|
class_comment: '',
|
|
|
|
|
|
icon: '',
|
|
|
|
|
|
sort: 9999,
|
|
|
|
|
|
pid: 0,
|
|
|
|
|
|
field: json.map((f: any) => ({
|
|
|
|
|
|
name: f.name || '',
|
|
|
|
|
|
type: f.type || 'varchar',
|
|
|
|
|
|
type_length: f.type_length || '',
|
|
|
|
|
|
default: f.default || '',
|
|
|
|
|
|
comment: f.comment || '',
|
|
|
|
|
|
not_null: f.not_null !== undefined ? f.not_null : true,
|
|
|
|
|
|
formShow: f.formShow !== undefined ? f.formShow : true,
|
|
|
|
|
|
tableShow: f.tableShow !== undefined ? f.tableShow : true,
|
|
|
|
|
|
formType: f.formType || 'VbenInput',
|
|
|
|
|
|
search: f.search !== undefined ? f.search : true,
|
|
|
|
|
|
searchValue: f.searchValue || '=',
|
|
|
|
|
|
})),
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 如果JSON包含完整结构,直接使用(单个模块)
|
2025-11-28 10:55:29 +08:00
|
|
|
|
if (json.class_name && json.field) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
class_name: json.class_name || '',
|
|
|
|
|
|
class_comment: json.class_comment || '',
|
|
|
|
|
|
icon: json.icon || '',
|
|
|
|
|
|
sort: json.sort ?? 9999,
|
|
|
|
|
|
pid: json.pid ?? 0,
|
|
|
|
|
|
field: Array.isArray(json.field)
|
|
|
|
|
|
? json.field.map((f: any) => ({
|
|
|
|
|
|
name: f.name || '',
|
|
|
|
|
|
type: f.type || 'varchar',
|
|
|
|
|
|
type_length: f.type_length || '',
|
|
|
|
|
|
default: f.default || '',
|
|
|
|
|
|
comment: f.comment || '',
|
|
|
|
|
|
not_null: f.not_null !== undefined ? f.not_null : true,
|
|
|
|
|
|
formShow: f.formShow !== undefined ? f.formShow : true,
|
|
|
|
|
|
tableShow: f.tableShow !== undefined ? f.tableShow : true,
|
|
|
|
|
|
formType: f.formType || 'VbenInput',
|
|
|
|
|
|
search: f.search !== undefined ? f.search : true,
|
|
|
|
|
|
searchValue: f.searchValue || '=',
|
|
|
|
|
|
}))
|
|
|
|
|
|
: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 默认返回空结构
|
|
|
|
|
|
return {
|
|
|
|
|
|
class_name: '',
|
|
|
|
|
|
class_comment: '',
|
|
|
|
|
|
icon: '',
|
|
|
|
|
|
sort: 9999,
|
|
|
|
|
|
pid: 0,
|
|
|
|
|
|
field: [createDefaultField()],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 手动模式数据转换
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function transformManualToStandard(
|
|
|
|
|
|
formData: Record<string, any>,
|
|
|
|
|
|
fields: FieldType[],
|
|
|
|
|
|
): CodeGenData {
|
|
|
|
|
|
return {
|
|
|
|
|
|
class_name: formData.class_name || '',
|
|
|
|
|
|
class_comment: formData.class_comment || '',
|
|
|
|
|
|
icon: formData.icon || '',
|
|
|
|
|
|
sort: formData.sort ?? 9999,
|
|
|
|
|
|
pid: formData.pid ?? 0,
|
|
|
|
|
|
field: Array.isArray(fields) ? fields : [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* AI模式数据转换
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function transformAiToStandard(aiData: any): CodeGenData {
|
|
|
|
|
|
// AI返回完整表单数据
|
|
|
|
|
|
if (aiData && aiData.class_name) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
class_name: aiData.class_name || '',
|
|
|
|
|
|
class_comment: aiData.class_comment || '',
|
|
|
|
|
|
icon: aiData.icon || '',
|
|
|
|
|
|
sort: aiData.sort ?? 9999,
|
|
|
|
|
|
pid: aiData.pid ?? 0,
|
|
|
|
|
|
field: Array.isArray(aiData.field)
|
|
|
|
|
|
? aiData.field.map((f: any) => ({
|
|
|
|
|
|
name: f.name || '',
|
|
|
|
|
|
type: f.type || 'varchar',
|
|
|
|
|
|
type_length: f.type_length || '',
|
|
|
|
|
|
default: f.default || '',
|
|
|
|
|
|
comment: f.comment || '',
|
|
|
|
|
|
not_null: f.not_null !== undefined ? f.not_null : true,
|
|
|
|
|
|
formShow: f.formShow !== undefined ? f.formShow : true,
|
|
|
|
|
|
tableShow: f.tableShow !== undefined ? f.tableShow : true,
|
|
|
|
|
|
formType: f.formType || 'VbenInput',
|
|
|
|
|
|
search: f.search !== undefined ? f.search : true,
|
|
|
|
|
|
searchValue: f.searchValue || '=',
|
|
|
|
|
|
}))
|
|
|
|
|
|
: [],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 默认返回空结构
|
|
|
|
|
|
return {
|
|
|
|
|
|
class_name: '',
|
|
|
|
|
|
class_comment: '',
|
|
|
|
|
|
icon: '',
|
|
|
|
|
|
sort: 9999,
|
|
|
|
|
|
pid: 0,
|
|
|
|
|
|
field: [createDefaultField()],
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 15:51:04 +08:00
|
|
|
|
/** 系统自动维护字段,禁止出现在业务 field 里 */
|
|
|
|
|
|
const RESERVED_FIELD_NAMES = new Set([
|
|
|
|
|
|
'id',
|
|
|
|
|
|
'status',
|
|
|
|
|
|
'created_at',
|
|
|
|
|
|
'updated_at',
|
|
|
|
|
|
'deleted_at',
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
2025-11-28 10:55:29 +08:00
|
|
|
|
/**
|
2026-08-10 15:51:04 +08:00
|
|
|
|
* 数据验证(导入前兜底,避免脏 class_name / 字段名进建表 SQL)
|
2025-11-28 10:55:29 +08:00
|
|
|
|
*/
|
|
|
|
|
|
export function validateData(data: CodeGenData): {
|
|
|
|
|
|
valid: boolean;
|
|
|
|
|
|
message?: string;
|
|
|
|
|
|
} {
|
|
|
|
|
|
if (!data.class_name || !data.class_name.trim()) {
|
|
|
|
|
|
return { valid: false, message: '类名不能为空' };
|
|
|
|
|
|
}
|
2026-08-10 15:51:04 +08:00
|
|
|
|
if (!/^[A-Z][A-Za-z0-9]{0,63}$/.test(data.class_name.trim())) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: '类名须为英文 PascalCase(如 VipMember),不能含中文或下划线',
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2025-11-28 10:55:29 +08:00
|
|
|
|
|
|
|
|
|
|
if (!data.class_comment || !data.class_comment.trim()) {
|
|
|
|
|
|
return { valid: false, message: '中文名称不能为空' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!data.icon || !data.icon.trim()) {
|
|
|
|
|
|
return { valid: false, message: '菜单图标不能为空' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!Array.isArray(data.field) || data.field.length === 0) {
|
|
|
|
|
|
return { valid: false, message: '至少需要一个字段' };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-10 15:51:04 +08:00
|
|
|
|
const seen = new Set<string>();
|
2025-11-28 10:55:29 +08:00
|
|
|
|
for (const field of data.field) {
|
2026-08-10 15:51:04 +08:00
|
|
|
|
const name = (field.name || '').trim();
|
|
|
|
|
|
if (!name) {
|
2025-11-28 10:55:29 +08:00
|
|
|
|
return { valid: false, message: '字段名不能为空' };
|
|
|
|
|
|
}
|
2026-08-10 15:51:04 +08:00
|
|
|
|
if (name.length > 64 || /[\s,。::|()()]/.test(name)) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: `字段名非法「${name}」,须为短小写 snake_case`,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!/^[a-z][a-z0-9_]{0,63}$/.test(name)) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
valid: false,
|
|
|
|
|
|
message: `字段名非法「${name}」,须为小写 snake_case(如 user_name)`,
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
if (RESERVED_FIELD_NAMES.has(name)) {
|
|
|
|
|
|
return { valid: false, message: `字段「${name}」为系统保留字段` };
|
|
|
|
|
|
}
|
|
|
|
|
|
if (seen.has(name)) {
|
|
|
|
|
|
return { valid: false, message: `字段名重复「${name}」` };
|
|
|
|
|
|
}
|
|
|
|
|
|
seen.add(name);
|
2025-11-28 10:55:29 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return { valid: true };
|
|
|
|
|
|
}
|
|
|
|
|
|
|